/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* Intel 82077 Floppy Disk Driver
*/
/*
* Notes
*
* 0. The driver supports two flavors of hardware design:
* "SUNW,fdtwo" - sun4m - 82077 with sun4m style Auxio
* "fdthree" - sun4u - 82077 with DMA
* In addition it supports an apparent bug in some versions of
* the 82077 controller.
*
* 1. The driver is mostly set up for multiple controllers, multiple
* drives. However- we *do* assume the use of the AUXIO register, and
* if we ever have > 1 fdc, we'll have to see what that means. This
* is all intrinsically machine specific, but there isn't much we
* can do about it.
*
* 2. The driver also is structured to deal with one drive active at
* a time. This is because the 82072 chip (no longer supported) was
* known to be buggy with respect to overlapped seeks.
*
* 3. The high level interrupt code is in assembler, and runs in a
* sparc trap window. It acts as a pseudo-dma engine as well as
* handles a couple of other interrupts. When it gets its job done,
* it schedules a second stage interrupt (soft interrupt) which
* is then fielded here in fd_lointr. When DMA is used, the fdintr_dma
* interrupt handler is used.
*
* 4. Nearly all locking is done on a lower level MUTEX_DRIVER
* mutex. The locking is quite conservative, and is generally
* established very close to any of the entries into the driver.
* There is nearly no locking done of the high level MUTEX_DRIVER
* mutex (which generally is a SPIN mutex because the floppy usually
* interrupts above LOCK_LEVEL). The assembler high level interrupt
* handler grabs the high level mutex, but the code in the driver
* here is especially structured to not need to do this.
*
* 5. Fdrawioctl commands that pass data are not optimized for
* speed. If they need to be faster, the driver structure will
* have to be redone such that fdrawioctl calls physio after
* cons'ing up a uio structure and that fdstart will be able
* to detect that a particular buffer is a 'special' buffer.
*
* 6. Removable media support is not complete.
*
*/
#include <sys/autoconf.h>
/*
* included to check for ELC or SLC which report floppy controller that
*/
#include "sys/dma_i8237A.h"
/*
* Defines
*/
#define C CE_CONT
/*
* Sony MP-F17W-50D Drive Parameters
* High Capacity
* Capacity unformatted 2Mb
* Capacity formatted 1.47Mb
* Encoding method MFM
* Recording density 17434 bpi
* Track density 135 tpi
* Cylinders 80
* Heads 2
* Tracks 160
* Rotational speed 300 rpm
* Transfer rate 250/500 kbps
* Latency (average) 100 ms
* Access time
* Average 95 ms
* Track to track 3 ms
* Head settling time 15 ms
* Motor start time 500 ms
* Head load time ? ms
*/
/*
* The max_fd_dma_len is used only when southbridge is present.
* It has been observed that when IFB tests are run the floppy dma could get
* starved and result in underrun errors. After experimenting it was found that
* doing dma in chunks of 2048 works OK.
* The reason for making this a global variable is that there could be
* situations under which the customer would like to get full performance
* from floppy. He may not be having IFB boards that cause underrun errors.
* Under those conditions we could set this value to a much higher value
*/
static void quiesce_fd_interrupt(struct fdctlr *);
/*
*/
static int fd_strategy(struct buf *);
static int
/*
* Device operations (dev_ops) entries function prototypes
*/
void **result);
/*
* Internal functions
*/
int *hard);
int locks);
static caddr_t fd_getauxiova();
static uint_t fdintr_dma();
static int fd_isauxiodip(dev_info_t *);
static void fd_media_watch(void *);
static void fdmotoff(void *);
static int fd_unit_is_open(struct fdunit *);
static int fd_unbind_handle(struct fdctlr *);
static void fdwatch(void *);
static void set_rotational_speed(struct fdctlr *, int);
/*
* External functions
*/
extern void set_auxioreg();
extern void call_debug();
/*
* The following macro checks whether the device in a SUSPENDED state.
* As per WDD guide lines the I/O requests to a suspended device should
* be blocked until the device is resumed.
* Here we cv_wait on c_suspend_cv, and there is a cv_broadcast() in
* DDI_RESUME to wake up this thread.
*
* NOTE: This code is not tested because the kernel threads are suspended
* before the device is suspended. So there can not be any I/O requests on
* a suspended device until the cpr implementation changes..
*/
{\
}\
}
/*
* bss (uninitialized data)
*/
/*
* initialized data
*/
static int fd_pollable = 0;
/* This variable allows the dynamic change of the burst size */
static struct driver_minor_data {
char *name;
int minor;
int type;
} fd_minor [] = {
{ "a", 0, S_IFBLK},
{ "a,raw", 0, S_IFCHR},
{0}
};
/*
* If the interrupt handler is invoked and no controllers expect an
* interrupt, the kernel panics. The following message is printed out.
*/
/*
*/
/* When DMA is used, set the ND bit to 0 */
/*
* default characteristics
*/
{ /* struct fd_char fdchar_1.7MB density */
0, /* medium */
500, /* transfer rate */
80, /* number of cylinders */
2, /* number of heads */
512, /* sector size */
21, /* sectors per track */
-1, /* (NA) # steps per data track */
},
{ /* struct fd_char fdchar_highdens */
0, /* medium */
500, /* transfer rate */
80, /* number of cylinders */
2, /* number of heads */
512, /* sector size */
18, /* sectors per track */
-1, /* (NA) # steps per data track */
},
{ /* struct fd_char fdchar_meddens */
1, /* medium */
500, /* transfer rate */
77, /* number of cylinders */
2, /* number of heads */
1024, /* sector size */
8, /* sectors per track */
-1, /* (NA) # steps per data track */
},
{ /* struct fd_char fdchar_lowdens */
0, /* medium */
250, /* transfer rate */
80, /* number of cylinders */
2, /* number of heads */
512, /* sector size */
9, /* sectors per track */
-1, /* (NA) # steps per data track */
}
};
/*
* Default Label & partition maps
*/
{ "3.5\" floppy cyl 80 alt 0 hd 2 sec 21" },
300, /* rotations per minute */
80, /* # physical cylinders */
0, /* alternates per cylinder */
1, /* interleave factor */
80, /* # of data cylinders */
0, /* # of alternate cylinders */
2, /* # of heads in this partition */
21, /* # of 512 byte sectors per track */
{
{ 0, 79 * 2 * 21 }, /* part 0 - all but last cyl */
{ 79, 1 * 2 * 21 }, /* part 1 - just the last cyl */
{ 0, 80 * 2 * 21 }, /* part 2 - "the whole thing" */
},
{ 0, /* version */
"", /* volume label */
3, /* no. of partitions */
{ 0 }, /* partition hdrs, sec 2 */
{ 0 }, /* mboot info. unsupported */
VTOC_SANE, /* verify vtoc sanity */
{ 0 }, /* reserved space */
0, /* timestamp */
},
};
{ "3.5\" floppy cyl 80 alt 0 hd 2 sec 18" },
300, /* rotations per minute */
80, /* # physical cylinders */
0, /* alternates per cylinder */
1, /* interleave factor */
80, /* # of data cylinders */
0, /* # of alternate cylinders */
2, /* # of heads in this partition */
18, /* # of 512 byte sectors per track */
{
{ 0, 79 * 2 * 18 }, /* part 0 - all but last cyl */
{ 79, 1 * 2 * 18 }, /* part 1 - just the last cyl */
{ 0, 80 * 2 * 18 }, /* part 2 - "the whole thing" */
},
{ 0, /* version */
"", /* volume label */
3, /* no. of partitions */
{ 0 }, /* partition hdrs, sec 2 */
{ 0 }, /* mboot info. unsupported */
VTOC_SANE, /* verify vtoc sanity */
{ 0 }, /* reserved space */
0, /* timestamp */
},
};
/*
* A medium density diskette has 1024 byte sectors. The dk_label structure
* assumes a sector is DEVBSIZE (512) bytes.
*/
{ "3.5\" floppy cyl 77 alt 0 hd 2 sec 8" },
360, /* rotations per minute */
77, /* # physical cylinders */
0, /* alternates per cylinder */
1, /* interleave factor */
77, /* # of data cylinders */
0, /* # of alternate cylinders */
2, /* # of heads in this partition */
16, /* # of 512 byte sectors per track */
{
{ 0, 76 * 2 * 8 * 2 }, /* part 0 - all but last cyl */
{ 76, 1 * 2 * 8 * 2 }, /* part 1 - just the last cyl */
{ 0, 77 * 2 * 8 * 2 }, /* part 2 - "the whole thing" */
},
{ 0, /* version */
"", /* volume label */
3, /* no. of partitions */
{ 0 }, /* partition hdrs, sec 2 */
{ 0 }, /* mboot info. unsupported */
VTOC_SANE, /* verify vtoc sanity */
{ 0 }, /* reserved space */
0, /* timestamp */
},
};
{ "3.5\" floppy cyl 80 alt 0 hd 2 sec 9" },
300, /* rotations per minute */
80, /* # physical cylinders */
0, /* alternates per cylinder */
1, /* interleave factor */
80, /* # of data cylinders */
0, /* # of alternate cylinders */
2, /* # of heads in this partition */
9, /* # of 512 byte sectors per track */
{
{ 0, 79 * 2 * 9 }, /* part 0 - all but last cyl */
{ 79, 1 * 2 * 9 }, /* part 1 - just the last cyl */
{ 0, 80 * 2 * 9 }, /* part 2 - "the whole thing" */
},
{ 0, /* version */
"", /* volume label */
3, /* no. of partitions */
{ 0 }, /* partition hdrs, sec 2 */
{ 0 }, /* mboot info. unsupported */
VTOC_SANE, /* verify vtoc sanity */
{ 0 }, /* reserved space */
0, /* timestamp */
},
};
static struct fdcmdinfo {
} fdcmds[] = {
"", 0, 0, 0, /* - */
"", 0, 0, 0, /* - */
"read_track", 9, 7, 1, /* 2 */
"specify", 3, 0, 3, /* 3 */
"sense_drv_status", 2, 1, 3, /* 4 */
"write", 9, 7, 1, /* 5 */
"read", 9, 7, 1, /* 6 */
"recalibrate", 2, 0, 2, /* 7 */
"sense_int_status", 1, 2, 3, /* 8 */
"write_del", 9, 7, 1, /* 9 */
"read_id", 2, 7, 2, /* A */
"read_del", 9, 7, 1, /* C */
"format_track", 10, 7, 1, /* D */
"dump_reg", 1, 10, 4, /* E */
"seek", 3, 0, 2, /* F */
"", 0, 0, 0, /* - */
"", 0, 0, 0, /* - */
"", 0, 0, 0, /* - */
"configure", 4, 0, 4, /* 13 */
/* relative seek */
};
fd_open, /* open */
fd_close, /* close */
fd_strategy, /* strategy */
nodev, /* print */
nodev, /* dump */
fd_read, /* read */
fd_write, /* write */
fd_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
fd_prop_op, /* cb_prop_op */
0, /* streamtab */
};
DEVO_REV, /* devo_rev, */
0, /* refcnt */
fd_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
fd_attach, /* attach */
fd_detach, /* detach */
nodev, /* reset */
&fd_cb_ops, /* driver operations */
(struct bus_ops *)0, /* bus operations */
fd_power, /* power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
/*
* error handling
*
* for debugging, set rwretry and skretry = 1
* set fderrlevel to 1
* set fderrmask to 224 or 100644
*
* after debug set rwretry to 10, skretry to 5, and fderrlevel to 3
* set fderrmask to FDEM_ALL
* remove the define FD_DEBUG
*
*/
/*
* loadable module support
*/
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. driver here */
"Floppy Driver", /* Name of the module. */
&fd_ops, /* Driver ops vector */
};
&modldrv,
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
{
}
int
_fini(void)
{
int e;
if ((e = mod_remove(&modlinkage)) != 0)
return (e);
/* ddi_soft_state_fini() */
return (0);
}
/* ARGSUSED */
static int
{
int hard_intr_set = 0;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Wake up any thread blocked due to I/O requests
* while the device was suspended.
*/
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* Check for the pollable property
* A pollable floppy drive currently only exists on the
* Sparcstation Voyager. This drive does not need to
* be turned on in order to sense whether or not a diskette
* is present.
*/
fd_pollable = 1;
/* Determine which type of controller is present and initialize it */
return (DDI_FAILURE);
}
/* Finish mapping the device registers & setting up structures */
return (DDI_FAILURE);
}
/*
* Initialize the DMA limit structures if it's being used.
*/
} else {
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
}
/* Register the interrupts */
&hard_intr_set) == DDI_FAILURE) {
(C, "fd_attach: registering interrupts failed\n"));
return (DDI_FAILURE);
}
/*
* set initial controller/drive/disk "characteristics/geometry"
*
* NOTE: The driver only supports one floppy drive. The hardware
* only supports one drive because there is only one auxio register
* for one drive.
*/
}
/* check for the manual eject property */
DDI_PROP_DONTPASS, FD_MANUAL_EJECT, 0)) {
} else {
/* an absence of the property indicates auto eject */
}
/*
* Check for the drive id. If the drive id property doesn't exist
* then the drive id is set to 0
*/
DDI_PROP_DONTPASS, FD_UNIT, 0);
DDI_PROP_DONTPASS, "dma-channel", 0);
}
/* Initially set the characteristics to high density */
/* Make sure drive is present */
return (DDI_FAILURE);
}
DDI_NT_FD, 0) == DDI_FAILURE) {
return (DDI_FAILURE);
}
}
/*
* Add a zero-length attribute to tell the world we support
* kernel ioctls (for layered drivers)
*/
DDI_KERNEL_IOCTL, NULL, 0);
return (DDI_SUCCESS);
}
/*
* Finish mapping the registers and initializing structures
*/
static int
{
/* Map the DMA registers of the platform supports DMA */
0, sizeof (struct sb_dma_reg), &attr,
&fdc->c_handlep_dma)) {
return (DDI_FAILURE);
}
0, sizeof (struct cheerio_dma_reg), &attr,
&fdc->c_handlep_dma)) {
return (DDI_FAILURE);
}
}
/* Reset the DMA engine and enable floppy interrupts */
/* Finish initializing structures associated with the device regs */
case FDCTYPE_82077:
/*
* Initialize addrs of key registers
*/
/*
* The 82077 doesn't use the first configuration parameter
* so let's adjust that while we know we're an 82077.
*/
fdconf[0] = 0;
break;
default:
break;
}
return (0);
}
/*
* Determine which type of floppy controller is present and
* initialize the registers accordingly
*/
static int
{
/* DDI_NEVERSWAP_ACC since the controller has a byte interface. */
(C, "fdattach_det_cltr: start \n"));
/*
* First, map in the controller's registers
* The controller has an 8-bit interface, so byte
* swapping isn't needed
*/
0, sizeof (union fdcreg),
&attr,
&fdc->c_handlep_cont)) {
return (DDI_FAILURE);
}
(C, "fdattach_det_cltr: mapped floppy regs\n"));
/*
* Set platform specific characteristics based on the device-tree
* node name.
*/
(C, "fdattach: slavio will be used!\n"));
/*
* Check the binding name to identify whether it is a South bridge based
* system or not.
*/
(C, "fdattach: southbridge will be used!\n"));
/*
* The driver assumes high density characteristics until
* the diskette is looked at.
*/
(C, "fdattach: cheerio will be used!\n"));
/*
* The cheerio auxio register should be memory mapped. The
* auxio register on other platforms is shared and mapped
* elsewhere in the kernel
*/
return (DDI_FAILURE);
}
/*
* The driver assumes high density characteristics until
* the diskette is looked at.
*/
(C, "fdattach: auxio register 0x%x\n",
*fdc->c_auxio_reg));
}
(C, "fdattach: no controller!\n"));
return (DDI_FAILURE);
} else {
return (0);
}
}
/*
* Register the floppy interrupts
*/
static int
{
int status;
/*
* First call ddi_get_iblock_cookie() to retrieve the
* the interrupt block cookie so that the mutexes may
* be initialized before adding the interrupt. If the
* mutexes are initialized after adding the interrupt, there
* could be a race condition.
*/
(C, "fdattach: ddi_get_iblock_cookie failed\n"));
return (DDI_FAILURE);
}
/* Initialize high level mutex */
/*
* Try to register fast trap handler, if unable try standard
* interrupt handler, else bad
*/
(C, "fdattach: standard intr\n"));
/*
* When DMA is used, the low level lock
* is used in the hard interrupt handler.
*/
*hard = 1;
} else {
(C, "fdattach: can't add dma intr\n"));
return (DDI_FAILURE);
}
} else {
/*
* Platforms that don't support DMA have both hard
* and soft interrupts.
*/
(C, "fdattach: standard intr\n"));
*hard = 1;
/* fast traps are not enabled */
fdc->c_fasttrap = 0;
} else {
(C, "fdattach: can't add intr\n"));
return (DDI_FAILURE);
}
/*
* Initialize the soft interrupt handler. First call
* ddi_get_soft_iblock_cookie() so that the mutex may
* be initialized before the handler is added.
*/
if (status != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Initialize low level mutex which is used in the soft
* interrupt handler
*/
return (DDI_FAILURE);
}
}
if (fdc->c_intrstat) {
}
/* condition variable to wait on while an io transaction occurs */
/* condition variable for the csb */
/* condition variable for motor on waiting period */
/* semaphore to serialize opens and closes */
/* condition variable to wait on suspended floppy controller. */
return (0);
}
/*
* Make sure the drive is present
* - acquires the low level lock
*/
static int
{
int tmp_fderrlevel;
(C, "fd_attach_check_drive\n"));
/* insure that the eject line is reset */
case FDCTYPE_82077:
/*
* Everything but the motor enable, drive select,
* and reset bits are turned off. These three
* bits remain as they are.
*/
/* LINTED */
drv_usecwait(5);
if (unit == 0) {
/* LINTED */
} else {
/* LINTED */
/* LINTED */
}
drv_usecwait(5);
set_auxioreg(AUX_TC4M, 0);
}
break;
default:
break;
}
return (DDI_FAILURE);
}
/* check for drive present */
(C, "fdattach: call fdrecalseek\n"));
/* Make sure the drive is present */
/* Do not hold the mutex over the call to untimeout */
if (timeid) {
}
(C, "fd_attach: no drive?\n"));
return (DDI_FAILURE);
}
return (0);
}
/*
* Clean up routine used by fd_detach and fd_attach
*
* Note: if the soft id is non-zero, then ddi_add_softintr() completed
* successfully. I can not make the same assumption about the iblock_cookie
* for the high level interrupt handler. So, the hard parameter indicates
* whether or not a high level interrupt handler has been added.
*
* If the locks parameter is nonzero, then all mutexes, semaphores and
* condition variables will be destroyed.
*
* Does not assume the low level mutex is held.
*
*/
static void
{
(C, "fd_cleanup instance: %d ctlr: 0x%p\n",
return;
}
/*
* Remove interrupt handlers first before anything else
* is deallocated.
*/
/* Remove hard interrupt if one is registered */
if (hard) {
}
/* Remove soft interrupt if one is registered */
/* Remove timers */
/*
* chipset) just in case it was on when timer was removed
*/
}
/* Remove memory handles */
if (fdc->c_handlep_cont)
if (fdc->c_handlep_aux)
if (fdc->c_handlep_dma)
/* Remove all minor nodes */
/* Remove unit structure if one exists */
sizeof (struct fd_drive));
}
if (fdc->c_intrstat) {
(C, "fd_cleanup: delete intrstat\n"));
}
if (locks) {
}
}
static int
{
switch (cmd) {
case DDI_DETACH:
/*
* The hard parameter is set to 1. If detach is called, then
* attach must have passed meaning that the high level
* interrupt handler was successfully added.
* Similarly, the locks parameter is also set to 1.
*/
return (DDI_SUCCESS);
case DDI_SUSPEND:
if (!fdc)
return (DDI_FAILURE);
/*
* After suspend, the system could be powered off.
* When it is later powered on the southbridge floppy
* controller will tristate the interrupt line causing
* continuous dma interrupts.
* To avoid getting continuous fd interrupts we will remove the
* dma interrupt handler installed. We will re-install the
* handler when we RESUME.
*/
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/* ARGSUSED */
static int
{
register int error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_FAILURE;
} else {
error = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = 0;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
/*
* property operation routine. return the number of blocks for the partition
* in question or forward the request to the property facilities.
*/
static int
{
/*
* Our dynamic properties are all device specific and size oriented.
* Requests issued under conditions where size is valid are passed
* to ddi_prop_op_nblocks with the size information, otherwise the
* request is passed to ddi_prop_op.
*/
if (dev == DDI_DEV_T_ANY) {
} else {
goto pass;
/* we have size if diskette opened and label read */
goto pass;
/* get nblocks value */
}
}
/* ARGSUSED3 */
static int
{
int part;
int unit;
return (ENXIO);
}
/*
*/
/* check partition */
return (ENXIO);
}
(C, "fdopen: ctlr %d unit %d part %d\n",
(C, "fdopen: flag 0x%x", flag));
/*
* Insure that drive is present with a recalibrate on first open.
*/
!= DDI_SUCCESS) {
failed. \n"));
return (EIO);
}
}
if (fd_unit_is_open(un) == 0) {
/*
* no check changed!
*/
if (err) {
(C, "fd%d: drive not ready\n", 0));
/* deselect drv on last close */
return (EIO);
}
}
/*
* Check for previous exclusive open, or trying to exclusive open
*/
} else {
}
return (EBUSY);
}
/* don't attempt access, just return successfully */
(C, "fd: return busy..\n"));
goto out;
}
/* didn't find label (couldn't read anything) */
(C,
"fd%d: unformatted diskette or no diskette in the drive\n",
0));
if (fd_unit_is_open(un) == 0) {
/* deselect drv on last close */
}
return (EIO);
}
/*
* if opening for writing, check write protect on diskette
*/
if (err) {
if (fd_unit_is_open(un) == 0)
return (EROFS);
}
}
out:
/*
* mark open as having succeeded
*/
}
} else {
}
return (0);
}
/*
* fd_part_is_open
* return 1 if the partition is open
* return 0 otherwise
*/
static int
{
int i;
for (i = 0; i < OTYPCNT - 1; i++)
return (1);
return (0);
}
/* ARGSUSED */
static int
{
return (ENXIO);
} else {
part_is_closed = 1;
}
if (part_is_closed)
if (fd_unit_is_open(un) == 0) {
/* deselect drive on last close */
}
return (0);
}
/*
* fd_strategy
* checks operation, hangs buf struct off fdctlr, calls fdstart
* if not already busy. Note that if we call start, then the operation
* will already be done on return (start sleeps).
*/
static int
{
(C, "fd_strategy: bp = 0x%p, dev = 0x%lx\n",
(C, "b_blkno=%x b_flags=%x b_count=%x\n",
/*
* If it's medium density and the block no. isn't a multiple
* of 1K, then return an error.
*/
(C, "b_blkno=0x%lx is not 1k aligned\n",
return (0);
}
} else {
}
/* If the block number is past the end, return an error */
(C, "fd%d: block %ld is past the end! (nblk=%d)\n",
return (0);
}
/* if at end of file, skip out now */
(C, "b_blkno is at the end!\n"));
/* a write needs to get an error! */
(C, "block is at end and this is a write\n"));
}
return (0);
}
/* if operation not a multiple of sector size, is error! */
(C, "fd%d: requested transfer size(0x%lx) is not"
" multiple of sector size(0x%x)\n", 0,
(C, " b_blkno=0x%lx b_flags=0x%x\n",
return (0);
}
/*
* Put the buf request in the controller's queue, FIFO.
*/
!= DDI_SUCCESS) {
return (0);
} else {
}
}
}
else
/* call fdstart to start the transfer */
return (0);
}
/* ARGSUSED2 */
static int
{
}
/* ARGSUSED2 */
static int
{
}
static void
{
/* Just return if we're about to call untimeout */
return;
}
/* LINTED */
}
}
/* ARGSUSED */
static int
{
union {
int temp;
} cpy;
int err = 0;
int transfer_rate;
/* The minor number should always be 0 */
return (ENXIO);
switch (cmd) {
case DKIOCINFO:
/*
* The meaning of the dki_slave and dki_unit fields
* is unclear. The sparc floppy driver follows the same
* convention as sd.c in that the instance number is
* returned in the dki_cnum field. The dki_slave field is
* ignored.
*
* The dki_cnum contains the controller instance
* and its value can be any positive number. Even
* though currently Sparc platforms only support
* one controller, the controller instance number
* can be any number since it is assigned by the
* system depending on the device properties.
*/
/*
* Sparc platforms support only one floppy drive.
* The device node for the controller is the same as
* the device node for the drive. The x86 driver is
* different in that it has a node for the controller
* and a child node for each drive. Since Sparc supports
* only one drive, the unit number will always be zero.
*/
/*
* The meaning of the dki_slave field is unclear.
* So, I will leave it set to 0.
*/
break;
case DKIOCGGEOM:
break;
case DKIOCSGEOM:
(C, "fd_ioctl: DKIOCSGEOM not supported\n"));
break;
/*
* return the map of all logical partitions
*/
case DKIOCGAPART:
/*
* We don't have anything to do if the application is ILP32
* because the label map has a 32-bit format. Otherwise
* convert.
*/
}
#ifdef _MULTI_DATAMODEL
else {
}
}
#endif /* _MULTI_DATAMODEL */
break;
/*
* Set the map of all logical partitions
*/
case DKIOCSAPART:
return (EFAULT);
else {
}
}
}
#ifdef _MULTI_DATAMODEL
else {
return (EFAULT);
else {
}
}
}
#endif /* _MULTI_DATAMODEL */
break;
case DKIOCGVTOC:
/*
* Exit if the diskette has no label.
* Also, get the label to make sure the
* correct one is being used since the diskette
* may have changed
*/
break;
}
/* Build a vtoc from the diskette's label */
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32: {
return (EFAULT);
break;
}
case DDI_MODEL_NONE:
return (EFAULT);
break;
}
#else /* ! _MULTI_DATAMODEL */
return (EFAULT);
#endif /* _MULTI_DATAMODEL */
break;
case DKIOCSVTOC:
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32: {
return (EFAULT);
}
break;
}
case DDI_MODEL_NONE:
return (EFAULT);
}
break;
}
#else /* ! _MULTI_DATAMODEL */
return (EFAULT);
#endif /* _MULTI_DATAMODEL */
/*
* The characteristics structure must be filled in because
* it helps build the vtoc.
*/
break;
}
break;
}
break;
case DKIOCSTATE:
sizeof (int), flag)) {
break;
}
break;
case FDIOGCHAR:
break;
case FDIOSCHAR:
break;
}
/*
* Check the fields in the fdchar structure that are either
* driver or controller dependent.
*/
(C, "fd_ioctl: FDIOSCHAR odd transfer rate %d\n",
break;
}
(C, "fd_ioctl: FDIOSCHAR bad no. of heads %d\n",
break;
}
/*
* The number of cylinders must be between 0 and 255
*/
(C, "fd_ioctl: FDIOSCHAR bad cyl no %d\n",
break;
}
/* Copy the fdchar structure */
break;
case FDEJECT: /* eject disk */
case DKIOCEJECT:
/*
* Fail the ioctl if auto-eject isn't supported
*/
} else {
PM_LEVEL_ON)) != DDI_SUCCESS) {
}
}
}
if (err == 0) {
}
/*
* Make sure the drive is turned off
*/
}
}
break;
case FDGETCHANGE: /* disk changed */
sizeof (int), flag)) {
break;
}
/* zero out the user's parameter */
!= DDI_SUCCESS) {
change failed. \n"));
return (EIO);
}
}
else
if (fd_pollable) {
/*
* If it's a "pollable" floppy, then we don't
* have to do all the fdcheckdisk nastyness to
* figure out if the thing is still there.
*/
} else {
}
} else {
/*
* check disk change signal is asserted.
* Now find out if the floppy is
* inserted
*/
} else {
/*
* Yes, the floppy was
* reinserted. Implies
* floppy change.
*/
}
} else {
}
}
/*
* For a pollable floppy, the floppy_change signal
* reflects whether the floppy is in there or not.
* We can not detect a floppy change if we don't poll
* this signal when the floppy is being changed.
* Because as soon as the floppy is put back, the
* signal is reset.
* BUT the pollable floppies are available only on
* Sparcstation Voyager Voyagers (Gypsy) only and
* those are motorized floppies. For motorized floppies,
* the floppy can only (assuming the user doesn't use a
* pin to take out the floppy) be taken out by
* issuing 'eject' command which sets the
* un->un_ejected flag. So, if the following
* condition is true, we can assume there
* was a floppy change.
*/
}
un->un_ejected = 0;
/* return the write-protection status */
}
sizeof (int), flag))
break;
case FDGETDRIVECHAR:
break;
}
/*
* Return the ejectable value based on the FD_MANUAL_EJECT
* property
*/
if (fd_pollable) /* pollable device */
/* the rest of the fd_drive struct is meaningless to us */
break;
case FDSETDRIVECHAR:
(C, "fd_ioctl: FDSETDRIVECHAR not supportedn\n"));
break;
case DKIOCREMOVABLE: {
int i = 1;
/* no brainer: floppies are always removable */
flag)) {
}
break;
}
case DKIOCGMEDIAINFO:
break;
case FDIOCMD:
{
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32: {
return (EFAULT);
}
break;
}
case DDI_MODEL_NONE:
return (EFAULT);
}
break;
}
#else /* ! _MULTI_DATAMODEL */
return (EFAULT);
}
#endif /* _MULTI_DATAMODEL */
(C, "fd_ioctl: call physio\n"));
break;
/*
* The manpage states that only the FDCMD_WRITE,
* FDCMD_READ, and the FDCMD_FORMAT_TR are available.
*/
(C, "fd_ioctl: FDIOCMD invalid command\n"));
break;
}
/* The command is FDCMD_FORMAT_TRACK */
/*
* Make sure the specified block number is in the correct
* range. (block numbers start at 0)
*/
break;
}
!= DDI_SUCCESS) {
change failed. \n"));
return (EIO);
}
}
break;
}
case FDRAW:
break;
#ifdef FD_DEBUG
case IOCTL_DEBUG:
fderrlevel--;
if (fderrlevel < 0)
fderrlevel = 3;
return (0);
#endif /* FD_DEBUG */
default:
(C, "fd_ioctl: invalid ioctl 0x%x\n", cmd));
break;
}
return (err);
}
/*
* fdrawioctl
*
* - acquires the low level lock
*/
static int
{
#ifdef _MULTI_DATAMODEL
#endif
int res;
err = 0;
/* Copy in the arguments */
switch (ddi_model_convert_from(mode)) {
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32:
(C, "fdrawioctl: copyin error, args32\n"));
return (EFAULT);
}
sizeof (fdr.fdr_result));
break;
#endif
default:
case DDI_MODEL_NONE:
(C, "fdrawioctl: copyin error, args\n"));
return (EFAULT);
}
break;
}
!= DDI_SUCCESS) {
failed. \n"));
return (EIO);
}
}
/* copy cmd bytes into csb */
csb->csb_retrys = 0;
case FDRAW_SPECIFY:
/*
* Ensure that the right DMA mode is selected. There is
* currently no way for the user to tell if DMA is
* happening so set the value for the user.
*/
else
csb->csb_nrslts = 0;
break;
case FDRAW_SENSE_DRV:
/* Insert the appropriate drive number */
break;
case FDRAW_REZERO:
case FDRAW_SEEK:
/* Insert the appropriate drive number */
break;
case FDRAW_FORMAT:
(C, "fdrawioctl: cmd is fdfraw format\n"));
/* Insert the appropriate drive number */
/*
* Allocate memory for the command.
* If PIO is being used, then add an extra 16 bytes
*/
&mem_handle);
if (res != DDI_SUCCESS) {
return (EIO);
}
return (EIO);
}
} else {
}
/* copy in the user's command bytes */
(C, "fdrawioctl: (err)free dma memory\n"));
} else {
}
(C, "fdrawioctl: ddi_copyin error\n"));
return (EFAULT);
}
break;
case FDRAW_WRCMD:
case FDRAW_WRITEDEL:
/* FALLTHROUGH */
case FDRAW_RDCMD:
case FDRAW_READDEL:
case FDRAW_READTRACK:
/* Insert the appropriate drive number */
break;
default:
return (EINVAL);
}
return (EINVAL);
}
/*
* In SunOS 4.X, we used to as_fault things in.
* We really cannot do this in 5.0/SVr4. Unless
* someone really believes that speed is of the
* essence here, it is just much simpler to do
*/
&mem_handle);
if (res != DDI_SUCCESS) {
return (EIO);
}
else
return (EIO);
}
} else {
}
else
"fdrawioctl: can't copy data\n"));
return (EFAULT);
}
}
} else {
}
} else {
}
(C, "nbytes: %x, opflags: %x, addr: %p, len: %x\n",
/*
* Note that we ignore any error return s from fdexec.
* This is the way the driver has been, and it may be
* that the raw ioctl senders simply don't want to
* see any errors returned in this fashion.
*/
} else {
}
(C, "fdrawioctl: can't copy read data\n"));
}
}
if (fc) {
(C, "fdrawioctl: free dma memory\n"));
} else {
}
}
/* copy cmd results into fdr */
for (i = 0; (int)i <= (int)csb->csb_nrslts; i++)
switch (ddi_model_convert_from(mode)) {
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32:
sizeof (fdr32.fdr_result));
(C, "fdrawioctl: can't copy results32\n"));
}
break;
#endif
case DDI_MODEL_NONE:
default:
(C, "fdrawioctl: can't copy results\n"));
}
break;
}
return (0);
}
/*
* fdformat
* format a track
* For PIO, builds a table of sector data values with 16 bytes
* (sizeof fdc's fifo) of dummy on end. This is so than when fdc->c_len
* goes to 0 and fd_intr sends a TC that all the real formatting will
* have already been done.
*
* - called with the low level lock held
*/
static int
{
int cmdresult;
int i;
/* setup common things in csb */
/*
* The controller needs to do a seek before
* each format to get to right cylinder.
*/
return (EIO);
}
/*
* now do the format itself
*/
/* always or in MFM bit */
csb->csb_retrys = 0;
/*
* NOTE: have to add size of fifo also - for dummy format action
* if PIO is being used.
*/
&mem_handle);
if (cmdresult != DDI_SUCCESS) {
return (cmdresult);
}
return (-1);
}
} else {
}
}
if (csb->csb_cmdstat)
}
} else {
}
return (cmdresult);
}
/*
* fdstart
* called from fd_strategy() or from fdXXXX() to setup and
* start operations of read or write only (using buf structs).
* Because the chip doesn't handle crossing cylinder boundaries on
* the fly, this takes care of those boundary conditions. Note that
* it sleeps until the operation is done *within fdstart* - so that
* when fdstart returns, the operation is already done.
*
* - called with the low level lock held
*
*/
static int slavio_index_pulse_work_around = 0;
static void
{
int sb_temp_buf_used = 0;
/*
* Initialize the buf structure. The residual count is
* initially the number of bytes to be read or written
*/
/*
* Find the unit and partition numbers.
*/
} else {
}
}
(C, "fdstart: bp=0x%p blkno=0x%x bcount=0x%x\n",
/*
* Get the csb and initialize the values that are the same
* for DMA and PIO.
*/
/*
* bugID:4133425 : If the controller is SLAVIO, and
* the read does not reach end of track, then modify
* the tlen to read until the end of track to a temp
* buffer and disable MT. After the read is over,
* copy the useful portion of the data to 'addr'.
* Enable this feature only when
* slavio_index_pulse_work_aound variable is
*/
else
} else {
else
}
else
/*
* opflags for interrupt handler, et.al.
*/
/*
* Make sure the transfer does not go off the end
* of the partition. Limit the actual amount transferred
* to fit the partition.
*/
blk = phys_blkno;
else
/*
* now we have the real start blk,
* addr and len for xfer op
* sectors per cylinder
*/
/*
* The controller can transfer up to a cylinder at a time.
* Early revs of the 82077 have a bug that causes the chip to
* fail to respond to the Terminal Count signal. Due to this
* bug, controllers with type FDCTYPE_TCBUG, only transfer up
* to a track at a time.
* See earlier comment for bugID:4133425 for index pulse
* work around.
*/
while (len != 0) {
/*
* If the desired block and length will go beyond the
* cylinder end, limit it to the cylinder end.
*/
partial_read = 1;
KM_SLEEP);
}
ch->fdc_sec_size))
* ch->fdc_sec_size;
} else {
* ch->fdc_sec_size))
* ch->fdc_sec_size;
else
}
/*
* To avoid underrun errors during IFB activity.
*/
if (tlen > max_fd_dma_len)
}
(C, " blk 0x%x, addr 0x%p, len 0x%x\n",
(C, "cyl:%x, head:%x, sec:%x\n",
(C, " resid 0x%lx, tlen %d\n",
/*
* Finish programming the command
*/
if (partial_read)
else
/* retry this many times max */
csb->csb_retrys = 0;
/* If platform supports DMA, set up DMA resources */
0xFFFF0000))) {
sb_temp_buf_used = 1;
}
}
tlen) != 0) {
(C, "fdstart: no dma resources\n"));
break;
}
}
/*
* error in fdexec
*/
"fdstart: bad exec of bp: 0x%p, err %d\n",
if (partial_read) {
partial_read = 0;
}
break;
}
/*
* If it was a partial read, copy the useful
* portion of data to 'addr'.
*/
if (partial_read) {
partial_read = 0;
}
if (sb_temp_buf_used) {
sb_temp_buf_used = 0;
}
}
}
(C, "fdstart done: b_resid %lu, b_count %lu, csb_rlen %d\n",
} else {
}
}
/*
* Look at the next buffer
*/
}
}
/*
* Set up DMA resources
* The DMA handle was initialized in fd_attach()
* Assumes the handle has already been allocated by fd_attach()
*/
static int
{
int res;
} else {
}
/* allow partial mapping to maximize the portability of the driver */
len));
/*
* Zero out the current cookie. This is done to ensure that
* the previous transfers cookie information can in no way be
* used.
*/
switch (res) {
case DDI_DMA_MAPPED:
/*
* There is one window. csb_windex is the index
* into the array of windows. If there are n
* windows then, (0 <= windex <= n-1). csb_windex
* represents the index of the next window
* to be processed.
*/
(C, "fdstart_dma: DDI_DMA_MAPPED\n"));
break;
case DDI_DMA_PARTIAL_MAP:
/*
* obtain the number of DMA windows
*/
return (-1);
}
(C, "fdstart_dma: partially mapped %d windows\n",
/*
* The DMA window currently in use is window number
* one.
*/
break;
case DDI_DMA_NORESOURCES:
(C, "fdstart_dma: no resources\n"));
return (-1);
case DDI_DMA_NOMAPPING:
(C, "fdstart_dma: no mapping\n"));
return (-1);
case DDI_DMA_TOOBIG:
(C, "fdstart_dma: too big\n"));
return (-1);
case DDI_DMA_INUSE:
(C, "fdstart_dma: dma inuse\n"));
return (-1);
default:
(C, "fdstart_dma: result is 0x%x\n", res));
return (-1);
};
(C, "fdstart_dma: bound the handle\n"));
return (0);
}
/*
* fd_unbind_handle: unbind a dma handle if one exists
* return EIO if unbind failes
*/
static int
{
if (fdc->sb_dma_lock) {
}
}
/*
* If the byte count isn't zero, then the DMA engine is
* still doing a transfer. If the byte count is nonzero,
* reset the DMA engine to cause it to drain.
*/
if (get_data_count_register(fdc) != 0) {
(C, "unbind & byte count isn't zero\n"));
}
(C, "problem unbinding the handle\n"));
return (EIO);
}
}
return (0);
}
/*
* fdexec
* all commands go through here. Assumes the command block
* fdctlr.c_csb is filled in. The bytes are sent to the
* controller and then we do whatever else the csb says -
* like wait for immediate results, etc.
*
* All waiting for operations done is in here - to allow retrys
* and checking for disk changed - so we don't have to worry
* about sleeping at interrupt level.
*
* RETURNS: 0 if all ok,
* ENXIO - diskette not in drive
* EBUSY - if chip is locked or busy
* EIO - for timeout during sending cmds to chip
*
* to sleep: set FDXC_SLEEP, to check for disk
* changed: set FDXC_CHECKCHG
*
* - called with the lock held
*/
static int
{
int i;
}
/*
*/
case 500:
break;
case 300:
break;
case 250:
break;
}
drv_usecwait(2);
/*
* If checking for changed is enabled (i.e., not seeking in checkdisk),
* we sample the DSKCHG line to see if the diskette has wandered away.
*/
(void) fd_unbind_handle(fdc);
return (ENXIO);
}
}
/*
* gather some statistics
*/
case FDRAW_RDCMD:
break;
case FDRAW_WRCMD:
break;
case FDRAW_REZERO:
break;
case FDRAW_FORMAT:
break;
default:
break;
}
/*
* Always set the opmode *prior* to poking the chip.
* This way we don't have to do any locking at high level.
*/
csb->csb_opmode = 0;
} else {
}
csb->csb_status = 0;
csb->csb_cmdstat = 0;
/*
* Program the DMA engine with the length and address of the transfer
* (DMA is only used on a read or a write)
*/
/* Reset the dcsr to clear it of all errors */
/* Program the DCSR */
else
}
/*
* I saw this (chip unexpectedly busy) happen when i shoved the
* floppy into the drive while
* we need to do a ctlr reset ...
*/
/* tried to give command to chip when it is busy! */
(void) fd_unbind_handle(fdc);
return (EBUSY);
}
/* Give command to the controller */
/* Test the readiness of the controller to receive the cmd */
break;
}
if (to == 0) {
(void) fd_unbind_handle(fdc);
return (EIO);
}
}
/*
* Start watchdog timer on data transfer type commands - required
* in case a diskette is not present or is unformatted
*/
}
/* If the operation has no results - then just return */
}
}
/*
* Make sure the last byte is received well by the
* controller. On faster CPU, it may still be busy
* by the time another command comes here.
*/
break;
}
if (to == 0) {
return (EIO);
}
/*
* An operation that has no results isn't doing DMA so,
* there is no reason to try to unbind a handle
*/
return (0);
}
/*
* If this operation has no interrupt AND an immediate result
* then we just busy wait for the results and stuff them into
* the csb
*/
csb->csb_nrslts = 0;
/*
* Wait while this command is still going on.
*/
/*
* If RQM + DIO, then a result byte is at hand.
*/
/*
* FDERRPRINT(FDEP_L4, FDEM_EXEC,
* (C, "fdexec: got result 0x%x\n",
* csb->csb_nrslts));
*/
} else if (--to == 0) {
(C, "fdexec: timeout, Msr%x, nr%x\n",
fdmotoff, a, Motoff_delay);
}
}
/*
* There is no DMA happening. No need to
* try freeing a handle.
*/
return (EIO);
}
}
}
/*
* If told to sleep here, well then sleep!
*/
if (flags & FDXC_SLEEP) {
}
}
/*
* kludge for end-of-cylinder error which must be ignored!!!
*/
/*
* See if there was an error detected, if so, fdrecover()
* will check it out and say what to do.
*
* Don't do this, though, if this was the Sense Drive Status
* or the Dump Registers command.
*/
(csb->csb_status)) &&
/* if it can restarted OK, then do so, else return error */
a, Motoff_delay);
}
}
/*
* If this was a dma transfer, unbind the handle so
* that other transfers may use it.
*/
(void) fd_unbind_handle(fdc);
return (EIO);
} else {
/* ASSUMES that cmd is still intact in csb */
goto retry;
}
}
/* things went ok */
}
}
if (fd_unbind_handle(fdc))
return (EIO);
return (0);
}
/*
* Turn on the drive's motor
*
* - called with the low level lock held
*/
static void
{
/*
* The low level mutex may not be held over the call to
* untimeout(). See the manpage for details.
*/
if (timeid) {
}
/*
* Turn on the motor
*/
(C, "fdexec: turning on motor\n"));
/* LINTED */
if (flags & FDXC_SLEEP) {
local_lbolt = ddi_get_lbolt();
} else {
drv_usecwait(1000000);
}
}
}
/*
* fdrecover
* see if possible to retry an operation.
* All we can do is restart the operation. If we are out of allowed
* retries - return non-zero so that the higher levels will be notified.
*
* RETURNS: 0 if ok to restart, !0 if can't or out of retries
* - called with the low level lock held
*/
static int
{
/* use private csb */
/* Reset the DMA engine as well */
}
/* check change first?? */
/* don't ckchg in fdexec, too convoluted */
}
/*
* gather statistics on errors
*/
}
}
}
}
/*
* If raw ioctl don't examine results just pass status
* back via fdraw. Raw commands are timed too, so put this
* after the above check.
*/
return (1);
}
/*
* if there was a pci bus error, do not retry
*/
(C, "fd%d: host bus error\n", 0));
return (1);
}
/*
* If there was an error with the DMA functions, do not retry
*/
return (1);
}
/*
* if we have run out of retries, return an error
* XXX need better status interp
*/
csb->csb_retrys++;
(C, "fd%d: %s failed (%x %x %x)\n",
(C, "fd%d: not writable\n", 0));
}
(C, "fd%d: crc error blk %d\n", 0,
}
/*
* When using southbridge chip we need to
* retry atleast 10 times to shake off the
* underrun err.
*/
return (0);
}
}
(C, "fd%d: bad format\n", 0));
}
(C, "fd%d: timeout\n", 0));
}
return (1);
}
/* seek, recal type commands - just look at st0 */
}
/* rd, wr, fmt type commands - look at st0, st1, st2 */
(C, "fd%d: %s error : st0=0x%x st1=0x%x st2=0x%x\n",
}
return (0); /* tell fdexec to retry */
}
/*
* Interrupt handle for DMA
*/
static uint_t
{
int tmp_dcsr;
int to;
int i = 0;
/* search for a controller that's expecting an interrupt */
return (res);
not_cheerio = 0;
}
}
if (fdc->sb_dma_lock) {
}
/*
* An interrupt can come from either the floppy controller or
* or the DMA engine. The DMA engine will only issue an
* interrupt if there was an error.
*/
case 0x1:
(C, "fdintr_dma: opmode 1\n"));
/*
* See if the interrupt is from the floppy
* controller. If there is, take out the status bytes.
*/
(C, "fdintr_dma: INT_PEND \n"));
/* check status */
i = 0;
/*
* CB turns off once all the result bytes are
* read.
*
* NOTE: the counters are there so that the
* handler will never get stuck in a loop.
* If the counters do reach their maximum
* values, then a catastrophic error has
* occurred. This should never be the case.
* The counters only came into play during
* development.
*/
(i < 1000001)) {
/*
* If RQM + DIO, then a result byte
* is at hand.
*/
(C,
"fdintr_dma: res 0x%x\n",
- 1]));
} else if (--to == 0) {
/*
* controller was never
* ready to give results
*/
break;
}
i++;
}
if (i == 10000) {
(C, "First loop overran\n"));
}
}
/*
* See if the interrupt is from the DMA engine,
* which will only interrupt on an error
*/
done = 1;
(C, "fdintr_dma: Error pending\n"));
break;
}
/* TCBUG kludge */
}
/* Exit if there were errors in the DMA */
done = 1;
(C, "fdintr_dma: errors in command\n"));
break;
}
(C, "fdintr_dma: dbcr 0x%x\n",
/*
* The csb_ccount is the number of cookies that still
* need to be processed. A cookie was just processed
* so decrement the cookie counter.
*/
done = 1;
break;
}
/*
* If there are no more cookies and all the windows
* have been DMA'd, then DMA is done.
*
*/
done = 1;
/*
* The handle is unbound in fdexec
*/
break;
}
if (ccount != 0) {
/* process the next cookie */
(C, "cookie length %lu\n",
} else {
(C, "fdintr_dma: process %d window\n",
(C, "fdintr_dma: process no. cookies %d\n",
(C, "cookie length %lu\n",
}
/*
* Program the DMA engine with the length and
* the address of the transfer
*/
"fdintr_dma: size 0x%lx\n",
/* reprogram the controller */
/*
* Test the readiness of the controller
* to receive the cmd
*/
break;
}
if (to == 0) {
(C,
"fdc: no RQM - stat 0x%x\n",
/* stop the DMA from happening */
done = 1;
break;
}
(C,
"fdintr_dma: sent 0x%x, Msr 0x%x\n",
}
/* reenable DMA */
if ((!not_cheerio) && (!done))
break;
case 0x2:
(C, "fintr_dma: opmode 2\n"));
/*
* See if the interrupt is from the DMA engine,
* which will only interrupt if there was an error.
*/
done = 1;
break;
}
/* See if the interrupt is from the floppy controller */
/*
* Wait until there's no longer a command
* in progress
*/
(C, "fdintr_dma: interrupt pending\n"));
i = 0;
i++;
}
if (i == 10000)
(C, "2nd loop overran !!!\n"));
/*
* Check the RQM bit to see if the controller is
* ready to transfer status of the command.
*/
i = 0;
i++;
}
if (i == 10000)
(C, "3rd loop overran !!!\n"));
/*
* Issue the Sense Interrupt Status Command
*/
i = 0;
i++;
}
if (i == 10000)
(C, "4th loop overran !!!\n"));
/* Store the first result byte */
i = 0;
i++;
}
if (i == 10000)
(C, "5th loop overran !!!\n"));
/* Store the second result byte */
done = 1;
}
}
/*
* We are done with the actual interrupt handling here.
* The portion below should be actually be done by fd_lointr().
* We should be triggering the fd_lointr here and exiting.
* However for want of time this will be done in the next FIX.
*
* Hence for now we will release hilock only and keep the remaining
* code as it is.
* Releasing of hilock ensures that we don't hold on to the
* lolock and hilock at the same time.
* hilock is acquired each time dma related registers are accessed.
*/
/* Make signal and get out of interrupt handler */
if (done) {
/* reset watchdog timer if armed and not already triggered */
}
/*
* wake them
*/
(C, "fdintr_dma: signal the waiter\n"));
/*
* FDCFLG_BUSY is NOT cleared, NOR is the csb given
* back; the operation just finished can look at the csb
*/
} else {
(C, "fdintr_dma: nobody sleeping (%x %x %x)\n",
}
}
/* update high level interrupt counter */
if (fdc->c_intrstat)
return (res);
}
/*
* fd_lointr
* This is the low level SW interrupt handler triggered by the high
* level interrupt handler (or by fdwatch).
*/
static uint_t
{
csb->csb_opmode));
/*
* Check that lowlevel interrupt really meant to trigger us.
*/
/*
* This should probably be protected, but, what the
* heck...the cost isn't worth the accuracy for this
* statistic.
*/
if (fdc->c_intrstat)
return (DDI_INTR_UNCLAIMED);
}
csb->csb_opmode = 0;
/* reset watchdog timer if armed and not already triggered */
}
/*
*/
/*
* FDCFLG_BUSY is NOT cleared, NOR is the csb given back; so
* the operation just finished can look at the csb
*/
} else {
(C, "fdintr: nobody sleeping (%x %x %x)\n",
}
if (fdc->c_intrstat)
return (DDI_INTR_CLAIMED);
}
/*
* fdwatch
* is called from timein() when a floppy operation has expired.
*/
static void
{
int old_opmode;
/*
* fdintr got here first, ergo, no timeout condition..
*/
(C, "fdwatch: no timeout\n"));
return;
}
/*
* XXXX: We should probably reset the bloody chip
*/
} else {
}
}
/*
* fdgetcsb
* wait until the csb is free
*/
static void
{
}
}
/*
* fdretcsb
* return csb
*/
static void
{
/*
* broadcast the signal. One thread will wake up and
* set the flags to FDCFLG_BUSY. If more than one thread is
* waiting then each thread will wake up in turn. The first
* thread to wake-up will set the FDCFLG_BUSY flag and the
* subsequent threads will will wake-up, but reset the
* flag to FDCFLG_WANT because the FDCFLG_BUSY bit is set.
*/
}
}
/*
* fdreset
* reset THE controller, and configure it to be
* the way it ought to be
*
* - called with the low level lock held
*/
static int
{
/* count resets */
/*
* On the 82077, the DSR will clear itself after a reset. Upon exiting
* the reset, a polling interrupt will be generated. If the floppy
* interrupt is enabled, it's possible for cv_signal() to be called
* before cv_wait(). This will cause the system to hang. Turn off
* the floppy interrupt to avoid this race condition
*/
/*
* We need to perform any timeouts before we Reset the
* controller. We cannot afford to drop the c_lolock mutex after
* Resetting the controller. The reason is that we get a spate
* of interrupts until we take the controller out of reset.
* The way we avoid this spate of continuous interrupts is by
* holding on to the c_lolock and forcing the fdintr_dma routine
* to go to sleep waiting for this mutex.
*/
/* Do not hold the mutex across the untimeout call */
if (timeid) {
}
/* LINTED */
}
/* toggle software reset */
drv_usecwait(5);
(C, "fdreset: toggled software reset\n"));
/*
* This sets the data rate to 500Kbps (for high density)
* XXX should use current characteristics instead XXX
*/
drv_usecwait(5);
case FDCTYPE_82077:
/*
* when we bring the controller out of reset it will generate
* a polling interrupt. fdintr() will field it and schedule
* fd_lointr(). There will be no one sleeping but we are
* expecting an interrupt so....
*/
/*
* The reset bit must be cleared to take the 077 out of
* reset state and the DMAGATE bit must be high to enable
* interrupts.
*/
/* LINTED */
local_lbolt = ddi_get_lbolt();
return (-1);
}
break;
default:
/*
* A timed wait is not used because it's possible for the timer
* to go off before the controller has a chance to interrupt.
*/
break;
}
/* setup common things in csb */
csb->csb_nrslts = 0;
csb->csb_maxretry = 0;
csb->csb_retrys = 0;
/* send SPECIFY command to fdc */
/* csb->unit is don't care */
else
/* XXX for now ignore errors, they "CAN'T HAPPEN" */
/* no results */
/* send CONFIGURE command to fdc */
/* csb->unit is don't care */
csb->csb_retrys = 0;
/* XXX for now ignore errors, they "CAN'T HAPPEN" */
return (0);
}
/*
* fdrecalseek
* performs recalibrates or seeks if the "arg" is -1 does a
* recalibrate on a drive, else it seeks to the cylinder of
* the drive. The recalibrate is also used to find a drive,
* ie if the drive is not there, the controller says "error"
* on the operation
* NOTE: that there is special handling of this operation in the hardware
* interrupt routine - it causes the operation to appear to have results;
* ie the results of the SENSE INTERRUPT STATUS that the hardware interrupt
* function did for us.
* section so create one before calling it!
*
* RETURNS: 0 for ok,
* else errno from fdexec,
* or ENODEV if error (infers hardware type error)
*
* - called with the low level lock held
*/
static int
{
int result;
/* XXX TODO: check see argument for <= num cyls OR < 256 */
} else {
}
/*
* MAYBE NYD need to set retries to different values? - depending on
* drive characteristics - if we get to high capacity drives
*/
csb->csb_retrys = 0;
/* send cmd off to fdexec */
goto out;
}
/*
* if recal, test for equipment check error
* ASSUMES result = 0 from above call
*/
if (arg == -1) {
result = 0;
} else {
/* for seeks, any old error will do */
}
out:
return (result);
}
/*
* fdsensedrv
* do a sense_drive command. used by fdopen and fdcheckdisk.
*
* - called with the lock held
*/
static int
{
/* setup common things in csb */
/* MOT bit set means don't delay */
csb->csb_retrys = 0;
/* XXX for now ignore errors, they "CAN'T HAPPEN" */
}
/*
* fdcheckdisk
* check to see if the disk is still there - do a recalibrate,
* then see if DSKCHG line went away, if so, diskette is in; else
* it's (still) out.
*/
static int
{
(C, "fdcheckdisk, unit %d\n", unit));
/*
* save old csb
*/
/*
* Read drive status to see if at TRK0, if so, seek to cyl 1,
* else seek to cyl 0. We do this because the controller is
* "smart" enough to not send any step pulses (which are how
* the DSKCHG line gets reset) if it sees TRK0 'cause it
* knows the drive is already recalibrated.
*/
/* check TRK0 bit in status */
else
seekto = 0;
/*
* DON'T recurse check changed
*/
/* "restore" old csb, check change state */
if (err) {
(C, "fdcheckdisk err %d\n", err));
return (err);
}
/*
* if disk change still asserted, no diskette in drive!
*/
(C, "fdcheckdisk no disk\n"));
return (1);
}
return (0);
}
/*
* fdselect() - select drive, needed for external to chip select logic
* fdeject() - ejects drive, must be previously selected
* fdsense_chng() - sense disk changed line from previously selected drive
* return s 1 is signal asserted, else 0
*/
/* ARGSUSED */
static void
{
case FDCTYPE_MACHIO:
break;
case FDCTYPE_SLAVIO:
case FDCTYPE_CHEERIO:
if (unit == 0) {
} else {
}
break;
default:
break;
}
}
/* ARGSUSED */
static void
{
/*
* assume delay of function calling sufficient settling time
* eject line is NOT driven by inverter so it is true low
*/
case FDCTYPE_MACHIO:
set_auxioreg(AUX_EJECT, 0);
drv_usecwait(2);
break;
case FDCTYPE_SLAVIO:
/* LINTED */
}
/* LINTED */
drv_usecwait(2);
/* LINTED */
break;
case FDCTYPE_CHEERIO:
/* LINTED */
}
/* LINTED */
drv_usecwait(2);
/* LINTED */
break;
}
/*
* XXX set ejected state?
*/
}
/* ARGSUSED */
static int
{
int changed = 0;
/*
* Do not turn on the motor of a pollable drive
*/
if (fd_pollable) {
/*
* Invert the sense of the DSKCHG for pollable drives
*/
changed = 0;
else
changed = 1;
return (changed);
}
case FDCTYPE_MACHIO:
changed = 1;
break;
case FDCTYPE_SB:
case FDCTYPE_SLAVIO:
case FDCTYPE_CHEERIO:
/* LINTED */
}
changed = 1;
break;
}
return (changed);
}
/*
* if it can read a valid label it does so, else it will use a
* default. If it can`t read the diskette - that is an error.
*
* RETURNS: 0 for ok - meaning that it could at least read the device,
* !0 for error XXX TBD NYD error codes
*
* - called with the low level lock held
*/
static int
{
short *sp;
short count;
int i, tries;
int err = 0;
short oldlvl;
(C, "fdgetlabel: unit %d\n", unit));
/* Do not print errors since this is a private cmd */
oldlvl = fderrlevel;
/*
* try different characteristics (ie densities) by attempting to read
* from the diskette. The diskette may not be present or
* is unformatted.
*
* First, the last sector of the first track is read. If this
* passes, attempt to read the last sector + 1 of the first track.
* For example, for a high density diskette, sector 18 is read. If
* the diskette is high density, this will pass. Next, try to
* read sector 19 of the first track. This should fail. If it
* passes, this is not a high density diskette. Finally, read
* the first sector which should contain a label.
*
* if un->un_curfdtype is -1 then the current characteristics
* were set by FDIOSCHAR and need to try it as well as everything
* in the table
*/
(C, "fdgetl: un_curfdtype is -1\n"));
} else {
/* Always start with the highest density (1.7MB) */
un->un_curfdtype = 0;
}
(C, "fdgetl: no. of tries %d\n", tries));
for (i = 0; i < tries; i++) {
(C, "fdgetl: trying %d\n", i));
sizeof (struct dk_label))) &&
sizeof (struct dk_label)))) {
(C, "fdgetl: succeeded\n"));
break;
}
/*
* try the next entry in the characteristics tbl
* If curfdtype is -1, the nxt entry in tbl is 0 (the first).
*/
}
/* print errors again */
fderrlevel = oldlvl;
/* Couldn't read anything */
if (err) {
/* The default characteristics are high density (1.4MB) */
(C, "fdgetl: Can't autosense diskette\n"));
goto out;
}
(C, "fdgetl: rate=%d ssize=%d !!!\n",
/*
* _something_ was read - look for unixtype label
*/
/*
* The label isn't a unix label. However, the diskette
* is formatted because we were able to read the first
* cylinder.
*/
(C, "fdgetl: not unix label\n"));
goto nolabel;
}
/*
* Checksum the label
*/
xsum = 0;
while (count--)
if (xsum) {
/*
* The checksum fails. However, the diskette is formatted
* because we were able to read the first cylinder
*/
(C, "fdgetl: bad cksum\n"));
goto nolabel;
}
/*
* The diskette has a unix label with a correct checksum.
* Copy the label into the unit structure
*/
goto out;
/*
* The diskette doesn't have a correct unix label, but it is formatted.
* Use a default label according to the diskette's density
* (mark default used)
*/
(C, "fdgetlabel: unit %d\n", unit));
case 9:
break;
case 8:
break;
case 18:
break;
case 21:
break;
default:
break;
}
out:
return (err);
}
/*
* fdrw- used only for reading labels and for DKIOCSVTOC ioctl
* which reads the 1 sector.
*/
static int
{
int cmdresult = 0;
int res;
!= DDI_SUCCESS) {
failed. \n"));
return (EIO);
}
}
/*
* kludge for lack of Multitrack functionality
*/
} else
} else { /* write */
/*
* kludge for lack of Multitrack functionality
*/
} else
}
else
/* always or in MFM bit */
/*
* kludge for end-of-cylinder error.
*/
else
csb->csb_retrys = 0;
/* If platform supports DMA, set up DMA resources */
&mem_handle);
if (res != DDI_SUCCESS) {
(C, "fdrw: dma mem alloc failed\n"));
return (EIO);
}
return (-1);
}
/*
* If the command is a write, copy the data to be written to
* dma_addr.
*/
}
} else {
}
if (mem_handle)
return (EIO);
}
/*
* if DMA was used and the command was a read
* copy the results into bufp
*/
}
}
if (csb->csb_cmdstat)
return (cmdresult);
}
/*
* fdunpacklabel
* this unpacks a (packed) struct dk_label into a standard dk_label.
*/
static void
{
sizeof (to->dkl_asciilabel));
/* logical partitions */
}
static struct fdctlr *
{
while (fdc) {
return (fdc);
}
return (fdc);
}
static int
{
int i;
for (i = 0; i < NDKMAP; i++)
if (un->un_lyropen[i])
return (1);
for (i = 0; i < OTYPCNT - 1; i++)
if (un->un_regopen[i])
return (1);
return (0);
}
/*
* Return the a vtoc structure in *vtoc.
* The vtoc is built from information in
* the diskette's label.
*/
static void
{
int i;
/* Initialize info. needed by mboot. (unsupported) */
/* Fill in vtoc sanity and version information */
/* Copy the volume name */
/*
* The dk_map32 structure is based on DEV_BSIZE byte blocks.
* However, medium density diskettes have 1024 byte blocks.
* The number of sectors per partition listed in the dk_map32 structure
* accounts for this by multiplying the number of 1024 byte
* blocks by 2. (See the packed_label initializations.) The
* 1024 byte block size can not be listed for medium density
* diskettes because the kernel is hard coded for DEV_BSIZE
* blocks.
*/
/* Copy the reserved space */
/*
* Convert partitioning information.
*
* Note the conversion from starting cylinder number
* to starting sector number.
*/
for (i = 0; i < V_NUMPAR; i++) {
lmap++;
lpart++;
vpart++;
}
/* Initialize timestamp and label */
}
/*
* Build a label out of a vtoc structure.
*/
static int
{
int ncyl;
int i;
/* Sanity-check the vtoc */
(C, "fd_build_label: sanity check on vtoc failed\n"));
return (EINVAL);
}
/*
* Check the partition information in the vtoc. The starting sectors
* must lie along partition boundaries. (NDKMAP entries are checked
* to ensure that the unused entries are set to 0 if vtoc->v_nparts
* is less than NDKMAP)
*/
for (i = 0; i < NDKMAP; i++) {
return (EINVAL);
}
ncyl++;
return (EINVAL);
}
vpart++;
}
/*
* reinitialize the existing label
*/
/* Put appropriate vtoc structure fields into the disk label */
/*
* Initialize cylinder information in the label.
* Note the conversion from starting sector number
* to starting cylinder number.
* Return error if division results in a remainder.
*/
lmap++;
lpart++;
vpart++;
}
/* Copy the timestamp and ascii label */
for (i = 0; i < NDKMAP; i++) {
}
(C, "fd_build_label: asciilabel %s\n",
/* Initialize the magic number */
/*
* The fdc_secptrack filed of the fd_char structure is the number
* of sectors per track where the sectors are fdc_sec_size. The
* dkl_nsect field of the dk_label structure is the number of
* 512 (DEVBSIZE) byte sectors per track.
*/
/* Create the checksum */
sum = 0;
i = sizeof (struct dk_label)/sizeof (short);
while (i--) {
}
return (0);
}
/*
* Check for auxio register node
*/
int
{
return (1);
}
return (0);
}
/*
* Search for auxio register node, then for address property
*/
{
/*
* Search sibling list, which happens to be safe inside attach
*/
while (auxdip) {
if (fd_isauxiodip(auxdip))
break;
}
return (NULL);
return (addr);
}
/*
* set_rotational speed
* 300 rpm for high and low density.
* 360 rpm for medium density.
* for now, we assume that 3rd density is supported only for Sun4M,
* not for Clones. (else we would have to check for 82077, and do
* specific things for the MEDIUM_DENSITY BIT for clones.
* this code should not break CLONES.
*
* REMARK: there is a SOny requirement, to deselect the drive then
* select it again after the medium density change, since the
* leading edge of the select line latches the rotational Speed.
* then after that, we have to wait 500 ms for the rotation to
* stabilize.
*
*/
static void
{
int check;
int is_medium;
/*
* if we do not have a Sun4m, medium density is not supported.
*/
return;
/*
* if FDUNIT_SET_SPEED is set, set the speed.
* else,
* if there is a change, do it, if not leave it alone.
* there is a change if un->un_chars->fdc_medium does not match
* un->un_flags & FDUNIT_MEDIUM
* un->un_flags & FDUNIT_MEDIUM specifies the last setting.
* un->un_chars->fdc_medium specifies next setting.
* if there is a change, wait 500ms according to Sony spec.
*/
check = 1;
} else {
/* Set the un_flags if necessary */
if (check)
}
if (check) {
drv_usecwait(5);
}
if (is_medium) {
} else {
}
}
if (is_medium) {
drv_usecwait(5);
}
drv_usecwait(500000);
}
}
static void
{
int unit;
if (un->un_media_timeout_id == 0) {
/*
* Untimeout is about to be called.
* Don't call fd_get_media_state again
*/
return;
}
if (un->un_media_timeout) {
}
}
enum dkio_state
{
/* check disk only if DSKCHG "high" */
} else {
}
} else {
}
return (state);
}
static int
{
int unit;
!= DDI_SUCCESS) {
failed. \n"));
return (EIO);
}
}
/* turn on timeout */
un->un_media_timeout = 0;
return (EINTR);
}
}
if (un->un_media_timeout_id) {
un->un_media_timeout_id = 0;
}
return (EIO);
}
}
return (0);
}
/*
* fd_get_media_info :
* Collects medium information for
* DKIOCGMEDIAINFO ioctl.
*/
static int
{
int err = 0;
return (err);
}
/*
* fd_power :
* Power entry point of fd driver.
*/
static int
{
int instance;
int rval;
(component != 0)) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
if (level == PM_LEVEL_OFF) {
}
if (level == PM_LEVEL_ON) {
}
return (rval);
}
/*
* fd_pm_lower_power :
* This function is called only during pm suspend. At this point,
* the power management framework thinks the device is idle for
* long enough to go to a low power mode. If the device is busy,
* then this function returns DDI_FAILURE.
*/
static int
{
return (DDI_SUCCESS);
}
/* if the device is busy then we fail the lower power request */
controller is busy.\n"));
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* fd_pm_raise_power :
* This function performs the necessary steps for resuming a
* device, either from pm suspend or CPR. Here the controller
* is reset, initialized and the state is set to FD_STATE_NORMAL.
*/
static int
{
int unit;
/* Reset the dma engine */
}
/*
* Force a rotational speed set in the next
* call to set_rotational_speed().
*/
/* Reset and configure the controller */
/* Recalibrate the drive */
failed\n"));
return (DDI_FAILURE);
}
/* Select the drive through the AUXIO registers */
return (DDI_SUCCESS);
}
/*
* create_pm_components :
* creates the power management components for auto pm framework.
*/
static void
{
}
}
/*
* set_data_count_register(struct fdctlr *fdc, uint32_t count)
* Set the data count in appropriate dma register.
*/
static void
{
switch (fdc->sb_dma_channel) {
case 0 :
count & 0xFFFF);
break;
case 1 :
count & 0xFFFF);
break;
case 2 :
count & 0xFFFF);
break;
case 3 :
count & 0xFFFF);
break;
default :
(C, "set_data_count: wrong channel %x\n",
fdc->sb_dma_channel));
break;
}
}
}
/*
* get_data_count_register(struct fdctlr *fdc)
* Read the data count from appropriate dma register.
*/
static uint32_t
{
switch (fdc->sb_dma_channel) {
case 0 :
break;
case 1 :
break;
case 2 :
break;
case 3 :
break;
default :
(C, "get_data_count: wrong channel %x\n",
fdc->sb_dma_channel));
break;
}
}
return (retval);
}
/*
* reset_dma_controller(struct fdctlr *fdc)
* Reset and initialize the dma controller.
*/
static void
{
;
}
}
/*
* Get the DMA control register for CHEERIO.
* For SouthBridge 8237 DMA controller, this register is not valid.
* So, just return 0.
*/
static uint32_t
{
}
return (retval);
}
/*
* set_data_address_register(struct fdctlr *fdc)
* Set the data address in appropriate dma register.
*/
static void
{
switch (fdc->sb_dma_channel) {
case 0 :
address & 0xFFFF);
break;
case 1 :
address & 0xFFFF);
break;
case 2 :
address & 0xFFFF);
break;
case 3 :
address & 0xFFFF);
break;
default :
(C, "set_data_address: wrong channel %x\n",
fdc->sb_dma_channel));
break;
}
}
}
/*
* set_dma_mode(struct fdctlr *fdc, int val)
* Set the appropriate dma direction and registers.
*/
static void
{
else
} else { /* Read operation */
}
}
}
/*
* controllers. The control register for the dma channel
* is initialized by this function.
*/
static void
{
}
}
static void
{
/* Unmask all the channels to release the DMA controller */
fdc->sb_dma_lock = 0;
}
static void
{
/*
* The following code is put here to take care of HW problem.
* The HW problem is as follows:
*
* After poweron the Southbridge floppy controller asserts the
* interrupt in tristate. This causes continuous interrupts to
* be generated.
* Until the Hardware is FIXED we will have to use the following code
* to set the interrupt line to proper state after poweron.
*/
0x0);
drv_usecwait(200);
0xC);
drv_usecwait(200);
drv_usecwait(200);
}
}