// SPDX-License-Identifier: GPL-2.0-only /* * drivers/block/ataflop.c * * Copyright (C) 1993 Greg Harp * Atari Support by Bjoern Brauel, Roman Hodek * * Big cleanup Sep 11..14 1994 Roman Hodek: * - Driver now works interrupt driven * - Support for two drives; should work, but I cannot test that :-( * - Reading is done in whole tracks and buffered to speed up things * - Disk change detection and drive deselecting after motor-off * similar to TOS * - Autodetection of disk format (DD/HD); untested yet, because I * don't have an HD drive :-( * * Fixes Nov 13 1994 Martin Schaller: * - Autodetection works now * - Support for 5 1/4'' disks * - Removed drive type (unknown on atari) * - Do seeks with 8 Mhz * * Changes by Andreas Schwab: * - After errors in multiple read mode try again reading single sectors * (Feb 1995): * - Clean up error handling * - Set blk_size for proper size checking * - Initialize track register when testing presence of floppy * - Implement some ioctl's * * Changes by Torsten Lang: * - When probing the floppies we should add the FDCCMDADD_H flag since * the FDC will otherwise wait forever when no disk is inserted... * * ++ Freddi Aschwanden (fa) 20.9.95 fixes for medusa: * - MFPDELAY() after each FDC access -> atari * - more/other disk formats * - DMA to the block buffer directly if we have a 32bit DMA * - for medusa, the step rate is always 3ms * - on medusa, use only cache_push() * Roman: * - Make disk format numbering independent from minors * - Let user set max. supported drive type (speeds up format * detection, saves buffer space) * * Roman 10/15/95: * - implement some more ioctls * - disk formatting * * Andreas 95/12/12: * - increase gap size at start of track for HD/ED disks * * Michael (MSch) 11/07/96: * - implemented FDSETPRM and FDDEFPRM ioctl * * Andreas (97/03/19): * - implemented missing BLK* ioctls * * Things left to do: * - Formatting * - Maybe a better strategy for disk change detection (does anyone * know one?)
*/
/* * Maximum disk size (in kilobytes). This default is used whenever the * current disk size is unknown.
*/ #define MAX_DISK_SIZE 3280
/* * MSch: User-provided type information. 'drive' points to * the respective entry of this array. Set by FDSETPRM ioctls.
*/ staticstruct atari_disk_type user_params[FD_MAX_UNITS];
/* * User-provided permanent type information. 'drive' points to * the respective entry of this array. Set by FDDEFPRM ioctls, * restored upon disk change by floppy_revalidate() if valid (as seen by * default_params[].blocks > 0 - a bit in unit[].flags might be used for this?)
*/ staticstruct atari_disk_type default_params[FD_MAX_UNITS];
/* current info on each unit */ staticstruct atari_floppy_struct { int connected; /* !=0 : drive is connected */ int autoprobe; /* !=0 : do autoprobe */
struct atari_disk_type *disktype; /* current type of disk */
int track; /* current head position or -1 if
unknown */ unsignedint steprate; /* steprate setting */ unsignedint wpstat; /* current state of WP signal (for
disk change detection) */ int flags; /* flags */ struct gendisk *disk[NUM_DISK_MINORS]; bool registered[NUM_DISK_MINORS]; int ref; int type; struct blk_mq_tag_set tag_set; int error_count;
} unit[FD_MAX_UNITS];
#define UD unit[drive] #define UDT unit[drive].disktype #define SUD unit[SelectedDrive] #define SUDT unit[SelectedDrive].disktype
/* Buffering variables: * First, there is a DMA buffer in ST-RAM that is used for floppy DMA * operations. Second, a track buffer is used to cache a whole track * of the disk to save read operations. These are two separate buffers * because that allows write operations without clearing the track buffer.
*/
/* * These are global variables, as that's the easiest way to give * information to interrupts. They are the data used for the current * request.
*/ staticint SelectedDrive = 0; staticint ReqCmd, ReqBlock; staticint ReqSide, ReqTrack, ReqSector, ReqCnt; staticint HeadSettleFlag = 0; staticunsignedchar *ReqData, *ReqBuffer; staticint MotorOn = 0, MotorOffTrys; staticint IsFormatting = 0, FormatError;
#define FLOPPY_TIMEOUT (6*HZ) #define RECALIBRATE_ERRORS 4 /* After this many errors the drive
* will be recalibrated. */ #define MAX_ERRORS 8 /* After this many errors the driver
* will give up. */
/* * The driver is trying to determine the correct media format * while Probing is set. fd_rwsec_done() clears it after a * successful access.
*/ staticint Probing = 0;
/* This flag is set when a dummy seek is necessary to make the WP * status bit accessible.
*/ staticint NeedSeek = 0;
#ifdef DEBUG #define DPRINT(a) printk a #else #define DPRINT(a) #endif
/* protect against various other ints mucking around with the PSG */
local_irq_save(flags);
atari_dont_touch_floppy_select = 0;
sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
sound_ym.wd_data = (sound_ym.rd_data_reg_sel |
(MACH_IS_FALCON ? 3 : 7)); /* no drives selected */ /* On Falcon, the drive B select line is used on the printer port, so
* leave it alone... */
SelectedDrive = -1;
local_irq_restore(flags);
}
/* This timer function deselects the drives when the FDC switched the * motor off. The deselection cannot happen earlier because the FDC * counts the index signals, which arrive only if one drive is selected.
*/
if (SelectedDrive < 0) /* no drive selected, needn't deselect anyone */ return;
if (stdma_islocked()) goto retry;
status = FDC_READ( FDCREG_STATUS );
if (!(status & 0x80)) { /* motor already turned off by FDC -> deselect drives */
MotorOn = 0;
fd_deselect(); return;
} /* not yet off, try again */
retry: /* Test again later; if tested too often, it seems there is no disk * in the drive and the FDC will leave the motor on forever (or, * at least until a disk is inserted). So we'll test only twice * per second from then on...
*/
mod_timer(&motor_off_timer,
jiffies + (MotorOffTrys++ < FD_MOTOR_OFF_MAXTRY ? HZ/20 : HZ/2));
}
/* This function is repeatedly called to detect disk changes (as good * as possible) and keep track of the current state of the write protection.
*/
local_irq_save(flags);
stdma_lock(floppy_irq, NULL);
atari_turnon_irq( IRQ_MFP_FDC ); /* should be already, just to be sure */
local_irq_restore(flags);
if (type) {
type = minor2disktype[type].index;
UDT = &atari_disk_type[type];
}
if (!UDT || desc->track >= UDT->blocks/UDT->spt/2 || desc->head >= 2) {
finish_fdc();
ret = -EINVAL; goto out;
}
nsect = UDT->spt;
p = TrackBuffer; /* The track buffer is used for the raw track data, so its
contents become invalid! */
BufferDrive = -1; /* stop deselect timer */
timer_delete(&motor_off_timer);
/* do_fd_action() is the general procedure for a fd request: All * required parameter settings (drive select, side select, track * position) are checked and set if needed. For each of these * parameters and the actual reading or writing exist two functions: * one that starts the setting (or skips it if possible) and one * callback for the "done" interrupt. Each done func calls the next * set function to propagate the request down to fd_rwsec_done().
*/
staticvoid do_fd_action( int drive )
{
DPRINT(("do_fd_action\n"));
if (ATARIHW_PRESENT(FDCSPEED))
dma_wd.fdc_speed = 0; /* always seek with 8 Mhz */
DPRINT(("fd_calibrate\n"));
SET_IRQ_HANDLER( fd_calibrate_done ); /* we can't verify, since the speed may be incorrect */
FDC_WRITE( FDCREG_CMD, FDCCMD_RESTORE | SUD.steprate );
if (read_track) { /* If reading a whole track, wait about one disk rotation and * then check if all sectors are read. The FDC will even * search for the first non-existent sector and need 1 sec to * recognise that it isn't present :-(
*/
MultReadInProgress = 1;
mod_timer(&readtrack_timer, /* 1 rot. + 5 rot.s if motor was off */
jiffies + HZ/5 + (old_motoron ? 0 : HZ));
}
start_timeout();
}
if (!MultReadInProgress) { /* This prevents a race condition that could arise if the * interrupt is triggered while the calling of this timer * callback function takes place. The IRQ function then has * already cleared 'MultReadInProgress' when flow of control * gets here.
*/
local_irq_restore(flags); return;
}
/* get the current DMA address */ /* ++ f.a. read twice to avoid being fooled by switcher */
addr = 0; do {
addr2 = addr;
addr = dma_wd.dma_lo & 0xff;
MFPDELAY();
addr |= (dma_wd.dma_md & 0xff) << 8;
MFPDELAY(); if (ATARIHW_PRESENT( EXTD_DMA ))
addr |= (st_dma_ext_dmahi & 0xffff) << 16; else
addr |= (dma_wd.dma_hi & 0xff) << 16;
MFPDELAY();
} while(addr != addr2);
if (addr >= PhysTrackBuffer + SUDT->spt*512) { /* already read enough data, force an FDC interrupt to stop * the read operation
*/
SET_IRQ_HANDLER( NULL );
MultReadInProgress = 0;
local_irq_restore(flags);
DPRINT(("fd_readtrack_check(): done\n"));
FDC_WRITE( FDCREG_CMD, FDCCMD_FORCI );
udelay(25);
/* No error until now -- the FDC would have interrupted * otherwise!
*/
fd_rwsec_done1(0);
} else { /* not yet finished, wait another tenth rotation */
local_irq_restore(flags);
DPRINT(("fd_readtrack_check(): not yet finished\n"));
mod_timer(&readtrack_timer, jiffies + HZ/5/10);
}
}
staticvoid fd_rwsec_done( int status )
{
DPRINT(("fd_rwsec_done()\n"));
if (read_track) {
timer_delete(&readtrack_timer); if (!MultReadInProgress) return;
MultReadInProgress = 0;
}
fd_rwsec_done1(status);
}
MotorOn = 1;
start_timeout(); /* wait for interrupt */
}
staticvoid fd_writetrack_done( int status )
{
DPRINT(("fd_writetrack_done()\n"));
stop_timeout();
if (status & FDCSTAT_WPROT) {
printk(KERN_NOTICE "fd%d: is write protected\n", SelectedDrive ); goto err_end;
} if (status & FDCSTAT_LOST) {
printk(KERN_ERR "fd%d: lost data (side %d, track %d)\n",
SelectedDrive, ReqSide, ReqTrack ); goto err_end;
}
complete(&format_wait); return;
err_end:
fd_error();
}
staticvoid fd_times_out(struct timer_list *unused)
{
atari_disable_irq( IRQ_MFP_FDC ); if (!FloppyIRQHandler) goto end; /* int occurred after timer was fired, but
* before we came here... */
SET_IRQ_HANDLER( NULL ); /* If the timeout occurred while the readtrack_check timer was
* active, we need to cancel it, else bad things will happen */ if (UseTrackbuffer)
timer_delete(&readtrack_timer);
FDC_WRITE( FDCREG_CMD, FDCCMD_FORCI );
udelay( 25 );
/* The (noop) seek operation here is needed to make the WP bit in the * FDC status register accessible for check_change. If the last disk * operation would have been a RDSEC, this bit would always read as 0 * no matter what :-( To save time, the seek goes to the track we're * already on.
*/
staticvoid finish_fdc( void )
{ if (!NeedSeek || !stdma_is_locked_by(floppy_irq)) {
finish_fdc_done( 0 );
} else {
DPRINT(("finish_fdc: dummy seek started\n"));
FDC_WRITE (FDCREG_DATA, SUD.track);
SET_IRQ_HANDLER( finish_fdc_done );
FDC_WRITE (FDCREG_CMD, FDCCMD_SEEK);
MotorOn = 1;
start_timeout(); /* we must wait for the IRQ here, because the ST-DMA is released immediately afterwards and the interrupt
may be delivered to the wrong driver. */
}
}
staticvoid finish_fdc_done( int dummy )
{ unsignedlong flags;
if (timer_pending(&fd_timer) && time_before(fd_timer.expires, jiffies + 5)) /* If the check for a disk change is done too early after this * last seek command, the WP bit still reads wrong :-((
*/
mod_timer(&fd_timer, jiffies + 5); else
start_check_change_timer();
start_motor_off_timer();
local_irq_save(flags); if (stdma_is_locked_by(floppy_irq))
stdma_release();
local_irq_restore(flags);
DPRINT(("finish_fdc() finished\n"));
}
/* The detection of disk changes is a dark chapter in Atari history :-( * Because the "Drive ready" signal isn't present in the Atari * hardware, one has to rely on the "Write Protect". This works fine, * as long as no write protected disks are used. TOS solves this * problem by introducing tri-state logic ("maybe changed") and * looking at the serial number in block 0. This isn't possible for * Linux, since the floppy driver can't make assumptions about the * filesystem used on the disk and thus the contents of block 0. I've * chosen the method to always say "The disk was changed" if it is * unsure whether it was. This implies that every open or mount * invalidates the disk buffers if you work with write protected * disks. But at least this is better than working with incorrect data * due to unrecognised disk changes.
*/
staticunsignedint floppy_check_events(struct gendisk *disk, unsignedint clearing)
{ struct atari_floppy_struct *p = disk->private_data; unsignedint drive = p - unit; if (test_bit (drive, &fake_change)) { /* simulated change (e.g. after formatting) */ return DISK_EVENT_MEDIA_CHANGE;
} if (test_bit (drive, &changed_floppies)) { /* surely changed (the WP signal changed at least once) */ return DISK_EVENT_MEDIA_CHANGE;
} if (UD.wpstat) { /* WP is on -> could be changed: to be sure, buffers should be * invalidated...
*/ return DISK_EVENT_MEDIA_CHANGE;
}
switch (cmd) { case FDGETPRM: if (type) { if (--type >= NUM_DISK_MINORS) return -ENODEV; if (minor2disktype[type].drive_types > DriveType) return -ENODEV;
type = minor2disktype[type].index;
dtp = &atari_disk_type[type]; if (UD.flags & FTD_MSG)
printk (KERN_ERR "floppy%d: found dtp %p name %s!\n",
drive, dtp, dtp->name);
} else { if (!UDT) return -ENXIO; else
dtp = UDT;
}
memset((void *)&getprm, 0, sizeof(getprm));
getprm.size = dtp->blocks;
getprm.sect = dtp->spt;
getprm.head = 2;
getprm.track = dtp->blocks/dtp->spt/2;
getprm.stretch = dtp->stretch; if (copy_to_user(argp, &getprm, sizeof(getprm))) return -EFAULT; return 0;
} switch (cmd) { case FDSETPRM: case FDDEFPRM: /* * MSch 7/96: simple 'set geometry' case: just set the * 'default' device params (minor == 0). * Currently, the drive geometry is cleared after each * disk change and subsequent revalidate()! simple * implementation of FDDEFPRM: save geometry from a * FDDEFPRM call and restore it in floppy_revalidate() !
*/
/* get the parameters from user space */ if (floppy->ref != 1 && floppy->ref != -1) return -EBUSY; if (copy_from_user(&setprm, argp, sizeof(setprm))) return -EFAULT; /* * first of all: check for floppy change and revalidate, * or the next access will revalidate - and clear UDT :-(
*/
if (floppy_check_events(disk, 0))
floppy_revalidate(disk);
/* what if type > 0 here? Overwrite specified entry ? */ if (type) { /* refuse to re-set a predefined type for now */
finish_fdc(); return -EINVAL;
}
/* * type == 0: first look for a matching entry in the type list, * and set the UD.disktype field to use the perdefined entry. * TODO: add user-defined format to head of autoprobe list ? * Useful to include the user-type for future autodetection!
*/
for (settype = 0; settype < NUM_DISK_MINORS; settype++) { int setidx = 0; if (minor2disktype[settype].drive_types > DriveType) { /* skip this one, invalid for drive ... */ continue;
}
setidx = minor2disktype[settype].index;
dtp = &atari_disk_type[setidx];
if (cmd == FDDEFPRM) { /* save settings as permanent default type */
default_params[drive].name = dtp->name;
default_params[drive].spt = dtp->spt;
default_params[drive].blocks = dtp->blocks;
default_params[drive].fdc_speed = dtp->fdc_speed;
default_params[drive].stretch = dtp->stretch;
}
return 0;
}
}
/* no matching disk type found above - setting user_params */
if (cmd == FDDEFPRM) { /* set permanent type */
dtp = &default_params[drive];
} else /* set user type (reset by disk change!) */
dtp = &user_params[drive];
UD.connected = 1;
UD.track = 0; switch( UserSteprate[drive] ) { case 2:
UD.steprate = FDCSTEP_2; break; case 3:
UD.steprate = FDCSTEP_3; break; case 6:
UD.steprate = FDCSTEP_6; break; case 12:
UD.steprate = FDCSTEP_12; break; default: /* should be -1 for "not set by user" */ if (ATARIHW_PRESENT( FDCSPEED ) || MACH_IS_MEDUSA)
UD.steprate = FDCSTEP_3; else
UD.steprate = FDCSTEP_6; break;
}
MotorOn = 1; /* from probe restore operation! */
}
/* This function tests the physical presence of a floppy drive (not * whether a disk is inserted). This is done by issuing a restore * command, waiting max. 2 seconds (that should be enough to move the * head across the whole disk) and looking at the state of the "TR00" * signal. This should now be raised if there is a drive connected * (and there is no hardware failure :-) Otherwise, the drive is * declared absent.
*/
staticint __init fd_test_drive_present( int drive )
{ unsignedlong timeout; unsignedchar status; int ok;
if (FDC_READ( FDCREG_STATUS ) & FDCSTAT_BUSY) { /* If FDC is still busy from probing, give it another FORCI * command to abort the operation. If this isn't done, the FDC * will interrupt later and its IRQ line stays low, because * the status register isn't read. And this will block any * interrupts on this IRQ line :-(
*/
FDC_WRITE( FDCREG_CMD, FDCCMD_FORCI );
udelay(500);
FDC_READ( FDCREG_STATUS );
udelay(20);
}
if (cnt > 0) {
start_motor_off_timer(); if (cnt == 1) fd_select_drive( 0 );
start_check_change_timer();
}
}
/* * floppy_open check for aliasing (/dev/fd0 can be the same as * /dev/PS0 etc), and disallows simultaneous access to the same * drive with different device numbers.
*/
staticint floppy_open(struct gendisk *disk, blk_mode_t mode)
{ struct atari_floppy_struct *p = disk->private_data; int type = disk->first_minor >> 2;
DPRINT(("fd_open: type=%d\n",type)); if (p->ref && p->type != type) return -EBUSY;
staticvoid ataflop_probe(dev_t dev)
{ int drive = MINOR(dev) & 3; int type = MINOR(dev) >> 2;
if (type)
type--;
if (drive >= FD_MAX_UNITS || type >= NUM_DISK_MINORS) return; if (unit[drive].disk[type]) return; if (ataflop_alloc_disk(drive, type)) return; if (add_disk(unit[drive].disk[type])) goto cleanup_disk;
unit[drive].registered[type] = true; return;
staticvoid atari_floppy_cleanup(void)
{ int i; int type;
for (i = 0; i < FD_MAX_UNITS; i++) { for (type = 0; type < NUM_DISK_MINORS; type++) { if (!unit[i].disk[type]) continue;
del_gendisk(unit[i].disk[type]);
put_disk(unit[i].disk[type]);
}
blk_mq_free_tag_set(&unit[i].tag_set);
}
staticvoid atari_cleanup_floppy_disk(struct atari_floppy_struct *fs)
{ int type;
for (type = 0; type < NUM_DISK_MINORS; type++) { if (!fs->disk[type]) continue; if (fs->registered[type])
del_gendisk(fs->disk[type]);
put_disk(fs->disk[type]);
}
blk_mq_free_tag_set(&fs->tag_set);
}
staticint __init atari_floppy_init (void)
{ int i; int ret;
if (!MACH_IS_ATARI) /* Amiga, Mac, ... don't have Atari-compatible floppy :-) */ return -ENODEV;
for (i = 0; i < FD_MAX_UNITS; i++) {
memset(&unit[i].tag_set, 0, sizeof(unit[i].tag_set));
unit[i].tag_set.ops = &ataflop_mq_ops;
unit[i].tag_set.nr_hw_queues = 1;
unit[i].tag_set.nr_maps = 1;
unit[i].tag_set.queue_depth = 2;
unit[i].tag_set.numa_node = NUMA_NO_NODE;
ret = blk_mq_alloc_tag_set(&unit[i].tag_set); if (ret) goto err;
ret = ataflop_alloc_disk(i, 0); if (ret) {
blk_mq_free_tag_set(&unit[i].tag_set); goto err;
}
}
if (UseTrackbuffer < 0) /* not set by user -> use default: for now, we turn track buffering off for all Medusas, though it could be used with ones that have a counter
card. But the test is too hard :-( */
UseTrackbuffer = !MACH_IS_MEDUSA;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.