📄 floppy.c
字号:
#define ECALL(x) if ((ret = (x))) return ret;#define _WAIT(x,i) CALL(ret=wait_til_done((x),i))#define WAIT(x) _WAIT((x),interruptible)#define IWAIT(x) _WAIT((x),1)/* Errors during formatting are counted here. */static int format_errors;/* Format request descriptor. */static struct format_descr format_req;/* * Rate is 0 for 500kb/s, 1 for 300kbps, 2 for 250kbps * Spec1 is 0xSH, where S is stepping rate (F=1ms, E=2ms, D=3ms etc), * H is head unload time (1=16ms, 2=32ms, etc) *//* * Track buffer * Because these are written to by the DMA controller, they must * not contain a 64k byte boundary crossing, or data will be * corrupted/lost. */static char *floppy_track_buffer;static int max_buffer_sectors;static int *errors;typedef void (*done_f)(int);static struct cont_t { void (*interrupt)(void); /* this is called after the interrupt of the * main command */ void (*redo)(void); /* this is called to retry the operation */ void (*error)(void); /* this is called to tally an error */ done_f done; /* this is called to say if the operation has * succeeded/failed */} *cont;static void floppy_ready(void);static void floppy_start(void);static void process_fd_request(void);static void recalibrate_floppy(void);static void floppy_shutdown(void);static int floppy_grab_irq_and_dma(void);static void floppy_release_irq_and_dma(void);/* * The "reset" variable should be tested whenever an interrupt is scheduled, * after the commands have been sent. This is to ensure that the driver doesn't * get wedged when the interrupt doesn't come because of a failed command. * reset doesn't need to be tested before sending commands, because * output_byte is automatically disabled when reset is set. */#define CHECK_RESET { if (FDCS->reset){ reset_fdc(); return; } }static void reset_fdc(void);/* * These are global variables, as that's the easiest way to give * information to interrupts. They are the data used for the current * request. */#define NO_TRACK -1#define NEED_1_RECAL -2#define NEED_2_RECAL -3static int usage_count;/* buffer related variables */static int buffer_track = -1;static int buffer_drive = -1;static int buffer_min = -1;static int buffer_max = -1;/* fdc related variables, should end up in a struct */static struct floppy_fdc_state fdc_state[N_FDC];static int fdc; /* current fdc */static struct floppy_struct *_floppy = floppy_type;static unsigned char current_drive;static long current_count_sectors;static unsigned char sector_t; /* sector in track */static unsigned char in_sector_offset; /* offset within physical sector, * expressed in units of 512 bytes */#ifndef fd_eject#define fd_eject(x) -EINVAL#endif#ifdef DEBUGTstatic long unsigned debugtimer;#endif/* * Debugging * ========= */static inline void set_debugt(void){#ifdef DEBUGT debugtimer = jiffies;#endif}static inline void debugt(const char *message){#ifdef DEBUGT if (DP->flags & DEBUGT) printk("%s dtime=%lu\n", message, jiffies-debugtimer);#endif}typedef void (*timeout_fn)(unsigned long);static struct timer_list fd_timeout ={ function: (timeout_fn) floppy_shutdown };static const char *timeout_message;#ifdef FLOPPY_SANITY_CHECKstatic void is_alive(const char *message){ /* this routine checks whether the floppy driver is "alive" */ if (fdc_busy && command_status < 2 && !timer_pending(&fd_timeout)){ DPRINT("timeout handler died: %s\n",message); }}#endif#ifdef FLOPPY_SANITY_CHECK#define OLOGSIZE 20static void (*lasthandler)(void);static unsigned long interruptjiffies;static unsigned long resultjiffies;static int resultsize;static unsigned long lastredo;static struct output_log { unsigned char data; unsigned char status; unsigned long jiffies;} output_log[OLOGSIZE];static int output_log_pos;#endif#define CURRENTD -1#define MAXTIMEOUT -2static void reschedule_timeout(int drive, const char *message, int marg){ if (drive == CURRENTD) drive = current_drive; del_timer(&fd_timeout); if (drive < 0 || drive > N_DRIVE) { fd_timeout.expires = jiffies + 20UL*HZ; drive=0; } else fd_timeout.expires = jiffies + UDP->timeout; add_timer(&fd_timeout); if (UDP->flags & FD_DEBUG){ DPRINT("reschedule timeout "); printk(message, marg); printk("\n"); } timeout_message = message;}static int maximum(int a, int b){ if (a > b) return a; else return b;}#define INFBOUND(a,b) (a)=maximum((a),(b));static int minimum(int a, int b){ if (a < b) return a; else return b;}#define SUPBOUND(a,b) (a)=minimum((a),(b));/* * Bottom half floppy driver. * ========================== * * This part of the file contains the code talking directly to the hardware, * and also the main service loop (seek-configure-spinup-command) *//* * disk change. * This routine is responsible for maintaining the FD_DISK_CHANGE flag, * and the last_checked date. * * last_checked is the date of the last check which showed 'no disk change' * FD_DISK_CHANGE is set under two conditions: * 1. The floppy has been changed after some i/o to that floppy already * took place. * 2. No floppy disk is in the drive. This is done in order to ensure that * requests are quickly flushed in case there is no disk in the drive. It * follows that FD_DISK_CHANGE can only be cleared if there is a disk in * the drive. * * For 1., maxblock is observed. Maxblock is 0 if no i/o has taken place yet. * For 2., FD_DISK_NEWCHANGE is watched. FD_DISK_NEWCHANGE is cleared on * each seek. If a disk is present, the disk change line should also be * cleared on each seek. Thus, if FD_DISK_NEWCHANGE is clear, but the disk * change line is set, this means either that no disk is in the drive, or * that it has been removed since the last seek. * * This means that we really have a third possibility too: * The floppy has been changed after the last seek. */static int disk_change(int drive){ int fdc=FDC(drive);#ifdef FLOPPY_SANITY_CHECK if (jiffies - UDRS->select_date < UDP->select_delay) DPRINT("WARNING disk change called early\n"); if (!(FDCS->dor & (0x10 << UNIT(drive))) || (FDCS->dor & 3) != UNIT(drive) || fdc != FDC(drive)){ DPRINT("probing disk change on unselected drive\n"); DPRINT("drive=%d fdc=%d dor=%x\n",drive, FDC(drive), (unsigned int)FDCS->dor); }#endif#ifdef DCL_DEBUG if (UDP->flags & FD_DEBUG){ DPRINT("checking disk change line for drive %d\n",drive); DPRINT("jiffies=%lu\n", jiffies); DPRINT("disk change line=%x\n",fd_inb(FD_DIR)&0x80); DPRINT("flags=%lx\n",UDRS->flags); }#endif if (UDP->flags & FD_BROKEN_DCL) return UTESTF(FD_DISK_CHANGED); if ((fd_inb(FD_DIR) ^ UDP->flags) & 0x80){ USETF(FD_VERIFY); /* verify write protection */ if (UDRS->maxblock){ /* mark it changed */ USETF(FD_DISK_CHANGED); } /* invalidate its geometry */ if (UDRS->keep_data >= 0) { if ((UDP->flags & FTD_MSG) && current_type[drive] != NULL) DPRINT("Disk type is undefined after " "disk change\n"); current_type[drive] = NULL; floppy_sizes[TOMINOR(drive)] = MAX_DISK_SIZE; } /*USETF(FD_DISK_NEWCHANGE);*/ return 1; } else { UDRS->last_checked=jiffies; UCLEARF(FD_DISK_NEWCHANGE); } return 0;}static inline int is_selected(int dor, int unit){ return ((dor & (0x10 << unit)) && (dor &3) == unit);}static int set_dor(int fdc, char mask, char data){ register unsigned char drive, unit, newdor,olddor; if (FDCS->address == -1) return -1; olddor = FDCS->dor; newdor = (olddor & mask) | data; if (newdor != olddor){ unit = olddor & 0x3; if (is_selected(olddor, unit) && !is_selected(newdor,unit)){ drive = REVDRIVE(fdc,unit);#ifdef DCL_DEBUG if (UDP->flags & FD_DEBUG){ DPRINT("calling disk change from set_dor\n"); }#endif disk_change(drive); } FDCS->dor = newdor; fd_outb(newdor, FD_DOR); unit = newdor & 0x3; if (!is_selected(olddor, unit) && is_selected(newdor,unit)){ drive = REVDRIVE(fdc,unit); UDRS->select_date = jiffies; } } /* * We should propogate failures to grab the resources back * nicely from here. Actually we ought to rewrite the fd * driver some day too. */ if (newdor & FLOPPY_MOTOR_MASK) floppy_grab_irq_and_dma(); if (olddor & FLOPPY_MOTOR_MASK) floppy_release_irq_and_dma(); return olddor;}static void twaddle(void){ if (DP->select_delay) return; fd_outb(FDCS->dor & ~(0x10<<UNIT(current_drive)), FD_DOR); fd_outb(FDCS->dor, FD_DOR); DRS->select_date = jiffies;}/* reset all driver information about the current fdc. This is needed after * a reset, and after a raw command. */static void reset_fdc_info(int mode){ int drive; FDCS->spec1 = FDCS->spec2 = -1; FDCS->need_configure = 1; FDCS->perp_mode = 1; FDCS->rawcmd = 0; for (drive = 0; drive < N_DRIVE; drive++) if (FDC(drive) == fdc && (mode || UDRS->track != NEED_1_RECAL)) UDRS->track = NEED_2_RECAL;}/* selects the fdc and drive, and enables the fdc's input/dma. */static void set_fdc(int drive){ if (drive >= 0 && drive < N_DRIVE){ fdc = FDC(drive); current_drive = drive; } if (fdc != 1 && fdc != 0) { printk("bad fdc value\n"); return; } set_dor(fdc,~0,8);#if N_FDC > 1 set_dor(1-fdc, ~8, 0);#endif if (FDCS->rawcmd == 2) reset_fdc_info(1); if (fd_inb(FD_STATUS) != STATUS_READY) FDCS->reset = 1;}/* locks the driver */static int _lock_fdc(int drive, int interruptible, int line){ if (!usage_count){ printk(KERN_ERR "Trying to lock fdc while usage count=0 at line %d\n", line); return -1; } if(floppy_grab_irq_and_dma()==-1) return -EBUSY; if (test_and_set_bit(0, &fdc_busy)) { DECLARE_WAITQUEUE(wait, current); add_wait_queue(&fdc_wait, &wait); for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (!test_and_set_bit(0, &fdc_busy)) break; schedule(); if (!NO_SIGNAL) { remove_wait_queue(&fdc_wait, &wait); return -EINTR; } } set_current_state(TASK_RUNNING); remove_wait_queue(&fdc_wait, &wait); } command_status = FD_COMMAND_NONE; reschedule_timeout(drive, "lock fdc", 0); set_fdc(drive); return 0;}#define lock_fdc(drive,interruptible) _lock_fdc(drive,interruptible, __LINE__)#define LOCK_FDC(drive,interruptible) \if (lock_fdc(drive,interruptible)) return -EINTR;/* unlocks the driver */static inline void unlock_fdc(void){ raw_cmd = 0; if (!fdc_busy) DPRINT("FDC access conflict!\n"); if (DEVICE_INTR) DPRINT("device interrupt still active at FDC release: %p!\n", DEVICE_INTR); command_status = FD_COMMAND_NONE; del_timer(&fd_timeout); cont = NULL; clear_bit(0, &fdc_busy); floppy_release_irq_and_dma(); wake_up(&fdc_wait);}/* switches the motor off after a given timeout */static void motor_off_callback(unsigned long nr){ unsigned char mask = ~(0x10 << UNIT(nr)); set_dor(FDC(nr), mask, 0);}/* schedules motor off */static void floppy_off(unsigned int drive){ unsigned long volatile delta; register int fdc=FDC(drive); if (!(FDCS->dor & (0x10 << UNIT(drive)))) return; del_timer(motor_off_timer+drive); /* make spindle stop in a position which minimizes spinup time * next time */ if (UDP->rps){ delta = jiffies - UDRS->first_read_date + HZ - UDP->spindown_offset; delta = ((delta * UDP->rps) % HZ) / UDP->rps; motor_off_timer[drive].expires = jiffies + UDP->spindown - delta; } add_timer(motor_off_timer+drive);}/* * cycle through all N_DRIVE floppy drives, for disk change testing. * stopping at current drive. This is done before any long operation, to * be sure to have up to date disk change information. */static void scandrives(void){ int i, drive, saved_drive; if (DP->select_delay) return; saved_drive = current_drive; for (i=0; i < N_DRIVE; i++){ drive = (saved_drive + i + 1) % N_DRIVE; if (UDRS->fd_ref == 0 || UDP->select_delay != 0) continue; /* skip closed drives */ set_fdc(drive); if (!(set_dor(fdc, ~3, UNIT(drive) | (0x10 << UNIT(drive))) & (0x10 << UNIT(drive)))) /* switch the motor off again, if it was off to * begin with */ set_dor(fdc, ~(0x10 << UNIT(drive)), 0); } set_fdc(saved_drive);}static void empty(void){}static struct tq_struct floppy_tq;static void schedule_bh( void (*handler)(void*) ){ floppy_tq.routine = (void *)(void *) handler; queue_task(&floppy_tq, &tq_immediate); mark_bh(IMMEDIATE_BH);}static struct timer_list fd_timer;static void cancel_activity(void){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -