📄 floppy.c
字号:
/* Block, waiting for disk interrupt. */
if (need_reset) return(ERR_TRANSFER); /* if controller is sick, abort op */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Get controller status and check for errors. */
r = fdc_results();
if (r != OK) return(r);
if (f_results[ST1] & WRITE_PROTECT) {
printf("%s: diskette is write protected.\n", f_name());
return(ERR_WR_PROTECT);
}
if ((f_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_TRANSFER);
if (f_results[ST1] | f_results[ST2]) return(ERR_TRANSFER);
if (f_device & FORMAT_DEV_BIT) return(OK);
/* Compare actual numbers of sectors transferred with expected number. */
s = (f_results[ST_CYL] - fp->fl_cylinder) * NR_HEADS * f_sectors;
s += (f_results[ST_HEAD] - fp->fl_head) * f_sectors;
s += (f_results[ST_SEC] - fp->fl_sector);
if ((s << SECTOR_SHIFT) != tp->tr_count) return(ERR_TRANSFER);
/* This sector is next for I/O: */
fp->fl_sector = f_results[ST_SEC];
return(OK);
}
/*==========================================================================*
* fdc_results *
*==========================================================================*/
PRIVATE int fdc_results()
{
/* Extract results from the controller after an operation, then allow floppy
* interrupts again.
*/
int result_nr, status;
struct milli_state ms;
/* Extract bytes from FDC until it says it has no more. The loop is
* really an outer loop on result_nr and an inner loop on status.
*/
result_nr = 0;
milli_start(&ms);
do {
/* Reading one byte is almost a mirror of fdc_out() - the DIRECTION
* bit must be set instead of clear, but the CTL_BUSY bit destroys
* the perfection of the mirror.
*/
status = in_byte(FDC_STATUS) & (MASTER | DIRECTION | CTL_BUSY);
if (status == (MASTER | DIRECTION | CTL_BUSY)) {
if (result_nr >= MAX_RESULTS) break; /* too many results */
f_results[result_nr++] = in_byte(FDC_DATA);
continue;
}
if (status == MASTER) { /* all read */
enable_irq(FLOPPY_IRQ);
return(OK); /* only good exit */
}
} while (milli_elapsed(&ms) < TIMEOUT);
need_reset = TRUE; /* controller chip must be reset */
enable_irq(FLOPPY_IRQ);
return(ERR_STATUS);
}
/*==========================================================================*
* f_handler *
*==========================================================================*/
PRIVATE int f_handler(irq)
int irq;
{
/* FDC interrupt, send message to floppy task. */
interrupt(FLOPPY);
return 0;
}
/*===========================================================================*
* fdc_out *
*===========================================================================*/
PRIVATE void fdc_out(val)
int val; /* write this byte to floppy disk controller */
{
/* Output a byte to the controller. This is not entirely trivial, since you
* can only write to it when it is listening, and it decides when to listen.
* If the controller refuses to listen, the FDC chip is given a hard reset.
*/
struct milli_state ms;
if (need_reset) return; /* if controller is not listening, return */
/* It may take several tries to get the FDC to accept a command. */
milli_start(&ms);
while ((in_byte(FDC_STATUS) & (MASTER | DIRECTION)) != (MASTER | 0)) {
if (milli_elapsed(&ms) >= TIMEOUT) {
/* Controller is not listening. Hit it over the head. */
need_reset = TRUE;
return;
}
}
out_byte(FDC_DATA, val);
}
/*===========================================================================*
* recalibrate *
*===========================================================================*/
PRIVATE int recalibrate(fp)
struct floppy *fp; /* pointer tot he drive struct */
{
/* The floppy disk controller has no way of determining its absolute arm
* position (cylinder). Instead, it steps the arm a cylinder at a time and
* keeps track of where it thinks it is (in software). However, after a
* SEEK, the hardware reads information from the diskette telling where the
* arm actually is. If the arm is in the wrong place, a recalibration is done,
* which forces the arm to cylinder 0. This way the controller can get back
* into sync with reality.
*/
int r;
/* Issue the RECALIBRATE command and wait for the interrupt. */
start_motor(); /* can't recalibrate with motor off */
fdc_out(FDC_RECALIBRATE); /* tell drive to recalibrate itself */
fdc_out(f_drive); /* specify drive */
if (need_reset) return(ERR_SEEK); /* don't wait if controller is sick */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Determine if the recalibration succeeded. */
fdc_out(FDC_SENSE); /* issue SENSE command to request results */
r = fdc_results(); /* get results of the FDC_RECALIBRATE command*/
fp->fl_curcyl = NO_CYL; /* force a SEEK next time */
if (r != OK || /* controller would not respond */
(f_results[ST0] & ST0_BITS) != SEEK_ST0 || f_results[ST_PCN] != 0) {
/* Recalibration failed. FDC must be reset. */
need_reset = TRUE;
return(ERR_RECALIBRATE);
} else {
/* Recalibration succeeded. */
fp->fl_calibration = CALIBRATED;
return(OK);
}
}
/*===========================================================================*
* f_reset *
*===========================================================================*/
PRIVATE void f_reset()
{
/* Issue a reset to the controller. This is done after any catastrophe,
* like the controller refusing to respond.
*/
int i;
message mess;
/* Disable interrupts and strobe reset bit low. */
need_reset = FALSE;
/* It is not clear why the next lock is needed. Writing 0 to DOR causes
* interrupt, while the PC documentation says turning bit 8 off disables
* interrupts. Without the lock:
* 1) the interrupt handler sets the floppy mask bit in the 8259.
* 2) writing ENABLE_INT to DOR causes the FDC to assert the interrupt
* line again, but the mask stops the cpu being interrupted.
* 3) the sense interrupt clears the interrupt (not clear which one).
* and for some reason the reset does not work.
*/
lock();
motor_status = 0;
motor_goal = 0;
out_byte(DOR, 0); /* strobe reset bit low */
out_byte(DOR, ENABLE_INT); /* strobe it high again */
unlock();
receive(HARDWARE, &mess); /* collect the RESET interrupt */
/* The controller supports 4 drives and returns a result for each of them.
* Collect all the results now. The old version only collected the first
* result. This happens to work for 2 drives, but it doesn't work for 3
* or more drives, at least with only drives 0 and 2 actually connected
* (the controller generates an extra interrupt for the middle drive when
* drive 2 is accessed and the driver panics).
*
* It would be better to keep collecting results until there are no more.
* For this, fdc_results needs to return the number of results (instead
* of OK) when it succeeds.
*/
for (i = 0; i < 4; i++) {
fdc_out(FDC_SENSE); /* probe FDC to make it return status */
(void) fdc_results(); /* flush controller */
}
for (i = 0; i < NR_DRIVES; i++) /* clear each drive */
floppy[i].fl_calibration = UNCALIBRATED;
/* The current timing parameters must be specified again. */
current_spec1 = 0;
}
/*===========================================================================*
* send_mess *
*===========================================================================*/
PRIVATE void send_mess()
{
/* This routine is called when the clock task has timed out on motor startup.*/
message mess;
send(FLOPPY, &mess);
}
/*===========================================================================*
* f_intr_wait *
*===========================================================================*/
PRIVATE int f_intr_wait()
{
/* Wait for an interrupt, but not forever. The FDC may have all the time of
* the world, but we humans do not.
*/
message mess;
f_busy = BSY_IO;
clock_mess(WAKEUP, f_timeout);
receive(HARDWARE, &mess);
if (f_busy == BSY_WAKEN) {
/* No interrupt from the FDC, this means that there is probably no
* floppy in the drive. Get the FDC down to earth and return error.
*/
f_reset();
return(ERR_TIMEOUT);
}
f_busy = BSY_IDLE;
return(OK);
}
/*===========================================================================*
* f_timeout *
*===========================================================================*/
PRIVATE void f_timeout()
{
/* When it takes too long for the FDC to get an interrupt (no floppy in the
* drive), this routine is called. It sets a flag and fakes a hardware
* interrupt.
*/
if (f_busy == BSY_IO) {
f_busy = BSY_WAKEN;
interrupt(FLOPPY);
}
}
/*==========================================================================*
* read_id *
*==========================================================================*/
PRIVATE int read_id(fp)
struct floppy *fp; /* pointer to the drive struct */
{
/* Determine current cylinder and sector. */
int result;
/* Never attempt a read id if the drive is uncalibrated or motor is off. */
if (fp->fl_calibration == UNCALIBRATED) return(ERR_READ_ID);
if ((motor_status & (1 << f_drive)) == 0) return(ERR_READ_ID);
/* The command is issued by outputting 2 bytes to the controller chip. */
fdc_out(FDC_READ_ID); /* issue the read id command */
fdc_out( (f_fp->fl_head << 2) | f_drive);
/* Block, waiting for disk interrupt. */
if (need_reset) return(ERR_READ_ID); /* if controller is sick, abort op */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Get controller status and check for errors. */
result = fdc_results();
if (result != OK) return(result);
if ((f_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_READ_ID);
if (f_results[ST1] | f_results[ST2]) return(ERR_READ_ID);
/* The next sector is next for I/O: */
f_fp->fl_sector = f_results[ST_SEC] + 1;
return(OK);
}
/*==========================================================================*
* f_do_open *
*==========================================================================*/
PRIVATE int f_do_open(dp, m_ptr)
struct driver *dp;
message *m_ptr; /* pointer to open message */
{
/* Handle an open on a floppy. Determine diskette type if need be. */
int dtype;
struct test_order *top;
/* Decode the message parameters. */
if (f_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);
dtype = f_device & DEV_TYPE_BITS; /* get density from minor dev */
if (dtype >= MINOR_fd0a) dtype = 0;
if (dtype != 0) {
/* All types except 0 indicate a specific drive/medium combination.*/
dtype = (dtype >> DEV_TYPE_SHIFT) - 1;
if (dtype >= NT) return(ENXIO);
f_fp->fl_density = dtype;
f_fp->fl_geom.dv_size = (long) nr_blocks[dtype] << SECTOR_SHIFT;
return(OK);
}
if (f_device & FORMAT_DEV_BIT) return(EIO); /* Can't format /dev/fdx */
/* No need to test if the motor is still running. */
if (motor_status & (1 << f_drive)) return(OK);
/* The device opened is /dev/fdx. Experimentally determine drive/medium.
* First check fl_density. If it is not NO_DENS, the drive has been used
* before and the value of fl_density tells what was found last time. Try
* that first.
*/
if (f_fp->fl_density != NO_DENS && test_read(f_fp->fl_density) == OK)
return(OK);
/* Either drive type is unknown or a different diskette is now present.
* Use test_order to try them one by one.
*/
for (top = &test_order[0]; top < &test_order[NT-1]; top++) {
dtype = top->t_density;
/* Skip densities that have been proven to be impossible */
if (!(f_fp->fl_class & (1 << dtype))) continue;
if (test_read(dtype) == OK) {
/* The test succeeded, use this knowledge to limit the
* drive class to match the density just read.
*/
f_fp->fl_class &= top->t_class;
return(OK);
}
/* Test failed, wrong density or did it time out? */
if (f_busy == BSY_WAKEN) break;
}
f_fp->fl_density = NO_DENS;
return(EIO); /* nothing worked */
}
/*==========================================================================*
* test_read *
*==========================================================================*/
PRIVATE int test_read(density)
int density;
{
/* Try to read the highest numbered sector on cylinder 2. Not all floppy
* types have as many sectors per track, and trying cylinder 2 finds the
* ones that need double stepping.
*/
message m;
int r, device;
f_fp->fl_density = density;
device = ((density + 1) << DEV_TYPE_SHIFT) + f_drive;
f_fp->fl_geom.dv_size = (long) nr_blocks[density] << SECTOR_SHIFT;
m.m_type = DEV_READ;
m.DEVICE = device;
m.PROC_NR = FLOPPY;
m.COUNT = SECTOR_SIZE;
m.POSITION = (long) test_sector[density] * SECTOR_SIZE;
m.ADDRESS = (char *) tmp_buf;
r = do_rdwt(&f_dtab, &m);
if (r != SECTOR_SIZE) return(EIO);
partition(&f_dtab, f_drive, P_FLOPPY);
return(OK);
}
/*============================================================================*
* f_geometry *
*============================================================================*/
PRIVATE void f_geometry(entry)
struct partition *entry;
{
entry->cylinders = nr_blocks[d] / (NR_HEADS * f_sectors);
entry->heads = NR_HEADS;
entry->sectors = f_sectors;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -