URL
https://opencores.org/ocsvn/or1k_old/or1k_old/trunk
Subversion Repositories or1k_old
Compare Revisions
- This comparison shows the changes necessary to convert path
/or1k_old/trunk/rc203soc/sw/uClinux/arch/armnommu/drivers/block
- from Rev 1765 to Rev 1782
- ↔ Reverse comparison
Rev 1765 → Rev 1782
/mfm.S
0,0 → 1,163
@ Read/Write DMA code for the ST506/MFM hard drive controllers on the A400 |
@ motherboard on ST506 podules. |
@ (c) David Alan Gilbert (gilbertd@cs.man.ac.uk) 1996 |
|
#include <asm/assembler.h> |
|
_hdc63463_irqdata: |
@ Controller base address |
.global _hdc63463_baseaddress |
_hdc63463_baseaddress: |
.word 0 |
|
.global _hdc63463_irqpolladdress |
_hdc63463_irqpolladdress: |
.word 0 |
|
.global _hdc63463_irqpollmask |
_hdc63463_irqpollmask: |
.word 0 |
|
@ where to read/write data from the kernel data space |
.global _hdc63463_dataptr |
_hdc63463_dataptr: |
.word 0 |
|
@ Number of bytes left to transfer |
.global _hdc63463_dataleft |
_hdc63463_dataleft: |
.word 0 |
|
@ ------------------------------------------------------------------------- |
@ hdc63463_writedma: DMA from host to controller |
@ internal reg usage: r0=hdc base address, r1=irq poll address, r2=poll mask |
@ r3=data ptr, r4=data left, r5,r6=temporary |
.global _hdc63463_writedma |
_hdc63463_writedma: |
stmfd sp!,{r4-r7} |
adr r5,_hdc63463_irqdata |
ldmia r5,{r0,r1,r2,r3,r4} |
|
|
writedma_again: |
|
@ test number of remaining bytes to transfer |
cmp r4,#0 |
beq writedma_end |
bmi writedma_end |
|
@ Check the hdc is interrupting |
ldrb r5,[r1,#0] |
tst r5,r2 |
beq writedma_end |
|
@ Transfer a block of upto 256 bytes |
cmp r4,#256 |
movlt r7,r4 |
movge r7,#256 |
|
@ Check the hdc is still busy and command has not ended and no errors |
ldr r5,[r0,#32] @ Status reg - 16 bit - its the top few bits which are status |
@ think we should continue DMA until it drops busy - perhaps this was |
@ the main problem with corrected errors causing a hang |
@tst r5,#0x3c00 @ Test for things which should be off |
@bne writedma_end |
and r5,r5,#0x8000 @ This is test for things which should be on: Busy |
cmp r5,#0x8000 |
bne writedma_end |
|
@ Bytes remaining at end |
sub r4,r4,r7 |
|
@ HDC Write register location |
add r0,r0,#32+8 |
|
writedma_loop: |
@ OK - pretty sure we should be doing this |
|
ldr r5,[r3],#4 @ Get a word to be written |
@ get bottom half to be sent first |
mov r6,r5,lsl#16 @ Separate the first 2 bytes |
orr r2,r6,r6,lsr #16 @ Duplicate them in the bottom half of the word |
@ now the top half |
mov r6,r5,lsr#16 @ Get 2nd 2 bytes |
orr r6,r6,r6,lsl#16 @ Duplicate |
@str r6,[r0] @ to hdc |
stmia r0,{r2,r6} |
subs r7,r7,#4 @ Dec. number of bytes left |
bne writedma_loop |
|
@ If we were too slow we had better go through again - DAG - took out with new interrupt routine |
@ sub r0,r0,#32+8 |
@ adr r2,_hdc63463_irqdata |
@ ldr r2,[r2,#8] |
@ b writedma_again |
|
writedma_end: |
adr r5,_hdc63463_irqdata+12 |
stmia r5,{r3,r4} |
ldmfd sp!,{r4-r7} |
RETINSTR(mov,pc,lr) |
|
@ ------------------------------------------------------------------------- |
@ hdc63463_readdma: DMA from controller to host |
@ internal reg usage: r0=hdc base address, r1=irq poll address, r2=poll mask |
@ r3=data ptr, r4=data left, r5,r6=temporary |
.global _hdc63463_readdma |
_hdc63463_readdma: |
stmfd sp!,{r4-r7} |
adr r5,_hdc63463_irqdata |
ldmia r5,{r0,r1,r2,r3,r4} |
|
readdma_again: |
@ test number of remaining bytes to transfer |
cmp r4,#0 |
beq readdma_end |
bmi readdma_end |
|
@ Check the hdc is interrupting |
ldrb r5,[r1,#0] |
tst r5,r2 |
beq readdma_end |
|
@ Check the hdc is still busy and command has not ended and no errors |
ldr r5,[r0,#32] @ Status reg - 16 bit - its the top few bits which are status |
@ think we should continue DMA until it drops busy - perhaps this was |
@ the main problem with corrected errors causing a hang |
@tst r5,#0x3c00 @ Test for things which should be off |
@bne readdma_end |
and r5,r5,#0x8000 @ This is test for things which should be on: Busy |
cmp r5,#0x8000 |
bne readdma_end |
|
@ Transfer a block of upto 256 bytes |
cmp r4,#256 |
movlt r7,r4 |
movge r7,#256 |
|
@ Bytes remaining at end |
sub r4,r4,r7 |
|
@ Set a pointer to the data register in the HDC |
add r0,r0,#8 |
readdma_loop: |
@ OK - pretty sure we should be doing this |
ldmia r0,{r5,r6} |
mov r5,r5,lsl#16 |
mov r6,r6,lsl#16 |
orr r6,r6,r5,lsr #16 |
str r6,[r3],#4 |
subs r7,r7,#4 @ Decrement bytes to go |
bne readdma_loop |
|
@ Try reading multiple blocks - if this was fast enough then I dont think |
@ this should help - NO taken out DAG - new interrupt handler has |
@ non-consecutive memory blocks |
@ sub r0,r0,#8 |
@ b readdma_again |
|
readdma_end: |
adr r5,_hdc63463_irqdata+12 |
stmia r5,{r3,r4} |
ldmfd sp!,{r4-r7} |
RETINSTR(mov,pc,lr) |
/ide.c
0,0 → 1,3913
/* |
* linux/drivers/block/ide.c Version 5.53 Jun 24, 1997 |
* |
* Copyright (C) 1994-1996 Linus Torvalds & authors (see below) |
*/ |
#define _IDE_C /* needed by <linux/blk.h> */ |
|
/* |
* Maintained by Mark Lord <mlord@pobox.com> |
* and Gadi Oxman <gadio@netvision.net.il> |
* |
* This is the multiple IDE interface driver, as evolved from hd.c. |
* It supports up to four IDE interfaces, on one or more IRQs (usually 14 & 15). |
* There can be up to two drives per interface, as per the ATA-2 spec. |
* |
* Primary: ide0, port 0x1f0; major=3; hda is minor=0; hdb is minor=64 |
* Secondary: ide1, port 0x170; major=22; hdc is minor=0; hdd is minor=64 |
* Tertiary: ide2, port 0x???; major=33; hde is minor=0; hdf is minor=64 |
* Quaternary: ide3, port 0x???; major=34; hdg is minor=0; hdh is minor=64 |
* |
* It is easy to extend ide.c to handle more than four interfaces: |
* |
* Change the MAX_HWIFS constant in ide.h. |
* |
* Define some new major numbers (in major.h), and insert them into |
* the ide_hwif_to_major table in ide.c. |
* |
* Fill in the extra values for the new interfaces into the two tables |
* inside ide.c: default_io_base[] and default_irqs[]. |
* |
* Create the new request handlers by cloning "do_ide3_request()" |
* for each new interface, and add them to the switch statement |
* in the ide_init() function in ide.c. |
* |
* Recompile, create the new /dev/ entries, and it will probably work. |
* |
* From hd.c: |
* | |
* | It traverses the request-list, using interrupts to jump between functions. |
* | As nearly all functions can be called within interrupts, we may not sleep. |
* | Special care is recommended. Have Fun! |
* | |
* | modified by Drew Eckhardt to check nr of hd's from the CMOS. |
* | |
* | Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug |
* | in the early extended-partition checks and added DM partitions. |
* | |
* | Early work on error handling by Mika Liljeberg (liljeber@cs.Helsinki.FI). |
* | |
* | IRQ-unmask, drive-id, multiple-mode, support for ">16 heads", |
* | and general streamlining by Mark Lord (mlord@pobox.com). |
* |
* October, 1994 -- Complete line-by-line overhaul for linux 1.1.x, by: |
* |
* Mark Lord (mlord@pobox.com) (IDE Perf.Pkg) |
* Delman Lee (delman@mipg.upenn.edu) ("Mr. atdisk2") |
* Scott Snyder (snyder@fnald0.fnal.gov) (ATAPI IDE cd-rom) |
* |
* This was a rewrite of just about everything from hd.c, though some original |
* code is still sprinkled about. Think of it as a major evolution, with |
* inspiration from lots of linux users, esp. hamish@zot.apana.org.au |
* |
* Version 1.0 ALPHA initial code, primary i/f working okay |
* Version 1.3 BETA dual i/f on shared irq tested & working! |
* Version 1.4 BETA added auto probing for irq(s) |
* Version 1.5 BETA added ALPHA (untested) support for IDE cd-roms, |
* ... |
* Version 3.5 correct the bios_cyl field if it's too small |
* (linux 1.1.76) (to help fdisk with brain-dead BIOSs) |
* Version 3.6 cosmetic corrections to comments and stuff |
* (linux 1.1.77) reorganise probing code to make it understandable |
* added halfway retry to probing for drive identification |
* added "hdx=noprobe" command line option |
* allow setting multmode even when identification fails |
* Version 3.7 move set_geometry=1 from do_identify() to ide_init() |
* increase DRQ_WAIT to eliminate nuisance messages |
* wait for DRQ_STAT instead of DATA_READY during probing |
* (courtesy of Gary Thomas gary@efland.UU.NET) |
* Version 3.8 fixed byte-swapping for confused Mitsumi cdrom drives |
* update of ide-cd.c from Scott, allows blocksize=1024 |
* cdrom probe fixes, inspired by jprang@uni-duisburg.de |
* Version 3.9 don't use LBA if lba_capacity looks funny |
* correct the drive capacity calculations |
* fix probing for old Seagates without IDE_ALTSTATUS_REG |
* fix byte-ordering for some NEC cdrom drives |
* Version 3.10 disable multiple mode by default; was causing trouble |
* Version 3.11 fix mis-identification of old WD disks as cdroms |
* Version 3,12 simplify logic for selecting initial mult_count |
* (fixes problems with buggy WD drives) |
* Version 3.13 remove excess "multiple mode disabled" messages |
* Version 3.14 fix ide_error() handling of BUSY_STAT |
* fix byte-swapped cdrom strings (again.. arghh!) |
* ignore INDEX bit when checking the ALTSTATUS reg |
* Version 3.15 add SINGLE_THREADED flag for use with dual-CMD i/f |
* ignore WRERR_STAT for non-write operations |
* added vlb_sync support for DC-2000A & others, |
* (incl. some Promise chips), courtesy of Frank Gockel |
* Version 3.16 convert vlb_32bit and vlb_sync into runtime flags |
* add ioctls to get/set VLB flags (HDIO_[SG]ET_CHIPSET) |
* rename SINGLE_THREADED to SUPPORT_SERIALIZE, |
* add boot flag to "serialize" operation for CMD i/f |
* add optional support for DTC2278 interfaces, |
* courtesy of andy@cercle.cts.com (Dyan Wile). |
* add boot flag to enable "dtc2278" probe |
* add probe to avoid EATA (SCSI) interfaces, |
* courtesy of neuffer@goofy.zdv.uni-mainz.de. |
* Version 4.00 tidy up verify_area() calls - heiko@colossus.escape.de |
* add flag to ignore WRERR_STAT for some drives |
* courtesy of David.H.West@um.cc.umich.edu |
* assembly syntax tweak to vlb_sync |
* removable drive support from scuba@cs.tu-berlin.de |
* add transparent support for DiskManager-6.0x "Dynamic |
* Disk Overlay" (DDO), most of this is in genhd.c |
* eliminate "multiple mode turned off" message at boot |
* Version 4.10 fix bug in ioctl for "hdparm -c3" |
* fix DM6:DDO support -- now works with LILO, fdisk, ... |
* don't treat some naughty WD drives as removable |
* Version 4.11 updated DM6 support using info provided by OnTrack |
* Version 5.00 major overhaul, multmode setting fixed, vlb_sync fixed |
* added support for 3rd/4th/alternative IDE ports |
* created ide.h; ide-cd.c now compiles separate from ide.c |
* hopefully fixed infinite "unexpected_intr" from cdroms |
* zillions of other changes and restructuring |
* somehow reduced overall memory usage by several kB |
* probably slowed things down slightly, but worth it |
* Version 5.01 AT LAST!! Finally understood why "unexpected_intr" |
* was happening at various times/places: whenever the |
* ide-interface's ctl_port was used to "mask" the irq, |
* it also would trigger an edge in the process of masking |
* which would result in a self-inflicted interrupt!! |
* (such a stupid way to build a hardware interrupt mask). |
* This is now fixed (after a year of head-scratching). |
* Version 5.02 got rid of need for {enable,disable}_irq_list() |
* Version 5.03 tune-ups, comments, remove "busy wait" from drive resets |
* removed PROBE_FOR_IRQS option -- no longer needed |
* OOOPS! fixed "bad access" bug for 2nd drive on an i/f |
* Version 5.04 changed "ira %d" to "irq %d" in DEBUG message |
* added more comments, cleaned up unexpected_intr() |
* OOOPS! fixed null pointer problem in ide reset code |
* added autodetect for Triton chipset -- no effect yet |
* Version 5.05 OOOPS! fixed bug in revalidate_disk() |
* OOOPS! fixed bug in ide_do_request() |
* added ATAPI reset sequence for cdroms |
* Version 5.10 added Bus-Mastered DMA support for Triton Chipset |
* some (mostly) cosmetic changes |
* Version 5.11 added ht6560b support by malafoss@snakemail.hut.fi |
* reworked PCI scanning code |
* added automatic RZ1000 detection/support |
* added automatic PCI CMD640 detection/support |
* added option for VLB CMD640 support |
* tweaked probe to find cdrom on hdb with disks on hda,hdc |
* Version 5.12 some performance tuning |
* added message to alert user to bad /dev/hd[cd] entries |
* OOOPS! fixed bug in atapi reset |
* driver now forces "serialize" again for all cmd640 chips |
* noticed REALLY_SLOW_IO had no effect, moved it to ide.c |
* made do_drive_cmd() into public ide_do_drive_cmd() |
* Version 5.13 fixed typo ('B'), thanks to houston@boyd.geog.mcgill.ca |
* fixed ht6560b support |
* Version 5.13b (sss) fix problem in calling ide_cdrom_setup() |
* don't bother invalidating nonexistent partitions |
* Version 5.14 fixes to cmd640 support.. maybe it works now(?) |
* added & tested full EZ-DRIVE support -- don't use LILO! |
* don't enable 2nd CMD640 PCI port during init - conflict |
* Version 5.15 bug fix in init_cmd640_vlb() |
* bug fix in interrupt sharing code |
* Version 5.16 ugh.. fix "serialize" support, broken in 5.15 |
* remove "Huh?" from cmd640 code |
* added qd6580 interface speed select from Colten Edwards |
* Version 5.17 kludge around bug in BIOS32 on Intel triton motherboards |
* Version 5.18 new CMD640 code, moved to cmd640.c, #include'd for now |
* new UMC8672 code, moved to umc8672.c, #include'd for now |
* disallow turning on DMA when h/w not capable of DMA |
* Version 5.19 fix potential infinite timeout on resets |
* extend reset poll into a general purpose polling scheme |
* add atapi tape drive support from Gadi Oxman |
* simplify exit from _intr routines -- no IDE_DO_REQUEST |
* Version 5.20 leave current rq on blkdev request list during I/O |
* generalized ide_do_drive_cmd() for tape/cdrom driver use |
* Version 5.21 fix nasty cdrom/tape bug (ide_preempt was messed up) |
* Version 5.22 fix ide_xlate_1024() to work with/without drive->id |
* Version 5.23 miscellaneous touch-ups |
* Version 5.24 fix #if's for SUPPORT_CMD640 |
* Version 5.25 more touch-ups, fix cdrom resets, ... |
* cmd640.c now configs/compiles separate from ide.c |
* Version 5.26 keep_settings now maintains the using_dma flag |
* fix [EZD] remap message to only output at boot time |
* fix "bad /dev/ entry" message to say hdc, not hdc0 |
* fix ide_xlate_1024() to respect user specified CHS |
* use CHS from partn table if it looks translated |
* re-merged flags chipset,vlb_32bit,vlb_sync into io_32bit |
* keep track of interface chipset type, when known |
* add generic PIO mode "tuneproc" mechanism |
* fix cmd640_vlb option |
* fix ht6560b support (was completely broken) |
* umc8672.c now configures/compiles separate from ide.c |
* move dtc2278 support to dtc2278.c |
* move ht6560b support to ht6560b.c |
* move qd6580 support to qd6580.c |
* add ali14xx support in ali14xx.c |
* Version 5.27 add [no]autotune parameters to help cmd640 |
* move rz1000 support to rz1000.c |
* Version 5.28 #include "ide_modes.h" |
* fix disallow_unmask: now per-interface "no_unmask" bit |
* force io_32bit to be the same on drive pairs of dtc2278 |
* improved IDE tape error handling, and tape DMA support |
* bugfix in ide_do_drive_cmd() for cdroms + serialize |
* Version 5.29 fixed non-IDE check for too many physical heads |
* don't use LBA if capacity is smaller than CHS |
* Version 5.30 remove real_devices kludge, formerly used by genhd.c |
* Version 5.32 change "KB" to "kB" |
* fix serialize (was broken in kernel 1.3.72) |
* add support for "hdparm -I" |
* use common code for disk/tape/cdrom IDE_DRIVE_CMDs |
* add support for Promise DC4030VL caching card |
* improved serialize support |
* put partition check back into alphabetical order |
* add config option for PCMCIA baggage |
* try to make PCMCIA support safer to use |
* improve security on ioctls(): all are suser() only |
* Version 5.33 improve handling of HDIO_DRIVE_CMDs that read data |
* Version 5.34 fix irq-sharing problem from 5.33 |
* fix cdrom ioctl problem from 5.33 |
* Version 5.35 cosmetic changes |
* fix cli() problem in try_to_identify() |
* Version 5.36 fixes to optional PCMCIA support |
* Version 5.37 don't use DMA when "noautotune" is specified |
* Version 5.37a (go) fix shared irq probing (was broken in kernel 1.3.72) |
* call unplug_device() from ide_do_drive_cmd() |
* Version 5.38 add "hdx=none" option, courtesy of Joel Maslak |
* mask drive irq after use, if sharing with another hwif |
* add code to help debug weird cmd640 problems |
* Version 5.39 fix horrible error in earlier irq sharing "fix" |
* Version 5.40 fix serialization -- was broken in 5.39 |
* help sharing by masking device irq after probing |
* Version 5.41 more fixes to irq sharing/serialize detection |
* disable io_32bit by default on drive reset |
* Version 5.42 simplify irq-masking after probe |
* fix NULL pointer deref in save_match() |
* Version 5.43 Ugh.. unexpected_intr is back: try to exterminate it |
* Version 5.44 Fix for "irq probe failed" on cmd640 |
* change path on message regarding MAKEDEV.ide |
* add a throttle to the unexpected_intr() messages |
* Version 5.45 fix ugly parameter parsing bugs (thanks Derek) |
* include Gadi's magic fix for cmd640 unexpected_intr |
* include mc68000 patches from Geert Uytterhoeven |
* add Gadi's fix for PCMCIA cdroms |
* Version 5.46 remove the mc68000 #ifdefs for 2.0.x |
* Version 5.47 fix set_tune race condition |
* fix bug in earlier PCMCIA cdrom update |
* Version 5.48 if def'd, invoke CMD640_DUMP_REGS when irq probe fails |
* lengthen the do_reset1() pulse, for laptops |
* add idebus=xx parameter for cmd640 and ali chipsets |
* no_unmask flag now per-drive instead of per-hwif |
* fix tune_req so that it gets done immediately |
* fix missing restore_flags() in ide_ioctl |
* prevent use of io_32bit on cmd640 with no prefetch |
* Version 5.49 fix minor quirks in probing routines |
* Version 5.50 allow values as small as 20 for idebus= |
* Version 5.51 force non io_32bit in drive_cmd_intr() |
* change delay_10ms() to delay_50ms() to fix problems |
* Version 5.52 fix incorrect invalidation of removable devices |
* add "hdx=slow" command line option |
* Version 5.53 add ATAPI floppy drive support |
* change default media for type 0 to floppy |
* add support for Exabyte Nest |
* add missing set_blocksize() in revalidate_disk() |
* handle bad status bit sequencing in ide_wait_stat() |
* support partition table translations with 255 heads |
* probe all interfaces by default |
* add probe for the i82371AB chipset |
* acknowledge media change on removable drives |
* add work-around for BMI drives |
* remove "LBA" from boot messages |
* Version 5.53.1 add UDMA "CRC retry" support |
* Version 5.53.2 add Promise/33 auto-detection and DMA support |
* fix MC_ERR handling |
* fix mis-detection of NEC cdrom as floppy |
* issue ATAPI reset and re-probe after "no response" |
* |
*-------------------------------------------------------------------------------- |
* Ported to ARM architecture by Russell King (rmk@ecs.soton.ac.uk) |
*-------------------------------------------------------------------------------- |
* Some additional driver compile-time options are in ide.h |
* |
* To do, in likely order of completion: |
* - modify kernel to obtain BIOS geometry for drives on 2nd/3rd/4th i/f |
*/ |
|
#undef REALLY_SLOW_IO /* most systems can safely undef this */ |
|
#include <linux/config.h> |
#include <linux/types.h> |
#include <linux/string.h> |
#include <linux/kernel.h> |
#include <linux/delay.h> |
#include <linux/timer.h> |
#include <linux/mm.h> |
#include <linux/ioport.h> |
#include <linux/interrupt.h> |
#include <linux/major.h> |
#include <linux/blkdev.h> |
#include <linux/errno.h> |
#include <linux/hdreg.h> |
#include <linux/genhd.h> |
#include <linux/malloc.h> |
|
#include <asm/byteorder.h> |
#include <asm/irq.h> |
#include <asm/segment.h> |
#include <asm/io.h> |
|
#ifdef CONFIG_PCI |
#include <linux/bios32.h> |
#include <linux/pci.h> |
#endif /* CONFIG_PCI */ |
|
#include "ide.h" |
#include "ide_modes.h" |
|
#ifdef CONFIG_BLK_DEV_PROMISE |
#include "promise.h" |
#define IS_PROMISE_DRIVE (HWIF(drive)->chipset == ide_promise) |
#else |
#define IS_PROMISE_DRIVE (0) /* auto-NULLs out Promise code */ |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
|
static const byte ide_hwif_to_major[MAX_HWIFS] = {IDE0_MAJOR, IDE1_MAJOR, IDE2_MAJOR, IDE3_MAJOR}; |
#if defined(CONFIG_ARCH_RPC) |
static const unsigned long default_io_base[MAX_HWIFS] = {0x1f0}; |
static const byte default_irqs[MAX_HWIFS] = {9}; |
#elif defined (CONFIG_ARCH_A5K) |
static const unsigned long default_io_base[MAX_HWIFS] = {0x1f0}; |
static const byte default_irqs[MAX_HWIFS] = {11}; |
#else |
static const unsigned long default_io_base[MAX_HWIFS] = {0}; |
static const byte default_irqs[MAX_HWIFS] = {0}; |
#endif |
|
static int idebus_parameter; /* holds the "idebus=" parameter */ |
static int system_bus_speed; /* holds what we think is VESA/PCI bus speed */ |
|
/* |
* This is declared extern in ide.h, for access by other IDE modules: |
*/ |
ide_hwif_t ide_hwifs[MAX_HWIFS]; /* master data repository */ |
|
#if (DISK_RECOVERY_TIME > 0) |
#error DISK_RECOVERY_TIME is not supported on the ARM. |
/* |
* For really screwy hardware (hey, at least it *can* be used with Linux) |
* we can enforce a minimum delay time between successive operations. |
*/ |
static unsigned long read_timer(void) |
{ |
unsigned long t, flags; |
int i; |
|
save_flags(flags); |
cli(); |
t = jiffies * 11932; |
outb_p(0, 0x43); |
i = inb_p(0x40); |
i |= inb(0x40) << 8; |
restore_flags(flags); |
return (t - i); |
} |
|
static void set_recovery_timer (ide_hwif_t *hwif) |
{ |
hwif->last_time = read_timer(); |
} |
#define SET_RECOVERY_TIMER(drive) set_recovery_timer (drive) |
|
#else |
|
#define SET_RECOVERY_TIMER(drive) |
|
#endif /* DISK_RECOVERY_TIME */ |
|
|
/* |
* Do not even *think* about calling this! |
*/ |
static void init_hwif_data (unsigned int index) |
{ |
byte *p; |
unsigned int unit; |
ide_hwif_t *hwif = &ide_hwifs[index]; |
|
/* bulk initialize hwif & drive info with zeros */ |
p = ((byte *) hwif) + sizeof(ide_hwif_t); |
do { |
*--p = 0; |
} while (p > (byte *) hwif); |
|
/* fill in any non-zero initial values */ |
hwif->index = index; |
hwif->io.io_base = default_io_base[index]; |
hwif->io.ctl_port = hwif->io.io_base ? hwif->io.io_base+0x206 : 0x000; |
hwif->io.io_shift = 0; |
#ifdef CONFIG_BLK_DEV_HD |
if (hwif->io.io_base == HD_DATA) |
hwif->noprobe = 1; /* may be overridden by ide_setup() */ |
#endif /* CONFIG_BLK_DEV_HD */ |
/* RMK: do not probe ifs set at address 0 */ |
if (hwif->io.io_base == 0) |
hwif->noprobe = 1; |
hwif->major = ide_hwif_to_major[index]; |
hwif->name[0] = 'i'; |
hwif->name[1] = 'd'; |
hwif->name[2] = 'e'; |
hwif->name[3] = '0' + index; |
#ifdef CONFIG_BLK_DEV_IDETAPE |
hwif->tape_drive = NULL; |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
|
drive->select.all = (unit<<4)|0xa0; |
drive->hwif = hwif; |
drive->ctl = 0x08; |
drive->ready_stat = READY_STAT; |
drive->bad_wstat = BAD_W_STAT; |
drive->special.b.recalibrate = 1; |
drive->special.b.set_geometry = 1; |
drive->name[0] = 'h'; |
drive->name[1] = 'd'; |
drive->name[2] = 'a' + (index * MAX_DRIVES) + unit; |
} |
} |
|
/* |
* init_ide_data() sets reasonable default values into all fields |
* of all instances of the hwifs and drives, but only on the first call. |
* Subsequent calls have no effect (they don't wipe out anything). |
* |
* This routine is normally called at driver initialization time, |
* but may also be called MUCH earlier during kernel "command-line" |
* parameter processing. As such, we cannot depend on any other parts |
* of the kernel (such as memory allocation) to be functioning yet. |
* |
* This is too bad, as otherwise we could dynamically allocate the |
* ide_drive_t structs as needed, rather than always consuming memory |
* for the max possible number (MAX_HWIFS * MAX_DRIVES) of them. |
*/ |
#define MAGIC_COOKIE 0x12345678 |
static void init_ide_data (void) |
{ |
unsigned int index; |
static unsigned long magic_cookie = MAGIC_COOKIE; |
|
if (magic_cookie != MAGIC_COOKIE) |
return; /* already initialized */ |
magic_cookie = 0; |
|
for (index = 0; index < MAX_HWIFS; ++index) |
init_hwif_data(index); |
|
idebus_parameter = 0; |
system_bus_speed = 0; |
} |
|
/* |
* ide_system_bus_speed() returns what we think is the system VESA/PCI |
* bus speed (in Mhz). This is used for calculating interface PIO timings. |
* The default is 40 for known PCI systems, 50 otherwise. |
* The "idebus=xx" parameter can be used to override this value. |
* The actual value to be used is computed/displayed the first time through. |
*/ |
int ide_system_bus_speed (void) |
{ |
if (!system_bus_speed) { |
if (idebus_parameter) |
system_bus_speed = idebus_parameter; /* user supplied value */ |
#ifdef CONFIG_PCI |
else if (pcibios_present()) |
system_bus_speed = 40; /* safe default value for PCI */ |
#endif /* CONFIG_PCI */ |
else |
system_bus_speed = 50; /* safe default value for VESA and PCI */ |
printk("ide: Assuming %dMhz system bus speed for PIO modes; override with idebus=xx\n", system_bus_speed); |
} |
return system_bus_speed; |
} |
|
#if SUPPORT_VLB_SYNC |
/* |
* Some localbus EIDE interfaces require a special access sequence |
* when using 32-bit I/O instructions to transfer data. We call this |
* the "vlb_sync" sequence, which consists of three successive reads |
* of the sector count register location, with interrupts disabled |
* to ensure that the reads all happen together. |
*/ |
static inline void do_vlb_sync (unsigned short port) { |
(void) inb (port); |
(void) inb (port); |
(void) inb (port); |
} |
#endif /* SUPPORT_VLB_SYNC */ |
|
/* |
* This is used for most PIO data transfers *from* the IDE interface |
*/ |
void ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount) |
{ |
unsigned long io_base = HWIF(drive)->io.io_base; |
unsigned long data_reg = io_base + (IDE_DATA_OFFSET << HWIF(drive)->io.io_shift); |
#ifndef __arm__ |
byte io_32bit = drive->io_32bit; |
#else |
byte io_32bit = 0; |
#endif |
|
if (io_32bit) { |
#if SUPPORT_VLB_SYNC |
if (io_32bit & 2) { |
cli(); |
do_vlb_sync(io_base+IDE_NSECTOR_OFFSET); |
insl(data_reg, buffer, wcount); |
if (drive->unmask) |
sti(); |
} else |
#endif /* SUPPORT_VLB_SYNC */ |
insl(data_reg, buffer, wcount); |
} else { |
#if SUPPORT_SLOW_DATA_PORTS |
if (drive->slow) { |
unsigned short *ptr = (unsigned short *) buffer; |
while (wcount--) { |
*ptr++ = inw_p(data_reg); |
*ptr++ = inw_p(data_reg); |
} |
} else |
#endif /* SUPPORT_SLOW_DATA_PORTS */ |
insw(data_reg, buffer, wcount<<1); |
} |
} |
|
/* |
* This is used for most PIO data transfers *to* the IDE interface |
*/ |
void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount) |
{ |
unsigned long io_base = HWIF(drive)->io.io_base; |
unsigned long data_reg = io_base + (IDE_DATA_OFFSET << HWIF(drive)->io.io_shift); |
#ifndef __arm__ |
byte io_32bit = drive->io_32bit; |
#else |
byte io_32bit = 0; |
#endif |
|
if (io_32bit) { |
#if SUPPORT_VLB_SYNC |
if (io_32bit & 2) { |
cli(); |
do_vlb_sync(io_base+IDE_NSECTOR_OFFSET); |
outsl(data_reg, buffer, wcount); |
if (drive->unmask) |
sti(); |
} else |
#endif /* SUPPORT_VLB_SYNC */ |
outsl(data_reg, buffer, wcount); |
} else { |
#if SUPPORT_SLOW_DATA_PORTS |
if (drive->slow) { |
unsigned short *ptr = (unsigned short *) buffer; |
while (wcount--) { |
outw_p(*ptr++, data_reg); |
outw_p(*ptr++, data_reg); |
} |
} else |
#endif /* SUPPORT_SLOW_DATA_PORTS */ |
outsw(data_reg, buffer, wcount<<1); |
} |
} |
|
/* |
* The following routines are mainly used by the ATAPI drivers. |
* |
* These routines will round up any request for an odd number of bytes, |
* so if an odd bytecount is specified, be sure that there's at least one |
* extra byte allocated for the buffer. |
*/ |
void atapi_input_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) |
{ |
++bytecount; |
ide_input_data (drive, buffer, bytecount / 4); |
if ((bytecount & 0x03) >= 2) |
insw (IDE_DATA_REG, ((byte *)buffer) + (bytecount & ~0x03), 1); |
} |
|
void atapi_output_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) |
{ |
++bytecount; |
ide_output_data (drive, buffer, bytecount / 4); |
if ((bytecount & 0x03) >= 2) |
outsw (IDE_DATA_REG, ((byte *)buffer) + (bytecount & ~0x03), 1); |
} |
|
/* |
* This should get invoked any time we exit the driver to |
* wait for an interrupt response from a drive. handler() points |
* at the appropriate code to handle the next interrupt, and a |
* timer is started to prevent us from waiting forever in case |
* something goes wrong (see the timer_expiry() handler later on). |
*/ |
void ide_set_handler (ide_drive_t *drive, ide_handler_t *handler, unsigned int timeout) |
{ |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
#ifdef DEBUG |
if (hwgroup->handler != NULL) { |
printk("%s: ide_set_handler: handler not null; old=%p, new=%p\n", |
drive->name, hwgroup->handler, handler); |
} |
#endif |
hwgroup->handler = handler; |
hwgroup->timer.expires = jiffies + timeout; |
add_timer(&(hwgroup->timer)); |
} |
|
/* |
* lba_capacity_is_ok() performs a sanity check on the claimed "lba_capacity" |
* value for this drive (from its reported identification information). |
* |
* Returns: 1 if lba_capacity looks sensible |
* 0 otherwise |
*/ |
static int lba_capacity_is_ok (struct hd_driveid *id) |
{ |
unsigned long lba_sects = id->lba_capacity; |
unsigned long chs_sects = id->cyls * id->heads * id->sectors; |
unsigned long _10_percent = chs_sects / 10; |
|
/* very large drives (8GB+) may lie about the number of cylinders */ |
if (id->cyls == 16383 && id->heads == 16 && id->sectors == 63 && lba_sects > chs_sects) { |
id->cyls = lba_sects / (16 * 63); /* correct cyls */ |
return 1; /* lba_capacity is our only option */ |
} |
/* perform a rough sanity check on lba_sects: within 10% is "okay" */ |
if ((lba_sects - chs_sects) < _10_percent) |
return 1; /* lba_capacity is good */ |
|
/* some drives have the word order reversed */ |
lba_sects = (lba_sects << 16) | (lba_sects >> 16); |
if ((lba_sects - chs_sects) < _10_percent) { |
id->lba_capacity = lba_sects; /* fix it */ |
return 1; /* lba_capacity is (now) good */ |
} |
return 0; /* lba_capacity value is bad */ |
} |
|
/* |
* current_capacity() returns the capacity (in sectors) of a drive |
* according to its current geometry/LBA settings. |
*/ |
static unsigned long current_capacity (ide_drive_t *drive) |
{ |
struct hd_driveid *id = drive->id; |
unsigned long capacity = drive->cyl * drive->head * drive->sect; |
|
if (!drive->present) |
return 0; |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) |
return idefloppy_capacity(drive); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
if (drive->media != ide_disk) |
return 0x7fffffff; /* cdrom or tape */ |
drive->select.b.lba = 0; |
/* Determine capacity, and use LBA if the drive properly supports it */ |
if (id != NULL && (id->capability & 2) && lba_capacity_is_ok(id)) { |
if (id->lba_capacity >= capacity) { |
drive->cyl = id->lba_capacity / (drive->head * drive->sect); |
capacity = id->lba_capacity; |
drive->select.b.lba = 1; |
} |
} |
return (capacity - drive->sect0); |
} |
|
/* |
* ide_geninit() is called exactly *once* for each major, from genhd.c, |
* at the beginning of the initial partition check for the drives. |
*/ |
static void ide_geninit (struct gendisk *gd) |
{ |
unsigned int unit; |
ide_hwif_t *hwif = gd->real_devices; |
|
for (unit = 0; unit < gd->nr_real; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->present && drive->media == ide_cdrom) |
ide_cdrom_setup(drive); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->present && drive->media == ide_tape) |
idetape_setup(drive); |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->present && drive->media == ide_floppy) |
idefloppy_setup(drive); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
drive->part[0].nr_sects = current_capacity(drive); |
if (!drive->present || (drive->media != ide_disk && drive->media != ide_floppy) || |
!drive->part[0].nr_sects) { |
drive->part[0].start_sect = -1; /* skip partition check */ |
} |
} |
} |
|
/* |
* init_gendisk() (as opposed to ide_geninit) is called for each major device, |
* after probing for drives, to allocate partition tables and other data |
* structures needed for the routines in genhd.c. ide_geninit() gets called |
* somewhat later, during the partition check. |
*/ |
static void init_gendisk (ide_hwif_t *hwif) |
{ |
struct gendisk *gd, **gdp; |
unsigned int unit, units, minors; |
int *bs; |
|
/* figure out maximum drive number on the interface */ |
for (units = MAX_DRIVES; units > 0; --units) { |
if (hwif->drives[units-1].present) |
break; |
} |
minors = units * (1<<PARTN_BITS); |
gd = kmalloc (sizeof(struct gendisk), GFP_KERNEL); |
gd->sizes = kmalloc (minors * sizeof(int), GFP_KERNEL); |
gd->part = kmalloc (minors * sizeof(struct hd_struct), GFP_KERNEL); |
bs = kmalloc (minors*sizeof(int), GFP_KERNEL); |
|
memset(gd->part, 0, minors * sizeof(struct hd_struct)); |
|
/* cdroms and msdos f/s are examples of non-1024 blocksizes */ |
blksize_size[hwif->major] = bs; |
for (unit = 0; unit < minors; ++unit) |
*bs++ = BLOCK_SIZE; |
|
for (unit = 0; unit < units; ++unit) |
hwif->drives[unit].part = &gd->part[unit << PARTN_BITS]; |
|
gd->major = hwif->major; /* our major device number */ |
gd->major_name = IDE_MAJOR_NAME; /* treated special in genhd.c */ |
gd->minor_shift = PARTN_BITS; /* num bits for partitions */ |
gd->max_p = 1<<PARTN_BITS; /* 1 + max partitions / drive */ |
gd->max_nr = units; /* max num real drives */ |
gd->nr_real = units; /* current num real drives */ |
gd->init = ide_geninit; /* initialization function */ |
gd->real_devices= hwif; /* ptr to internal data */ |
gd->next = NULL; /* linked list of major devs */ |
|
for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) ; |
hwif->gd = *gdp = gd; /* link onto tail of list */ |
} |
|
static void do_reset1 (ide_drive_t *, int); /* needed below */ |
|
#ifdef CONFIG_BLK_DEV_IDEATAPI |
/* |
* atapi_reset_pollfunc() gets invoked to poll the interface for completion every 50ms |
* during an atapi drive reset operation. If the drive has not yet responded, |
* and we have not yet hit our maximum waiting time, then the timer is restarted |
* for another 50ms. |
*/ |
static void atapi_reset_pollfunc (ide_drive_t *drive) |
{ |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
byte stat; |
|
OUT_BYTE (drive->select.all, IDE_SELECT_REG); |
udelay (10); |
|
if (OK_STAT(stat=GET_STAT(), 0, BUSY_STAT)) { |
printk("%s: ATAPI reset complete\n", drive->name); |
} else { |
if (jiffies < hwgroup->poll_timeout) { |
ide_set_handler (drive, &atapi_reset_pollfunc, HZ/20); |
return; /* continue polling */ |
} |
hwgroup->poll_timeout = 0; /* end of polling */ |
printk("%s: ATAPI reset timed-out, status=0x%02x\n", drive->name, stat); |
do_reset1 (drive, 1); /* do it the old fashioned way */ |
return; |
} |
hwgroup->poll_timeout = 0; /* done polling */ |
} |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
|
/* |
* reset_pollfunc() gets invoked to poll the interface for completion every 50ms |
* during an ide reset operation. If the drives have not yet responded, |
* and we have not yet hit our maximum waiting time, then the timer is restarted |
* for another 50ms. |
*/ |
static void reset_pollfunc (ide_drive_t *drive) |
{ |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
ide_hwif_t *hwif = HWIF(drive); |
byte tmp; |
|
if (!OK_STAT(tmp=GET_STAT(), 0, BUSY_STAT)) { |
if (jiffies < hwgroup->poll_timeout) { |
ide_set_handler (drive, &reset_pollfunc, HZ/20); |
return; /* continue polling */ |
} |
printk("%s: reset timed-out, status=0x%02x\n", hwif->name, tmp); |
} else { |
printk("%s: reset: ", hwif->name); |
if ((tmp = GET_ERR()) == 1) |
printk("success\n"); |
else { |
#if FANCY_STATUS_DUMPS |
printk("master: "); |
switch (tmp & 0x7f) { |
case 1: printk("passed"); |
break; |
case 2: printk("formatter device error"); |
break; |
case 3: printk("sector buffer error"); |
break; |
case 4: printk("ECC circuitry error"); |
break; |
case 5: printk("controlling MPU error"); |
break; |
default:printk("error (0x%02x?)", tmp); |
} |
if (tmp & 0x80) |
printk("; slave: failed"); |
printk("\n"); |
#else |
printk("failed\n"); |
#endif /* FANCY_STATUS_DUMPS */ |
} |
} |
hwgroup->poll_timeout = 0; /* done polling */ |
} |
|
/* |
* do_reset1() attempts to recover a confused drive by resetting it. |
* Unfortunately, resetting a disk drive actually resets all devices on |
* the same interface, so it can really be thought of as resetting the |
* interface rather than resetting the drive. |
* |
* ATAPI devices have their own reset mechanism which allows them to be |
* individually reset without clobbering other devices on the same interface. |
* |
* Unfortunately, the IDE interface does not generate an interrupt to let |
* us know when the reset operation has finished, so we must poll for this. |
* Equally poor, though, is the fact that this may a very long time to complete, |
* (up to 30 seconds worstcase). So, instead of busy-waiting here for it, |
* we set a timer to poll at 50ms intervals. |
*/ |
static void do_reset1 (ide_drive_t *drive, int do_not_try_atapi) |
{ |
unsigned int unit; |
unsigned long flags; |
ide_hwif_t *hwif = HWIF(drive); |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
|
save_flags(flags); |
cli(); /* Why ? */ |
|
#ifdef CONFIG_BLK_DEV_IDEATAPI |
/* For an ATAPI device, first try an ATAPI SRST. */ |
if (drive->media != ide_disk) { |
if (!do_not_try_atapi) { |
if (!drive->keep_settings) { |
drive->unmask = 0; |
drive->io_32bit = 0; |
} |
OUT_BYTE (drive->select.all, IDE_SELECT_REG); |
udelay (20); |
OUT_BYTE (WIN_SRST, IDE_COMMAND_REG); |
hwgroup->poll_timeout = jiffies + WAIT_WORSTCASE; |
ide_set_handler (drive, &atapi_reset_pollfunc, HZ/20); |
restore_flags (flags); |
return; |
} |
} |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
|
/* |
* First, reset any device state data we were maintaining |
* for any of the drives on this interface. |
*/ |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *rdrive = &hwif->drives[unit]; |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (rdrive->media == ide_tape) |
rdrive->tape.reset_issued = 1; |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
rdrive->special.all = 0; |
rdrive->special.b.set_geometry = 1; |
rdrive->special.b.recalibrate = 1; |
if (OK_TO_RESET_CONTROLLER) |
rdrive->mult_count = 0; |
if (!rdrive->keep_settings) { |
rdrive->mult_req = 0; |
rdrive->unmask = 0; |
rdrive->io_32bit = 0; |
if (rdrive->using_dma) { |
rdrive->using_dma = 0; |
printk("%s: disabled DMA\n", rdrive->name); |
} |
} |
if (rdrive->mult_req != rdrive->mult_count) |
rdrive->special.b.set_multmode = 1; |
} |
|
#if OK_TO_RESET_CONTROLLER |
/* |
* Note that we also set nIEN while resetting the device, |
* to mask unwanted interrupts from the interface during the reset. |
* However, due to the design of PC hardware, this will cause an |
* immediate interrupt due to the edge transition it produces. |
* This single interrupt gives us a "fast poll" for drives that |
* recover from reset very quickly, saving us the first 50ms wait time. |
*/ |
OUT_BYTE(drive->ctl|6,IDE_CONTROL_REG); /* set SRST and nIEN */ |
udelay(10); /* more than enough time */ |
OUT_BYTE(drive->ctl|2,IDE_CONTROL_REG); /* clear SRST, leave nIEN */ |
udelay(10); /* more than enough time */ |
hwgroup->poll_timeout = jiffies + WAIT_WORSTCASE; |
ide_set_handler (drive, &reset_pollfunc, HZ/20); |
#endif /* OK_TO_RESET_CONTROLLER */ |
|
restore_flags (flags); |
} |
|
/* |
* ide_do_reset() is the entry point to the drive/interface reset code. |
*/ |
void ide_do_reset (ide_drive_t *drive) |
{ |
do_reset1 (drive, 0); |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->media == ide_tape) |
drive->tape.reset_issued=1; |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
} |
|
/* |
* Clean up after success/failure of an explicit drive cmd |
*/ |
void ide_end_drive_cmd (ide_drive_t *drive, byte stat, byte err) |
{ |
unsigned long flags; |
struct request *rq = HWGROUP(drive)->rq; |
|
if (rq->cmd == IDE_DRIVE_CMD) { |
byte *args = (byte *) rq->buffer; |
rq->errors = !OK_STAT(stat,READY_STAT,BAD_STAT); |
if (args) { |
args[0] = stat; |
args[1] = err; |
args[2] = IN_BYTE(IDE_NSECTOR_REG); |
} |
} |
save_flags(flags); |
cli(); |
blk_dev[MAJOR(rq->rq_dev)].current_request = rq->next; |
HWGROUP(drive)->rq = NULL; |
rq->rq_status = RQ_INACTIVE; |
if (rq->sem != NULL) |
up(rq->sem); |
restore_flags(flags); |
} |
|
/* |
* Error reporting, in human readable form (luxurious, but a memory hog). |
*/ |
byte ide_dump_status (ide_drive_t *drive, const char *msg, byte stat) |
{ |
unsigned long flags; |
byte err = 0; |
|
save_flags (flags); |
sti(); |
printk("%s: %s: status=0x%02x", drive->name, msg, stat); |
#if FANCY_STATUS_DUMPS |
if (drive->media == ide_disk) { |
printk(" { "); |
if (stat & BUSY_STAT) |
printk("Busy "); |
else { |
if (stat & READY_STAT) printk("DriveReady "); |
if (stat & WRERR_STAT) printk("DeviceFault "); |
if (stat & SEEK_STAT) printk("SeekComplete "); |
if (stat & DRQ_STAT) printk("DataRequest "); |
if (stat & ECC_STAT) printk("CorrectedError "); |
if (stat & INDEX_STAT) printk("Index "); |
if (stat & ERR_STAT) printk("Error "); |
} |
printk("}"); |
} |
#endif /* FANCY_STATUS_DUMPS */ |
printk("\n"); |
if ((stat & (BUSY_STAT|ERR_STAT)) == ERR_STAT) { |
err = GET_ERR(); |
printk("%s: %s: error=0x%02x", drive->name, msg, err); |
#if FANCY_STATUS_DUMPS |
if (drive->media == ide_disk) { |
printk(" { "); |
if (err & BBD_ERR) printk("BadSector "); |
if (err & ECC_ERR) printk("UncorrectableError "); |
if (err & ID_ERR) printk("SectorIdNotFound "); |
if (err & ABRT_ERR) printk("DriveStatusError "); |
if (err & TRK0_ERR) printk("TrackZeroNotFound "); |
if (err & MARK_ERR) printk("AddrMarkNotFound "); |
printk("}"); |
if (err & (BBD_ERR|ECC_ERR|ID_ERR|MARK_ERR)) { |
byte cur = IN_BYTE(IDE_SELECT_REG); |
if (cur & 0x40) { /* using LBA? */ |
printk(", LBAsect=%ld", (unsigned long) |
((cur&0xf)<<24) |
|(IN_BYTE(IDE_HCYL_REG)<<16) |
|(IN_BYTE(IDE_LCYL_REG)<<8) |
| IN_BYTE(IDE_SECTOR_REG)); |
} else { |
printk(", CHS=%d/%d/%d", |
(IN_BYTE(IDE_HCYL_REG)<<8) + |
IN_BYTE(IDE_LCYL_REG), |
cur & 0xf, |
IN_BYTE(IDE_SECTOR_REG)); |
} |
if (HWGROUP(drive)->rq) |
printk(", sector=%ld", HWGROUP(drive)->rq->sector); |
} |
} |
#endif /* FANCY_STATUS_DUMPS */ |
printk("\n"); |
} |
restore_flags (flags); |
return err; |
} |
|
/* |
* try_to_flush_leftover_data() is invoked in response to a drive |
* unexpectedly having its DRQ_STAT bit set. As an alternative to |
* resetting the drive, this routine tries to clear the condition |
* by read a sector's worth of data from the drive. Of course, |
* this may not help if the drive is *waiting* for data from *us*. |
*/ |
static void try_to_flush_leftover_data (ide_drive_t *drive) |
{ |
int i = (drive->mult_count ? drive->mult_count : 1) * SECTOR_WORDS; |
|
while (i > 0) { |
unsigned long buffer[16]; |
unsigned int wcount = (i > 16) ? 16 : i; |
i -= wcount; |
ide_input_data (drive, buffer, wcount); |
} |
} |
|
/* |
* ide_error() takes action based on the error returned by the controller. |
*/ |
void ide_error (ide_drive_t *drive, const char *msg, byte stat) |
{ |
struct request *rq; |
byte err; |
|
err = ide_dump_status(drive, msg, stat); |
if ((rq = HWGROUP(drive)->rq) == NULL || drive == NULL) |
return; |
/* retry only "normal" I/O: */ |
if (rq->cmd == IDE_DRIVE_CMD) { |
rq->errors = 1; |
ide_end_drive_cmd(drive, stat, err); |
return; |
} |
if (stat & BUSY_STAT) { /* other bits are useless when BUSY */ |
rq->errors |= ERROR_RESET; |
} else { |
if (drive->media == ide_disk && (stat & ERR_STAT)) { |
/* err has different meaning on cdrom and tape */ |
if (err == ABRT_ERR) { |
if (drive->select.b.lba && IN_BYTE(IDE_COMMAND_REG) == WIN_SPECIFY) |
return; /* some newer drives don't support WIN_SPECIFY */ |
} else if ((err & (ABRT_ERR | ICRC_ERR)) == (ABRT_ERR | ICRC_ERR)) |
; /* UDMA crc error -- just retry the operation */ |
else if (err & (BBD_ERR | ECC_ERR)) /* retries won't help these */ |
rq->errors = ERROR_MAX; |
else if (err & TRK0_ERR) /* help it find track zero */ |
rq->errors |= ERROR_RECAL; |
else if (err & MC_ERR) |
drive->special.b.mc = 1; |
} |
if ((stat & DRQ_STAT) && rq->cmd != WRITE) |
try_to_flush_leftover_data(drive); |
} |
if (GET_STAT() & (BUSY_STAT|DRQ_STAT)) |
rq->errors |= ERROR_RESET; /* Mmmm.. timing problem */ |
|
if (rq->errors >= ERROR_MAX) { |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->media == ide_tape) { |
rq->errors = 0; |
idetape_end_request(0, HWGROUP(drive)); |
} else |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) { |
rq->errors = 0; |
idefloppy_end_request(0, HWGROUP(drive)); |
} else |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
if (drive->media == ide_scsi) { |
rq->errors = 0; |
idescsi_end_request(0, HWGROUP(drive)); |
} else |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
ide_end_request(0, HWGROUP(drive)); |
} |
else { |
if ((rq->errors & ERROR_RESET) == ERROR_RESET) { |
++rq->errors; |
ide_do_reset(drive); |
return; |
} else if ((rq->errors & ERROR_RECAL) == ERROR_RECAL) |
drive->special.b.recalibrate = 1; |
++rq->errors; |
} |
} |
|
/* |
* read_intr() is the handler for disk read/multread interrupts |
*/ |
static void read_intr (ide_drive_t *drive) |
{ |
byte stat; |
int i; |
unsigned int msect, nsect; |
struct request *rq; |
|
if (!OK_STAT(stat=GET_STAT(),DATA_READY,BAD_R_STAT)) { |
ide_error(drive, "read_intr", stat); |
return; |
} |
msect = drive->mult_count; |
read_next: |
rq = HWGROUP(drive)->rq; |
if (msect) { |
if ((nsect = rq->current_nr_sectors) > msect) |
nsect = msect; |
msect -= nsect; |
} else |
nsect = 1; |
ide_input_data(drive, rq->buffer, nsect * SECTOR_WORDS); |
#ifdef DEBUG |
printk("%s: read: sectors(%ld-%ld), buffer=0x%08lx, remaining=%ld\n", |
drive->name, rq->sector, rq->sector+nsect-1, |
(unsigned long) rq->buffer+(nsect<<9), rq->nr_sectors-nsect); |
#endif |
rq->sector += nsect; |
rq->buffer += nsect<<9; |
rq->errors = 0; |
i = (rq->nr_sectors -= nsect); |
if ((rq->current_nr_sectors -= nsect) <= 0) |
ide_end_request(1, HWGROUP(drive)); |
if (i > 0) { |
if (msect) |
goto read_next; |
ide_set_handler (drive, &read_intr, WAIT_CMD); |
} |
} |
|
/* |
* write_intr() is the handler for disk write interrupts |
*/ |
static void write_intr (ide_drive_t *drive) |
{ |
byte stat; |
int i; |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
struct request *rq = hwgroup->rq; |
|
if (OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) { |
#ifdef DEBUG |
printk("%s: write: sector %ld, buffer=0x%08lx, remaining=%ld\n", |
drive->name, rq->sector, (unsigned long) rq->buffer, |
rq->nr_sectors-1); |
#endif |
if ((rq->nr_sectors == 1) ^ ((stat & DRQ_STAT) != 0)) { |
rq->sector++; |
rq->buffer += 512; |
rq->errors = 0; |
i = --rq->nr_sectors; |
--rq->current_nr_sectors; |
if (rq->current_nr_sectors <= 0) |
ide_end_request(1, hwgroup); |
if (i > 0) { |
ide_output_data (drive, rq->buffer, SECTOR_WORDS); |
ide_set_handler (drive, &write_intr, WAIT_CMD); |
} |
return; |
} |
} |
ide_error(drive, "write_intr", stat); |
} |
|
/* |
* ide_multwrite() transfers a block of up to mcount sectors of data |
* to a drive as part of a disk multiple-sector write operation. |
*/ |
void ide_multwrite (ide_drive_t *drive, unsigned int mcount) |
{ |
struct request *rq = &HWGROUP(drive)->wrq; |
|
do { |
unsigned int nsect = rq->current_nr_sectors; |
if (nsect > mcount) |
nsect = mcount; |
mcount -= nsect; |
|
ide_output_data(drive, rq->buffer, nsect<<7); |
#ifdef DEBUG |
printk("%s: multwrite: sector %ld, buffer=0x%08lx, count=%d, remaining=%ld\n", |
drive->name, rq->sector, (unsigned long) rq->buffer, |
nsect, rq->nr_sectors - nsect); |
#endif |
if ((rq->nr_sectors -= nsect) <= 0) |
break; |
if ((rq->current_nr_sectors -= nsect) == 0) { |
if ((rq->bh = rq->bh->b_reqnext) != NULL) { |
rq->current_nr_sectors = rq->bh->b_size>>9; |
rq->buffer = rq->bh->b_data; |
} else { |
panic("%s: buffer list corrupted\n", drive->name); |
break; |
} |
} else { |
rq->buffer += nsect << 9; |
} |
} while (mcount); |
} |
|
/* |
* multwrite_intr() is the handler for disk multwrite interrupts |
*/ |
static void multwrite_intr (ide_drive_t *drive) |
{ |
byte stat; |
int i; |
ide_hwgroup_t *hwgroup = HWGROUP(drive); |
struct request *rq = &hwgroup->wrq; |
|
if (OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) { |
if (stat & DRQ_STAT) { |
if (rq->nr_sectors) { |
ide_multwrite(drive, drive->mult_count); |
ide_set_handler (drive, &multwrite_intr, WAIT_CMD); |
return; |
} |
} else { |
if (!rq->nr_sectors) { /* all done? */ |
rq = hwgroup->rq; |
for (i = rq->nr_sectors; i > 0;){ |
i -= rq->current_nr_sectors; |
ide_end_request(1, hwgroup); |
} |
return; |
} |
} |
} |
ide_error(drive, "multwrite_intr", stat); |
} |
|
/* |
* Issue a simple drive command |
* The drive must be selected beforehand. |
*/ |
static void ide_cmd(ide_drive_t *drive, byte cmd, byte nsect, ide_handler_t *handler) |
{ |
ide_set_handler (drive, handler, WAIT_CMD); |
OUT_BYTE(drive->ctl,IDE_CONTROL_REG); |
OUT_BYTE(nsect,IDE_NSECTOR_REG); |
OUT_BYTE(cmd,IDE_COMMAND_REG); |
} |
|
/* |
* set_multmode_intr() is invoked on completion of a WIN_SETMULT cmd. |
*/ |
static void set_multmode_intr (ide_drive_t *drive) |
{ |
byte stat = GET_STAT(); |
|
sti(); |
if (OK_STAT(stat,READY_STAT,BAD_STAT)) { |
drive->mult_count = drive->mult_req; |
} else { |
drive->mult_req = drive->mult_count = 0; |
drive->special.b.recalibrate = 1; |
(void) ide_dump_status(drive, "set_multmode", stat); |
} |
} |
|
/* |
* set_geometry_intr() is invoked on completion of a WIN_SPECIFY cmd. |
*/ |
static void set_geometry_intr (ide_drive_t *drive) |
{ |
byte stat = GET_STAT(); |
|
sti(); |
if (!OK_STAT(stat,READY_STAT,BAD_STAT)) |
ide_error(drive, "set_geometry_intr", stat); |
} |
|
/* |
* recal_intr() is invoked on completion of a WIN_RESTORE (recalibrate) cmd. |
*/ |
static void recal_intr (ide_drive_t *drive) |
{ |
byte stat = GET_STAT(); |
|
sti(); |
if (!OK_STAT(stat,READY_STAT,BAD_STAT)) |
ide_error(drive, "recal_intr", stat); |
} |
|
/* |
* mc_intr() is invoked on completion of a WIN_ACKMC cmd. |
*/ |
static void mc_intr (ide_drive_t *drive) |
{ |
byte stat = GET_STAT(); |
|
sti(); |
if (!OK_STAT(stat,READY_STAT,BAD_STAT)) |
ide_error(drive, "mc_intr", stat); |
drive->special.b.mc = 0; |
} |
|
/* |
* drive_cmd_intr() is invoked on completion of a special DRIVE_CMD. |
*/ |
static void drive_cmd_intr (ide_drive_t *drive) |
{ |
struct request *rq = HWGROUP(drive)->rq; |
byte *args = (byte *) rq->buffer; |
byte stat = GET_STAT(); |
|
sti(); |
if ((stat & DRQ_STAT) && args && args[3]) { |
byte io_32bit = drive->io_32bit; |
drive->io_32bit = 0; |
ide_input_data(drive, &args[4], args[3] * SECTOR_WORDS); |
drive->io_32bit = io_32bit; |
stat = GET_STAT(); |
} |
if (OK_STAT(stat,READY_STAT,BAD_STAT)) |
ide_end_drive_cmd (drive, stat, GET_ERR()); |
else |
ide_error(drive, "drive_cmd", stat); /* calls ide_end_drive_cmd */ |
} |
|
/* |
* do_special() is used to issue WIN_SPECIFY, WIN_RESTORE, and WIN_SETMULT |
* commands to a drive. It used to do much more, but has been scaled back. |
*/ |
static inline void do_special (ide_drive_t *drive) |
{ |
special_t *s = &drive->special; |
|
#ifdef DEBUG |
printk("%s: do_special: 0x%02x\n", drive->name, s->all); |
#endif |
if (s->b.set_geometry) { |
s->b.set_geometry = 0; |
if (drive->media == ide_disk && !drive->no_geom) { |
OUT_BYTE(drive->sect,IDE_SECTOR_REG); |
OUT_BYTE(drive->cyl,IDE_LCYL_REG); |
OUT_BYTE(drive->cyl>>8,IDE_HCYL_REG); |
OUT_BYTE(((drive->head-1)|drive->select.all)&0xBF,IDE_SELECT_REG); |
if (!IS_PROMISE_DRIVE) |
ide_cmd(drive, WIN_SPECIFY, drive->sect, &set_geometry_intr); |
} |
} else if (s->b.recalibrate) { |
s->b.recalibrate = 0; |
if (drive->media == ide_disk && !IS_PROMISE_DRIVE) |
ide_cmd(drive, WIN_RESTORE, drive->sect, &recal_intr); |
} else if (s->b.set_tune) { |
ide_tuneproc_t *tuneproc = HWIF(drive)->tuneproc; |
s->b.set_tune = 0; |
if (tuneproc != NULL) |
tuneproc(drive, drive->tune_req); |
} else if (s->b.set_multmode) { |
s->b.set_multmode = 0; |
if (drive->media == ide_disk) { |
if (drive->id && drive->mult_req > drive->id->max_multsect) |
drive->mult_req = drive->id->max_multsect; |
if (!IS_PROMISE_DRIVE) |
ide_cmd(drive, WIN_SETMULT, drive->mult_req, &set_multmode_intr); |
} else |
drive->mult_req = 0; |
} else if (s->b.mc) { |
s->b.mc = 0; |
if (drive->media == ide_disk && !IS_PROMISE_DRIVE) |
ide_cmd(drive, WIN_ACKMC, drive->sect, &mc_intr); |
} else if (s->all) { |
int special = s->all; |
s->all = 0; |
printk("%s: bad special flag: 0x%02x\n", drive->name, special); |
} |
} |
|
/* |
* This routine busy-waits for the drive status to be not "busy". |
* It then checks the status for all of the "good" bits and none |
* of the "bad" bits, and if all is okay it returns 0. All other |
* cases return 1 after invoking ide_error() -- caller should just return. |
* |
* This routine should get fixed to not hog the cpu during extra long waits.. |
* That could be done by busy-waiting for the first jiffy or two, and then |
* setting a timer to wake up at half second intervals thereafter, |
* until timeout is achieved, before timing out. |
*/ |
int ide_wait_stat (ide_drive_t *drive, byte good, byte bad, unsigned long timeout) |
{ |
byte stat; |
unsigned long flags; |
|
udelay(1); /* spec allows drive 400ns to assert "BUSY" */ |
if ((stat = GET_STAT()) & BUSY_STAT) { |
save_flags(flags); |
sti(); |
timeout += jiffies; |
while ((stat = GET_STAT()) & BUSY_STAT) { |
if (jiffies > timeout) { |
restore_flags(flags); |
ide_error(drive, "status timeout", stat); |
return 1; |
} |
} |
restore_flags(flags); |
} |
udelay(1); /* allow status to settle, then read it again */ |
if (OK_STAT((stat = GET_STAT()), good, bad)) |
return 0; |
ide_error(drive, "status error", stat); |
return 1; |
} |
|
/* |
* do_rw_disk() issues READ and WRITE commands to a disk, |
* using LBA if supported, or CHS otherwise, to address sectors. |
* It also takes care of issuing special DRIVE_CMDs. |
*/ |
static inline void do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned long block) |
{ |
ide_hwif_t *hwif = HWIF(drive); |
unsigned long io_base = hwif->io.io_base; |
unsigned long io_shift = hwif->io.io_shift; |
#ifdef CONFIG_BLK_DEV_PROMISE |
int use_promise_io = 0; |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
|
OUT_BYTE(drive->ctl,IDE_CONTROL_REG); |
OUT_BYTE(rq->nr_sectors,io_base + (IDE_NSECTOR_OFFSET << io_shift)); |
#ifdef CONFIG_BLK_DEV_PROMISE |
if (IS_PROMISE_DRIVE) { |
if (hwif->is_promise2 || rq->cmd == READ) { |
use_promise_io = 1; |
} |
} |
if (drive->select.b.lba || use_promise_io) { |
#else /* !CONFIG_BLK_DEV_PROMISE */ |
if (drive->select.b.lba) { |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
#ifdef DEBUG |
printk("%s: %sing: LBAsect=%ld, sectors=%ld, buffer=0x%08lx\n", |
drive->name, (rq->cmd==READ)?"read":"writ", |
block, rq->nr_sectors, (unsigned long) rq->buffer); |
#endif |
OUT_BYTE(block,io_base + (IDE_SECTOR_OFFSET << io_shift)); |
OUT_BYTE(block>>=8,io_base + (IDE_LCYL_OFFSET << io_shift)); |
OUT_BYTE(block>>=8,io_base + (IDE_HCYL_OFFSET << io_shift)); |
OUT_BYTE(((block>>8)&0x0f)|drive->select.all,io_base + (IDE_SELECT_OFFSET << io_shift)); |
} else { |
unsigned int sect,head,cyl,track; |
track = block / drive->sect; |
sect = block % drive->sect + 1; |
OUT_BYTE(sect,io_base + (IDE_SECTOR_OFFSET << io_shift)); |
head = track % drive->head; |
cyl = track / drive->head; |
OUT_BYTE(cyl,io_base + (IDE_LCYL_OFFSET << io_shift)); |
OUT_BYTE(cyl>>8,io_base + (IDE_HCYL_OFFSET << io_shift)); |
OUT_BYTE(head|drive->select.all,io_base + (IDE_SELECT_OFFSET << io_shift)); |
#ifdef DEBUG |
printk("%s: %sing: CHS=%d/%d/%d, sectors=%ld, buffer=0x%08lx\n", |
drive->name, (rq->cmd==READ)?"read":"writ", cyl, |
head, sect, rq->nr_sectors, (unsigned long) rq->buffer); |
#endif |
} |
#ifdef CONFIG_BLK_DEV_PROMISE |
if (use_promise_io) { |
do_promise_io (drive, rq); |
return; |
} |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
if (rq->cmd == READ) { |
#ifdef CONFIG_BLK_DEV_TRITON |
if (drive->using_dma && !(HWIF(drive)->dmaproc(ide_dma_read, drive))) |
return; |
#endif /* CONFIG_BLK_DEV_TRITON */ |
ide_set_handler(drive, &read_intr, WAIT_CMD); |
OUT_BYTE(drive->mult_count ? WIN_MULTREAD : WIN_READ, io_base + (IDE_COMMAND_OFFSET << io_shift)); |
return; |
} |
if (rq->cmd == WRITE) { |
#ifdef CONFIG_BLK_DEV_TRITON |
if (drive->using_dma && !(HWIF(drive)->dmaproc(ide_dma_write, drive))) |
return; |
#endif /* CONFIG_BLK_DEV_TRITON */ |
OUT_BYTE(drive->mult_count ? WIN_MULTWRITE : WIN_WRITE, io_base + (IDE_COMMAND_OFFSET << io_shift)); |
if (ide_wait_stat(drive, DATA_READY, drive->bad_wstat, WAIT_DRQ)) { |
printk("%s: no DRQ after issuing %s\n", drive->name, |
drive->mult_count ? "MULTWRITE" : "WRITE"); |
return; |
} |
if (!drive->unmask) |
cli(); |
if (drive->mult_count) { |
HWGROUP(drive)->wrq = *rq; /* scratchpad */ |
ide_set_handler (drive, &multwrite_intr, WAIT_CMD); |
ide_multwrite(drive, drive->mult_count); |
} else { |
ide_set_handler (drive, &write_intr, WAIT_CMD); |
ide_output_data(drive, rq->buffer, SECTOR_WORDS); |
} |
return; |
} |
printk("%s: bad command: %d\n", drive->name, rq->cmd); |
ide_end_request(0, HWGROUP(drive)); |
} |
|
/* |
* execute_drive_cmd() issues a special drive command, |
* usually initiated by ioctl() from the external hdparm program. |
*/ |
static void execute_drive_cmd (ide_drive_t *drive, struct request *rq) |
{ |
ide_hwif_t *hwif = HWIF(drive); |
unsigned long io_base = hwif->io.io_base; |
unsigned long io_shift = hwif->io.io_shift; |
byte *args = rq->buffer; |
if (args) { |
#ifdef DEBUG |
printk("%s: DRIVE_CMD cmd=0x%02x sc=0x%02x fr=0x%02x xx=0x%02x\n", |
drive->name, args[0], args[1], args[2], args[3]); |
#endif |
OUT_BYTE(args[2],io_base + (IDE_FEATURE_OFFSET << io_shift)); |
ide_cmd(drive, args[0], args[1], &drive_cmd_intr); |
return; |
} else { |
/* |
* NULL is actually a valid way of waiting for |
* all current requests to be flushed from the queue. |
*/ |
#ifdef DEBUG |
printk("%s: DRIVE_CMD (null)\n", drive->name); |
#endif |
ide_end_drive_cmd(drive, GET_STAT(), GET_ERR()); |
return; |
} |
} |
|
/* |
* do_request() initiates handling of a new I/O request |
*/ |
static inline void do_request (ide_hwif_t *hwif, struct request *rq) |
{ |
unsigned int minor, unit; |
unsigned long block, blockend; |
ide_drive_t *drive; |
|
sti(); |
#ifdef DEBUG |
printk("%s: do_request: current=0x%08lx\n", hwif->name, (unsigned long) rq); |
#endif |
minor = MINOR(rq->rq_dev); |
unit = minor >> PARTN_BITS; |
if (MAJOR(rq->rq_dev) != hwif->major || unit >= MAX_DRIVES) { |
printk("%s: bad device number: %s\n", |
hwif->name, kdevname(rq->rq_dev)); |
goto kill_rq; |
} |
drive = &hwif->drives[unit]; |
#ifdef DEBUG |
if (rq->bh && !buffer_locked(rq->bh)) { |
printk("%s: block not locked\n", drive->name); |
goto kill_rq; |
} |
#endif |
block = rq->sector; |
blockend = block + rq->nr_sectors; |
if ((blockend < block) || (blockend > drive->part[minor&PARTN_MASK].nr_sects)) { |
printk("%s%c: bad access: block=%ld, count=%ld\n", drive->name, |
(minor&PARTN_MASK)?'0'+(minor&PARTN_MASK):' ', block, rq->nr_sectors); |
goto kill_rq; |
} |
block += drive->part[minor&PARTN_MASK].start_sect + drive->sect0; |
#if FAKE_FDISK_FOR_EZDRIVE |
if (block == 0 && drive->remap_0_to_1) |
block = 1; /* redirect MBR access to EZ-Drive partn table */ |
#endif /* FAKE_FDISK_FOR_EZDRIVE */ |
((ide_hwgroup_t *)hwif->hwgroup)->drive = drive; |
#if (DISK_RECOVERY_TIME > 0) |
while ((read_timer() - hwif->last_time) < DISK_RECOVERY_TIME); |
#endif |
|
#ifdef CONFIG_BLK_DEV_IDETAPE |
POLL_HWIF_TAPE_DRIVE; /* macro from ide-tape.h */ |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
|
SELECT_DRIVE(hwif,drive); |
if (ide_wait_stat(drive, drive->ready_stat, BUSY_STAT|DRQ_STAT, WAIT_READY)) { |
printk("%s: drive not ready for command\n", drive->name); |
return; |
} |
|
if (!drive->special.all) { |
if (rq->cmd == IDE_DRIVE_CMD) { |
execute_drive_cmd(drive, rq); |
return; |
} |
#ifdef CONFIG_BLK_DEV_IDEATAPI |
switch (drive->media) { |
case ide_disk: |
do_rw_disk (drive, rq, block); |
return; |
#ifdef CONFIG_BLK_DEV_IDECD |
case ide_cdrom: |
ide_do_rw_cdrom (drive, block); |
return; |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
case ide_tape: |
idetape_do_request (drive, rq, block); |
return; |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
case ide_floppy: |
idefloppy_do_request (drive, rq, block); |
return; |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
case ide_scsi: |
idescsi_do_request (drive, rq, block); |
return; |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
|
default: |
printk("%s: media type %d not supported\n", |
drive->name, drive->media); |
goto kill_rq; |
} |
#else |
do_rw_disk (drive, rq, block); /* simpler and faster */ |
return; |
#endif /* CONFIG_BLK_DEV_IDEATAPI */; |
} |
do_special(drive); |
return; |
kill_rq: |
ide_end_request(0, hwif->hwgroup); |
} |
|
/* |
* The driver enables interrupts as much as possible. In order to do this, |
* (a) the device-interrupt is always masked before entry, and |
* (b) the timeout-interrupt is always disabled before entry. |
* |
* If we enter here from, say irq14, and then start a new request for irq15, |
* (possible with "serialize" option) then we cannot ensure that we exit |
* before the irq15 hits us. So, we must be careful not to let this bother us. |
* |
* Interrupts are still masked (by default) whenever we are exchanging |
* data/cmds with a drive, because some drives seem to have very poor |
* tolerance for latency during I/O. For devices which don't suffer from |
* this problem (most don't), the unmask flag can be set using the "hdparm" |
* utility, to permit other interrupts during data/cmd transfers. |
*/ |
void ide_do_request (ide_hwgroup_t *hwgroup) |
{ |
cli(); /* paranoia */ |
if (hwgroup->handler != NULL) { |
printk("%s: EEeekk!! handler not NULL in ide_do_request()\n", hwgroup->hwif->name); |
return; |
} |
do { |
ide_hwif_t *hwif = hwgroup->hwif; |
struct request *rq; |
if ((rq = hwgroup->rq) == NULL) { |
if (hwif->sharing_irq && hwgroup->drive) /* set nIEN */ |
OUT_BYTE(hwgroup->drive->ctl|2,hwif->io.ctl_port); |
/* |
* hwgroup->next_hwif is different from hwgroup->hwif |
* only when a request is inserted using "ide_next". |
* This saves wear and tear on IDE tapes. |
*/ |
hwif = hwgroup->next_hwif; |
do { |
rq = blk_dev[hwif->major].current_request; |
if (rq != NULL && rq->rq_status != RQ_INACTIVE) |
goto got_rq; |
} while ((hwif = hwif->next) != hwgroup->next_hwif); |
hwgroup->active = 0; |
return; /* no work left for this hwgroup */ |
} |
got_rq: |
do_request(hwgroup->hwif = hwgroup->next_hwif = hwif, hwgroup->rq = rq); |
cli(); |
} while (hwgroup->handler == NULL); |
} |
|
/* |
* do_hwgroup_request() invokes ide_do_request() after first masking |
* all possible interrupts for the current hwgroup. This prevents race |
* conditions in the event that an unexpected interrupt occurs while |
* we are in the driver. |
* |
* Note that when an interrupt is used to reenter the driver, the first level |
* handler will already have masked the irq that triggered, but any other ones |
* for the hwgroup will still be unmasked. The driver tries to be careful |
* about such things. |
*/ |
static void do_hwgroup_request (ide_hwgroup_t *hwgroup) |
{ |
if (hwgroup->handler == NULL) { |
ide_hwif_t *hgif = hwgroup->hwif; |
ide_hwif_t *hwif = hgif; |
hwgroup->active = 1; |
do { |
disable_irq(hwif->irq); |
} while ((hwif = hwif->next) != hgif); |
ide_do_request (hwgroup); |
do { |
enable_irq(hwif->irq); |
} while ((hwif = hwif->next) != hgif); |
} |
} |
|
static void do_ide0_request (void) /* invoked with cli() */ |
{ |
do_hwgroup_request (ide_hwifs[0].hwgroup); |
} |
|
#if MAX_HWIFS > 1 |
static void do_ide1_request (void) /* invoked with cli() */ |
{ |
do_hwgroup_request (ide_hwifs[1].hwgroup); |
} |
#endif |
|
#if MAX_HWIFS > 2 |
static void do_ide2_request (void) /* invoked with cli() */ |
{ |
do_hwgroup_request (ide_hwifs[2].hwgroup); |
} |
#endif |
|
#if MAX_HWIFS > 3 |
static void do_ide3_request (void) /* invoked with cli() */ |
{ |
do_hwgroup_request (ide_hwifs[3].hwgroup); |
} |
#endif |
|
static void timer_expiry (unsigned long data) |
{ |
ide_hwgroup_t *hwgroup = (ide_hwgroup_t *) data; |
ide_drive_t *drive = hwgroup->drive; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
if (hwgroup->poll_timeout != 0) { /* polling in progress? */ |
ide_handler_t *handler = hwgroup->handler; |
hwgroup->handler = NULL; |
handler(drive); |
} else if (hwgroup->handler == NULL) { /* not waiting for anything? */ |
sti(); /* drive must have responded just as the timer expired */ |
printk("%s: marginal timeout\n", drive->name); |
} else { |
hwgroup->handler = NULL; /* abort the operation */ |
if (hwgroup->hwif->dmaproc) |
(void) hwgroup->hwif->dmaproc (ide_dma_abort, drive); |
ide_error(drive, "irq timeout", GET_STAT()); |
} |
if (hwgroup->handler == NULL) |
do_hwgroup_request (hwgroup); |
restore_flags(flags); |
} |
|
/* |
* There's nothing really useful we can do with an unexpected interrupt, |
* other than reading the status register (to clear it), and logging it. |
* There should be no way that an irq can happen before we're ready for it, |
* so we needn't worry much about losing an "important" interrupt here. |
* |
* On laptops (and "green" PCs), an unexpected interrupt occurs whenever the |
* drive enters "idle", "standby", or "sleep" mode, so if the status looks |
* "good", we just ignore the interrupt completely. |
* |
* This routine assumes cli() is in effect when called. |
* |
* If an unexpected interrupt happens on irq15 while we are handling irq14 |
* and if the two interfaces are "serialized" (CMD640), then it looks like |
* we could screw up by interfering with a new request being set up for irq15. |
* |
* In reality, this is a non-issue. The new command is not sent unless the |
* drive is ready to accept one, in which case we know the drive is not |
* trying to interrupt us. And ide_set_handler() is always invoked before |
* completing the issuance of any new drive command, so we will not be |
* accidently invoked as a result of any valid command completion interrupt. |
* |
*/ |
static void unexpected_intr (int irq, ide_hwgroup_t *hwgroup) |
{ |
byte stat; |
unsigned int unit; |
ide_hwif_t *hwif = hwgroup->hwif; |
|
/* |
* handle the unexpected interrupt |
*/ |
do { |
if (hwif->irq == irq) { |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
if (!drive->present) |
continue; |
SELECT_DRIVE(hwif,drive); |
udelay(100); /* Ugly, but wait_stat() may not be safe here */ |
if (!OK_STAT(stat=GET_STAT(), drive->ready_stat, BAD_STAT)) { |
/* Try to not flood the console with msgs */ |
static unsigned long last_msgtime = 0; |
if ((last_msgtime + (HZ/2)) < jiffies) { |
last_msgtime = jiffies; |
(void) ide_dump_status(drive, "unexpected_intr", stat); |
} |
} |
if ((stat & DRQ_STAT)) |
try_to_flush_leftover_data(drive); |
} |
} |
} while ((hwif = hwif->next) != hwgroup->hwif); |
SELECT_DRIVE(hwif,hwgroup->drive); /* Ugh.. probably interrupts current I/O */ |
udelay(100); /* Ugly, but wait_stat() may not be safe here */ |
} |
|
/* |
* entry point for all interrupts, caller does cli() for us |
*/ |
void ide_intr (int irq, void *dev_id, struct pt_regs *regs) |
{ |
ide_hwgroup_t *hwgroup = dev_id; |
ide_handler_t *handler; |
|
if (irq == hwgroup->hwif->irq && (handler = hwgroup->handler) != NULL) { |
ide_drive_t *drive = hwgroup->drive; |
hwgroup->handler = NULL; |
del_timer(&(hwgroup->timer)); |
if (drive->unmask) |
sti(); |
handler(drive); |
cli(); /* this is necessary, as next rq may be different irq */ |
if (hwgroup->handler == NULL) { |
SET_RECOVERY_TIMER(HWIF(drive)); |
ide_do_request(hwgroup); |
} |
} else { |
unexpected_intr(irq, hwgroup); |
} |
cli(); |
} |
|
/* |
* get_info_ptr() returns the (ide_drive_t *) for a given device number. |
* It returns NULL if the given device number does not match any present drives. |
*/ |
static ide_drive_t *get_info_ptr (kdev_t i_rdev) |
{ |
int major = MAJOR(i_rdev); |
unsigned int h; |
|
for (h = 0; h < MAX_HWIFS; ++h) { |
ide_hwif_t *hwif = &ide_hwifs[h]; |
if (hwif->present && major == hwif->major) { |
unsigned unit = DEVICE_NR(i_rdev); |
if (unit < MAX_DRIVES) { |
ide_drive_t *drive = &hwif->drives[unit]; |
if (drive->present) |
return drive; |
} else if (major == IDE0_MAJOR && unit < 4) { |
printk("ide: probable bad entry for /dev/hd%c\n", 'a'+unit); |
printk("ide: to fix it, run: /usr/src/linux/scripts/MAKEDEV.ide\n"); |
} |
break; |
} |
} |
return NULL; |
} |
|
/* |
* This function is intended to be used prior to invoking ide_do_drive_cmd(). |
*/ |
void ide_init_drive_cmd (struct request *rq) |
{ |
rq->buffer = NULL; |
rq->cmd = IDE_DRIVE_CMD; |
rq->sector = 0; |
rq->nr_sectors = 0; |
rq->current_nr_sectors = 0; |
rq->sem = NULL; |
rq->bh = NULL; |
rq->bhtail = NULL; |
rq->next = NULL; |
|
#if 0 /* these are done each time through ide_do_drive_cmd() */ |
rq->errors = 0; |
rq->rq_status = RQ_ACTIVE; |
rq->rq_dev = ????; |
#endif |
} |
|
/* |
* This function issues a special IDE device request |
* onto the request queue. |
* |
* If action is ide_wait, then then rq is queued at the end of |
* the request queue, and the function sleeps until it has been |
* processed. This is for use when invoked from an ioctl handler. |
* |
* If action is ide_preempt, then the rq is queued at the head of |
* the request queue, displacing the currently-being-processed |
* request and this function returns immediately without waiting |
* for the new rq to be completed. This is VERY DANGEROUS, and is |
* intended for careful use by the ATAPI tape/cdrom driver code. |
* |
* If action is ide_next, then the rq is queued immediately after |
* the currently-being-processed-request (if any), and the function |
* returns without waiting for the new rq to be completed. As above, |
* This is VERY DANGEROUS, and is intended for careful use by the |
* ATAPI tape/cdrom driver code. |
* |
* If action is ide_end, then the rq is queued at the end of the |
* request queue, and the function returns immediately without waiting |
* for the new rq to be completed. This is again intended for careful |
* use by the ATAPI tape/cdrom driver code. (Currently used by ide-tape.c, |
* when operating in the pipelined operation mode). |
*/ |
int ide_do_drive_cmd (ide_drive_t *drive, struct request *rq, ide_action_t action) |
{ |
unsigned long flags; |
unsigned int major = HWIF(drive)->major; |
struct request *cur_rq; |
struct blk_dev_struct *bdev = &blk_dev[major]; |
struct semaphore sem = MUTEX_LOCKED; |
|
if (IS_PROMISE_DRIVE && rq->buffer != NULL) |
return -ENOSYS; /* special drive cmds not supported */ |
rq->errors = 0; |
rq->rq_status = RQ_ACTIVE; |
rq->rq_dev = MKDEV(major,(drive->select.b.unit)<<PARTN_BITS); |
if (action == ide_wait) |
rq->sem = &sem; |
unplug_device(bdev); |
|
save_flags(flags); |
cli(); |
if (action == ide_next) |
HWGROUP(drive)->next_hwif = HWIF(drive); |
cur_rq = bdev->current_request; |
|
if (cur_rq == NULL || action == ide_preempt) { |
rq->next = cur_rq; |
bdev->current_request = rq; |
if (action == ide_preempt) |
HWGROUP(drive)->rq = NULL; |
} else { |
if (action == ide_wait || action == ide_end) { |
while (cur_rq->next != NULL) /* find end of list */ |
cur_rq = cur_rq->next; |
} |
rq->next = cur_rq->next; |
cur_rq->next = rq; |
} |
if (!HWGROUP(drive)->active) { |
do_hwgroup_request(HWGROUP(drive)); |
cli(); |
} |
if (action == ide_wait && rq->rq_status != RQ_INACTIVE) |
down(&sem); /* wait for it to be serviced */ |
restore_flags(flags); |
return rq->errors ? -EIO : 0; /* return -EIO if errors */ |
} |
|
static int ide_open(struct inode * inode, struct file * filp) |
{ |
ide_drive_t *drive; |
unsigned long flags; |
|
if ((drive = get_info_ptr(inode->i_rdev)) == NULL) |
return -ENXIO; |
save_flags(flags); |
cli(); |
while (drive->busy) |
sleep_on(&drive->wqueue); |
drive->usage++; |
restore_flags(flags); |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->media == ide_cdrom) |
return ide_cdrom_open (inode, filp, drive); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->media == ide_tape) |
return idetape_blkdev_open (inode, filp, drive); |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) |
return idefloppy_open (inode, filp, drive); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
if (drive->media == ide_scsi) |
return idescsi_open (inode, filp, drive); |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
if (drive->removable && drive->usage == 1) { |
byte door_lock[] = {WIN_DOORLOCK,0,0,0}; |
struct request rq; |
check_disk_change(inode->i_rdev); |
ide_init_drive_cmd (&rq); |
rq.buffer = door_lock; |
/* |
* Ignore the return code from door_lock, |
* since the open() has already succeeded, |
* and the door_lock is irrelevant at this point. |
*/ |
(void) ide_do_drive_cmd(drive, &rq, ide_wait); |
} |
return 0; |
} |
|
/* |
* Releasing a block device means we sync() it, so that it can safely |
* be forgotten about... |
*/ |
static void ide_release(struct inode * inode, struct file * file) |
{ |
ide_drive_t *drive; |
|
if ((drive = get_info_ptr(inode->i_rdev)) != NULL) { |
fsync_dev(inode->i_rdev); |
drive->usage--; |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->media == ide_cdrom) { |
ide_cdrom_release (inode, file, drive); |
return; |
} |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->media == ide_tape) { |
idetape_blkdev_release (inode, file, drive); |
return; |
} |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) { |
idefloppy_release (inode, file, drive); |
return; |
} |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
if (drive->media == ide_scsi) { |
idescsi_ide_release (inode, file, drive); |
return; |
} |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
if (drive->removable && !drive->usage) { |
byte door_unlock[] = {WIN_DOORUNLOCK,0,0,0}; |
struct request rq; |
invalidate_buffers(inode->i_rdev); |
ide_init_drive_cmd (&rq); |
rq.buffer = door_unlock; |
(void) ide_do_drive_cmd(drive, &rq, ide_wait); |
} |
} |
} |
|
/* |
* This routine is called to flush all partitions and partition tables |
* for a changed disk, and then re-read the new partition table. |
* If we are revalidating a disk because of a media change, then we |
* enter with usage == 0. If we are using an ioctl, we automatically have |
* usage == 1 (we need an open channel to use an ioctl :-), so this |
* is our limit. |
*/ |
static int revalidate_disk(kdev_t i_rdev) |
{ |
ide_drive_t *drive; |
unsigned int p, major, minor; |
long flags; |
|
if ((drive = get_info_ptr(i_rdev)) == NULL) |
return -ENODEV; |
|
major = MAJOR(i_rdev); |
minor = drive->select.b.unit << PARTN_BITS; |
save_flags(flags); |
cli(); |
if (drive->busy || (drive->usage > 1)) { |
restore_flags(flags); |
return -EBUSY; |
}; |
drive->busy = 1; |
restore_flags(flags); |
|
for (p = 0; p < (1<<PARTN_BITS); ++p) { |
if (drive->part[p].nr_sects > 0) { |
kdev_t devp = MKDEV(major, minor+p); |
fsync_dev (devp); |
invalidate_inodes (devp); |
invalidate_buffers (devp); |
set_blocksize(devp, 1024); |
} |
drive->part[p].start_sect = 0; |
drive->part[p].nr_sects = 0; |
}; |
|
drive->part[0].nr_sects = current_capacity(drive); |
if ((drive->media != ide_disk && drive->media != ide_floppy) || !drive->part[0].nr_sects) |
drive->part[0].start_sect = -1; |
resetup_one_dev(HWIF(drive)->gd, drive->select.b.unit); |
|
drive->busy = 0; |
wake_up(&drive->wqueue); |
return 0; |
} |
|
static int write_fs_long (unsigned long useraddr, long value) |
{ |
int err; |
|
if (NULL == (long *)useraddr) |
return -EINVAL; |
if ((err = verify_area(VERIFY_WRITE, (long *)useraddr, sizeof(long)))) |
return err; |
put_user((unsigned)value, (long *) useraddr); |
return 0; |
} |
|
static int ide_ioctl (struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg) |
{ |
int err; |
ide_drive_t *drive; |
unsigned long flags; |
struct request rq; |
|
if (!inode || !(inode->i_rdev)) |
return -EINVAL; |
if ((drive = get_info_ptr(inode->i_rdev)) == NULL) |
return -ENODEV; |
ide_init_drive_cmd (&rq); |
switch (cmd) { |
case HDIO_GETGEO: |
{ |
struct hd_geometry *loc = (struct hd_geometry *) arg; |
if (!loc || (drive->media != ide_disk && drive->media != ide_floppy)) return -EINVAL; |
err = verify_area(VERIFY_WRITE, loc, sizeof(*loc)); |
if (err) return err; |
put_user(drive->bios_head, (byte *) &loc->heads); |
put_user(drive->bios_sect, (byte *) &loc->sectors); |
put_user(drive->bios_cyl, (unsigned short *) &loc->cylinders); |
put_user((unsigned)drive->part[MINOR(inode->i_rdev)&PARTN_MASK].start_sect, |
(unsigned long *) &loc->start); |
return 0; |
} |
case BLKFLSBUF: |
if (!suser()) return -EACCES; |
fsync_dev(inode->i_rdev); |
invalidate_buffers(inode->i_rdev); |
return 0; |
|
case BLKRASET: |
if (!suser()) return -EACCES; |
if(arg > 0xff) return -EINVAL; |
read_ahead[MAJOR(inode->i_rdev)] = arg; |
return 0; |
|
case BLKRAGET: |
return write_fs_long(arg, read_ahead[MAJOR(inode->i_rdev)]); |
|
case BLKGETSIZE: /* Return device size */ |
return write_fs_long(arg, drive->part[MINOR(inode->i_rdev)&PARTN_MASK].nr_sects); |
case BLKRRPART: /* Re-read partition tables */ |
if (!suser()) return -EACCES; |
return revalidate_disk(inode->i_rdev); |
|
case HDIO_GET_KEEPSETTINGS: |
return write_fs_long(arg, drive->keep_settings); |
|
case HDIO_GET_UNMASKINTR: |
return write_fs_long(arg, drive->unmask); |
|
case HDIO_GET_DMA: |
return write_fs_long(arg, drive->using_dma); |
|
case HDIO_GET_32BIT: |
return write_fs_long(arg, drive->io_32bit); |
|
case HDIO_GET_MULTCOUNT: |
return write_fs_long(arg, drive->mult_count); |
|
case HDIO_GET_IDENTITY: |
if (!arg || (MINOR(inode->i_rdev) & PARTN_MASK)) |
return -EINVAL; |
if (drive->id == NULL) |
return -ENOMSG; |
err = verify_area(VERIFY_WRITE, (char *)arg, sizeof(*drive->id)); |
if (!err) |
memcpy_tofs((char *)arg, (char *)drive->id, sizeof(*drive->id)); |
return err; |
|
case HDIO_GET_NOWERR: |
return write_fs_long(arg, drive->bad_wstat == BAD_R_STAT); |
|
case HDIO_SET_DMA: |
if (!suser()) return -EACCES; |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->media == ide_cdrom) |
return -EPERM; |
#endif /* CONFIG_BLK_DEV_IDECD */ |
if (!drive->id || !(drive->id->capability & 1) || !HWIF(drive)->dmaproc) |
return -EPERM; |
case HDIO_SET_KEEPSETTINGS: |
case HDIO_SET_UNMASKINTR: |
case HDIO_SET_NOWERR: |
if (arg > 1) |
return -EINVAL; |
case HDIO_SET_32BIT: |
if (!suser()) return -EACCES; |
if ((MINOR(inode->i_rdev) & PARTN_MASK)) |
return -EINVAL; |
save_flags(flags); |
cli(); |
switch (cmd) { |
case HDIO_SET_DMA: |
if (!(HWIF(drive)->dmaproc)) { |
restore_flags(flags); |
return -EPERM; |
} |
drive->using_dma = arg; |
break; |
case HDIO_SET_KEEPSETTINGS: |
drive->keep_settings = arg; |
break; |
case HDIO_SET_UNMASKINTR: |
if (arg && drive->no_unmask) { |
restore_flags(flags); |
return -EPERM; |
} |
drive->unmask = arg; |
break; |
case HDIO_SET_NOWERR: |
drive->bad_wstat = arg ? BAD_R_STAT : BAD_W_STAT; |
break; |
case HDIO_SET_32BIT: |
if (arg > (1 + (SUPPORT_VLB_SYNC<<1))) { |
restore_flags(flags); |
return -EINVAL; |
} |
if (arg && drive->no_io_32bit) { |
restore_flags(flags); |
return -EPERM; |
} |
drive->io_32bit = arg; |
#ifdef CONFIG_BLK_DEV_DTC2278 |
if (HWIF(drive)->chipset == ide_dtc2278) |
HWIF(drive)->drives[!drive->select.b.unit].io_32bit = arg; |
#endif /* CONFIG_BLK_DEV_DTC2278 */ |
break; |
} |
restore_flags(flags); |
return 0; |
|
case HDIO_SET_MULTCOUNT: |
if (!suser()) return -EACCES; |
if (MINOR(inode->i_rdev) & PARTN_MASK) |
return -EINVAL; |
if (drive->id && arg > drive->id->max_multsect) |
return -EINVAL; |
save_flags(flags); |
cli(); |
if (drive->special.b.set_multmode) { |
restore_flags(flags); |
return -EBUSY; |
} |
drive->mult_req = arg; |
drive->special.b.set_multmode = 1; |
restore_flags(flags); |
(void) ide_do_drive_cmd (drive, &rq, ide_wait); |
return (drive->mult_count == arg) ? 0 : -EIO; |
|
case HDIO_DRIVE_CMD: |
{ |
byte args[4], *argbuf = args; |
int argsize = 4; |
if (!suser() || securelevel > 0) return -EACCES; |
if (NULL == (void *) arg) { |
err = ide_do_drive_cmd(drive, &rq, ide_wait); |
} else if (!(err = verify_area(VERIFY_READ,(void *)arg, 4))) { |
memcpy_fromfs(args, (void *)arg, 4); |
if (args[3]) { |
argsize = 4 + (SECTOR_WORDS * 4 * args[3]); |
argbuf = kmalloc(argsize, GFP_KERNEL); |
if (argbuf == NULL) |
return -ENOMEM; |
argbuf[0] = args[0]; |
argbuf[1] = args[1]; |
argbuf[2] = args[2]; |
argbuf[3] = args[3]; |
} |
if (!(err = verify_area(VERIFY_WRITE,(void *)arg, argsize))) { |
rq.buffer = argbuf; |
err = ide_do_drive_cmd(drive, &rq, ide_wait); |
memcpy_tofs((void *)arg, argbuf, argsize); |
} |
if (argsize > 4) |
kfree(argbuf); |
} |
return err; |
} |
case HDIO_SET_PIO_MODE: |
if (!suser()) return -EACCES; |
if (MINOR(inode->i_rdev) & PARTN_MASK) |
return -EINVAL; |
if (!HWIF(drive)->tuneproc) |
return -ENOSYS; |
save_flags(flags); |
cli(); |
if (drive->special.b.set_tune) { |
restore_flags(flags); |
return -EBUSY; |
} |
drive->tune_req = (byte) arg; |
drive->special.b.set_tune = 1; |
restore_flags(flags); |
(void) ide_do_drive_cmd (drive, &rq, ide_wait); |
return 0; |
|
RO_IOCTLS(inode->i_rdev, arg); |
|
default: |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->media == ide_cdrom) |
return ide_cdrom_ioctl(drive, inode, file, cmd, arg); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
if (drive->media == ide_tape) |
return idetape_blkdev_ioctl(drive, inode, file, cmd, arg); |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) |
return idefloppy_ioctl(drive, inode, file, cmd, arg); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
if (drive->media == ide_scsi) |
return idescsi_ioctl(drive, inode, file, cmd, arg); |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
return -EPERM; |
} |
} |
|
static int ide_check_media_change (kdev_t i_rdev) |
{ |
ide_drive_t *drive; |
|
if ((drive = get_info_ptr(i_rdev)) == NULL) |
return -ENODEV; |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->media == ide_cdrom) |
return ide_cdrom_check_media_change (drive); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
if (drive->media == ide_floppy) |
return idefloppy_media_change (drive); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
if (drive->removable) /* for disks */ |
return 1; /* always assume it was changed */ |
return 0; |
} |
|
void ide_fixstring (byte *s, const int bytecount, const int byteswap) |
{ |
byte *p = s, *end = &s[bytecount & ~1]; /* bytecount must be even */ |
|
if (byteswap) { |
/* convert from big-endian to host byte order */ |
for (p = end ; p != s;) { |
unsigned short *pp = (unsigned short *) (p -= 2); |
*pp = ntohs(*pp); |
} |
} |
|
/* strip leading blanks */ |
while (s != end && *s == ' ') |
++s; |
|
/* compress internal blanks and strip trailing blanks */ |
while (s != end && *s) { |
if (*s++ != ' ' || (s != end && *s && *s != ' ')) |
*p++ = *(s-1); |
} |
|
/* wipe out trailing garbage */ |
while (p != end) |
*p++ = '\0'; |
} |
|
static inline void do_identify (ide_drive_t *drive, byte cmd) |
{ |
int bswap; |
struct hd_driveid *id; |
unsigned long capacity, check; |
|
id = drive->id = kmalloc (SECTOR_WORDS*4, GFP_KERNEL); |
ide_input_data(drive, id, SECTOR_WORDS);/* read 512 bytes of id info */ |
sti(); |
|
#if defined (CONFIG_SCSI_EATA_DMA) || defined (CONFIG_SCSI_EATA_PIO) || defined (CONFIG_SCSI_EATA) |
/* |
* EATA SCSI controllers do a hardware ATA emulation: |
* Ignore them if there is a driver for them available. |
*/ |
if ((id->model[0] == 'P' && id->model[1] == 'M') |
|| (id->model[0] == 'S' && id->model[1] == 'K')) { |
printk("%s: EATA SCSI HBA %.10s\n", drive->name, id->model); |
drive->present = 0; |
return; |
} |
#endif |
|
/* |
* WIN_IDENTIFY returns little-endian info, |
* WIN_PIDENTIFY *usually* returns little-endian info. |
*/ |
bswap = 1; |
if (cmd == WIN_PIDENTIFY) { |
if ((id->model[0] == 'N' && id->model[1] == 'E') /* NEC */ |
|| (id->model[0] == 'F' && id->model[1] == 'X') /* Mitsumi */ |
|| (id->model[0] == 'P' && id->model[1] == 'i'))/* Pioneer */ |
bswap = 0; /* Vertos drives may still be weird */ |
} |
ide_fixstring (id->model, sizeof(id->model), bswap); |
ide_fixstring (id->fw_rev, sizeof(id->fw_rev), bswap); |
ide_fixstring (id->serial_no, sizeof(id->serial_no), bswap); |
|
if (strstr(id->model, "E X A B Y T E N E S T")) |
return; |
|
#ifdef CONFIG_BLK_DEV_IDEATAPI |
/* |
* Check for an ATAPI device |
*/ |
if (cmd == WIN_PIDENTIFY) { |
byte type = (id->config >> 8) & 0x1f; |
printk("%s: %s, ATAPI ", drive->name, id->model); |
#ifdef CONFIG_BLK_DEV_PROMISE |
if (HWIF(drive)->is_promise2) { |
printk(" -- not supported on 2nd Promise port\n"); |
drive->present = 0; |
return; |
} |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
if (!drive->ide_scsi) switch (type) { |
case 0: |
if (!strstr(id->model, "oppy") && !strstr(id->model, "poyp") && !strstr(id->model, "ZIP")) |
printk("cdrom or floppy?, assuming "); |
if (drive->media != ide_cdrom && !strstr(id->model, "CD-ROM")) { |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
printk("FLOPPY drive\n"); |
drive->media = ide_floppy; |
if (idefloppy_identify_device(drive, id)) |
drive->present = 1; |
return; |
#else |
printk("FLOPPY "); |
break; |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
} |
/* Early cdrom models used zero */ |
case 5: |
#ifdef CONFIG_BLK_DEV_IDECD |
printk ("CDROM drive\n"); |
drive->media = ide_cdrom; |
drive->present = 1; |
drive->removable = 1; |
return; |
#else |
printk ("CDROM "); |
break; |
#endif /* CONFIG_BLK_DEV_IDECD */ |
case 1: |
#ifdef CONFIG_BLK_DEV_IDETAPE |
printk ("TAPE drive"); |
if (idetape_identify_device (drive,id)) { |
drive->media = ide_tape; |
drive->present = 1; |
drive->removable = 1; |
if (drive->autotune != 2 && HWIF(drive)->dmaproc != NULL) { |
if (!HWIF(drive)->dmaproc(ide_dma_check, drive)) |
printk(", DMA"); |
} |
printk("\n"); |
} |
else { |
drive->present = 0; |
printk ("\nide-tape: the tape is not supported by this version of the driver\n"); |
} |
return; |
#else |
printk ("TAPE "); |
break; |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
default: |
drive->present = 0; |
printk("Type %d - Unknown device\n", type); |
return; |
} |
#ifdef CONFIG_BLK_DEV_IDESCSI |
printk("drive - enabling SCSI emulation\n"); |
drive->media = ide_scsi; |
drive->present = 1; |
idescsi_setup(drive); |
#else |
drive->present = 0; |
printk("- not supported by this kernel\n"); |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
return; |
} |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
|
/* check for removable disks (eg. SYQUEST), ignore 'WD' drives */ |
if (id->config & (1<<7)) { /* removable disk ? */ |
if (id->model[0] != 'W' || id->model[1] != 'D') |
drive->removable = 1; |
} |
|
/* SunDisk drives: treat as non-removable, force one unit */ |
if (id->model[0] == 'S' && id->model[1] == 'u') { |
drive->removable = 0; |
if (drive->select.all & (1<<4)) { |
drive->present = 0; |
return; |
} |
} |
|
drive->media = ide_disk; |
/* Extract geometry if we did not already have one for the drive */ |
if (!drive->present) { |
drive->present = 1; |
drive->cyl = drive->bios_cyl = id->cyls; |
drive->head = drive->bios_head = id->heads; |
drive->sect = drive->bios_sect = id->sectors; |
} |
/* Handle logical geometry translation by the drive */ |
if ((id->field_valid & 1) && id->cur_cyls && id->cur_heads |
&& (id->cur_heads <= 16) && id->cur_sectors) |
{ |
/* |
* Extract the physical drive geometry for our use. |
* Note that we purposely do *not* update the bios info. |
* This way, programs that use it (like fdisk) will |
* still have the same logical view as the BIOS does, |
* which keeps the partition table from being screwed. |
* |
* An exception to this is the cylinder count, |
* which we reexamine later on to correct for 1024 limitations. |
*/ |
drive->cyl = id->cur_cyls; |
drive->head = id->cur_heads; |
drive->sect = id->cur_sectors; |
|
/* check for word-swapped "capacity" field in id information */ |
capacity = drive->cyl * drive->head * drive->sect; |
check = (id->cur_capacity0 << 16) | id->cur_capacity1; |
if (check == capacity) { /* was it swapped? */ |
/* yes, bring it into little-endian order: */ |
id->cur_capacity0 = (capacity >> 0) & 0xffff; |
id->cur_capacity1 = (capacity >> 16) & 0xffff; |
} |
} |
/* Use physical geometry if what we have still makes no sense */ |
if ((!drive->head || drive->head > 16) && id->heads && id->heads <= 16) { |
drive->cyl = id->cyls; |
drive->head = id->heads; |
drive->sect = id->sectors; |
} |
|
/* calculate drive capacity, and select LBA if possible */ |
(void) current_capacity (drive); |
|
/* Correct the number of cyls if the bios value is too small */ |
if (drive->sect == drive->bios_sect && drive->head == drive->bios_head) { |
if (drive->cyl > drive->bios_cyl) |
drive->bios_cyl = drive->cyl; |
} |
|
if (!strncmp(id->model, "BMI ", 4) && |
strstr(id->model, " ENHANCED IDE ") && |
drive->select.b.lba) |
drive->no_geom = 1; |
|
printk ("%s: %.40s, %ldMB w/%dkB Cache, CHS=%d/%d/%d", |
drive->name, id->model, current_capacity(drive)/2048L, id->buf_size/2, |
drive->bios_cyl, drive->bios_head, drive->bios_sect); |
|
drive->mult_count = 0; |
if (id->max_multsect) { |
drive->mult_req = INITIAL_MULT_COUNT; |
if (drive->mult_req > id->max_multsect) |
drive->mult_req = id->max_multsect; |
if (drive->mult_req || ((id->multsect_valid & 1) && id->multsect)) |
drive->special.b.set_multmode = 1; |
} |
if (drive->autotune != 2 && HWIF(drive)->dmaproc != NULL) { |
if (!(HWIF(drive)->dmaproc(ide_dma_check, drive))) { |
if ((id->field_valid & 4) && (id->dma_ultra & (id->dma_ultra >> 8) & 7)) |
printk(", UDMA"); |
else |
printk(", DMA"); |
} |
} |
printk("\n"); |
} |
|
/* |
* Delay for *at least* 50ms. As we don't know how much time is left |
* until the next tick occurs, we wait an extra tick to be safe. |
* This is used only during the probing/polling for drives at boot time. |
*/ |
static void delay_50ms (void) |
{ |
unsigned long timer = jiffies + ((HZ + 19)/20) + 1; |
while (timer > jiffies); |
} |
|
/* |
* try_to_identify() sends an ATA(PI) IDENTIFY request to a drive |
* and waits for a response. It also monitors irqs while this is |
* happening, in hope of automatically determining which one is |
* being used by the interface. |
* |
* Returns: 0 device was identified |
* 1 device timed-out (no response to identify request) |
* 2 device aborted the command (refused to identify itself) |
*/ |
static int try_to_identify (ide_drive_t *drive, byte cmd) |
{ |
int hd_status, rc; |
unsigned long timeout; |
unsigned long irqs_on = 0; |
int irq_off; |
|
if (!HWIF(drive)->irq) { /* already got an IRQ? */ |
probe_irq_off(probe_irq_on()); /* clear dangling irqs */ |
irqs_on = probe_irq_on(); /* start monitoring irqs */ |
OUT_BYTE(drive->ctl,IDE_CONTROL_REG); /* enable device irq */ |
} |
|
delay_50ms(); /* take a deep breath */ |
if ((IN_BYTE(IDE_ALTSTATUS_REG) ^ IN_BYTE(IDE_STATUS_REG)) & ~INDEX_STAT) { |
printk("%s: probing with STATUS instead of ALTSTATUS\n", drive->name); |
hd_status = IDE_STATUS_REG; /* ancient Seagate drives */ |
} else |
hd_status = IDE_ALTSTATUS_REG; /* use non-intrusive polling */ |
|
#if CONFIG_BLK_DEV_PROMISE |
if (IS_PROMISE_DRIVE) { |
if (promise_cmd(drive,PROMISE_IDENTIFY)) { |
if (irqs_on) |
(void) probe_irq_off(irqs_on); |
return 1; |
} |
} else |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
OUT_BYTE(cmd,IDE_COMMAND_REG); /* ask drive for ID */ |
timeout = ((cmd == WIN_IDENTIFY) ? WAIT_WORSTCASE : WAIT_PIDENTIFY) / 2; |
timeout += jiffies; |
do { |
if (jiffies > timeout) { |
if (irqs_on) |
(void) probe_irq_off(irqs_on); |
return 1; /* drive timed-out */ |
} |
delay_50ms(); /* give drive a breather */ |
} while (IN_BYTE(hd_status) & BUSY_STAT); |
|
delay_50ms(); /* wait for IRQ and DRQ_STAT */ |
if (OK_STAT(GET_STAT(),DRQ_STAT,BAD_R_STAT)) { |
unsigned long flags; |
save_flags(flags); |
cli(); /* some systems need this */ |
do_identify(drive, cmd); /* drive returned ID */ |
rc = 0; /* drive responded with ID */ |
(void) GET_STAT(); /* clear drive IRQ */ |
restore_flags(flags); |
} else |
rc = 2; /* drive refused ID */ |
if (!HWIF(drive)->irq) { |
irq_off = probe_irq_off(irqs_on); /* get our irq number */ |
if (irq_off > 0) { |
HWIF(drive)->irq = irq_off; /* save it for later */ |
irqs_on = probe_irq_on(); |
OUT_BYTE(drive->ctl|2,IDE_CONTROL_REG); /* mask device irq */ |
udelay(5); |
(void) probe_irq_off(irqs_on); |
(void) probe_irq_off(probe_irq_on()); /* clear self-inflicted irq */ |
(void) GET_STAT(); /* clear drive IRQ */ |
|
} else { /* Mmmm.. multiple IRQs.. don't know which was ours */ |
printk("%s: IRQ probe failed (%d)\n", drive->name, irq_off); |
#ifdef CONFIG_BLK_DEV_CMD640 |
#ifdef CMD640_DUMP_REGS |
if (HWIF(drive)->chipset == ide_cmd640) { |
printk("%s: Hmmm.. probably a driver problem.\n", drive->name); |
CMD640_DUMP_REGS; |
} |
#endif /* CMD640_DUMP_REGS */ |
#endif /* CONFIG_BLK_DEV_CMD640 */ |
} |
} |
return rc; |
} |
|
/* |
* do_probe() has the difficult job of finding a drive if it exists, |
* without getting hung up if it doesn't exist, without trampling on |
* ethernet cards, and without leaving any IRQs dangling to haunt us later. |
* |
* If a drive is "known" to exist (from CMOS or kernel parameters), |
* but does not respond right away, the probe will "hang in there" |
* for the maximum wait time (about 30 seconds), otherwise it will |
* exit much more quickly. |
* |
* Returns: 0 device was identified |
* 1 device timed-out (no response to identify request) |
* 2 device aborted the command (refused to identify itself) |
* 3 bad status from device (possible for ATAPI drives) |
* 4 probe was not attempted because failure was obvious |
*/ |
static int do_probe (ide_drive_t *drive, byte cmd) |
{ |
int rc; |
ide_hwif_t *hwif = HWIF(drive); |
unsigned long timeout; |
#ifdef CONFIG_BLK_DEV_IDEATAPI |
if (drive->present) { /* avoid waiting for inappropriate probes */ |
if ((drive->media != ide_disk) && (cmd == WIN_IDENTIFY)) |
return 4; |
} |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
#ifdef DEBUG |
printk("probing for %s: present=%d, media=%d, probetype=%s\n", |
drive->name, drive->present, drive->media, |
(cmd == WIN_IDENTIFY) ? "ATA" : "ATAPI"); |
#endif |
SELECT_DRIVE(hwif,drive); |
delay_50ms(); |
if (IN_BYTE(IDE_SELECT_REG) != drive->select.all && !drive->present) { |
OUT_BYTE(0xa0,IDE_SELECT_REG); /* exit with drive0 selected */ |
delay_50ms(); /* allow BUSY_STAT to assert & clear */ |
return 3; /* no i/f present: avoid killing ethernet cards */ |
} |
|
if (OK_STAT(GET_STAT(),READY_STAT,BUSY_STAT) |
|| drive->present || cmd == WIN_PIDENTIFY) |
{ |
if ((rc = try_to_identify(drive,cmd))) /* send cmd and wait */ |
rc = try_to_identify(drive,cmd); /* failed: try again */ |
if (rc == 1 && cmd == WIN_PIDENTIFY && drive->autotune != 2) { |
printk("%s: no response (status = 0x%02x), resetting drive\n", drive->name, GET_STAT()); |
delay_50ms(); |
OUT_BYTE (drive->select.all, IDE_SELECT_REG); |
delay_50ms(); |
OUT_BYTE(WIN_SRST, IDE_COMMAND_REG); |
timeout = jiffies; |
while ((GET_STAT() & BUSY_STAT) && jiffies < timeout + WAIT_WORSTCASE) |
delay_50ms(); |
rc = try_to_identify(drive, cmd); |
} |
if (rc == 1) |
printk("%s: no response (status = 0x%02x)\n", drive->name, GET_STAT()); |
(void) GET_STAT(); /* ensure drive irq is clear */ |
} else { |
rc = 3; /* not present or maybe ATAPI */ |
} |
if (drive->select.b.unit != 0) { |
OUT_BYTE(0xa0,IDE_SELECT_REG); /* exit with drive0 selected */ |
delay_50ms(); |
(void) GET_STAT(); /* ensure drive irq is clear */ |
} |
return rc; |
} |
|
static void enable_nest (ide_drive_t *drive) |
{ |
unsigned long timeout; |
|
printk("%s: enabling %s -- ", HWIF(drive)->name, drive->id->model); |
SELECT_DRIVE(HWIF(drive), drive); |
delay_50ms(); |
OUT_BYTE(EXABYTE_ENABLE_NEST, IDE_COMMAND_REG); |
timeout = jiffies + WAIT_WORSTCASE; |
do { |
if (jiffies > timeout) { |
printk("failed (timeout)\n"); |
return; |
} |
delay_50ms(); |
} while (GET_STAT() & BUSY_STAT); |
delay_50ms(); |
if (!OK_STAT(GET_STAT(), 0, BAD_STAT)) |
printk("failed (status = 0x%02x)\n", GET_STAT()); |
else |
printk("success\n"); |
if (do_probe(drive, WIN_IDENTIFY) >= 2) { /* if !(success||timed-out) */ |
#ifdef CONFIG_BLK_DEV_IDEATAPI |
(void) do_probe(drive, WIN_PIDENTIFY); /* look for ATAPI device */ |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
} |
} |
|
/* |
* probe_for_drive() tests for existence of a given drive using do_probe(). |
* |
* Returns: 0 no device was found |
* 1 device was found (note: drive->present might still be 0) |
*/ |
static inline byte probe_for_drive (ide_drive_t *drive) |
{ |
if (drive->noprobe) /* skip probing? */ |
return drive->present; |
if (do_probe(drive, WIN_IDENTIFY) >= 2) { /* if !(success||timed-out) */ |
#ifdef CONFIG_BLK_DEV_IDEATAPI |
(void) do_probe(drive, WIN_PIDENTIFY); /* look for ATAPI device */ |
#endif /* CONFIG_BLK_DEV_IDEATAPI */ |
} |
if (drive->id && strstr(drive->id->model, "E X A B Y T E N E S T")) |
enable_nest(drive); |
if (!drive->present) |
return 0; /* drive not found */ |
if (drive->id == NULL) { /* identification failed? */ |
if (drive->media == ide_disk) { |
printk ("%s: non-IDE drive, CHS=%d/%d/%d\n", |
drive->name, drive->cyl, drive->head, drive->sect); |
} |
#ifdef CONFIG_BLK_DEV_IDECD |
else if (drive->media == ide_cdrom) { |
printk("%s: ATAPI cdrom (?)\n", drive->name); |
} |
#endif /* CONFIG_BLK_DEV_IDECD */ |
else { |
drive->present = 0; /* nuke it */ |
} |
} |
return 1; /* drive was found */ |
} |
|
/* |
* We query CMOS about hard disks : it could be that we have a SCSI/ESDI/etc |
* controller that is BIOS compatible with ST-506, and thus showing up in our |
* BIOS table, but not register compatible, and therefore not present in CMOS. |
* |
* Furthermore, we will assume that our ST-506 drives <if any> are the primary |
* drives in the system -- the ones reflected as drive 1 or 2. The first |
* drive is stored in the high nibble of CMOS byte 0x12, the second in the low |
* nibble. This will be either a 4 bit drive type or 0xf indicating use byte |
* 0x19 for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS. A non-zero value |
* means we have an AT controller hard disk for that drive. |
* |
* Of course, there is no guarantee that either drive is actually on the |
* "primary" IDE interface, but we don't bother trying to sort that out here. |
* If a drive is not actually on the primary interface, then these parameters |
* will be ignored. This results in the user having to supply the logical |
* drive geometry as a boot parameter for each drive not on the primary i/f. |
* |
* The only "perfect" way to handle this would be to modify the setup.[cS] code |
* to do BIOS calls Int13h/Fn08h and Int13h/Fn48h to get all of the drive info |
* for us during initialization. I have the necessary docs -- any takers? -ml |
*/ |
static void probe_cmos_for_drives (ide_hwif_t *hwif) |
{ |
#ifdef __i386__ |
extern struct drive_info_struct drive_info; |
byte cmos_disks, *BIOS = (byte *) &drive_info; |
int unit; |
|
#ifdef CONFIG_BLK_DEV_PROMISE |
if (hwif->is_promise2) |
return; |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
outb_p(0x12,0x70); /* specify CMOS address 0x12 */ |
cmos_disks = inb_p(0x71); /* read the data from 0x12 */ |
/* Extract drive geometry from CMOS+BIOS if not already setup */ |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
if ((cmos_disks & (0xf0 >> (unit*4))) && !drive->present && !drive->nobios) { |
drive->cyl = drive->bios_cyl = *(unsigned short *)BIOS; |
drive->head = drive->bios_head = *(BIOS+2); |
drive->sect = drive->bios_sect = *(BIOS+14); |
drive->ctl = *(BIOS+8); |
drive->present = 1; |
} |
BIOS += 16; |
} |
#endif |
} |
|
/* |
* This routine only knows how to look for drive units 0 and 1 |
* on an interface, so any setting of MAX_DRIVES > 2 won't work here. |
*/ |
static void probe_hwif (ide_hwif_t *hwif) |
{ |
unsigned int unit; |
|
if (hwif->noprobe) |
return; |
if (hwif->io.io_base == HD_DATA) |
probe_cmos_for_drives (hwif); |
#if CONFIG_BLK_DEV_PROMISE |
if (!hwif->is_promise2 && |
(check_region(hwif->io_base,8) || check_region(hwif->ctl_port,1))) { |
#else |
if (check_region(hwif->io.io_base,8) || check_region(hwif->io.ctl_port,1)) { |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
int msgout = 0; |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
if (drive->present) { |
drive->present = 0; |
printk("%s: ERROR, PORTS ALREADY IN USE\n", drive->name); |
msgout = 1; |
} |
} |
if (!msgout) |
printk("%s: ports already in use, skipping probe\n", hwif->name); |
} else { |
unsigned long flags; |
save_flags(flags); |
|
sti(); /* needed for jiffies and irq probing */ |
/* |
* Second drive should only exist if first drive was found, |
* but a lot of cdrom drives are configured as single slaves. |
*/ |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
(void) probe_for_drive (drive); |
if (drive->present && drive->media == ide_disk) { |
if ((!drive->head || drive->head > 16) && !drive->select.b.lba) { |
printk("%s: INVALID GEOMETRY: %d PHYSICAL HEADS?\n", |
drive->name, drive->head); |
drive->present = 0; |
} |
} |
if (drive->present && !hwif->present) { |
hwif->present = 1; |
request_region(hwif->io.io_base, 8, hwif->name); |
request_region(hwif->io.ctl_port, 1, hwif->name); |
} |
} |
restore_flags(flags); |
for (unit = 0; unit < MAX_DRIVES; ++unit) { |
ide_drive_t *drive = &hwif->drives[unit]; |
if (drive->present && drive->media != ide_tape) { |
ide_tuneproc_t *tuneproc = HWIF(drive)->tuneproc; |
if (tuneproc != NULL && drive->autotune == 1) |
tuneproc(drive, 255); /* auto-tune PIO mode */ |
} |
} |
} |
} |
|
/* |
* stridx() returns the offset of c within s, |
* or -1 if c is '\0' or not found within s. |
*/ |
static int stridx (const char *s, char c) |
{ |
char *i = strchr(s, c); |
return (i && c) ? i - s : -1; |
} |
|
/* |
* match_parm() does parsing for ide_setup(): |
* |
* 1. the first char of s must be '='. |
* 2. if the remainder matches one of the supplied keywords, |
* the index (1 based) of the keyword is negated and returned. |
* 3. if the remainder is a series of no more than max_vals numbers |
* separated by commas, the numbers are saved in vals[] and a |
* count of how many were saved is returned. Base10 is assumed, |
* and base16 is allowed when prefixed with "0x". |
* 4. otherwise, zero is returned. |
*/ |
static int match_parm (char *s, const char *keywords[], int vals[], int max_vals) |
{ |
static const char *decimal = "0123456789"; |
static const char *hex = "0123456789abcdef"; |
int i, n; |
|
if (*s++ == '=') { |
/* |
* Try matching against the supplied keywords, |
* and return -(index+1) if we match one |
*/ |
if (keywords != NULL) { |
for (i = 0; *keywords != NULL; ++i) { |
if (!strcmp(s, *keywords++)) |
return -(i+1); |
} |
} |
/* |
* Look for a series of no more than "max_vals" |
* numeric values separated by commas, in base10, |
* or base16 when prefixed with "0x". |
* Return a count of how many were found. |
*/ |
for (n = 0; (i = stridx(decimal, *s)) >= 0;) { |
vals[n] = i; |
while ((i = stridx(decimal, *++s)) >= 0) |
vals[n] = (vals[n] * 10) + i; |
if (*s == 'x' && !vals[n]) { |
while ((i = stridx(hex, *++s)) >= 0) |
vals[n] = (vals[n] * 0x10) + i; |
} |
if (++n == max_vals) |
break; |
if (*s == ',') |
++s; |
} |
if (!*s) |
return n; |
} |
return 0; /* zero = nothing matched */ |
} |
|
/* |
* ide_setup() gets called VERY EARLY during initialization, |
* to handle kernel "command line" strings beginning with "hdx=" |
* or "ide". Here is the complete set currently supported: |
* |
* "hdx=" is recognized for all "x" from "a" to "h", such as "hdc". |
* "idex=" is recognized for all "x" from "0" to "3", such as "ide1". |
* |
* "hdx=noprobe" : drive may be present, but do not probe for it |
* "hdx=none" : drive is NOT present, ignore cmos and do not probe |
* "hdx=nowerr" : ignore the WRERR_STAT bit on this drive |
* "hdx=cdrom" : drive is present, and is a cdrom drive |
* "hdx=cyl,head,sect" : disk drive is present, with specified geometry |
* "hdx=autotune" : driver will attempt to tune interface speed |
* to the fastest PIO mode supported, |
* if possible for this drive only. |
* Not fully supported by all chipset types, |
* and quite likely to cause trouble with |
* older/odd IDE drives. |
* |
* "idebus=xx" : inform IDE driver of VESA/PCI bus speed in Mhz, |
* where "xx" is between 20 and 66 inclusive, |
* used when tuning chipset PIO modes. |
* For PCI bus, 25 is correct for a P75 system, |
* 30 is correct for P90,P120,P180 systems, |
* and 33 is used for P100,P133,P166 systems. |
* If in doubt, use idebus=33 for PCI. |
* As for VLB, it is safest to not specify it. |
* |
* "idex=noprobe" : do not attempt to access/use this interface |
* "idex=base" : probe for an interface at the addr specified, |
* where "base" is usually 0x1f0 or 0x170 |
* and "ctl" is assumed to be "base"+0x206 |
* "idex=base,ctl" : specify both base and ctl |
* "idex=base,ctl,irq" : specify base, ctl, and irq number |
* "idex=autotune" : driver will attempt to tune interface speed |
* to the fastest PIO mode supported, |
* for all drives on this interface. |
* Not fully supported by all chipset types, |
* and quite likely to cause trouble with |
* older/odd IDE drives. |
* "idex=noautotune" : driver will NOT attempt to tune interface speed |
* This is the default for most chipsets, |
* except the cmd640. |
* "idex=serialize" : do not overlap operations on idex and ide(x^1) |
* |
* The following are valid ONLY on ide0, |
* and the defaults for the base,ctl ports must not be altered. |
* |
* "ide0=dtc2278" : probe/support DTC2278 interface |
* "ide0=ht6560b" : probe/support HT6560B interface |
* "ide0=cmd640_vlb" : *REQUIRED* for VLB cards with the CMD640 chip |
* (not for PCI -- automatically detected) |
* "ide0=qd6580" : probe/support qd6580 interface |
* "ide0=ali14xx" : probe/support ali14xx chipsets (ALI M1439, M1443, M1445) |
* "ide0=umc8672" : probe/support umc8672 chipsets |
*/ |
void ide_setup (char *s) |
{ |
int i, vals[3]; |
ide_hwif_t *hwif; |
ide_drive_t *drive; |
unsigned int hw, unit; |
const char max_drive = 'a' + ((MAX_HWIFS * MAX_DRIVES) - 1); |
const char max_hwif = '0' + (MAX_HWIFS - 1); |
|
printk("ide_setup: %s", s); |
init_ide_data (); |
|
/* |
* Look for drive options: "hdx=" |
*/ |
if (s[0] == 'h' && s[1] == 'd' && s[2] >= 'a' && s[2] <= max_drive) { |
const char *hd_words[] = {"none", "noprobe", "nowerr", "cdrom", |
"serialize", "autotune", "noautotune", |
"slow", "ide-scsi", NULL}; |
unit = s[2] - 'a'; |
hw = unit / MAX_DRIVES; |
unit = unit % MAX_DRIVES; |
hwif = &ide_hwifs[hw]; |
drive = &hwif->drives[unit]; |
switch (match_parm(&s[3], hd_words, vals, 3)) { |
case -1: /* "none" */ |
drive->nobios = 1; /* drop into "noprobe" */ |
case -2: /* "noprobe" */ |
drive->noprobe = 1; |
goto done; |
case -3: /* "nowerr" */ |
drive->bad_wstat = BAD_R_STAT; |
hwif->noprobe = 0; |
goto done; |
case -4: /* "cdrom" */ |
drive->present = 1; |
drive->media = ide_cdrom; |
hwif->noprobe = 0; |
goto done; |
case -5: /* "serialize" */ |
printk(" -- USE \"ide%d=serialize\" INSTEAD", hw); |
goto do_serialize; |
case -6: /* "autotune" */ |
drive->autotune = 1; |
goto done; |
case -7: /* "noautotune" */ |
drive->autotune = 2; |
goto done; |
case -8: /* "slow" */ |
drive->slow = 1; |
goto done; |
case -9: /* "ide-scsi" */ |
drive->ide_scsi = 1; |
goto done; |
case 3: /* cyl,head,sect */ |
drive->media = ide_disk; |
drive->cyl = drive->bios_cyl = vals[0]; |
drive->head = drive->bios_head = vals[1]; |
drive->sect = drive->bios_sect = vals[2]; |
drive->present = 1; |
drive->forced_geom = 1; |
hwif->noprobe = 0; |
goto done; |
default: |
goto bad_option; |
} |
} |
|
if (s[0] != 'i' || s[1] != 'd' || s[2] != 'e') |
goto bad_option; |
/* |
* Look for bus speed option: "idebus=" |
*/ |
if (s[3] == 'b' && s[4] == 'u' && s[5] == 's') { |
if (match_parm(&s[6], NULL, vals, 1) != 1) |
goto bad_option; |
if (vals[0] >= 20 && vals[0] <= 66) |
idebus_parameter = vals[0]; |
else |
printk(" -- BAD BUS SPEED! Expected value from 20 to 66"); |
goto done; |
} |
/* |
* Look for interface options: "idex=" |
*/ |
if (s[3] >= '0' && s[3] <= max_hwif) { |
/* |
* Be VERY CAREFUL changing this: note hardcoded indexes below |
*/ |
const char *ide_words[] = {"noprobe", "serialize", "autotune", "noautotune", |
"qd6580", "ht6560b", "cmd640_vlb", "dtc2278", "umc8672", "ali14xx", "dc4030", NULL}; |
hw = s[3] - '0'; |
hwif = &ide_hwifs[hw]; |
i = match_parm(&s[4], ide_words, vals, 3); |
|
/* |
* Cryptic check to ensure chipset not already set for hwif: |
*/ |
if (i > 0 || i <= -5) { |
if (hwif->chipset != ide_unknown) |
goto bad_option; |
if (i <= -5) { |
if (ide_hwifs[1].chipset != ide_unknown) |
goto bad_option; |
/* |
* Interface keywords work only for ide0: |
*/ |
if (hw != 0) |
goto bad_hwif; |
} |
} |
|
switch (i) { |
#ifdef CONFIG_BLK_DEV_PROMISE |
case -11: /* "dc4030" */ |
{ |
setup_dc4030(hwif); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
#ifdef CONFIG_BLK_DEV_ALI14XX |
case -10: /* "ali14xx" */ |
{ |
extern void init_ali14xx (void); |
init_ali14xx(); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_ALI14XX */ |
#ifdef CONFIG_BLK_DEV_UMC8672 |
case -9: /* "umc8672" */ |
{ |
extern void init_umc8672 (void); |
init_umc8672(); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_UMC8672 */ |
#ifdef CONFIG_BLK_DEV_DTC2278 |
case -8: /* "dtc2278" */ |
{ |
extern void init_dtc2278 (void); |
init_dtc2278(); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_DTC2278 */ |
#ifdef CONFIG_BLK_DEV_CMD640 |
case -7: /* "cmd640_vlb" */ |
{ |
extern int cmd640_vlb; /* flag for cmd640.c */ |
cmd640_vlb = 1; |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_CMD640 */ |
#ifdef CONFIG_BLK_DEV_HT6560B |
case -6: /* "ht6560b" */ |
{ |
extern void init_ht6560b (void); |
init_ht6560b(); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_HT6560B */ |
#if CONFIG_BLK_DEV_QD6580 |
case -5: /* "qd6580" (has secondary i/f) */ |
{ |
extern void init_qd6580 (void); |
init_qd6580(); |
goto done; |
} |
#endif /* CONFIG_BLK_DEV_QD6580 */ |
case -4: /* "noautotune" */ |
hwif->drives[0].autotune = 2; |
hwif->drives[1].autotune = 2; |
goto done; |
case -3: /* "autotune" */ |
hwif->drives[0].autotune = 1; |
hwif->drives[1].autotune = 1; |
goto done; |
case -2: /* "serialize" */ |
do_serialize: |
ide_hwifs[hw].serialized = 1; /* serialize */ |
ide_hwifs[hw^1].serialized = 1; /* with mate */ |
goto done; |
|
case -1: /* "noprobe" */ |
hwif->noprobe = 1; |
goto done; |
|
case 1: /* base */ |
vals[1] = vals[0] + 0x206; /* default ctl */ |
case 2: /* base,ctl */ |
vals[2] = 0; /* default irq = probe for it */ |
case 3: /* base,ctl,irq */ |
hwif->io.io_base = vals[0]; |
hwif->io.ctl_port = vals[1]; |
hwif->io.io_shift = 0; |
hwif->irq = vals[2]; |
hwif->noprobe = 0; |
hwif->chipset = ide_generic; |
goto done; |
|
case 0: goto bad_option; |
default: |
printk(" -- SUPPORT NOT CONFIGURED IN THIS KERNEL\n"); |
return; |
} |
} |
bad_option: |
printk(" -- BAD OPTION\n"); |
return; |
bad_hwif: |
printk("-- NOT SUPPORTED ON ide%d", hw); |
done: |
printk("\n"); |
} |
|
/* |
* This routine is called from the partition-table code in genhd.c |
* to "convert" a drive to a logical geometry with fewer than 1024 cyls. |
* |
* The second parameter, "xparm", determines exactly how the translation |
* will be handled: |
* 0 = convert to CHS with fewer than 1024 cyls |
* using the same method as Ontrack DiskManager. |
* 1 = same as "0", plus offset everything by 63 sectors. |
* -1 = similar to "0", plus redirect sector 0 to sector 1. |
* >1 = convert to a CHS geometry with "xparm" heads. |
* |
* Returns 0 if the translation was not possible, if the device was not |
* an IDE disk drive, or if a geometry was "forced" on the commandline. |
* Returns 1 if the geometry translation was successful. |
*/ |
int ide_xlate_1024 (kdev_t i_rdev, int xparm, const char *msg) |
{ |
ide_drive_t *drive; |
static const byte head_vals[] = {4, 8, 16, 32, 64, 128, 255, 0}; |
const byte *heads = head_vals; |
unsigned long tracks; |
|
if ((drive = get_info_ptr(i_rdev)) == NULL || drive->forced_geom) |
return 0; |
|
if (xparm > 1 && xparm <= drive->bios_head && drive->bios_sect == 63) |
return 0; /* we already have a translation */ |
|
printk("%s ", msg); |
|
if (drive->id) { |
drive->cyl = drive->id->cyls; |
drive->head = drive->id->heads; |
drive->sect = drive->id->sectors; |
} |
drive->bios_cyl = drive->cyl; |
drive->bios_head = drive->head; |
drive->bios_sect = drive->sect; |
drive->special.b.set_geometry = 1; |
|
tracks = drive->bios_cyl * drive->bios_head * drive->bios_sect / 63; |
drive->bios_sect = 63; |
if (xparm > 1) { |
drive->bios_head = xparm; |
drive->bios_cyl = tracks / drive->bios_head; |
} else { |
while (drive->bios_cyl >= 1024) { |
drive->bios_head = *heads; |
drive->bios_cyl = tracks / drive->bios_head; |
if (0 == *++heads) |
break; |
} |
#if FAKE_FDISK_FOR_EZDRIVE |
if (xparm == -1) { |
drive->remap_0_to_1 = 1; |
msg = "0->1"; |
} else |
#endif /* FAKE_FDISK_FOR_EZDRIVE */ |
if (xparm == 1) { |
drive->sect0 = 63; |
drive->bios_cyl = (tracks - 1) / drive->bios_head; |
msg = "+63"; |
} |
printk("[remap %s] ", msg); |
} |
drive->part[0].nr_sects = current_capacity(drive); |
printk("[%d/%d/%d]", drive->bios_cyl, drive->bios_head, drive->bios_sect); |
return 1; |
} |
|
#if MAX_HWIFS > 1 |
/* |
* save_match() is used to simplify logic in init_irq() below. |
* |
* A loophole here is that we may not know about a particular |
* hwif's irq until after that hwif is actually probed/initialized.. |
* This could be a problem for the case where an hwif is on a |
* dual interface that requires serialization (eg. cmd640) and another |
* hwif using one of the same irqs is initialized beforehand. |
* |
* This routine detects and reports such situations, but does not fix them. |
*/ |
static void save_match (ide_hwif_t *hwif, ide_hwif_t *new, ide_hwif_t **match) |
{ |
ide_hwif_t *m = *match; |
|
if (m && m->hwgroup && m->hwgroup != new->hwgroup) { |
if (!new->hwgroup) |
return; |
printk("%s: potential irq problem with %s and %s\n", hwif->name, new->name, m->name); |
} |
if (!m || m->irq != hwif->irq) /* don't undo a prior perfect match */ |
*match = new; |
} |
#endif /* MAX_HWIFS > 1 */ |
|
/* |
* This routine sets up the irq for an ide interface, and creates a new |
* hwgroup for the irq/hwif if none was previously assigned. |
* |
* Much of the code is for correctly detecting/handling irq sharing |
* and irq serialization situations. This is somewhat complex because |
* it handles static as well as dynamic (PCMCIA) IDE interfaces. |
* |
* The SA_INTERRUPT in sa_flags means ide_intr() is always entered with |
* interrupts completely disabled. This can be bad for interrupt latency, |
* but anything else has led to problems on some machines. We re-enable |
* interrupts as much as we can safely do in most places. |
*/ |
static int init_irq (ide_hwif_t *hwif) |
{ |
unsigned long flags; |
#if MAX_HWIFS > 1 |
unsigned int index; |
#endif /* MAX_HWIFS > 1 */ |
ide_hwgroup_t *hwgroup; |
ide_hwif_t *match = NULL; |
|
save_flags(flags); |
cli(); |
|
hwif->hwgroup = NULL; |
#if MAX_HWIFS > 1 |
/* |
* Group up with any other hwifs that share our irq(s). |
*/ |
for (index = 0; index < MAX_HWIFS; index++) { |
ide_hwif_t *h = &ide_hwifs[index]; |
if (h->hwgroup) { /* scan only initialized hwif's */ |
if (hwif->irq == h->irq) { |
hwif->sharing_irq = h->sharing_irq = 1; |
save_match(hwif, h, &match); |
} |
if (hwif->serialized) { |
ide_hwif_t *mate = &ide_hwifs[hwif->index^1]; |
if (index == mate->index || h->irq == mate->irq) |
save_match(hwif, h, &match); |
} |
if (h->serialized) { |
ide_hwif_t *mate = &ide_hwifs[h->index^1]; |
if (hwif->irq == mate->irq) |
save_match(hwif, h, &match); |
} |
} |
} |
#endif /* MAX_HWIFS > 1 */ |
/* |
* If we are still without a hwgroup, then form a new one |
*/ |
if (match) { |
hwgroup = match->hwgroup; |
} else { |
hwgroup = kmalloc(sizeof(ide_hwgroup_t), GFP_KERNEL); |
hwgroup->hwif = hwgroup->next_hwif = hwif->next = hwif; |
hwgroup->rq = NULL; |
hwgroup->handler = NULL; |
if (hwif->drives[0].present) |
hwgroup->drive = &hwif->drives[0]; |
else |
hwgroup->drive = &hwif->drives[1]; |
hwgroup->poll_timeout = 0; |
hwgroup->active = 0; |
init_timer(&hwgroup->timer); |
hwgroup->timer.function = &timer_expiry; |
hwgroup->timer.data = (unsigned long) hwgroup; |
} |
|
/* |
* Allocate the irq, if not already obtained for another hwif |
*/ |
if (!match || match->irq != hwif->irq) { |
if (request_irq(hwif->irq, ide_intr, SA_INTERRUPT, hwif->name, hwgroup)) { |
if (!match) |
kfree(hwgroup); |
restore_flags(flags); |
return 1; |
} |
} |
|
/* |
* Everything is okay, so link us into the hwgroup |
*/ |
hwif->hwgroup = hwgroup; |
hwif->next = hwgroup->hwif->next; |
hwgroup->hwif->next = hwif; |
|
restore_flags(flags); /* safe now that hwif->hwgroup is set up */ |
|
printk("%s at 0x%03lx-0x%03lx,0x%03lx on irq %d", hwif->name, |
hwif->io.io_base, hwif->io.io_base+7, hwif->io.ctl_port, hwif->irq); |
if (match) |
printk(" (%sed with %s)", hwif->sharing_irq ? "shar" : "serializ", match->name); |
printk("\n"); |
return 0; |
} |
|
static struct file_operations ide_fops = { |
NULL, /* lseek - default */ |
block_read, /* read - general block-dev read */ |
block_write, /* write - general block-dev write */ |
NULL, /* readdir - bad */ |
NULL, /* select */ |
ide_ioctl, /* ioctl */ |
NULL, /* mmap */ |
ide_open, /* open */ |
ide_release, /* release */ |
block_fsync /* fsync */ |
,NULL, /* fasync */ |
ide_check_media_change, /* check_media_change */ |
revalidate_disk /* revalidate */ |
}; |
|
#ifdef CONFIG_PCI |
#if defined(CONFIG_BLK_DEV_RZ1000) || defined(CONFIG_BLK_DEV_TRITON) |
|
typedef void (ide_pci_init_proc_t)(byte, byte); |
|
/* |
* ide_probe_pci() scans PCI for a specific vendor/device function, |
* and invokes the supplied init routine for each instance detected. |
*/ |
static void ide_probe_pci (unsigned short vendor, unsigned short device, ide_pci_init_proc_t *init, int func_adj) |
{ |
unsigned long flags; |
unsigned index; |
byte fn, bus; |
|
save_flags(flags); |
cli(); |
for (index = 0; !pcibios_find_device (vendor, device, index, &bus, &fn); ++index) { |
init (bus, fn + func_adj); |
} |
restore_flags(flags); |
} |
|
#endif /* defined(CONFIG_BLK_DEV_RZ1000) || defined(CONFIG_BLK_DEV_TRITON) */ |
|
static void ide_probe_promise_20246(void) |
{ |
byte fn, bus; |
unsigned short io[6], count = 0; |
unsigned int reg, tmp, i; |
ide_hwif_t *hwif; |
|
memset(io, 0, 6 * sizeof(unsigned short)); |
if (pcibios_find_device(PCI_VENDOR_ID_PROMISE, PCI_DEVICE_ID_PROMISE_20246, 0, &bus, &fn)) |
return; |
printk("ide: Promise Technology IDE Ultra-DMA 33 on PCI bus %d function %d\n", bus, fn); |
for (reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4) { |
pcibios_read_config_dword(bus, fn, reg, &tmp); |
if (tmp & PCI_BASE_ADDRESS_SPACE_IO) |
io[count++] = tmp & PCI_BASE_ADDRESS_IO_MASK; |
} |
for (i = 2; i < 4; i++) { |
hwif = ide_hwifs + i; |
if (hwif->chipset == ide_generic) { |
printk("ide%d: overridden with command line parameter\n", i); |
return; |
} |
tmp = (i - 2) * 2; |
if (!io[tmp] || !io[tmp + 1]) { |
printk("ide%d: invalid port address %x, %x -- aborting\n", i, io[tmp], io[tmp + 1]); |
return; |
} |
hwif->io_base = io[tmp]; |
hwif->ctl_port = io[tmp + 1] + 2; |
hwif->noprobe = 0; |
} |
#ifdef CONFIG_BLK_DEV_TRITON |
ide_init_promise (bus, fn, &ide_hwifs[2], &ide_hwifs[3], io[4]); |
#endif |
} |
|
#endif /* CONFIG_PCI */ |
|
/* |
* ide_init_pci() finds/initializes "known" PCI IDE interfaces |
* |
* This routine should ideally be using pcibios_find_class() to find |
* all IDE interfaces, but that function causes some systems to "go weird". |
*/ |
static void probe_for_hwifs (void) |
{ |
#ifdef CONFIG_PCI |
/* |
* Find/initialize PCI IDE interfaces |
*/ |
if (pcibios_present()) { |
#ifdef CONFIG_BLK_DEV_RZ1000 |
ide_pci_init_proc_t init_rz1000; |
ide_probe_pci (PCI_VENDOR_ID_PCTECH, PCI_DEVICE_ID_PCTECH_RZ1000, &init_rz1000, 0); |
ide_probe_pci (PCI_VENDOR_ID_PCTECH, PCI_DEVICE_ID_PCTECH_RZ1001, &init_rz1000, 0); |
#endif /* CONFIG_BLK_DEV_RZ1000 */ |
#ifdef CONFIG_BLK_DEV_TRITON |
/* |
* Apparently the BIOS32 services on Intel motherboards are |
* buggy and won't find the PCI_DEVICE_ID_INTEL_82371_1 for us. |
* So instead, we search for PCI_DEVICE_ID_INTEL_82371_0, |
* and then add 1. |
*/ |
ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371_0, &ide_init_triton, 1); |
ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_1, &ide_init_triton, 0); |
ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB, &ide_init_triton, 0); |
#endif /* CONFIG_BLK_DEV_TRITON */ |
ide_probe_promise_20246(); |
} |
#endif /* CONFIG_PCI */ |
#ifdef CONFIG_BLK_DEV_CMD640 |
{ |
extern void ide_probe_for_cmd640x (void); |
ide_probe_for_cmd640x(); |
} |
#endif |
#ifdef CONFIG_BLK_DEV_PROMISE |
init_dc4030(); |
#endif |
#ifdef CONFIG_BLK_DEV_IDE_ICSIDE |
icside_init (); |
#endif |
#ifdef CONFIG_BLK_DEV_IDE_RAPIDE |
rapide_init (); |
#endif |
} |
|
static int hwif_init (int h) |
{ |
ide_hwif_t *hwif = &ide_hwifs[h]; |
void (*rfn)(void); |
|
if (!hwif->present) |
return 0; |
if (!hwif->irq) { |
if (!(hwif->irq = default_irqs[h])) { |
printk("%s: DISABLED, NO IRQ\n", hwif->name); |
return (hwif->present = 0); |
} |
} |
#ifdef CONFIG_BLK_DEV_HD |
if (hwif->irq == HD_IRQ && hwif->io_base != HD_DATA) { |
printk("%s: CANNOT SHARE IRQ WITH OLD HARDDISK DRIVER (hd.c)\n", hwif->name); |
return (hwif->present = 0); |
} |
#endif /* CONFIG_BLK_DEV_HD */ |
|
hwif->present = 0; /* we set it back to 1 if all is ok below */ |
switch (hwif->major) { |
case IDE0_MAJOR: rfn = &do_ide0_request; break; |
#if MAX_HWIFS > 1 |
case IDE1_MAJOR: rfn = &do_ide1_request; break; |
#endif |
#if MAX_HWIFS > 2 |
case IDE2_MAJOR: rfn = &do_ide2_request; break; |
#endif |
#if MAX_HWIFS > 3 |
case IDE3_MAJOR: rfn = &do_ide3_request; break; |
#endif |
default: |
printk("%s: request_fn NOT DEFINED\n", hwif->name); |
return (hwif->present = 0); |
} |
if (register_blkdev (hwif->major, hwif->name, &ide_fops)) { |
printk("%s: UNABLE TO GET MAJOR NUMBER %d\n", hwif->name, hwif->major); |
} else if (init_irq (hwif)) { |
printk("%s: UNABLE TO GET IRQ %d\n", hwif->name, hwif->irq); |
(void) unregister_blkdev (hwif->major, hwif->name); |
} else { |
init_gendisk(hwif); |
blk_dev[hwif->major].request_fn = rfn; |
read_ahead[hwif->major] = 8; /* (4kB) */ |
hwif->present = 1; /* success */ |
} |
return hwif->present; |
} |
|
/* |
* A variable so that we know if we're adding drives at boot time, |
* so we don't do a partition check on it. |
*/ |
static int ide_at_boot; |
/* |
* This is gets invoked once during initialization, to set *everything* up |
*/ |
int ide_init (void) |
{ |
int index; |
|
ide_at_boot = 1; |
|
init_ide_data (); |
/* |
* Probe for special "known" interface chipsets |
*/ |
probe_for_hwifs (); |
|
/* |
* Probe for drives in the usual way.. CMOS/BIOS, then poke at ports |
*/ |
for (index = 0; index < MAX_HWIFS; ++index) |
probe_hwif (&ide_hwifs[index]); |
for (index = 0; index < MAX_HWIFS; ++index) |
hwif_init (index); |
|
#ifdef CONFIG_BLK_DEV_IDETAPE |
idetape_register_chrdev(); /* Register character device interface to the ide tape */ |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
|
ide_at_boot = 0; |
|
return 0; |
} |
|
#ifdef CONFIG_BLK_DEV_IDE_CARDS |
int ide_register_port (int io_base, int ctl_port, int stepping, int irq) |
{ |
int index, i, rc = -1; |
ide_hwif_t *hwif; |
ide_drive_t *drive; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
for (index = 0; index < MAX_HWIFS; ++index) { |
hwif = &ide_hwifs[index]; |
if (hwif->present) { |
if (hwif->io.io_base == io_base || hwif->io.ctl_port == ctl_port) |
break; /* this ide port already exists */ |
} else { |
if (ide_at_boot) { |
if (hwif->io.io_base == 0) { |
hwif->io.io_base = io_base; |
hwif->io.ctl_port = ctl_port; |
hwif->io.io_shift = stepping; |
hwif->irq = irq; |
hwif->noprobe = 0; |
rc = index; |
break; |
} |
} else { |
hwif->io.io_base = io_base; |
hwif->io.ctl_port = ctl_port; |
hwif->io.io_shift = stepping; |
hwif->irq = irq; |
hwif->noprobe = 0; |
probe_hwif(hwif); |
if (!hwif_init(index)) |
break; |
for (i = 0; i < hwif->gd->nr_real; i++) { |
drive = &hwif->drives[i]; |
revalidate_disk(MKDEV(hwif->major, i<<PARTN_BITS)); |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->present && drive->media == ide_cdrom) |
ide_cdrom_setup(drive); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
} |
rc = index; |
break; |
} |
} |
} |
restore_flags(flags); |
return rc; |
} |
#endif |
|
#ifdef CONFIG_BLK_DEV_IDE_PCMCIA |
int ide_register(int io_base, int ctl_port, int irq) |
{ |
int index, i, rc = -1; |
ide_hwif_t *hwif; |
ide_drive_t *drive; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
for (index = 0; index < MAX_HWIFS; ++index) { |
hwif = &ide_hwifs[index]; |
if (hwif->present) { |
if (hwif->io.io_base == io_base || hwif->io.ctl_port == ctl_port) |
break; /* this ide port already exists */ |
} else { |
hwif->io.io_base = io_base; |
hwif->io.ctl_port = ctl_port; |
hwif->io.io_shift = 0; |
hwif->irq = irq; |
hwif->noprobe = 0; |
probe_hwif(hwif); |
if (!hwif_init(index)) |
break; |
for (i = 0; i < hwif->gd->nr_real; i++) { |
drive = &hwif->drives[i]; |
revalidate_disk(MKDEV(hwif->major, i<<PARTN_BITS)); |
#ifdef CONFIG_BLK_DEV_IDECD |
if (drive->present && drive->media == ide_cdrom) |
ide_cdrom_setup(drive); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
} |
rc = index; |
break; |
} |
} |
restore_flags(flags); |
return rc; |
} |
#endif |
|
#if defined(CONFIG_BLK_DEV_IDE_PCMCIA) || defined(CONFIG_BLK_DEV_IDE_CARDS) |
void ide_unregister (unsigned int index) |
{ |
struct gendisk *gd, **gdp; |
ide_hwif_t *hwif, *g; |
ide_hwgroup_t *hwgroup; |
int irq_count = 0; |
unsigned long flags; |
|
if (index >= MAX_HWIFS) |
return; |
save_flags(flags); |
cli(); |
hwif = &ide_hwifs[index]; |
if (!hwif->present || hwif->drives[0].busy || hwif->drives[1].busy) { |
restore_flags(flags); |
return; |
} |
hwif->present = 0; |
hwgroup = hwif->hwgroup; |
|
/* |
* free the irq if we were the only hwif using it |
*/ |
g = hwgroup->hwif; |
do { |
if (g->irq == hwif->irq) |
++irq_count; |
g = g->next; |
} while (g != hwgroup->hwif); |
if (irq_count == 1) |
free_irq(hwif->irq, hwgroup); |
|
/* |
* Note that we only release the standard ports, |
* and do not even try to handle any extra ports |
* allocated for weird IDE interface chipsets. |
*/ |
release_region(hwif->io.io_base, 8 << hwif->io.io_shift); |
release_region(hwif->io.ctl_port, 1); |
|
/* |
* Remove us from the hwgroup, and free |
* the hwgroup if we were the only member |
*/ |
while (hwgroup->hwif->next != hwif) |
hwgroup->hwif = hwgroup->hwif->next; |
hwgroup->hwif->next = hwif->next; |
if (hwgroup->hwif == hwif) |
hwgroup->hwif = hwif->next; |
if (hwgroup->next_hwif == hwif) |
hwgroup->next_hwif = hwif->next; |
if (hwgroup->hwif == hwif) |
kfree(hwgroup); |
|
/* |
* Remove us from the kernel's knowledge |
*/ |
unregister_blkdev(hwif->major, hwif->name); |
kfree(blksize_size[hwif->major]); |
blk_dev[hwif->major].request_fn = NULL; |
blksize_size[hwif->major] = NULL; |
for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) |
if (*gdp == hwif->gd) |
break; |
if (*gdp == NULL) |
printk("gd not in disk chain!\n"); |
else { |
gd = *gdp; *gdp = gd->next; |
kfree(gd->sizes); |
kfree(gd->part); |
kfree(gd); |
} |
init_hwif_data (index); /* restore hwif data to pristine status */ |
restore_flags(flags); |
} |
#endif /* CONFIG_BLK_DEV_IDE_PCMCIA */ |
/ide-rapide.c
0,0 → 1,78
/* |
* linux/arch/arm/drivers/block/ide-ics.c |
* |
* Copyright (c) 1996 Russell King. |
* |
* Changelog: |
* 08-06-1996 RMK Created |
*/ |
|
#include <linux/module.h> |
#include <linux/malloc.h> |
#include <linux/blkdev.h> |
#include <linux/errno.h> |
#include <asm/ecard.h> |
|
#include "ide.h" |
|
static const card_ids rapide_cids[] = { |
{ 0xffff, 0xffff } |
}; |
|
static struct expansion_card *ec[MAX_ECARDS]; |
static int result[MAX_ECARDS]; |
|
static inline int rapide_register (struct expansion_card *ec) |
{ |
unsigned long port = ecard_address (ec, ECARD_MEMC, 0); |
|
return ide_register_port (port, port + 0x206, 4, ec->irq); |
} |
|
int rapide_init (void) |
{ |
int i; |
|
for (i = 0; i < MAX_ECARDS; i++) |
ec[i] = NULL; |
|
ecard_startfind (); |
|
for (i = 0; ; i++) { |
if ((ec[i] = ecard_find (0, rapide_cids)) == NULL) |
break; |
|
ecard_claim (ec[i]); |
result[i] = rapide_register (ec[i]); |
} |
for (i = 0; i < MAX_ECARDS; i++) |
if (ec[i] && result[i] < 0) { |
ecard_release (ec[i]); |
ec[i] = NULL; |
} |
return 0; |
} |
|
#ifdef MODULE |
|
int init_module (void) |
{ |
return rapide_init(); |
} |
|
void cleanup_module (void) |
{ |
int i; |
|
for (i = 0; i < MAX_ECARDS; i++) |
if (ec[i]) { |
unsigned long port; |
port = ecard_address (ec[i], ECARD_MEMC, 0); |
|
ide_unregister_port (port, ec[i]->irq, 16); |
ecard_release (ec[i]); |
ec[i] = NULL; |
} |
} |
#endif |
|
/rd.c
0,0 → 1,708
/* |
* ramdisk.c - Multiple ramdisk driver - gzip-loading version - v. 0.8 beta. |
* |
* (C) Chad Page, Theodore Ts'o, et. al, 1995. |
* |
* This ramdisk is designed to have filesystems created on it and mounted |
* just like a regular floppy disk. |
* |
* It also does something suggested by Linus: use the buffer cache as the |
* ramdisk data. This makes it possible to dynamically allocate the ramdisk |
* buffer - with some consequences I have to deal with as I write this. |
* |
* This code is based on the original ramdisk.c, written mostly by |
* Theodore Ts'o (TYT) in 1991. The code was largely rewritten by |
* Chad Page to use the buffer cache to store the ramdisk data in |
* 1995; Theodore then took over the driver again, and cleaned it up |
* for inclusion in the mainline kernel. |
* |
* The original CRAMDISK code was written by Richard Lyons, and |
* adapted by Chad Page to use the new ramdisk interface. Theodore |
* Ts'o rewrote it so that both the compressed ramdisk loader and the |
* kernel decompressor uses the same inflate.c codebase. The ramdisk |
* loader now also loads into a dynamic (buffer cache based) ramdisk, |
* not the old static ramdisk. Support for the old static ramdisk has |
* been completely removed. |
* |
* Loadable module support added by Tom Dyas. |
* |
* Further cleanups by Chad Page (page0588@sundance.sjsu.edu): |
* Cosmetic changes in #ifdef MODULE, code movement, etc... |
* When the ramdisk is rmmod'ed, free the protected buffers |
* Default ramdisk size changed to 2.88MB |
* |
* Added initrd: Werner Almesberger & Hans Lermen, Feb '96 |
* |
* 4/25/96 : Made ramdisk size a parameter (default is now 4MB) |
* - Chad Page |
*/ |
|
#include <linux/config.h> |
#include <linux/sched.h> |
#include <linux/minix_fs.h> |
#include <linux/ext2_fs.h> |
#include <linux/fs.h> |
#include <linux/kernel.h> |
#include <linux/string.h> |
#include <linux/mm.h> |
#include <linux/mman.h> |
#include <linux/malloc.h> |
#include <linux/ioctl.h> |
#include <linux/fd.h> |
#include <linux/module.h> |
|
#include <asm/system.h> |
#include <asm/segment.h> |
|
extern void wait_for_keypress(void); |
|
/* |
* 35 has been officially registered as the RAMDISK major number, but |
* so is the original MAJOR number of 1. We're using 1 in |
* include/linux/major.h for now |
*/ |
#define MAJOR_NR RAMDISK_MAJOR |
#include <linux/blk.h> |
|
/* The ramdisk size is now a parameter */ |
#define NUM_RAMDISKS 16 /* This cannot be overridden (yet) */ |
|
#ifndef MODULE |
/* We don't have to load ramdisks or gunzip them in a module... */ |
#define RD_LOADER |
#define BUILD_CRAMDISK |
|
void rd_load(void); |
static int crd_load(struct file *fp, struct file *outfp); |
|
#ifdef CONFIG_BLK_DEV_INITRD |
static int initrd_users = 0; |
#endif |
#endif |
|
/* Various static variables go here... mostly used within the ramdisk code only. */ |
|
static int rd_length[NUM_RAMDISKS]; |
static int rd_blocksizes[NUM_RAMDISKS]; |
|
/* |
* Parameters for the boot-loading of the ramdisk. These are set by |
* init/main.c (from arguments to the kernel command line) or from the |
* architecture-specific setup routine (from the stored bootsector |
* information). |
*/ |
int rd_size = 4096; /* Size of the ramdisks */ |
|
#ifndef MODULE |
int rd_doload = 0; /* 1 = load ramdisk, 0 = don't load */ |
int rd_prompt = 1; /* 1 = prompt for ramdisk, 0 = don't prompt */ |
int rd_image_start = 0; /* starting block # of image */ |
#ifdef CONFIG_BLK_DEV_INITRD |
unsigned long initrd_start,initrd_end; |
int mount_initrd = 1; /* zero if initrd should not be mounted */ |
#endif |
#endif |
|
/* |
* Basically, my strategy here is to set up a buffer-head which can't be |
* deleted, and make that my Ramdisk. If the request is outside of the |
* allocated size, we must get rid of it... |
* |
*/ |
static void rd_request(void) |
{ |
unsigned int minor; |
int offset, len; |
|
repeat: |
INIT_REQUEST; |
|
minor = MINOR(CURRENT->rq_dev); |
|
if (minor >= NUM_RAMDISKS) { |
end_request(0); |
goto repeat; |
} |
|
offset = CURRENT->sector << 9; |
len = CURRENT->current_nr_sectors << 9; |
|
if ((offset + len) > rd_length[minor]) { |
end_request(0); |
goto repeat; |
} |
|
/* |
* If we're reading, fill the buffer with 0's. This is okay since |
* we're using protected buffers which should never get freed... |
* |
* If we're writing, we protect the buffer. |
*/ |
|
if (CURRENT->cmd == READ) |
memset(CURRENT->buffer, 0, len); |
else |
set_bit(BH_Protected, &CURRENT->bh->b_state); |
|
end_request(1); |
goto repeat; |
} |
|
static int rd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) |
{ |
int err; |
|
if (!inode || !inode->i_rdev) |
return -EINVAL; |
|
switch (cmd) { |
case BLKFLSBUF: |
if (!suser()) return -EACCES; |
invalidate_buffers(inode->i_rdev); |
break; |
case BLKGETSIZE: /* Return device size */ |
if (!arg) return -EINVAL; |
err = verify_area(VERIFY_WRITE, (long *) arg, |
sizeof(long)); |
if (err) |
return err; |
put_user(rd_length[MINOR(inode->i_rdev)] / 512, |
(long *) arg); |
return 0; |
|
default: |
break; |
}; |
|
return 0; |
} |
|
|
#ifdef CONFIG_BLK_DEV_INITRD |
|
static int initrd_read(struct inode *inode,struct file *file,char *buf, |
int count) |
{ |
int left; |
|
left = initrd_end-initrd_start-file->f_pos; |
if (count > left) count = left; |
if (count <= 0) return 0; |
memcpy_tofs(buf,(char *) initrd_start+file->f_pos,count); |
file->f_pos += count; |
return count; |
} |
|
|
static void initrd_release(struct inode *inode,struct file *file) |
{ |
unsigned long i; |
|
if (--initrd_users) return; |
for (i = initrd_start; i < initrd_end; i += PAGE_SIZE) |
free_page(i); |
initrd_start = 0; |
} |
|
|
static struct file_operations initrd_fops = { |
NULL, /* lseek */ |
initrd_read, /* read */ |
NULL, /* write */ |
NULL, /* readdir */ |
NULL, /* select */ |
NULL, /* ioctl */ |
NULL, /* mmap */ |
NULL, /* open */ |
initrd_release, /* release */ |
NULL /* fsync */ |
}; |
|
#endif |
|
|
static int rd_open(struct inode * inode, struct file * filp) |
{ |
#ifdef CONFIG_BLK_DEV_INITRD |
if (DEVICE_NR(inode->i_rdev) == INITRD_MINOR) { |
if (!initrd_start) return -ENODEV; |
initrd_users++; |
filp->f_op = &initrd_fops; |
return 0; |
} |
#endif |
|
if (DEVICE_NR(inode->i_rdev) >= NUM_RAMDISKS) |
return -ENXIO; |
|
MOD_INC_USE_COUNT; |
|
return 0; |
} |
|
#ifdef MODULE |
static void rd_release(struct inode * inode, struct file * filp) |
{ |
MOD_DEC_USE_COUNT; |
} |
#endif |
|
static struct file_operations fd_fops = { |
NULL, /* lseek - default */ |
block_read, /* read - block dev read */ |
block_write, /* write - block dev write */ |
NULL, /* readdir - not here! */ |
NULL, /* select */ |
rd_ioctl, /* ioctl */ |
NULL, /* mmap */ |
rd_open, /* open */ |
#ifndef MODULE |
NULL, /* no special release code... */ |
#else |
rd_release, /* module needs to decrement use count */ |
#endif |
block_fsync /* fsync */ |
}; |
|
/* This is the registration and initialization section of the ramdisk driver */ |
int rd_init(void) |
{ |
int i; |
|
if (register_blkdev(MAJOR_NR, "ramdisk", &fd_fops)) { |
printk("RAMDISK: Could not get major %d", MAJOR_NR); |
return -EIO; |
} |
|
blk_dev[MAJOR_NR].request_fn = &rd_request; |
|
for (i = 0; i < NUM_RAMDISKS; i++) { |
rd_length[i] = (rd_size * 1024); |
rd_blocksizes[i] = 1024; |
} |
|
blksize_size[MAJOR_NR] = rd_blocksizes; |
|
printk("Ramdisk driver initialized : %d ramdisks of %dK size\n", |
NUM_RAMDISKS, rd_size); |
|
return 0; |
} |
|
/* loadable module support */ |
|
#ifdef MODULE |
|
int init_module(void) |
{ |
int error = rd_init(); |
if (!error) |
printk(KERN_INFO "RAMDISK: Loaded as module.\n"); |
return error; |
} |
|
/* Before freeing the module, invalidate all of the protected buffers! */ |
void cleanup_module(void) |
{ |
int i; |
|
for (i = 0 ; i < NUM_RAMDISKS; i++) |
invalidate_buffers(MKDEV(MAJOR_NR, i)); |
|
unregister_blkdev( MAJOR_NR, "ramdisk" ); |
blk_dev[MAJOR_NR].request_fn = 0; |
} |
|
#endif /* MODULE */ |
|
/* End of non-loading portions of the ramdisk driver */ |
|
#ifdef RD_LOADER |
/* |
* This routine tries to a ramdisk image to load, and returns the |
* number of blocks to read for a non-compressed image, 0 if the image |
* is a compressed image, and -1 if an image with the right magic |
* numbers could not be found. |
* |
* We currently check for the following magic numbers: |
* minix |
* ext2 |
* gzip |
*/ |
int |
identify_ramdisk_image(kdev_t device, struct file *fp, int start_block) |
{ |
const int size = 512; |
struct minix_super_block *minixsb; |
struct ext2_super_block *ext2sb; |
int nblocks = -1; |
int max_blocks; |
unsigned char *buf; |
|
buf = kmalloc(size, GFP_KERNEL); |
if (buf == 0) |
return -1; |
|
minixsb = (struct minix_super_block *) buf; |
ext2sb = (struct ext2_super_block *) buf; |
memset(buf, 0xe5, size); |
|
/* |
* Read block 0 to test for gzipped kernel |
*/ |
if (fp->f_op->lseek) |
fp->f_op->lseek(fp->f_inode, fp, start_block * BLOCK_SIZE, 0); |
fp->f_pos = start_block * BLOCK_SIZE; |
|
fp->f_op->read(fp->f_inode, fp, buf, size); |
|
/* |
* If it matches the gzip magic numbers, return -1 |
*/ |
if (buf[0] == 037 && ((buf[1] == 0213) || (buf[1] == 0236))) { |
printk(KERN_NOTICE |
"RAMDISK: Compressed image found at block %d\n", |
start_block); |
nblocks = 0; |
goto done; |
} |
|
/* |
* Read block 1 to test for minix and ext2 superblock |
*/ |
if (fp->f_op->lseek) |
fp->f_op->lseek(fp->f_inode, fp, |
(start_block+1) * BLOCK_SIZE, 0); |
fp->f_pos = (start_block+1) * BLOCK_SIZE; |
|
fp->f_op->read(fp->f_inode, fp, buf, size); |
|
/* Try minix */ |
if (minixsb->s_magic == MINIX_SUPER_MAGIC || |
minixsb->s_magic == MINIX_SUPER_MAGIC2) { |
printk(KERN_NOTICE |
"RAMDISK: Minix filesystem found at block %d\n", |
start_block); |
nblocks = minixsb->s_nzones << minixsb->s_log_zone_size; |
goto done; |
} |
|
/* Try ext2 */ |
if (ext2sb->s_magic == EXT2_SUPER_MAGIC) { |
printk(KERN_NOTICE |
"RAMDISK: Ext2 filesystem found at block %d\n", |
start_block); |
nblocks = ext2sb->s_blocks_count; |
goto done; |
} |
printk(KERN_NOTICE |
"RAMDISK: Couldn't find valid ramdisk image starting at %d.\n", |
start_block); |
|
done: |
if (fp->f_op->lseek) |
fp->f_op->lseek(fp->f_inode, fp, start_block * BLOCK_SIZE, 0); |
fp->f_pos = start_block * BLOCK_SIZE; |
|
if ((nblocks > 0) && blk_size[MAJOR(device)]) { |
max_blocks = blk_size[MAJOR(device)][MINOR(device)]; |
max_blocks -= start_block; |
if (nblocks > max_blocks) { |
printk(KERN_NOTICE |
"RAMDISK: Restricting filesystem size " |
"from %d to %d blocks.\n", |
nblocks, max_blocks); |
nblocks = max_blocks; |
} |
} |
kfree(buf); |
return nblocks; |
} |
|
/* |
* This routine loads in the ramdisk image. |
*/ |
static void rd_load_image(kdev_t device,int offset, int unit) |
{ |
struct inode inode, out_inode; |
struct file infile, outfile; |
unsigned short fs; |
kdev_t ram_device; |
int nblocks, i; |
char *buf; |
unsigned short rotate = 0; |
char rotator[4] = { '|' , '/' , '-' , '\\' }; |
|
ram_device = MKDEV(MAJOR_NR, unit); |
|
memset(&infile, 0, sizeof(infile)); |
memset(&inode, 0, sizeof(inode)); |
inode.i_rdev = device; |
infile.f_mode = 1; /* read only */ |
infile.f_inode = &inode; |
|
memset(&outfile, 0, sizeof(outfile)); |
memset(&out_inode, 0, sizeof(out_inode)); |
out_inode.i_rdev = ram_device; |
outfile.f_mode = 3; /* read/write */ |
outfile.f_inode = &out_inode; |
|
if (blkdev_open(&inode, &infile) != 0) return; |
if (blkdev_open(&out_inode, &outfile) != 0) return; |
|
fs = get_fs(); |
set_fs(KERNEL_DS); |
|
nblocks = identify_ramdisk_image(device, &infile, offset); |
if (nblocks < 0) |
goto done; |
|
if (nblocks == 0) { |
#ifdef BUILD_CRAMDISK |
if (crd_load(&infile, &outfile) == 0) |
goto successful_load; |
#else |
printk(KERN_NOTICE |
"RAMDISK: Kernel does not support compressed " |
"ramdisk images\n"); |
#endif |
goto done; |
} |
|
if (nblocks > (rd_length[0] >> BLOCK_SIZE_BITS)) { |
printk("RAMDISK: image too big! (%d/%d blocks)\n", |
nblocks, rd_length[0] >> BLOCK_SIZE_BITS); |
goto done; |
} |
|
/* |
* OK, time to copy in the data |
*/ |
buf = kmalloc(BLOCK_SIZE, GFP_KERNEL); |
if (buf == 0) { |
printk(KERN_ERR "RAMDISK: could not allocate buffer\n"); |
goto done; |
} |
|
printk(KERN_NOTICE "RAMDISK: Loading %d blocks into ram disk... ", nblocks); |
for (i=0; i < nblocks; i++) { |
infile.f_op->read(infile.f_inode, &infile, buf, |
BLOCK_SIZE); |
outfile.f_op->write(outfile.f_inode, &outfile, buf, |
BLOCK_SIZE); |
if (!(i % 16)) { |
printk("%c\b", rotator[rotate & 0x3]); |
rotate++; |
} |
} |
printk("done.\n"); |
kfree(buf); |
|
successful_load: |
invalidate_buffers(device); |
ROOT_DEV = MKDEV(MAJOR_NR,unit); |
|
done: |
if (infile.f_op->release) |
infile.f_op->release(&inode, &infile); |
set_fs(fs); |
} |
|
|
static void rd_load_disk(int n) |
{ |
#ifdef CONFIG_BLK_DEV_INITRD |
extern kdev_t real_root_dev; |
#endif |
|
if (rd_doload == 0) |
return; |
|
if (MAJOR(ROOT_DEV) != FLOPPY_MAJOR |
#ifdef CONFIG_BLK_DEV_INITRD |
&& MAJOR(real_root_dev) != FLOPPY_MAJOR |
#endif |
) |
return; |
|
if (rd_prompt) { |
#ifdef CONFIG_BLK_DEV_FD |
floppy_eject(); |
#endif |
printk(KERN_NOTICE |
"VFS: Insert root floppy disk to be loaded into ramdisk and press ENTER\n"); |
wait_for_keypress(); |
} |
|
rd_load_image(ROOT_DEV,rd_image_start,n); |
|
} |
|
void rd_load(void) |
{ |
rd_load_disk(0); |
} |
|
void rd_load_secondary(void) |
{ |
rd_load_disk(1); |
} |
|
#ifdef CONFIG_BLK_DEV_INITRD |
void initrd_load(void) |
{ |
rd_load_image(MKDEV(MAJOR_NR, INITRD_MINOR),0,0); |
} |
#endif |
|
#endif /* RD_LOADER */ |
|
#ifdef BUILD_CRAMDISK |
|
/* |
* gzip declarations |
*/ |
|
#define OF(args) args |
|
#define memzero(s, n) memset ((s), 0, (n)) |
|
|
typedef unsigned char uch; |
typedef unsigned short ush; |
typedef unsigned long ulg; |
|
#define INBUFSIZ 4096 |
#define WSIZE 0x8000 /* window size--must be a power of two, and */ |
/* at least 32K for zip's deflate method */ |
|
static uch *inbuf; |
static uch *window; |
|
static unsigned insize = 0; /* valid bytes in inbuf */ |
static unsigned inptr = 0; /* index of next byte to be processed in inbuf */ |
static unsigned outcnt = 0; /* bytes in output buffer */ |
static int exit_code = 0; |
static long bytes_out = 0; |
static struct file *crd_infp, *crd_outfp; |
|
#define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf()) |
|
/* Diagnostic functions (stubbed out) */ |
#define Assert(cond,msg) |
#define Trace(x) |
#define Tracev(x) |
#define Tracevv(x) |
#define Tracec(c,x) |
#define Tracecv(c,x) |
|
#define STATIC static |
|
static int fill_inbuf(void); |
static void flush_window(void); |
static void *malloc(int size); |
static void free(void *where); |
static void error(char *m); |
static void gzip_mark(void **); |
static void gzip_release(void **); |
|
#include "../../../../lib/inflate.c" |
|
static void *malloc(int size) |
{ |
return kmalloc(size, GFP_KERNEL); |
} |
|
static void free(void *where) |
{ |
kfree(where); |
} |
|
static void gzip_mark(void **ptr) |
{ |
} |
|
static void gzip_release(void **ptr) |
{ |
} |
|
|
/* =========================================================================== |
* Fill the input buffer. This is called only when the buffer is empty |
* and at least one byte is really needed. |
*/ |
static int fill_inbuf() |
{ |
if (exit_code) return -1; |
|
insize = crd_infp->f_op->read(crd_infp->f_inode, crd_infp, |
inbuf, INBUFSIZ); |
if (insize == 0) return -1; |
|
inptr = 1; |
|
return inbuf[0]; |
} |
|
/* =========================================================================== |
* Write the output window window[0..outcnt-1] and update crc and bytes_out. |
* (Used for the decompressed data only.) |
*/ |
static void flush_window() |
{ |
ulg c = crc; /* temporary variable */ |
unsigned n; |
uch *in, ch; |
|
crd_outfp->f_op->write(crd_outfp->f_inode, crd_outfp, window, |
outcnt); |
in = window; |
for (n = 0; n < outcnt; n++) { |
ch = *in++; |
c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8); |
} |
crc = c; |
bytes_out += (ulg)outcnt; |
outcnt = 0; |
} |
|
static void error(char *x) |
{ |
printk(KERN_ERR "%s", x); |
exit_code = 1; |
} |
|
static int |
crd_load(struct file * fp, struct file *outfp) |
{ |
int result; |
|
insize = 0; /* valid bytes in inbuf */ |
inptr = 0; /* index of next byte to be processed in inbuf */ |
outcnt = 0; /* bytes in output buffer */ |
exit_code = 0; |
bytes_out = 0; |
crc = 0xFFFFFFFF; |
|
crd_infp = fp; |
crd_outfp = outfp; |
inbuf = kmalloc(INBUFSIZ, GFP_KERNEL); |
if (inbuf == 0) { |
printk(KERN_ERR "RAMDISK: Couldn't allocate gzip buffer\n"); |
return -1; |
} |
window = kmalloc(WSIZE, GFP_KERNEL); |
if (window == 0) { |
printk(KERN_ERR "RAMDISK: Couldn't allocate gzip window\n"); |
kfree(inbuf); |
return -1; |
} |
makecrc(); |
result = gunzip(); |
kfree(inbuf); |
kfree(window); |
return result; |
} |
|
#endif /* BUILD_CRAMDISK */ |
|
/ide.h
0,0 → 1,761
/* |
* linux/drivers/block/ide.h |
* |
* Copyright (C) 1994, 1995 Linus Torvalds & authors |
*/ |
|
#include <linux/config.h> |
|
/* |
* This is the multiple IDE interface driver, as evolved from hd.c. |
* It supports up to four IDE interfaces, on one or more IRQs (usually 14 & 15). |
* There can be up to two drives per interface, as per the ATA-2 spec. |
* |
* Primary i/f: ide0: major=3; (hda) minor=0; (hdb) minor=64 |
* Secondary i/f: ide1: major=22; (hdc or hd1a) minor=0; (hdd or hd1b) minor=64 |
* Tertiary i/f: ide2: major=33; (hde) minor=0; (hdf) minor=64 |
* Quaternary i/f: ide3: major=34; (hdg) minor=0; (hdh) minor=64 |
*/ |
|
/****************************************************************************** |
* IDE driver configuration options (play with these as desired): |
* |
* REALLY_SLOW_IO can be defined in ide.c and ide-cd.c, if necessary |
*/ |
#undef REALLY_FAST_IO /* define if ide ports are perfect */ |
#define INITIAL_MULT_COUNT 0 /* off=0; on=2,4,8,16,32, etc.. */ |
|
#ifndef SUPPORT_SLOW_DATA_PORTS /* 1 to support slow data ports */ |
#define SUPPORT_SLOW_DATA_PORTS 0 /* 0 to reduce kernel size */ |
#endif |
#ifndef SUPPORT_VLB_SYNC /* 1 to support weird 32-bit chips */ |
#define SUPPORT_VLB_SYNC 0 /* 0 to reduce kernel size */ |
#endif |
#ifndef DISK_RECOVERY_TIME /* off=0; on=access_delay_time */ |
#define DISK_RECOVERY_TIME 0 /* for hardware that needs it */ |
#endif |
#ifndef OK_TO_RESET_CONTROLLER /* 1 needed for good error recovery */ |
#define OK_TO_RESET_CONTROLLER 1 /* 0 for use with AH2372A/B interface */ |
#endif |
#ifndef FAKE_FDISK_FOR_EZDRIVE /* 1 to help linux fdisk with EZDRIVE */ |
#define FAKE_FDISK_FOR_EZDRIVE 1 /* 0 to reduce kernel size */ |
#endif |
#ifndef FANCY_STATUS_DUMPS /* 1 for human-readable drive errors */ |
#define FANCY_STATUS_DUMPS 1 /* 0 to reduce kernel size */ |
#endif |
|
#ifdef CONFIG_BLK_DEV_CMD640 |
#if 0 /* change to 1 when debugging cmd640 problems */ |
void cmd640_dump_regs (void); |
#define CMD640_DUMP_REGS cmd640_dump_regs() /* for debugging cmd640 chipset */ |
#endif |
#endif /* CONFIG_BLK_DEV_CMD640 */ |
|
#if defined(CONFIG_BLK_DEV_IDECD) || defined(CONFIG_BLK_DEV_IDETAPE) || \ |
defined(CONFIG_BLK_DEV_IDEFLOPPY) || defined(CONFIG_BLK_DEV_IDESCSI) |
#define CONFIG_BLK_DEV_IDEATAPI 1 |
#endif |
|
/* |
* IDE_DRIVE_CMD is used to implement many features of the hdparm utility |
*/ |
#define IDE_DRIVE_CMD 99 /* (magic) undef to reduce kernel size*/ |
|
/* |
* "No user-serviceable parts" beyond this point :) |
*****************************************************************************/ |
|
#if defined(CONFIG_BLK_DEV_IDESCSI) && !defined(CONFIG_SCSI) |
#error "SCSI must also be selected" |
#endif |
|
typedef unsigned char byte; /* used everywhere */ |
|
/* |
* Probably not wise to fiddle with these |
*/ |
#define ERROR_MAX 8 /* Max read/write errors per sector */ |
#define ERROR_RESET 3 /* Reset controller every 4th retry */ |
#define ERROR_RECAL 1 /* Recalibrate every 2nd retry */ |
|
/* |
* Ensure that various configuration flags have compatible settings |
*/ |
#ifdef REALLY_SLOW_IO |
#undef REALLY_FAST_IO |
#endif |
|
/* |
* Definitions for accessing IDE controller registers |
*/ |
|
#define HWIF(drive) ((ide_hwif_t *)((drive)->hwif)) |
#define HWGROUP(drive) ((ide_hwgroup_t *)(HWIF(drive)->hwgroup)) |
|
#define IDE_DATA_OFFSET (0) |
#define IDE_ERROR_OFFSET (1) |
#define IDE_NSECTOR_OFFSET (2) |
#define IDE_SECTOR_OFFSET (3) |
#define IDE_LCYL_OFFSET (4) |
#define IDE_HCYL_OFFSET (5) |
#define IDE_SELECT_OFFSET (6) |
#define IDE_STATUS_OFFSET (7) |
#define IDE_FEATURE_OFFSET IDE_ERROR_OFFSET |
#define IDE_COMMAND_OFFSET IDE_STATUS_OFFSET |
|
#define IDE_DATA_REG (HWIF(drive)->io.io_base+(IDE_DATA_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_ERROR_REG (HWIF(drive)->io.io_base+(IDE_ERROR_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_NSECTOR_REG (HWIF(drive)->io.io_base+(IDE_NSECTOR_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_SECTOR_REG (HWIF(drive)->io.io_base+(IDE_SECTOR_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_LCYL_REG (HWIF(drive)->io.io_base+(IDE_LCYL_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_HCYL_REG (HWIF(drive)->io.io_base+(IDE_HCYL_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_SELECT_REG (HWIF(drive)->io.io_base+(IDE_SELECT_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_STATUS_REG (HWIF(drive)->io.io_base+(IDE_STATUS_OFFSET<<HWIF(drive)->io.io_shift)) |
#define IDE_CONTROL_REG (HWIF(drive)->io.ctl_port) |
#define IDE_FEATURE_REG IDE_ERROR_REG |
#define IDE_COMMAND_REG IDE_STATUS_REG |
#define IDE_ALTSTATUS_REG IDE_CONTROL_REG |
#define IDE_IREASON_REG IDE_NSECTOR_REG |
#define IDE_BCOUNTL_REG IDE_LCYL_REG |
#define IDE_BCOUNTH_REG IDE_HCYL_REG |
|
#ifdef REALLY_FAST_IO |
#define OUT_BYTE(b,p) outb((b),(p)) |
#define IN_BYTE(p) (byte)inb(p) |
#else |
#define OUT_BYTE(b,p) outb_p((b),(p)) |
#define IN_BYTE(p) (byte)inb_p(p) |
#endif /* REALLY_FAST_IO */ |
|
#define GET_ERR() IN_BYTE(IDE_ERROR_REG) |
#define GET_STAT() IN_BYTE(IDE_STATUS_REG) |
#define OK_STAT(stat,good,bad) (((stat)&((good)|(bad)))==(good)) |
#define BAD_R_STAT (BUSY_STAT | ERR_STAT) |
#define BAD_W_STAT (BAD_R_STAT | WRERR_STAT) |
#define BAD_STAT (BAD_R_STAT | DRQ_STAT) |
#define DRIVE_READY (READY_STAT | SEEK_STAT) |
#define DATA_READY (DRQ_STAT) |
|
/* |
* Some more useful definitions |
*/ |
#define IDE_MAJOR_NAME "ide" /* the same for all i/f; see also genhd.c */ |
#define MAJOR_NAME IDE_MAJOR_NAME |
#define PARTN_BITS 6 /* number of minor dev bits for partitions */ |
#define PARTN_MASK ((1<<PARTN_BITS)-1) /* a useful bit mask */ |
#define MAX_DRIVES 2 /* per interface; 2 assumed by lots of code */ |
#ifndef MAX_HWIFS |
#define MAX_HWIFS 4 /* an arbitrary, but realistic limit */ |
#endif |
#define SECTOR_WORDS (512 / 4) /* number of 32bit words per sector */ |
|
/* |
* Timeouts for various operations: |
*/ |
#define WAIT_DRQ (5*HZ/100) /* 50msec - spec allows up to 20ms */ |
#ifdef CONFIG_APM |
#define WAIT_READY (5*HZ) /* 5sec - some laptops are very slow */ |
#else |
#define WAIT_READY (3*HZ/100) /* 30msec - should be instantaneous */ |
#endif /* CONFIG_APM */ |
#define WAIT_PIDENTIFY (1*HZ) /* 1sec - should be less than 3ms (?) */ |
#define WAIT_WORSTCASE (30*HZ) /* 30sec - worst case when spinning up */ |
#define WAIT_CMD (10*HZ) /* 10sec - maximum wait for an IRQ to happen */ |
|
#if defined(CONFIG_BLK_DEV_HT6560B) || defined(CONFIG_BLK_DEV_PROMISE) |
#define SELECT_DRIVE(hwif,drive) \ |
{ \ |
if (hwif->selectproc) \ |
hwif->selectproc(drive); \ |
else \ |
OUT_BYTE((drive)->select.all, hwif->io_base + (IDE_SELECT_OFFSET << hwif->io.io_shift)); \ |
} |
#else |
#define SELECT_DRIVE(hwif,drive) OUT_BYTE((drive)->select.all, hwif->io.io_base + (IDE_SELECT_OFFSET << hwif->io.io_shift)); |
#endif /* CONFIG_BLK_DEV_HT6560B || CONFIG_BLK_DEV_PROMISE */ |
|
#ifdef CONFIG_BLK_DEV_IDETAPE |
#include "ide-tape.h" |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
|
#ifdef CONFIG_BLK_DEV_IDECD |
|
struct atapi_request_sense { |
unsigned char error_code : 7; |
unsigned char valid : 1; |
byte reserved1; |
unsigned char sense_key : 4; |
unsigned char reserved2 : 1; |
unsigned char ili : 1; |
unsigned char reserved3 : 2; |
byte info[4]; |
byte sense_len; |
byte command_info[4]; |
byte asc; |
byte ascq; |
byte fru; |
byte sense_key_specific[3]; |
}; |
|
struct packet_command { |
char *buffer; |
int buflen; |
int stat; |
struct atapi_request_sense *sense_data; |
unsigned char c[12]; |
}; |
|
|
/* Structure of a MSF cdrom address. */ |
struct atapi_msf { |
byte reserved; |
byte minute; |
byte second; |
byte frame; |
}; |
|
|
/* Space to hold the disk TOC. */ |
|
#define MAX_TRACKS 99 |
struct atapi_toc_header { |
unsigned short toc_length; |
byte first_track; |
byte last_track; |
}; |
|
struct atapi_toc_entry { |
byte reserved1; |
unsigned control : 4; |
unsigned adr : 4; |
byte track; |
byte reserved2; |
union { |
unsigned lba; |
struct atapi_msf msf; |
} addr; |
}; |
|
struct atapi_toc { |
int last_session_lba; |
int xa_flag; |
unsigned capacity; |
struct atapi_toc_header hdr; |
struct atapi_toc_entry ent[MAX_TRACKS+1]; |
/* One extra for the leadout. */ |
}; |
|
|
/* This structure is annoyingly close to, but not identical with, |
the cdrom_subchnl structure from cdrom.h. */ |
struct atapi_cdrom_subchnl |
{ |
u_char acdsc_reserved; |
u_char acdsc_audiostatus; |
u_short acdsc_length; |
u_char acdsc_format; |
|
u_char acdsc_adr: 4; |
u_char acdsc_ctrl: 4; |
u_char acdsc_trk; |
u_char acdsc_ind; |
union { |
struct atapi_msf msf; |
int lba; |
} acdsc_absaddr; |
union { |
struct atapi_msf msf; |
int lba; |
} acdsc_reladdr; |
}; |
|
|
/* Extra per-device info for cdrom drives. */ |
struct cdrom_info { |
|
/* Buffer for table of contents. NULL if we haven't allocated |
a TOC buffer for this device yet. */ |
|
struct atapi_toc *toc; |
|
/* Sector buffer. If a read request wants only the first part |
of a cdrom block, we cache the rest of the block here, |
in the expectation that that data is going to be wanted soon. |
SECTOR_BUFFERED is the number of the first buffered sector, |
and NSECTORS_BUFFERED is the number of sectors in the buffer. |
Before the buffer is allocated, we should have |
SECTOR_BUFFER == NULL and NSECTORS_BUFFERED == 0. */ |
|
unsigned long sector_buffered; |
unsigned long nsectors_buffered; |
char *sector_buffer; |
|
/* The result of the last successful request sense command |
on this device. */ |
struct atapi_request_sense sense_data; |
|
int max_sectors; |
}; |
|
#endif /* CONFIG_BLK_DEV_IDECD */ |
|
/* |
* Now for the data we need to maintain per-drive: ide_drive_t |
*/ |
|
typedef enum {ide_disk, ide_cdrom, ide_tape, ide_floppy, ide_scsi} ide_media_t; |
|
typedef union { |
unsigned all : 8; /* all of the bits together */ |
struct { |
unsigned set_geometry : 1; /* respecify drive geometry */ |
unsigned recalibrate : 1; /* seek to cyl 0 */ |
unsigned set_multmode : 1; /* set multmode count */ |
unsigned set_tune : 1; /* tune interface for drive */ |
unsigned mc : 1; /* acknowledge media change */ |
unsigned reserved : 3; /* unused */ |
} b; |
} special_t; |
|
typedef union { |
unsigned all : 8; /* all of the bits together */ |
struct { |
unsigned head : 4; /* always zeros here */ |
unsigned unit : 1; /* drive select number, 0 or 1 */ |
unsigned bit5 : 1; /* always 1 */ |
unsigned lba : 1; /* using LBA instead of CHS */ |
unsigned bit7 : 1; /* always 1 */ |
} b; |
} select_t; |
|
typedef struct ide_drive_s { |
special_t special; /* special action flags */ |
unsigned present : 1; /* drive is physically present */ |
unsigned noprobe : 1; /* from: hdx=noprobe */ |
unsigned keep_settings : 1; /* restore settings after drive reset */ |
unsigned busy : 1; /* currently doing revalidate_disk() */ |
unsigned removable : 1; /* 1 if need to do check_media_change */ |
unsigned using_dma : 1; /* disk is using dma for read/write */ |
unsigned forced_geom : 1; /* 1 if hdx=c,h,s was given at boot */ |
unsigned unmask : 1; /* flag: okay to unmask other irqs */ |
unsigned no_unmask : 1; /* disallow setting unmask bit */ |
unsigned no_io_32bit : 1; /* disallow enabling 32bit I/O */ |
unsigned nobios : 1; /* flag: do not probe bios for drive */ |
unsigned slow : 1; /* flag: slow data port */ |
unsigned autotune : 2; /* 1=autotune, 2=noautotune, 0=default */ |
#if FAKE_FDISK_FOR_EZDRIVE |
unsigned remap_0_to_1 : 1; /* flag: partitioned with ezdrive */ |
#endif /* FAKE_FDISK_FOR_EZDRIVE */ |
unsigned no_geom : 1; /* flag: do not set geometry */ |
ide_media_t media; /* disk, cdrom, tape, floppy */ |
select_t select; /* basic drive/head select reg value */ |
byte ctl; /* "normal" value for IDE_CONTROL_REG */ |
byte ready_stat; /* min status value for drive ready */ |
byte mult_count; /* current multiple sector setting */ |
byte mult_req; /* requested multiple sector setting */ |
byte tune_req; /* requested drive tuning setting */ |
byte io_32bit; /* 0=16-bit, 1=32-bit, 2/3=32bit+sync */ |
byte bad_wstat; /* used for ignoring WRERR_STAT */ |
byte sect0; /* offset of first sector for DM6:DDO */ |
byte usage; /* current "open()" count for drive */ |
byte head; /* "real" number of heads */ |
byte sect; /* "real" sectors per track */ |
byte bios_head; /* BIOS/fdisk/LILO number of heads */ |
byte bios_sect; /* BIOS/fdisk/LILO sectors per track */ |
unsigned short bios_cyl; /* BIOS/fdisk/LILO number of cyls */ |
unsigned short cyl; /* "real" number of cyls */ |
void *hwif; /* actually (ide_hwif_t *) */ |
struct wait_queue *wqueue; /* used to wait for drive in open() */ |
struct hd_driveid *id; /* drive model identification info */ |
struct hd_struct *part; /* drive partition table */ |
char name[4]; /* drive name, such as "hda" */ |
#ifdef CONFIG_BLK_DEV_IDECD |
struct cdrom_info cdrom_info; /* for ide-cd.c */ |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
idetape_tape_t tape; /* for ide-tape.c */ |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
void *floppy; /* for ide-floppy.c */ |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
#ifdef CONFIG_BLK_DEV_IDESCSI |
void *scsi; /* for ide-scsi.c */ |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
byte ide_scsi; /* use ide-scsi driver */ |
} ide_drive_t; |
|
typedef struct hwreg_s { |
unsigned long io_base; |
unsigned long io_shift; /* 0 = normal, 4 = ICS ide */ |
unsigned long ctl_port; |
} hwreg_t; |
/* |
* An ide_dmaproc_t() initiates/aborts DMA read/write operations on a drive. |
* |
* The caller is assumed to have selected the drive and programmed the drive's |
* sector address using CHS or LBA. All that remains is to prepare for DMA |
* and then issue the actual read/write DMA/PIO command to the drive. |
* |
* Returns 0 if all went well. |
* Returns 1 if DMA read/write could not be started, in which case the caller |
* should either try again later, or revert to PIO for the current request. |
*/ |
typedef enum { ide_dma_read = 0, ide_dma_write = 1, |
ide_dma_abort = 2, ide_dma_check = 3, |
ide_dma_status_bad = 4, ide_dma_transferred = 5, |
ide_dma_begin = 6 } |
ide_dma_action_t; |
|
typedef int (ide_dmaproc_t)(ide_dma_action_t, ide_drive_t *); |
|
|
/* |
* An ide_tuneproc_t() is used to set the speed of an IDE interface |
* to a particular PIO mode. The "byte" parameter is used |
* to select the PIO mode by number (0,1,2,3,4,5), and a value of 255 |
* indicates that the interface driver should "auto-tune" the PIO mode |
* according to the drive capabilities in drive->id; |
* |
* Not all interface types support tuning, and not all of those |
* support all possible PIO settings. They may silently ignore |
* or round values as they see fit. |
*/ |
typedef void (ide_tuneproc_t)(ide_drive_t *, byte); |
|
/* |
* This is used to provide HT6560B & PROMISE interface support. |
*/ |
typedef void (ide_selectproc_t) (ide_drive_t *); |
|
/* |
* hwif_chipset_t is used to keep track of the specific hardware |
* chipset used by each IDE interface, if known. |
*/ |
typedef enum { ide_unknown, ide_generic, ide_triton, |
ide_cmd640, ide_dtc2278, ide_ali14xx, |
ide_qd6580, ide_umc8672, ide_ht6560b, |
ide_promise, ide_promise_udma, ide_dmaics } |
hwif_chipset_t; |
|
typedef struct hwif_s { |
struct hwif_s *next; /* for linked-list in ide_hwgroup_t */ |
void *hwgroup; /* actually (ide_hwgroup_t *) */ |
hwreg_t io; |
ide_drive_t drives[MAX_DRIVES]; /* drive info */ |
struct gendisk *gd; /* gendisk structure */ |
ide_tuneproc_t *tuneproc; /* routine to tune PIO mode for drives */ |
#if defined(CONFIG_BLK_DEV_HT6560B) || defined(CONFIG_BLK_DEV_PROMISE) |
ide_selectproc_t *selectproc; /* tweaks hardware to select drive */ |
#endif |
ide_dmaproc_t *dmaproc; /* dma read/write/abort routine */ |
unsigned long *dmatable; /* dma physical region descriptor table */ |
unsigned short dma_base; /* base addr for dma ports (triton) */ |
byte irq; /* our irq number */ |
byte major; /* our major number */ |
char name[5]; /* name of interface, eg. "ide0" */ |
byte index; /* 0 for ide0; 1 for ide1; ... */ |
hwif_chipset_t chipset; /* sub-module for tuning.. */ |
unsigned noprobe : 1; /* don't probe for this interface */ |
unsigned present : 1; /* this interface exists */ |
unsigned serialized : 1; /* serialized operation with mate hwif */ |
unsigned sharing_irq: 1; /* 1 = sharing irq with another hwif */ |
#ifdef CONFIG_BLK_DEV_PROMISE |
unsigned is_promise2: 1; /* 2nd i/f on promise DC4030 */ |
#endif /* CONFIG_BLK_DEV_PROMISE */ |
#if (DISK_RECOVERY_TIME > 0) |
unsigned long last_time; /* time when previous rq was done */ |
#endif |
#ifdef CONFIG_BLK_DEV_IDECD |
struct request request_sense_request; /* from ide-cd.c */ |
struct packet_command request_sense_pc; /* from ide-cd.c */ |
#endif /* CONFIG_BLK_DEV_IDECD */ |
#ifdef CONFIG_BLK_DEV_IDETAPE |
ide_drive_t *tape_drive; /* Pointer to the tape on this interface */ |
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
} ide_hwif_t; |
|
/* |
* internal ide interrupt handler type |
*/ |
typedef void (ide_handler_t)(ide_drive_t *); |
|
typedef struct hwgroup_s { |
ide_handler_t *handler;/* irq handler, if active */ |
ide_drive_t *drive; /* current drive */ |
ide_hwif_t *hwif; /* ptr to current hwif in linked-list */ |
ide_hwif_t *next_hwif; /* next selected hwif (for tape) */ |
struct request *rq; /* current request */ |
struct timer_list timer; /* failsafe timer */ |
struct request wrq; /* local copy of current write rq */ |
unsigned long poll_timeout; /* timeout value during long polls */ |
int active; /* set when servicing requests */ |
} ide_hwgroup_t; |
|
/* |
* ide_hwifs[] is the master data structure used to keep track |
* of just about everything in ide.c. Whenever possible, routines |
* should be using pointers to a drive (ide_drive_t *) or |
* pointers to a hwif (ide_hwif_t *), rather than indexing this |
* structure directly (the allocation/layout may change!). |
* |
*/ |
#ifndef _IDE_C |
extern ide_hwif_t ide_hwifs[]; /* master data repository */ |
#endif |
|
/* |
* One final include file, which references some of the data/defns from above |
*/ |
#define IDE_DRIVER /* "parameter" for blk.h */ |
#include <linux/blk.h> |
|
#if (DISK_RECOVERY_TIME > 0) |
void ide_set_recovery_timer (ide_hwif_t *); |
#define SET_RECOVERY_TIMER(drive) ide_set_recovery_timer (drive) |
#else |
#define SET_RECOVERY_TIMER(drive) |
#endif |
|
/* |
* This is used for (nearly) all data transfers from the IDE interface |
*/ |
void ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount); |
|
/* |
* This is used for (nearly) all data transfers to the IDE interface |
*/ |
void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount); |
|
/* |
* This is used for (nearly) all ATAPI data transfers from/to the IDE interface |
*/ |
void atapi_input_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount); |
void atapi_output_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount); |
|
/* |
* This is used on exit from the driver, to designate the next irq handler |
* and also to start the safety timer. |
*/ |
void ide_set_handler (ide_drive_t *drive, ide_handler_t *handler, unsigned int timeout); |
|
/* |
* Error reporting, in human readable form (luxurious, but a memory hog). |
*/ |
byte ide_dump_status (ide_drive_t *drive, const char *msg, byte stat); |
|
/* |
* ide_error() takes action based on the error returned by the controller. |
* The calling function must return afterwards, to restart the request. |
*/ |
void ide_error (ide_drive_t *drive, const char *msg, byte stat); |
|
/* |
* ide_fixstring() cleans up and (optionally) byte-swaps a text string, |
* removing leading/trailing blanks and compressing internal blanks. |
* It is primarily used to tidy up the model name/number fields as |
* returned by the WIN_[P]IDENTIFY commands. |
*/ |
void ide_fixstring (byte *s, const int bytecount, const int byteswap); |
|
/* |
* This routine busy-waits for the drive status to be not "busy". |
* It then checks the status for all of the "good" bits and none |
* of the "bad" bits, and if all is okay it returns 0. All other |
* cases return 1 after invoking ide_error() -- caller should return. |
* |
*/ |
int ide_wait_stat (ide_drive_t *drive, byte good, byte bad, unsigned long timeout); |
|
/* |
* This routine is called from the partition-table code in genhd.c |
* to "convert" a drive to a logical geometry with fewer than 1024 cyls. |
* |
* The second parameter, "xparm", determines exactly how the translation |
* will be handled: |
* 0 = convert to CHS with fewer than 1024 cyls |
* using the same method as Ontrack DiskManager. |
* 1 = same as "0", plus offset everything by 63 sectors. |
* -1 = similar to "0", plus redirect sector 0 to sector 1. |
* >1 = convert to a CHS geometry with "xparm" heads. |
* |
* Returns 0 if the translation was not possible, if the device was not |
* an IDE disk drive, or if a geometry was "forced" on the commandline. |
* Returns 1 if the geometry translation was successful. |
*/ |
int ide_xlate_1024 (kdev_t, int, const char *); |
|
/* |
* Start a reset operation for an IDE interface. |
* The caller should return immediately after invoking this. |
*/ |
void ide_do_reset (ide_drive_t *); |
|
/* |
* This function is intended to be used prior to invoking ide_do_drive_cmd(). |
*/ |
void ide_init_drive_cmd (struct request *rq); |
|
/* |
* "action" parameter type for ide_do_drive_cmd() below. |
*/ |
typedef enum |
{ide_wait, /* insert rq at end of list, and wait for it */ |
ide_next, /* insert rq immediately after current request */ |
ide_preempt, /* insert rq in front of current request */ |
ide_end} /* insert rq at end of list, but don't wait for it */ |
ide_action_t; |
|
/* |
* This function issues a special IDE device request |
* onto the request queue. |
* |
* If action is ide_wait, then then rq is queued at the end of |
* the request queue, and the function sleeps until it has been |
* processed. This is for use when invoked from an ioctl handler. |
* |
* If action is ide_preempt, then the rq is queued at the head of |
* the request queue, displacing the currently-being-processed |
* request and this function returns immediately without waiting |
* for the new rq to be completed. This is VERY DANGEROUS, and is |
* intended for careful use by the ATAPI tape/cdrom driver code. |
* |
* If action is ide_next, then the rq is queued immediately after |
* the currently-being-processed-request (if any), and the function |
* returns without waiting for the new rq to be completed. As above, |
* This is VERY DANGEROUS, and is intended for careful use by the |
* ATAPI tape/cdrom driver code. |
* |
* If action is ide_end, then the rq is queued at the end of the |
* request queue, and the function returns immediately without waiting |
* for the new rq to be completed. This is again intended for careful |
* use by the ATAPI tape/cdrom driver code. (Currently used by ide-tape.c, |
* when operating in the pipelined operation mode). |
*/ |
int ide_do_drive_cmd (ide_drive_t *drive, struct request *rq, ide_action_t action); |
|
/* |
* Clean up after success/failure of an explicit drive cmd. |
* stat/err are used only when (HWGROUP(drive)->rq->cmd == IDE_DRIVE_CMD). |
*/ |
void ide_end_drive_cmd (ide_drive_t *drive, byte stat, byte err); |
|
/* |
* ide_system_bus_speed() returns what we think is the system VESA/PCI |
* bus speed (in Mhz). This is used for calculating interface PIO timings. |
* The default is 40 for known PCI systems, 50 otherwise. |
* The "idebus=xx" parameter can be used to override this value. |
*/ |
int ide_system_bus_speed (void); |
|
/* |
* ide_multwrite() transfers a block of up to mcount sectors of data |
* to a drive as part of a disk multwrite operation. |
*/ |
void ide_multwrite (ide_drive_t *drive, unsigned int mcount); |
|
#ifdef CONFIG_BLK_DEV_IDECD |
/* |
* These are routines in ide-cd.c invoked from ide.c |
*/ |
void ide_do_rw_cdrom (ide_drive_t *, unsigned long); |
int ide_cdrom_ioctl (ide_drive_t *, struct inode *, struct file *, unsigned int, unsigned long); |
int ide_cdrom_check_media_change (ide_drive_t *); |
int ide_cdrom_open (struct inode *, struct file *, ide_drive_t *); |
void ide_cdrom_release (struct inode *, struct file *, ide_drive_t *); |
void ide_cdrom_setup (ide_drive_t *); |
#endif /* CONFIG_BLK_DEV_IDECD */ |
|
#ifdef CONFIG_BLK_DEV_IDETAPE |
|
/* |
* Functions in ide-tape.c which are invoked from ide.c: |
*/ |
|
/* |
* idetape_identify_device is called during device probing stage to |
* probe for an ide atapi tape drive and to initialize global variables |
* in ide-tape.c which provide the link between the character device |
* and the corresponding block device. |
* |
* Returns 1 if an ide tape was detected and is supported. |
* Returns 0 otherwise. |
*/ |
|
int idetape_identify_device (ide_drive_t *drive,struct hd_driveid *id); |
|
/* |
* idetape_setup is called a bit later than idetape_identify_device, |
* during the search for disk partitions, to initialize various tape |
* state variables in ide_drive_t *drive. |
*/ |
|
void idetape_setup (ide_drive_t *drive); |
|
/* |
* idetape_do_request is our request function. It is called by ide.c |
* to process a new request. |
*/ |
|
void idetape_do_request (ide_drive_t *drive, struct request *rq, unsigned long block); |
|
/* |
* idetape_end_request is used to finish servicing a request, and to |
* insert a pending pipeline request into the main device queue. |
*/ |
|
void idetape_end_request (byte uptodate, ide_hwgroup_t *hwgroup); |
|
/* |
* Block device interface functions. |
*/ |
|
int idetape_blkdev_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg); |
int idetape_blkdev_open (struct inode *inode, struct file *filp, ide_drive_t *drive); |
void idetape_blkdev_release (struct inode *inode, struct file *filp, ide_drive_t *drive); |
|
/* |
* idetape_register_chrdev initializes the character device interface to |
* the ide tape drive. |
*/ |
|
void idetape_register_chrdev (void); |
|
#endif /* CONFIG_BLK_DEV_IDETAPE */ |
|
#ifdef CONFIG_BLK_DEV_IDEFLOPPY |
int idefloppy_identify_device (ide_drive_t *drive,struct hd_driveid *id); |
void idefloppy_setup (ide_drive_t *drive); |
void idefloppy_do_request (ide_drive_t *drive, struct request *rq, unsigned long block); |
void idefloppy_end_request (byte uptodate, ide_hwgroup_t *hwgroup); |
int idefloppy_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg); |
int idefloppy_open (struct inode *inode, struct file *filp, ide_drive_t *drive); |
void idefloppy_release (struct inode *inode, struct file *filp, ide_drive_t *drive); |
int idefloppy_media_change (ide_drive_t *drive); |
unsigned long idefloppy_capacity (ide_drive_t *drive); |
#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ |
|
#ifdef CONFIG_BLK_DEV_IDESCSI |
void idescsi_setup (ide_drive_t *drive); |
void idescsi_do_request (ide_drive_t *drive, struct request *rq, unsigned long block); |
void idescsi_end_request (byte uptodate, ide_hwgroup_t *hwgroup); |
int idescsi_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); |
int idescsi_open (struct inode *inode, struct file *filp, ide_drive_t *drive); |
void idescsi_ide_release (struct inode *inode, struct file *filp, ide_drive_t *drive); |
#endif /* CONFIG_BLK_DEV_IDESCSI */ |
|
#ifdef CONFIG_BLK_DEV_TRITON |
void ide_init_triton (byte, byte); |
void ide_init_promise (byte bus, byte fn, ide_hwif_t *hwif0, ide_hwif_t *hwif1, unsigned short dma); |
#endif /* CONFIG_BLK_DEV_TRITON */ |
|
#ifdef CONFIG_BLK_DEV_IDE_CARDS |
extern int ide_register_port (int io_base, int ctrl_base, int stepping, int irq); |
#ifdef CONFIG_BLK_DEV_IDE_ICSIDE |
extern int icside_init (void); |
#endif |
#ifdef CONFIG_BLK_DEV_IDE_RAPIDE |
extern int rapide_init (void); |
#endif |
#endif |
/ll_rw_blk.c
0,0 → 1,699
/* |
* linux/drivers/block/ll_rw_blk.c |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
* Copyright (C) 1994, Karl Keyte: Added support for disk statistics |
*/ |
|
/* |
* This handles all read/write requests to block devices |
*/ |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/kernel_stat.h> |
#include <linux/errno.h> |
#include <linux/string.h> |
#include <linux/config.h> |
#include <linux/locks.h> |
#include <linux/mm.h> |
|
#include <asm/system.h> |
#include <asm/io.h> |
#include "blk.h" |
|
/* |
* The request-struct contains all necessary data |
* to load a nr of sectors into memory |
*/ |
static struct request all_requests[NR_REQUEST]; |
|
/* |
* The "disk" task queue is used to start the actual requests |
* after a plug |
*/ |
DECLARE_TASK_QUEUE(tq_disk); |
|
/* |
* used to wait on when there are no free requests |
*/ |
struct wait_queue * wait_for_request; |
|
/* This specifies how many sectors to read ahead on the disk. */ |
|
int read_ahead[MAX_BLKDEV]; |
|
/* blk_dev_struct is: |
* *request_fn |
* *current_request |
*/ |
struct blk_dev_struct blk_dev[MAX_BLKDEV]; /* initialized by blk_dev_init() */ |
|
/* |
* blk_size contains the size of all block-devices in units of 1024 byte |
* sectors: |
* |
* blk_size[MAJOR][MINOR] |
* |
* if (!blk_size[MAJOR]) then no minor size checking is done. |
*/ |
int * blk_size[MAX_BLKDEV]; |
|
/* |
* blksize_size contains the size of all block-devices: |
* |
* blksize_size[MAJOR][MINOR] |
* |
* if (!blksize_size[MAJOR]) then 1024 bytes is assumed. |
*/ |
int * blksize_size[MAX_BLKDEV]; |
|
/* |
* hardsect_size contains the size of the hardware sector of a device. |
* |
* hardsect_size[MAJOR][MINOR] |
* |
* if (!hardsect_size[MAJOR]) |
* then 512 bytes is assumed. |
* else |
* sector_size is hardsect_size[MAJOR][MINOR] |
* This is currently set by some scsi device and read by the msdos fs driver |
* This might be a some uses later. |
*/ |
int * hardsect_size[MAX_BLKDEV]; |
|
/* |
* remove the plug and let it rip.. |
*/ |
void unplug_device(void * data) |
{ |
struct blk_dev_struct * dev = (struct blk_dev_struct *) data; |
unsigned long flags; |
|
save_flags_cli(flags); |
if (dev->current_request == &dev->plug) { |
struct request * next = dev->plug.next; |
dev->current_request = next; |
if (next) { |
dev->plug.next = NULL; |
(dev->request_fn)(); |
} |
} |
restore_flags(flags); |
} |
|
/* |
* "plug" the device if there are no outstanding requests: this will |
* force the transfer to start only after we have put all the requests |
* on the list. |
* |
* This is called with interrupts off and no requests on the queue. |
*/ |
static inline void plug_device(struct blk_dev_struct * dev) |
{ |
dev->current_request = &dev->plug; |
queue_task_irq_off(&dev->plug_tq, &tq_disk); |
} |
|
/* |
* look for a free request in the first N entries. |
* NOTE: interrupts must be disabled on the way in, and will still |
* be disabled on the way out. |
*/ |
static inline struct request * get_request(int n, kdev_t dev) |
{ |
static struct request *prev_found = NULL, *prev_limit = NULL; |
register struct request *req, *limit; |
|
if (n <= 0) |
panic("get_request(%d): impossible!\n", n); |
|
limit = all_requests + n; |
if (limit != prev_limit) { |
prev_limit = limit; |
prev_found = all_requests; |
} |
req = prev_found; |
for (;;) { |
req = ((req > all_requests) ? req : limit) - 1; |
if (req->rq_status == RQ_INACTIVE) |
break; |
if (req == prev_found) |
return NULL; |
} |
prev_found = req; |
req->rq_status = RQ_ACTIVE; |
req->rq_dev = dev; |
return req; |
} |
|
/* |
* wait until a free request in the first N entries is available. |
*/ |
static struct request * __get_request_wait(int n, kdev_t dev) |
{ |
register struct request *req; |
struct wait_queue wait = { current, NULL }; |
|
add_wait_queue(&wait_for_request, &wait); |
for (;;) { |
current->state = TASK_UNINTERRUPTIBLE; |
cli(); |
req = get_request(n, dev); |
sti(); |
if (req) |
break; |
run_task_queue(&tq_disk); |
schedule(); |
} |
remove_wait_queue(&wait_for_request, &wait); |
current->state = TASK_RUNNING; |
return req; |
} |
|
static inline struct request * get_request_wait(int n, kdev_t dev) |
{ |
register struct request *req; |
|
cli(); |
req = get_request(n, dev); |
sti(); |
if (req) |
return req; |
return __get_request_wait(n, dev); |
} |
|
/* RO fail safe mechanism */ |
|
static long ro_bits[MAX_BLKDEV][8]; |
|
int is_read_only(kdev_t dev) |
{ |
int minor,major; |
|
major = MAJOR(dev); |
minor = MINOR(dev); |
if (major < 0 || major >= MAX_BLKDEV) return 0; |
return ro_bits[major][minor >> 5] & (1 << (minor & 31)); |
} |
|
void set_device_ro(kdev_t dev,int flag) |
{ |
int minor,major; |
|
major = MAJOR(dev); |
minor = MINOR(dev); |
if (major < 0 || major >= MAX_BLKDEV) return; |
if (flag) ro_bits[major][minor >> 5] |= 1 << (minor & 31); |
else ro_bits[major][minor >> 5] &= ~(1 << (minor & 31)); |
} |
|
static inline void drive_stat_acct(int cmd, unsigned long nr_sectors, |
short disk_index) |
{ |
kstat.dk_drive[disk_index]++; |
if (cmd == READ) { |
kstat.dk_drive_rio[disk_index]++; |
kstat.dk_drive_rblk[disk_index] += nr_sectors; |
} else if (cmd == WRITE) { |
kstat.dk_drive_wio[disk_index]++; |
kstat.dk_drive_wblk[disk_index] += nr_sectors; |
} else |
printk(KERN_ERR "drive_stat_acct: cmd not R/W?\n"); |
} |
|
/* |
* add-request adds a request to the linked list. |
* It disables interrupts so that it can muck with the |
* request-lists in peace. |
* |
* By this point, req->cmd is always either READ/WRITE, never READA/WRITEA, |
* which is important for drive_stat_acct() above. |
*/ |
|
void add_request(struct blk_dev_struct * dev, struct request * req) |
{ |
struct request * tmp; |
short disk_index; |
|
switch (MAJOR(req->rq_dev)) { |
case SCSI_DISK_MAJOR: |
disk_index = (MINOR(req->rq_dev) & 0x0070) >> 4; |
if (disk_index < 4) |
drive_stat_acct(req->cmd, req->nr_sectors, disk_index); |
break; |
case IDE0_MAJOR: /* same as HD_MAJOR */ |
case XT_DISK_MAJOR: |
disk_index = (MINOR(req->rq_dev) & 0x0040) >> 6; |
drive_stat_acct(req->cmd, req->nr_sectors, disk_index); |
break; |
case IDE1_MAJOR: |
disk_index = ((MINOR(req->rq_dev) & 0x0040) >> 6) + 2; |
drive_stat_acct(req->cmd, req->nr_sectors, disk_index); |
default: |
break; |
} |
|
req->next = NULL; |
cli(); |
if (req->bh) |
mark_buffer_clean(req->bh); |
if (!(tmp = dev->current_request)) { |
dev->current_request = req; |
(dev->request_fn)(); |
sti(); |
return; |
} |
for ( ; tmp->next ; tmp = tmp->next) { |
if ((IN_ORDER(tmp,req) || |
!IN_ORDER(tmp,tmp->next)) && |
IN_ORDER(req,tmp->next)) |
break; |
} |
req->next = tmp->next; |
tmp->next = req; |
|
/* for SCSI devices, call request_fn unconditionally */ |
if (scsi_blk_major(MAJOR(req->rq_dev))) |
(dev->request_fn)(); |
|
sti(); |
} |
|
#define MAX_SECTORS 244 |
|
static inline void attempt_merge (struct request *req) |
{ |
struct request *next = req->next; |
|
if (!next) |
return; |
if (req->sector + req->nr_sectors != next->sector) |
return; |
if (next->sem || req->cmd != next->cmd || req->rq_dev != next->rq_dev || req->nr_sectors + next->nr_sectors >= MAX_SECTORS) |
return; |
#if 0 |
printk ("%s: merge %ld, %ld + %ld == %ld\n", kdevname(req->rq_dev), req->sector, req->nr_sectors, next->nr_sectors, req->nr_sectors + next->nr_sectors); |
#endif |
req->bhtail->b_reqnext = next->bh; |
req->bhtail = next->bhtail; |
req->nr_sectors += next->nr_sectors; |
next->rq_status = RQ_INACTIVE; |
req->next = next->next; |
wake_up (&wait_for_request); |
} |
|
void make_request(int major,int rw, struct buffer_head * bh) |
{ |
unsigned int sector, count; |
struct request * req; |
int rw_ahead, max_req; |
|
count = bh->b_size >> 9; |
sector = bh->b_rsector; |
|
/* Uhhuh.. Nasty dead-lock possible here.. */ |
if (buffer_locked(bh)) { |
#if 0 |
printk("make_request(): buffer already locked\n"); |
#endif |
return; |
} |
/* Maybe the above fixes it, and maybe it doesn't boot. Life is interesting */ |
|
lock_buffer(bh); |
|
if (blk_size[major]) |
if (blk_size[major][MINOR(bh->b_rdev)] < (sector + count)>>1) { |
bh->b_state &= (1 << BH_Lock) | (1 << BH_FreeOnIO); |
/* This may well happen - the kernel calls bread() |
without checking the size of the device, e.g., |
when mounting a device. */ |
printk(KERN_INFO |
"attempt to access beyond end of device\n"); |
printk(KERN_INFO "%s: rw=%d, want=%d, limit=%d\n", |
kdevname(bh->b_rdev), rw, |
(sector + count)>>1, |
blk_size[major][MINOR(bh->b_rdev)]); |
unlock_buffer(bh); |
return; |
} |
|
rw_ahead = 0; /* normal case; gets changed below for READA/WRITEA */ |
switch (rw) { |
case READA: |
rw_ahead = 1; |
rw = READ; /* drop into READ */ |
case READ: |
if (buffer_uptodate(bh)) { |
#if 0 |
printk ("make_request(): buffer uptodate for READ\n"); |
#endif |
unlock_buffer(bh); /* Hmmph! Already have it */ |
return; |
} |
kstat.pgpgin++; |
max_req = NR_REQUEST; /* reads take precedence */ |
break; |
case WRITEA: |
rw_ahead = 1; |
rw = WRITE; /* drop into WRITE */ |
case WRITE: |
if (!buffer_dirty(bh)) { |
#if 0 |
printk ("make_request(): buffer clean for WRITE\n"); |
#endif |
unlock_buffer(bh); /* Hmmph! Nothing to write */ |
return; |
} |
/* We don't allow the write-requests to fill up the |
* queue completely: we want some room for reads, |
* as they take precedence. The last third of the |
* requests are only for reads. |
*/ |
kstat.pgpgout++; |
max_req = (NR_REQUEST * 2) / 3; |
break; |
default: |
printk(KERN_ERR "make_request: bad block dev cmd," |
" must be R/W/RA/WA\n"); |
unlock_buffer(bh); |
return; |
} |
|
/* look for a free request. */ |
/* Loop uses two requests, 1 for loop and 1 for the real device. |
* Cut max_req in half to avoid running out and deadlocking. */ |
if (major == LOOP_MAJOR) |
max_req >>= 1; |
|
/* |
* Try to coalesce the new request with old requests |
*/ |
cli(); |
req = blk_dev[major].current_request; |
if (!req) { |
/* MD and loop can't handle plugging without deadlocking */ |
if (major != MD_MAJOR && major != LOOP_MAJOR) |
plug_device(blk_dev + major); |
} else switch (major) { |
case IDE0_MAJOR: /* same as HD_MAJOR */ |
case IDE1_MAJOR: |
case FLOPPY_MAJOR: |
case IDE2_MAJOR: |
case IDE3_MAJOR: |
case XT_DISK_MAJOR: |
/* |
* The scsi disk and cdrom drivers completely remove the request |
* from the queue when they start processing an entry. For this |
* reason it is safe to continue to add links to the top entry for |
* those devices. |
* |
* All other drivers need to jump over the first entry, as that |
* entry may be busy being processed and we thus can't change it. |
*/ |
req = req->next; |
if (!req) |
break; |
/* fall through */ |
|
case SCSI_DISK_MAJOR: |
case SCSI_CDROM_MAJOR: |
|
do { |
if (req->sem) |
continue; |
if (req->cmd != rw) |
continue; |
if (req->nr_sectors >= MAX_SECTORS) |
continue; |
if (req->rq_dev != bh->b_rdev) |
continue; |
/* Can we add it to the end of this request? */ |
if (req->sector + req->nr_sectors == sector) { |
req->bhtail->b_reqnext = bh; |
req->bhtail = bh; |
req->nr_sectors += count; |
/* Can we now merge this req with the next? */ |
attempt_merge(req); |
/* or to the beginning? */ |
} else if (req->sector - count == sector) { |
bh->b_reqnext = req->bh; |
req->bh = bh; |
req->buffer = bh->b_data; |
req->current_nr_sectors = count; |
req->sector = sector; |
req->nr_sectors += count; |
} else |
continue; |
|
mark_buffer_clean(bh); |
sti(); |
return; |
} while ((req = req->next) != NULL); |
} |
|
/* find an unused request. */ |
req = get_request(max_req, bh->b_rdev); |
sti(); |
|
/* if no request available: if rw_ahead, forget it; otherwise try again blocking.. */ |
if (!req) { |
if (rw_ahead) { |
unlock_buffer(bh); |
return; |
} |
req = __get_request_wait(max_req, bh->b_rdev); |
} |
|
/* fill up the request-info, and add it to the queue */ |
req->cmd = rw; |
req->errors = 0; |
req->sector = sector; |
req->nr_sectors = count; |
req->current_nr_sectors = count; |
req->buffer = bh->b_data; |
req->sem = NULL; |
req->bh = bh; |
req->bhtail = bh; |
req->next = NULL; |
add_request(major+blk_dev,req); |
} |
|
/* This function can be used to request a number of buffers from a block |
device. Currently the only restriction is that all buffers must belong to |
the same device */ |
|
void ll_rw_block(int rw, int nr, struct buffer_head * bh[]) |
{ |
unsigned int major; |
int correct_size; |
struct blk_dev_struct * dev; |
int i; |
|
/* Make sure that the first block contains something reasonable */ |
while (!*bh) { |
bh++; |
if (--nr <= 0) |
return; |
} |
|
dev = NULL; |
if ((major = MAJOR(bh[0]->b_dev)) < MAX_BLKDEV) |
dev = blk_dev + major; |
if (!dev || !dev->request_fn) { |
printk(KERN_ERR |
"ll_rw_block: Trying to read nonexistent block-device %s (%ld)\n", |
kdevname(bh[0]->b_dev), bh[0]->b_blocknr); |
goto sorry; |
} |
|
/* Determine correct block size for this device. */ |
correct_size = BLOCK_SIZE; |
if (blksize_size[major]) { |
i = blksize_size[major][MINOR(bh[0]->b_dev)]; |
if (i) |
correct_size = i; |
} |
|
/* Verify requested block sizes. */ |
for (i = 0; i < nr; i++) { |
if (bh[i] && bh[i]->b_size != correct_size) { |
printk(KERN_NOTICE "ll_rw_block: device %s: " |
"only %d-char blocks implemented (%lu)\n", |
kdevname(bh[0]->b_dev), |
correct_size, bh[i]->b_size); |
goto sorry; |
} |
|
/* Md remaps blocks now */ |
bh[i]->b_rdev = bh[i]->b_dev; |
bh[i]->b_rsector=bh[i]->b_blocknr*(bh[i]->b_size >> 9); |
#ifdef CONFIG_BLK_DEV_MD |
if (major==MD_MAJOR && |
md_map (MINOR(bh[i]->b_dev), &bh[i]->b_rdev, |
&bh[i]->b_rsector, bh[i]->b_size >> 9)) { |
printk (KERN_ERR |
"Bad md_map in ll_rw_block\n"); |
goto sorry; |
} |
#endif |
} |
|
if ((rw == WRITE || rw == WRITEA) && is_read_only(bh[0]->b_dev)) { |
printk(KERN_NOTICE "Can't write to read-only device %s\n", |
kdevname(bh[0]->b_dev)); |
goto sorry; |
} |
|
for (i = 0; i < nr; i++) { |
if (bh[i]) { |
set_bit(BH_Req, &bh[i]->b_state); |
#ifdef CONFIG_BLK_DEV_MD |
if (MAJOR(bh[i]->b_dev) == MD_MAJOR) { |
md_make_request(MINOR (bh[i]->b_dev), rw, bh[i]); |
continue; |
} |
#endif |
make_request(MAJOR(bh[i]->b_rdev), rw, bh[i]); |
} |
} |
return; |
|
sorry: |
for (i = 0; i < nr; i++) { |
if (bh[i]) { |
clear_bit(BH_Dirty, &bh[i]->b_state); |
clear_bit(BH_Uptodate, &bh[i]->b_state); |
} |
} |
return; |
} |
|
void ll_rw_swap_file(int rw, kdev_t dev, unsigned int *b, int nb, char *buf) |
{ |
int i, j; |
int buffersize; |
int max_req; |
unsigned long rsector; |
kdev_t rdev; |
struct request * req[8]; |
unsigned int major = MAJOR(dev); |
struct semaphore sem = MUTEX_LOCKED; |
|
if (major >= MAX_BLKDEV || !(blk_dev[major].request_fn)) { |
printk(KERN_NOTICE "ll_rw_swap_file: trying to swap to" |
" nonexistent block-device\n"); |
return; |
} |
max_req = NR_REQUEST; |
switch (rw) { |
case READ: |
break; |
case WRITE: |
max_req = (NR_REQUEST * 2) / 3; |
if (is_read_only(dev)) { |
printk(KERN_NOTICE |
"Can't swap to read-only device %s\n", |
kdevname(dev)); |
return; |
} |
break; |
default: |
panic("ll_rw_swap: bad block dev cmd, must be R/W"); |
} |
buffersize = PAGE_SIZE / nb; |
|
if (major == LOOP_MAJOR) |
max_req >>= 1; |
for (j=0, i=0; i<nb;) |
{ |
for (; j < 8 && i < nb; j++, i++, buf += buffersize) |
{ |
rdev = dev; |
rsector = b[i] * (buffersize >> 9); |
#ifdef CONFIG_BLK_DEV_MD |
if (major==MD_MAJOR && |
md_map (MINOR(dev), &rdev, |
&rsector, buffersize >> 9)) { |
printk (KERN_ERR |
"Bad md_map in ll_rw_swap_file\n"); |
return; |
} |
#endif |
|
if (j == 0) { |
req[j] = get_request_wait(max_req, rdev); |
} else { |
cli(); |
req[j] = get_request(max_req, rdev); |
sti(); |
if (req[j] == NULL) |
break; |
} |
req[j]->cmd = rw; |
req[j]->errors = 0; |
req[j]->sector = rsector; |
req[j]->nr_sectors = buffersize >> 9; |
req[j]->current_nr_sectors = buffersize >> 9; |
req[j]->buffer = buf; |
req[j]->sem = &sem; |
req[j]->bh = NULL; |
req[j]->next = NULL; |
add_request(MAJOR(rdev)+blk_dev,req[j]); |
} |
run_task_queue(&tq_disk); |
while (j > 0) { |
j--; |
down(&sem); |
} |
} |
} |
|
int blk_dev_init(void) |
{ |
struct request * req; |
struct blk_dev_struct *dev; |
|
for (dev = blk_dev + MAX_BLKDEV; dev-- != blk_dev;) { |
dev->request_fn = NULL; |
dev->current_request = NULL; |
dev->plug.rq_status = RQ_INACTIVE; |
dev->plug.cmd = -1; |
dev->plug.next = NULL; |
dev->plug_tq.routine = &unplug_device; |
dev->plug_tq.data = dev; |
} |
|
req = all_requests + NR_REQUEST; |
while (--req >= all_requests) { |
req->rq_status = RQ_INACTIVE; |
req->next = NULL; |
} |
memset(ro_bits,0,sizeof(ro_bits)); |
#ifdef CONFIG_BLK_DEV_RAM |
rd_init(); |
#endif |
#ifdef CONFIG_BLK_DEV_LOOP |
loop_init(); |
#endif |
#ifdef CONFIG_BLK_DEV_IDE |
ide_init(); /* this MUST precede hd_init */ |
#endif |
#ifdef CONFIG_BLK_DEV_HD |
hd_init(); |
#endif |
#ifdef CONFIG_BLK_DEV_XD |
xd_init(); |
#endif |
#ifdef CONFIG_BLK_DEV_FD |
floppy_init(); |
#else |
#if !defined(CONFIG_ARCH_ARC) && !defined(CONFIG_ARCH_SA110EVAL) |
outb_p(0xc, 0x3f2); |
#endif |
#endif |
#ifdef CONFIG_BLK_DEV_MD |
md_init(); |
#endif CONFIG_BLK_DEV_MD |
return 0; |
} |
/floppy.c
0,0 → 1,4276
/* |
* linux/arch/arm/drivers/block/floppy.c |
* [ was linux/drivers/block/floppy.c ] |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
* Copyright (C) 1993, 1994 Alain Knaff |
* Modifications Copyright (C) 1995 Russell King |
*/ |
/* |
* 02.12.91 - Changed to static variables to indicate need for reset |
* and recalibrate. This makes some things easier (output_byte reset |
* checking etc), and means less interrupt jumping in case of errors, |
* so the code is hopefully easier to understand. |
*/ |
|
/* |
* This file is certainly a mess. I've tried my best to get it working, |
* but I don't like programming floppies, and I have only one anyway. |
* Urgel. I should check for more errors, and do more graceful error |
* recovery. Seems there are problems with several drives. I've tried to |
* correct them. No promises. |
*/ |
|
/* |
* As with hd.c, all routines within this file can (and will) be called |
* by interrupts, so extreme caution is needed. A hardware interrupt |
* handler may not sleep, or a kernel panic will happen. Thus I cannot |
* call "floppy-on" directly, but have to set a special timer interrupt |
* etc. |
*/ |
|
/* |
* 28.02.92 - made track-buffering routines, based on the routines written |
* by entropy@wintermute.wpi.edu (Lawrence Foard). Linus. |
*/ |
|
/* |
* Automatic floppy-detection and formatting written by Werner Almesberger |
* (almesber@nessie.cs.id.ethz.ch), who also corrected some problems with |
* the floppy-change signal detection. |
*/ |
|
/* |
* 1992/7/22 -- Hennus Bergman: Added better error reporting, fixed |
* FDC data overrun bug, added some preliminary stuff for vertical |
* recording support. |
* |
* 1992/9/17: Added DMA allocation & DMA functions. -- hhb. |
* |
* TODO: Errors are still not counted properly. |
*/ |
|
/* 1992/9/20 |
* Modifications for ``Sector Shifting'' by Rob Hooft (hooft@chem.ruu.nl) |
* modeled after the freeware MS-DOS program fdformat/88 V1.8 by |
* Christoph H. Hochst\"atter. |
* I have fixed the shift values to the ones I always use. Maybe a new |
* ioctl() should be created to be able to modify them. |
* There is a bug in the driver that makes it impossible to format a |
* floppy as the first thing after bootup. |
*/ |
|
/* |
* 1993/4/29 -- Linus -- cleaned up the timer handling in the kernel, and |
* this helped the floppy driver as well. Much cleaner, and still seems to |
* work. |
*/ |
|
/* 1994/6/24 --bbroad-- added the floppy table entries and made |
* minor modifications to allow 2.88 floppies to be run. |
*/ |
|
/* 1994/7/13 -- Paul Vojta -- modified the probing code to allow three or more |
* disk types. |
*/ |
|
/* |
* 1994/8/8 -- Alain Knaff -- Switched to fdpatch driver: Support for bigger |
* format bug fixes, but unfortunately some new bugs too... |
*/ |
|
/* 1994/9/17 -- Koen Holtman -- added logging of physical floppy write |
* errors to allow safe writing by specialized programs. |
*/ |
|
/* 1995/4/24 -- Dan Fandrich -- added support for Commodore 1581 3.5" disks |
* by defining bit 1 of the "stretch" parameter to mean put sectors on the |
* opposite side of the disk, leaving the sector IDs alone (i.e. Commodore's |
* drives are "upside-down"). |
*/ |
|
/* |
* 1995/8/26 -- Andreas Busse -- added Mips support. |
*/ |
|
/* |
* 1995/8/16 -- Russell King -- added ARM support. |
*/ |
|
/* |
* 1995/10/18 -- Ralf Baechle -- Portability cleanup; move machine dependent |
* features to asm/floppy.h. |
*/ |
|
|
#define FLOPPY_SANITY_CHECK |
#undef FLOPPY_SILENT_DCL_CLEAR |
|
#define REALLY_SLOW_IO |
|
#define DEBUGT 2 |
#define DCL_DEBUG /* debug disk change line */ |
|
/* do print messages for unexpected interrupts */ |
static int print_unex=1; |
#include <linux/utsname.h> |
#include <linux/module.h> |
#include <linux/sched.h> |
#include <linux/fs.h> |
#include <linux/kernel.h> |
#include <linux/timer.h> |
#include <linux/tqueue.h> |
#define FDPATCHES |
#include <linux/fdreg.h> |
#include <linux/fd.h> |
|
#define OLDFDRAWCMD 0x020d /* send a raw command to the FDC */ |
|
struct old_floppy_raw_cmd { |
void *data; |
long length; |
|
unsigned char rate; |
unsigned char flags; |
unsigned char cmd_count; |
unsigned char cmd[9]; |
unsigned char reply_count; |
unsigned char reply[7]; |
int track; |
}; |
|
#include <linux/errno.h> |
#include <linux/malloc.h> |
#include <linux/mm.h> |
#include <linux/string.h> |
#include <linux/fcntl.h> |
#include <linux/delay.h> |
#ifndef CONFIG_ARM |
#include <linux/mc146818rtc.h> /* CMOS defines */ |
#endif |
#include <linux/ioport.h> |
#include <linux/interrupt.h> |
|
#include <asm/dma.h> |
#include <asm/irq.h> |
#include <asm/system.h> |
#include <asm/io.h> |
#include <asm/segment.h> |
|
/* the following is the mask of allowed drives. By default units 2 and |
* 3 of both floppy controllers are disabled, because switching on the |
* motor of these drives causes system hangs on some PCI computers. drive |
* 0 is the low bit (0x1), and drive 7 is the high bit (0x80). Bits are on if |
* a drive is allowed. */ |
static int FLOPPY_IRQ=IRQ_FLOPPYDISK; |
static int FLOPPY_DMA=DMA_FLOPPY; |
static int allowed_drive_mask = 0x33; |
|
static int use_virtual_dma=0; /* virtual DMA for Intel */ |
static unsigned short virtual_dma_port=0x3f0; |
void floppy_interrupt(int irq, void *dev_id, struct pt_regs * regs); |
static int set_dor(int fdc, char mask, char data); |
static inline int __get_order(unsigned long size); |
#include <asm/floppy.h> |
|
|
#define MAJOR_NR FLOPPY_MAJOR |
|
#include <linux/blk.h> |
#include <linux/cdrom.h> /* for the compatibility eject ioctl */ |
|
|
#ifndef FLOPPY_MOTOR_MASK |
#define FLOPPY_MOTOR_MASK 0xf0 |
#endif |
|
#ifndef fd_get_dma_residue |
#define fd_get_dma_residue() get_dma_residue(FLOPPY_DMA) |
#endif |
|
/* Dma Memory related stuff */ |
|
/* Pure 2^n version of get_order */ |
static inline int __get_order(unsigned long size) |
{ |
int order; |
|
size = (size-1) >> (PAGE_SHIFT-1); |
order = -1; |
do { |
size >>= 1; |
order++; |
} while (size); |
return order; |
} |
|
#ifndef fd_dma_mem_free |
#define fd_dma_mem_free(addr, size) free_pages(addr, __get_order(size)) |
#endif |
|
#ifndef fd_dma_mem_alloc |
#define fd_dma_mem_alloc(size) __get_dma_pages(GFP_KERNEL,__get_order(size)) |
#endif |
|
/* End dma memory related stuff */ |
|
static unsigned int fake_change = 0; |
static int initialising=1; |
|
static inline int TYPE(kdev_t x) { |
return (MINOR(x)>>2) & 0x1f; |
} |
static inline int DRIVE(kdev_t x) { |
return (MINOR(x)&0x03) | ((MINOR(x)&0x80) >> 5); |
} |
#define ITYPE(x) (((x)>>2) & 0x1f) |
#define TOMINOR(x) ((x & 3) | ((x & 4) << 5)) |
#define UNIT(x) ((x) & 0x03) /* drive on fdc */ |
#define FDC(x) (((x) & 0x04) >> 2) /* fdc of drive */ |
#define REVDRIVE(fdc, unit) ((unit) + ((fdc) << 2)) |
/* reverse mapping from unit and fdc to drive */ |
#define DP (&drive_params[current_drive]) |
#define DRS (&drive_state[current_drive]) |
#define DRWE (&write_errors[current_drive]) |
#define FDCS (&fdc_state[fdc]) |
#define CLEARF(x) (clear_bit(x##_BIT, &DRS->flags)) |
#define SETF(x) (set_bit(x##_BIT, &DRS->flags)) |
#define TESTF(x) (test_bit(x##_BIT, &DRS->flags)) |
|
#define UDP (&drive_params[drive]) |
#define UDRS (&drive_state[drive]) |
#define UDRWE (&write_errors[drive]) |
#define UFDCS (&fdc_state[FDC(drive)]) |
#define UCLEARF(x) (clear_bit(x##_BIT, &UDRS->flags)) |
#define USETF(x) (set_bit(x##_BIT, &UDRS->flags)) |
#define UTESTF(x) (test_bit(x##_BIT, &UDRS->flags)) |
|
#define DPRINT(format, args...) printk(DEVICE_NAME "%d: " format, current_drive , ## args) |
|
#define PH_HEAD(floppy,head) (((((floppy)->stretch & 2) >>1) ^ head) << 2) |
#define STRETCH(floppy) ((floppy)->stretch & FD_STRETCH) |
|
#define CLEARSTRUCT(x) memset((x), 0, sizeof(*(x))) |
|
#define INT_OFF save_flags(flags); cli() |
#define INT_ON restore_flags(flags) |
|
/* read/write */ |
#define COMMAND raw_cmd->cmd[0] |
#define DR_SELECT raw_cmd->cmd[1] |
#define TRACK raw_cmd->cmd[2] |
#define HEAD raw_cmd->cmd[3] |
#define SECTOR raw_cmd->cmd[4] |
#define SIZECODE raw_cmd->cmd[5] |
#define SECT_PER_TRACK raw_cmd->cmd[6] |
#define GAP raw_cmd->cmd[7] |
#define SIZECODE2 raw_cmd->cmd[8] |
#define NR_RW 9 |
|
/* format */ |
#define F_SIZECODE raw_cmd->cmd[2] |
#define F_SECT_PER_TRACK raw_cmd->cmd[3] |
#define F_GAP raw_cmd->cmd[4] |
#define F_FILL raw_cmd->cmd[5] |
#define NR_F 6 |
|
/* |
* Maximum disk size (in kilobytes). This default is used whenever the |
* current disk size is unknown. |
* [Now it is rather a minimum] |
*/ |
#define MAX_DISK_SIZE 4 /* 3984*/ |
|
#define K_64 0x10000 /* 64KB */ |
|
/* |
* globals used by 'result()' |
*/ |
#define MAX_REPLIES 16 |
static unsigned char reply_buffer[MAX_REPLIES]; |
static int inr; /* size of reply buffer, when called from interrupt */ |
#define ST0 (reply_buffer[0]) |
#define ST1 (reply_buffer[1]) |
#define ST2 (reply_buffer[2]) |
#define ST3 (reply_buffer[0]) /* result of GETSTATUS */ |
#define R_TRACK (reply_buffer[3]) |
#define R_HEAD (reply_buffer[4]) |
#define R_SECTOR (reply_buffer[5]) |
#define R_SIZECODE (reply_buffer[6]) |
|
#define SEL_DLY (2*HZ/100) |
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) |
/* |
* this struct defines the different floppy drive types. |
*/ |
static struct { |
struct floppy_drive_params params; |
const char *name; /* name printed while booting */ |
} default_drive_params[]= { |
/* NOTE: the time values in jiffies should be in msec! |
CMOS drive type |
| Maximum data rate supported by drive type |
| | Head load time, msec |
| | | Head unload time, msec (not used) |
| | | | Step rate interval, usec |
| | | | | Time needed for spinup time (jiffies) |
| | | | | | Timeout for spinning down (jiffies) |
| | | | | | | Spindown offset (where disk stops) |
| | | | | | | | Select delay |
| | | | | | | | | RPS |
| | | | | | | | | | Max number of tracks |
| | | | | | | | | | | Interrupt timeout |
| | | | | | | | | | | | Max nonintlv. sectors |
| | | | | | | | | | | | | -Max Errors- flags */ |
{{0, 500, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 80, 3*HZ, 20, {3,1,2,0,2}, 0, |
0, { 7, 4, 8, 2, 1, 5, 3,10}, 3*HZ/2, 0 }, "unknown" }, |
|
{{1, 300, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 40, 3*HZ, 17, {3,1,2,0,2}, 0, |
0, { 1, 0, 0, 0, 0, 0, 0, 0}, 3*HZ/2, 1 }, "360K PC" }, /*5 1/4 360 KB PC*/ |
|
{{2, 500, 16, 16, 6000, 4*HZ/10, 3*HZ, 14, SEL_DLY, 6, 83, 3*HZ, 17, {3,1,2,0,2}, 0, |
0, { 2, 5, 6,23,10,20,11, 0}, 3*HZ/2, 2 }, "1.2M" }, /*5 1/4 HD AT*/ |
|
{{3, 250, 16, 16, 3000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, |
0, { 4,22,21,30, 3, 0, 0, 0}, 3*HZ/2, 4 }, "720k" }, /*3 1/2 DD*/ |
|
{{4, 500, 16, 16, 4000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, |
0, { 7, 4,25,22,31,21,29,11}, 3*HZ/2, 7 }, "1.44M" }, /*3 1/2 HD*/ |
|
{{5, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, |
0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M AMI BIOS" }, /*3 1/2 ED*/ |
|
{{6, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, |
0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M" } /*3 1/2 ED*/ |
/* | --autodetected formats--- | | | |
* read_track | | Name printed when booting |
* | Native format |
* Frequency of disk change checks */ |
}; |
|
static struct floppy_drive_params drive_params[N_DRIVE]; |
static struct floppy_drive_struct drive_state[N_DRIVE]; |
static struct floppy_write_errors write_errors[N_DRIVE]; |
static struct floppy_raw_cmd *raw_cmd, default_raw_cmd; |
|
/* |
* This struct defines the different floppy types. |
* |
* Bit 0 of 'stretch' tells if the tracks need to be doubled for some |
* types (e.g. 360kB diskette in 1.2MB drive, etc.). Bit 1 of 'stretch' |
* tells if the disk is in Commodore 1581 format, which means side 0 sectors |
* are located on side 1 of the disk but with a side 0 ID, and vice-versa. |
* This is the same as the Sharp MZ-80 5.25" CP/M disk format, except that the |
* 1581's logical side 0 is on physical side 1, whereas the Sharp's logical |
* side 0 is on physical side 0 (but with the misnamed sector IDs). |
* 'stretch' should probably be renamed to something more general, like |
* 'options'. Other parameters should be self-explanatory (see also |
* setfdprm(8)). |
*/ |
/* |
Size |
| Sectors per track |
| | Head |
| | | Tracks |
| | | | Stretch |
| | | | | Gap 1 size |
| | | | | | Data rate, | 0x40 for perp |
| | | | | | | Spec1 (stepping rate, head unload |
| | | | | | | | /fmt gap (gap2) */ |
static struct floppy_struct floppy_type[32] = { |
{ 0, 0,0, 0,0,0x00,0x00,0x00,0x00,NULL }, /* 0 no testing */ |
{ 720, 9,2,40,0,0x2A,0x02,0xDF,0x50,"d360" }, /* 1 360KB PC */ |
{ 2400,15,2,80,0,0x1B,0x00,0xDF,0x54,"h1200" }, /* 2 1.2MB AT */ |
{ 720, 9,1,80,0,0x2A,0x02,0xDF,0x50,"D360" }, /* 3 360KB SS 3.5" */ |
{ 1440, 9,2,80,0,0x2A,0x02,0xDF,0x50,"D720" }, /* 4 720KB 3.5" */ |
{ 720, 9,2,40,1,0x23,0x01,0xDF,0x50,"h360" }, /* 5 360KB AT */ |
{ 1440, 9,2,80,0,0x23,0x01,0xDF,0x50,"h720" }, /* 6 720KB AT */ |
{ 2880,18,2,80,0,0x1B,0x00,0xCF,0x6C,"H1440" }, /* 7 1.44MB 3.5" */ |
{ 5760,36,2,80,0,0x1B,0x43,0xAF,0x54,"E2880" }, /* 8 2.88MB 3.5" */ |
{ 6240,39,2,80,0,0x1B,0x43,0xAF,0x28,"E3120" }, /* 9 3.12MB 3.5" */ |
|
{ 2880,18,2,80,0,0x25,0x00,0xDF,0x02,"h1440" }, /* 10 1.44MB 5.25" */ |
{ 3360,21,2,80,0,0x1C,0x00,0xCF,0x0C,"H1680" }, /* 11 1.68MB 3.5" */ |
{ 820,10,2,41,1,0x25,0x01,0xDF,0x2E,"h410" }, /* 12 410KB 5.25" */ |
{ 1640,10,2,82,0,0x25,0x02,0xDF,0x2E,"H820" }, /* 13 820KB 3.5" */ |
{ 2952,18,2,82,0,0x25,0x00,0xDF,0x02,"h1476" }, /* 14 1.48MB 5.25" */ |
{ 3444,21,2,82,0,0x25,0x00,0xDF,0x0C,"H1722" }, /* 15 1.72MB 3.5" */ |
{ 840,10,2,42,1,0x25,0x01,0xDF,0x2E,"h420" }, /* 16 420KB 5.25" */ |
{ 1660,10,2,83,0,0x25,0x02,0xDF,0x2E,"H830" }, /* 17 830KB 3.5" */ |
{ 2988,18,2,83,0,0x25,0x00,0xDF,0x02,"h1494" }, /* 18 1.49MB 5.25" */ |
{ 3486,21,2,83,0,0x25,0x00,0xDF,0x0C,"H1743" }, /* 19 1.74 MB 3.5" */ |
|
{ 1760,11,2,80,0,0x1C,0x09,0xCF,0x00,"h880" }, /* 20 880KB 5.25" */ |
{ 2080,13,2,80,0,0x1C,0x01,0xCF,0x00,"D1040" }, /* 21 1.04MB 3.5" */ |
{ 2240,14,2,80,0,0x1C,0x19,0xCF,0x00,"D1120" }, /* 22 1.12MB 3.5" */ |
{ 3200,20,2,80,0,0x1C,0x20,0xCF,0x2C,"h1600" }, /* 23 1.6MB 5.25" */ |
{ 3520,22,2,80,0,0x1C,0x08,0xCF,0x2e,"H1760" }, /* 24 1.76MB 3.5" */ |
{ 3840,24,2,80,0,0x1C,0x20,0xCF,0x00,"H1920" }, /* 25 1.92MB 3.5" */ |
{ 6400,40,2,80,0,0x25,0x5B,0xCF,0x00,"E3200" }, /* 26 3.20MB 3.5" */ |
{ 7040,44,2,80,0,0x25,0x5B,0xCF,0x00,"E3520" }, /* 27 3.52MB 3.5" */ |
{ 7680,48,2,80,0,0x25,0x63,0xCF,0x00,"E3840" }, /* 28 3.84MB 3.5" */ |
|
{ 3680,23,2,80,0,0x1C,0x10,0xCF,0x00,"H1840" }, /* 29 1.84MB 3.5" */ |
{ 1600,10,2,80,0,0x25,0x02,0xDF,0x2E,"D800" }, /* 30 800KB 3.5" */ |
{ 3200,20,2,80,0,0x1C,0x00,0xCF,0x2C,"H1600" }, /* 31 1.6MB 3.5" */ |
}; |
|
#define NUMBER(x) (sizeof(x) / sizeof(*(x))) |
#define SECTSIZE (_FD_SECTSIZE(*floppy)) |
|
/* Auto-detection: Disk type used until the next media change occurs. */ |
static struct floppy_struct *current_type[N_DRIVE] = { |
NULL, NULL, NULL, NULL, |
NULL, NULL, NULL, NULL |
}; |
|
/* |
* User-provided type information. current_type points to |
* the respective entry of this array. |
*/ |
static struct floppy_struct user_params[N_DRIVE]; |
|
static int floppy_sizes[256]; |
static int floppy_blocksizes[256] = { 0, }; |
|
/* |
* The driver is trying to determine the correct media format |
* while probing is set. rw_interrupt() clears it after a |
* successful access. |
*/ |
static int probing = 0; |
|
/* Synchronization of FDC access. */ |
#define FD_COMMAND_NONE -1 |
#define FD_COMMAND_ERROR 2 |
#define FD_COMMAND_OKAY 3 |
|
static volatile int command_status = FD_COMMAND_NONE, fdc_busy = 0; |
static struct wait_queue *fdc_wait = NULL, *command_done = NULL; |
#define NO_SIGNAL (!(current->signal & ~current->blocked) || !interruptible) |
#define CALL(x) if ((x) == -EINTR) return -EINTR |
#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=NULL; |
static int max_buffer_sectors=0; |
|
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=NULL; |
|
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 -3 |
|
/* */ |
static int usage_count = 0; |
|
|
/* 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 = 0; |
static long current_count_sectors = 0; |
static unsigned char sector_t; /* sector in track */ |
|
#ifndef fd_eject |
#define fd_eject(x) -EINVAL |
#endif |
|
#ifdef DEBUGT |
static 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 ={ NULL, NULL, 0, 0, |
(timeout_fn) floppy_shutdown }; |
|
static const char *timeout_message; |
|
#ifdef FLOPPY_SANITY_CHECK |
static void is_alive(const char *message) |
{ |
/* this routine checks whether the floppy driver is "alive" */ |
if (fdc_busy && command_status < 2 && !fd_timeout.prev){ |
DPRINT("timeout handler died: %s\n",message); |
} |
} |
#endif |
|
#ifdef FLOPPY_SANITY_CHECK |
|
#define OLOGSIZE 20 |
|
static void (*lasthandler)(void) = NULL; |
static int interruptjiffies=0; |
static int resultjiffies=0; |
static int resultsize=0; |
static int lastredo=0; |
|
static struct output_log { |
unsigned char data; |
unsigned char status; |
unsigned long jiffies; |
} output_log[OLOGSIZE]; |
|
static int output_log_pos=0; |
#endif |
|
#define CURRENTD -1 |
#define MAXTIMEOUT -2 |
|
static 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 + 20*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=%ld\n", jiffies); |
DPRINT("disk change line=%x\n",fd_inb(FD_DIR)&0x80); |
DPRINT("flags=%x\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_setdor(newdor); |
|
unit = newdor & 0x3; |
if (!is_selected(olddor, unit) && is_selected(newdor,unit)){ |
drive = REVDRIVE(fdc,unit); |
UDRS->select_date = jiffies; |
} |
} |
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_setdor(FDCS->dor & ~(0x10<<UNIT(current_drive))); |
fd_setdor(FDCS->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) |
{ |
unsigned long flags; |
|
if (!usage_count){ |
printk("trying to lock fdc while usage count=0\n"); |
return -1; |
} |
floppy_grab_irq_and_dma(); |
INT_OFF; |
while (fdc_busy && NO_SIGNAL) |
interruptible_sleep_on(&fdc_wait); |
if (fdc_busy){ |
INT_ON; |
return -EINTR; |
} |
fdc_busy = 1; |
INT_ON; |
command_status = FD_COMMAND_NONE; |
reschedule_timeout(drive, "lock fdc", 0); |
set_fdc(drive); |
return 0; |
} |
|
#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; |
fdc_busy = 0; |
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); |
} |
|
static struct timer_list motor_off_timer[N_DRIVE] = { |
{ NULL, NULL, 0, 0, motor_off_callback }, |
{ NULL, NULL, 0, 1, motor_off_callback }, |
{ NULL, NULL, 0, 2, motor_off_callback }, |
{ NULL, NULL, 0, 3, motor_off_callback }, |
{ NULL, NULL, 0, 4, motor_off_callback }, |
{ NULL, NULL, 0, 5, motor_off_callback }, |
{ NULL, NULL, 0, 6, motor_off_callback }, |
{ NULL, NULL, 0, 7, motor_off_callback } |
}; |
|
/* 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 = |
{ 0, 0, 0, 0 }; |
|
static struct timer_list fd_timer ={ NULL, NULL, 0, 0, 0 }; |
|
static void cancel_activity(void) |
{ |
CLEAR_INTR; |
floppy_tq.routine = (void *)(void *) empty; |
del_timer(&fd_timer); |
} |
|
/* this function makes sure that the disk stays in the drive during the |
* transfer */ |
static void fd_watchdog(void) |
{ |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("calling disk change from watchdog\n"); |
} |
#endif |
|
if (disk_change(current_drive)){ |
DPRINT("disk removed during i/o\n"); |
cancel_activity(); |
cont->done(0); |
reset_fdc(); |
} else { |
del_timer(&fd_timer); |
fd_timer.function = (timeout_fn) fd_watchdog; |
fd_timer.expires = jiffies + HZ / 10; |
add_timer(&fd_timer); |
} |
} |
|
static void main_command_interrupt(void) |
{ |
del_timer(&fd_timer); |
cont->interrupt(); |
} |
|
/* waits for a delay (spinup or select) to pass */ |
static int wait_for_completion(int delay, timeout_fn function) |
{ |
if (FDCS->reset){ |
reset_fdc(); /* do the reset during sleep to win time |
* if we don't need to sleep, it's a good |
* occasion anyways */ |
return 1; |
} |
|
if ((signed) (jiffies - delay) < 0){ |
del_timer(&fd_timer); |
fd_timer.function = function; |
fd_timer.expires = delay; |
add_timer(&fd_timer); |
return 1; |
} |
return 0; |
} |
|
static int hlt_disabled=0; |
static void floppy_disable_hlt(void) |
{ |
unsigned long flags; |
|
INT_OFF; |
if (!hlt_disabled){ |
hlt_disabled=1; |
#ifdef HAVE_DISABLE_HLT |
disable_hlt(); |
#endif |
} |
INT_ON; |
} |
|
static void floppy_enable_hlt(void) |
{ |
unsigned long flags; |
|
INT_OFF; |
if (hlt_disabled){ |
hlt_disabled=0; |
#ifdef HAVE_DISABLE_HLT |
enable_hlt(); |
#endif |
} |
INT_ON; |
} |
|
|
static void setup_DMA(void) |
{ |
unsigned long flags; |
|
#ifdef FLOPPY_SANITY_CHECK |
if (raw_cmd->length == 0){ |
int i; |
|
printk("zero dma transfer size:"); |
for (i=0; i < raw_cmd->cmd_count; i++) |
printk("%x,", raw_cmd->cmd[i]); |
printk("\n"); |
cont->done(0); |
FDCS->reset = 1; |
return; |
} |
#if 0 |
if ((long) raw_cmd->kernel_data % 512){ |
printk("non aligned address: %p\n", raw_cmd->kernel_data); |
cont->done(0); |
FDCS->reset=1; |
return; |
} |
if (CROSS_64KB(raw_cmd->kernel_data, raw_cmd->length)) { |
printk("DMA crossing 64-K boundary %p-%p\n", |
raw_cmd->kernel_data, |
raw_cmd->kernel_data + raw_cmd->length); |
cont->done(0); |
FDCS->reset=1; |
return; |
} |
#endif |
#endif |
INT_OFF; |
fd_disable_dma(); |
fd_clear_dma_ff(); |
fd_cacheflush(raw_cmd->kernel_data, raw_cmd->length); |
fd_set_dma_mode((raw_cmd->flags & FD_RAW_READ)? |
DMA_MODE_READ : DMA_MODE_WRITE); |
fd_set_dma_addr(virt_to_bus(raw_cmd->kernel_data)); |
fd_set_dma_count(raw_cmd->length); |
virtual_dma_port = FDCS->address; |
fd_enable_dma(); |
INT_ON; |
floppy_disable_hlt(); |
} |
|
void show_floppy(void); |
|
/* waits until the fdc becomes ready */ |
static int wait_til_ready(void) |
{ |
int counter, status; |
if(FDCS->reset) |
return -1; |
for (counter = 0; counter < 10000; counter++) { |
status = fd_inb(FD_STATUS); |
if (status & STATUS_READY) |
return status; |
} |
if (!initialising) { |
DPRINT("Getstatus times out (%x) on fdc %d\n", |
status, fdc); |
show_floppy(); |
} |
FDCS->reset = 1; |
return -1; |
} |
|
/* sends a command byte to the fdc */ |
static int output_byte(char byte) |
{ |
int status; |
|
if ((status = wait_til_ready()) < 0) |
return -1; |
if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY){ |
fd_outb(byte,FD_DATA); |
#ifdef FLOPPY_SANITY_CHECK |
output_log[output_log_pos].data = byte; |
output_log[output_log_pos].status = status; |
output_log[output_log_pos].jiffies = jiffies; |
output_log_pos = (output_log_pos + 1) % OLOGSIZE; |
#endif |
return 0; |
} |
FDCS->reset = 1; |
if (!initialising) { |
DPRINT("Unable to send byte %x to FDC. Fdc=%x Status=%x\n", |
byte, fdc, status); |
show_floppy(); |
} |
return -1; |
} |
#define LAST_OUT(x) if (output_byte(x)<0){ reset_fdc();return;} |
|
/* gets the response from the fdc */ |
static int result(void) |
{ |
int i, status; |
|
for(i=0; i < MAX_REPLIES; i++) { |
if ((status = wait_til_ready()) < 0) |
break; |
status &= STATUS_DIR|STATUS_READY|STATUS_BUSY|STATUS_DMA; |
if ((status & ~STATUS_BUSY) == STATUS_READY){ |
#ifdef FLOPPY_SANITY_CHECK |
resultjiffies = jiffies; |
resultsize = i; |
#endif |
return i; |
} |
if (status == (STATUS_DIR|STATUS_READY|STATUS_BUSY)) |
reply_buffer[i] = fd_inb(FD_DATA); |
else |
break; |
} |
if(!initialising) { |
DPRINT("get result error. Fdc=%d Last status=%x Read bytes=%d\n", |
fdc, status, i); |
show_floppy(); |
} |
FDCS->reset = 1; |
return -1; |
} |
|
#define MORE_OUTPUT -2 |
/* does the fdc need more output? */ |
static int need_more_output(void) |
{ |
int status; |
if( (status = wait_til_ready()) < 0) |
return -1; |
if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY) |
return MORE_OUTPUT; |
return result(); |
} |
|
/* Set perpendicular mode as required, based on data rate, if supported. |
* 82077 Now tested. 1Mbps data rate only possible with 82077-1. |
*/ |
static inline void perpendicular_mode(void) |
{ |
unsigned char perp_mode; |
|
if (raw_cmd->rate & 0x40){ |
switch(raw_cmd->rate & 3){ |
case 0: |
perp_mode=2; |
break; |
case 3: |
perp_mode=3; |
break; |
default: |
DPRINT("Invalid data rate for perpendicular mode!\n"); |
cont->done(0); |
FDCS->reset = 1; /* convenient way to return to |
* redo without to much hassle (deep |
* stack et al. */ |
return; |
} |
} else |
perp_mode = 0; |
|
if (FDCS->perp_mode == perp_mode) |
return; |
if (FDCS->version >= FDC_82077_ORIG) { |
output_byte(FD_PERPENDICULAR); |
output_byte(perp_mode); |
FDCS->perp_mode = perp_mode; |
} else if (perp_mode) { |
DPRINT("perpendicular mode not supported by this FDC.\n"); |
} |
} /* perpendicular_mode */ |
|
static int fifo_depth = 0xa; |
static int no_fifo = 0; |
|
static int fdc_configure(void) |
{ |
/* Turn on FIFO */ |
output_byte(FD_CONFIGURE); |
if(need_more_output() != MORE_OUTPUT) |
return 0; |
output_byte(0); |
output_byte(0x10 | (no_fifo & 0x20) | (fifo_depth & 0xf)); |
output_byte(0); /* pre-compensation from track |
0 upwards */ |
return 1; |
} |
|
#define NOMINAL_DTR 500 |
|
/* Issue a "SPECIFY" command to set the step rate time, head unload time, |
* head load time, and DMA disable flag to values needed by floppy. |
* |
* The value "dtr" is the data transfer rate in Kbps. It is needed |
* to account for the data rate-based scaling done by the 82072 and 82077 |
* FDC types. This parameter is ignored for other types of FDCs (i.e. |
* 8272a). |
* |
* Note that changing the data transfer rate has a (probably deleterious) |
* effect on the parameters subject to scaling for 82072/82077 FDCs, so |
* fdc_specify is called again after each data transfer rate |
* change. |
* |
* srt: 1000 to 16000 in microseconds |
* hut: 16 to 240 milliseconds |
* hlt: 2 to 254 milliseconds |
* |
* These values are rounded up to the next highest available delay time. |
*/ |
static void fdc_specify(void) |
{ |
unsigned char spec1, spec2; |
int srt, hlt, hut; |
unsigned long dtr = NOMINAL_DTR; |
unsigned long scale_dtr = NOMINAL_DTR; |
int hlt_max_code = 0x7f; |
int hut_max_code = 0xf; |
|
if (FDCS->need_configure && FDCS->version >= FDC_82072A) { |
fdc_configure(); |
FDCS->need_configure = 0; |
/*DPRINT("FIFO enabled\n");*/ |
} |
|
switch (raw_cmd->rate & 0x03) { |
case 3: |
dtr = 1000; |
break; |
case 1: |
dtr = 300; |
if (FDCS->version >= FDC_82078) { |
/* chose the default rate table, not the one |
* where 1 = 2 Mbps */ |
output_byte(FD_DRIVESPEC); |
if(need_more_output() == MORE_OUTPUT) { |
output_byte(UNIT(current_drive)); |
output_byte(0xc0); |
} |
} |
break; |
case 2: |
dtr = 250; |
break; |
} |
|
if (FDCS->version >= FDC_82072) { |
scale_dtr = dtr; |
hlt_max_code = 0x00; /* 0==256msec*dtr0/dtr (not linear!) */ |
hut_max_code = 0x0; /* 0==256msec*dtr0/dtr (not linear!) */ |
} |
|
/* Convert step rate from microseconds to milliseconds and 4 bits */ |
srt = 16 - (DP->srt*scale_dtr/1000 + NOMINAL_DTR - 1)/NOMINAL_DTR; |
SUPBOUND(srt, 0xf); |
INFBOUND(srt, 0); |
|
hlt = (DP->hlt*scale_dtr/2 + NOMINAL_DTR - 1)/NOMINAL_DTR; |
if (hlt < 0x01) |
hlt = 0x01; |
else if (hlt > 0x7f) |
hlt = hlt_max_code; |
|
hut = (DP->hut*scale_dtr/16 + NOMINAL_DTR - 1)/NOMINAL_DTR; |
if (hut < 0x1) |
hut = 0x1; |
else if (hut > 0xf) |
hut = hut_max_code; |
|
spec1 = (srt << 4) | hut; |
spec2 = (hlt << 1) | (use_virtual_dma & 1); |
|
/* If these parameters did not change, just return with success */ |
if (FDCS->spec1 != spec1 || FDCS->spec2 != spec2) { |
/* Go ahead and set spec1 and spec2 */ |
output_byte(FD_SPECIFY); |
output_byte(FDCS->spec1 = spec1); |
output_byte(FDCS->spec2 = spec2); |
} |
} /* fdc_specify */ |
|
/* Set the FDC's data transfer rate on behalf of the specified drive. |
* NOTE: with 82072/82077 FDCs, changing the data rate requires a reissue |
* of the specify command (i.e. using the fdc_specify function). |
*/ |
static int fdc_dtr(void) |
{ |
/* If data rate not already set to desired value, set it. */ |
if ((raw_cmd->rate & 3) == FDCS->dtr) |
return 0; |
|
/* Set dtr */ |
fd_outb(raw_cmd->rate & 3, FD_DCR); |
|
/* TODO: some FDC/drive combinations (C&T 82C711 with TEAC 1.2MB) |
* need a stabilization period of several milliseconds to be |
* enforced after data rate changes before R/W operations. |
* Pause 5 msec to avoid trouble. (Needs to be 2 jiffies) |
*/ |
FDCS->dtr = raw_cmd->rate & 3; |
return(wait_for_completion(jiffies+2*HZ/100, |
(timeout_fn) floppy_ready)); |
} /* fdc_dtr */ |
|
static void tell_sector(void) |
{ |
printk(": track %d, head %d, sector %d, size %d", |
R_TRACK, R_HEAD, R_SECTOR, R_SIZECODE); |
} /* tell_sector */ |
|
|
/* |
* OK, this error interpreting routine is called after a |
* DMA read/write has succeeded |
* or failed, so we check the results, and copy any buffers. |
* hhb: Added better error reporting. |
* ak: Made this into a separate routine. |
*/ |
static int interpret_errors(void) |
{ |
char bad; |
int res = get_dma_residue(FLOPPY_DMA); |
if(res) {printk("\n-- DMA residue (%d)",res); tell_sector(); printk("\n");} |
|
if (inr!=7) { |
DPRINT("-- FDC reply error"); |
FDCS->reset = 1; |
return 1; |
} |
|
/* check IC to find cause of interrupt */ |
switch (ST0 & ST0_INTR) { |
case 0x40: /* error occurred during command execution */ |
if (ST1 & ST1_EOC) |
return 0; /* occurs with pseudo-DMA */ |
bad = 1; |
if (ST1 & ST1_WP) { |
DPRINT("Drive is write protected\n"); |
CLEARF(FD_DISK_WRITABLE); |
cont->done(0); |
bad = 2; |
} else if (ST1 & ST1_ND) { |
SETF(FD_NEED_TWADDLE); |
} else if (ST1 & ST1_OR) { |
if (DP->flags & FTD_MSG) |
DPRINT("Over/Underrun - retrying\n"); |
bad = 0; |
}else if (*errors >= DP->max_errors.reporting){ |
DPRINT(""); |
if (ST0 & ST0_ECE) { |
printk("Recalibrate failed!"); |
} else if (ST2 & ST2_CRC) { |
printk("data CRC error"); |
tell_sector(); |
} else if (ST1 & ST1_CRC) { |
printk("CRC error"); |
tell_sector(); |
} else if ((ST1 & (ST1_MAM|ST1_ND)) || (ST2 & ST2_MAM)) { |
if (!probing) { |
printk("sector not found"); |
tell_sector(); |
} else |
printk("probe failed..."); |
} else if (ST2 & ST2_WC) { /* seek error */ |
printk("wrong cylinder"); |
} else if (ST2 & ST2_BC) { /* cylinder marked as bad */ |
printk("bad cylinder"); |
} else { |
printk("unknown error. ST[0..2] are: 0x%x 0x%x 0x%x", ST0, ST1, ST2); |
tell_sector(); |
} |
printk("\n"); |
|
} |
if (ST2 & ST2_WC || ST2 & ST2_BC) |
/* wrong cylinder => recal */ |
DRS->track = NEED_2_RECAL; |
return bad; |
case 0x80: /* invalid command given */ |
DPRINT("Invalid FDC command given!\n"); |
cont->done(0); |
return 2; |
case 0xc0: |
DPRINT("Abnormal termination caused by polling\n"); |
cont->error(); |
return 2; |
default: /* (0) Normal command termination */ |
return 0; |
} |
} |
|
/* |
* This routine is called when everything should be correctly set up |
* for the transfer (i.e. floppy motor is on, the correct floppy is |
* selected, and the head is sitting on the right track). |
*/ |
static void setup_rw_floppy(void) |
{ |
int i,ready_date,r, flags,dflags; |
timeout_fn function; |
|
flags = raw_cmd->flags; |
if (flags & (FD_RAW_READ | FD_RAW_WRITE)) |
flags |= FD_RAW_INTR; |
|
if ((flags & FD_RAW_SPIN) && !(flags & FD_RAW_NO_MOTOR)){ |
ready_date = DRS->spinup_date + DP->spinup; |
/* If spinup will take a long time, rerun scandrives |
* again just before spinup completion. Beware that |
* after scandrives, we must again wait for selection. |
*/ |
if ((signed) (ready_date - jiffies) > DP->select_delay){ |
ready_date -= DP->select_delay; |
function = (timeout_fn) floppy_start; |
} else |
function = (timeout_fn) setup_rw_floppy; |
|
/* wait until the floppy is spinning fast enough */ |
if (wait_for_completion(ready_date,function)) |
return; |
} |
dflags = DRS->flags; |
|
if ((flags & FD_RAW_READ) || (flags & FD_RAW_WRITE)) |
setup_DMA(); |
|
if (flags & FD_RAW_INTR) |
SET_INTR(main_command_interrupt); |
|
r=0; |
for (i=0; i< raw_cmd->cmd_count; i++) |
r|=output_byte(raw_cmd->cmd[i]); |
|
#ifdef DEBUGT |
debugt("rw_command: "); |
#endif |
if (r){ |
cont->error(); |
reset_fdc(); |
return; |
} |
|
if (!(flags & FD_RAW_INTR)){ |
inr = result(); |
cont->interrupt(); |
} else if (flags & FD_RAW_NEED_DISK) |
fd_watchdog(); |
} |
|
static int blind_seek; |
|
/* |
* This is the routine called after every seek (or recalibrate) interrupt |
* from the floppy controller. |
*/ |
static void seek_interrupt(void) |
{ |
#ifdef DEBUGT |
debugt("seek interrupt:"); |
#endif |
if (inr != 2 || (ST0 & 0xF8) != 0x20) { |
DPRINT("seek failed\n"); |
DRS->track = NEED_2_RECAL; |
cont->error(); |
cont->redo(); |
return; |
} |
if (DRS->track >= 0 && DRS->track != ST1 && !blind_seek){ |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("clearing NEWCHANGE flag because of effective seek\n"); |
DPRINT("jiffies=%ld\n", jiffies); |
} |
#endif |
CLEARF(FD_DISK_NEWCHANGE); /* effective seek */ |
DRS->select_date = jiffies; |
} |
DRS->track = ST1; |
floppy_ready(); |
} |
|
static void check_wp(void) |
{ |
if (TESTF(FD_VERIFY)) { |
/* check write protection */ |
output_byte(FD_GETSTATUS); |
output_byte(UNIT(current_drive)); |
if (result() != 1){ |
FDCS->reset = 1; |
return; |
} |
CLEARF(FD_VERIFY); |
CLEARF(FD_NEED_TWADDLE); |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("checking whether disk is write protected\n"); |
DPRINT("wp=%x\n",ST3 & 0x40); |
} |
#endif |
if (!(ST3 & 0x40)) |
SETF(FD_DISK_WRITABLE); |
else |
CLEARF(FD_DISK_WRITABLE); |
} |
} |
|
static void seek_floppy(void) |
{ |
int track; |
|
blind_seek=0; |
|
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("calling disk change from seek\n"); |
} |
#endif |
|
if (!TESTF(FD_DISK_NEWCHANGE) && |
disk_change(current_drive) && |
(raw_cmd->flags & FD_RAW_NEED_DISK)){ |
/* the media changed flag should be cleared after the seek. |
* If it isn't, this means that there is really no disk in |
* the drive. |
*/ |
SETF(FD_DISK_CHANGED); |
cont->done(0); |
cont->redo(); |
return; |
} |
if (DRS->track <= NEED_1_RECAL){ |
recalibrate_floppy(); |
return; |
} else if (TESTF(FD_DISK_NEWCHANGE) && |
(raw_cmd->flags & FD_RAW_NEED_DISK) && |
(DRS->track <= NO_TRACK || DRS->track == raw_cmd->track)) { |
/* we seek to clear the media-changed condition. Does anybody |
* know a more elegant way, which works on all drives? */ |
if (raw_cmd->track) |
track = raw_cmd->track - 1; |
else { |
if (DP->flags & FD_SILENT_DCL_CLEAR){ |
set_dor(fdc, ~(0x10 << UNIT(current_drive)), 0); |
blind_seek = 1; |
raw_cmd->flags |= FD_RAW_NEED_SEEK; |
} |
track = 1; |
} |
} else { |
check_wp(); |
if (raw_cmd->track != DRS->track && |
(raw_cmd->flags & FD_RAW_NEED_SEEK)) |
track = raw_cmd->track; |
else { |
setup_rw_floppy(); |
return; |
} |
} |
|
SET_INTR(seek_interrupt); |
output_byte(FD_SEEK); |
output_byte(UNIT(current_drive)); |
LAST_OUT(track); |
#ifdef DEBUGT |
debugt("seek command:"); |
#endif |
} |
|
static void recal_interrupt(void) |
{ |
#ifdef DEBUGT |
debugt("recal interrupt:"); |
#endif |
if (inr !=2) |
FDCS->reset = 1; |
else if (ST0 & ST0_ECE) { |
switch(DRS->track){ |
case NEED_1_RECAL: |
#ifdef DEBUGT |
debugt("recal interrupt need 1 recal:"); |
#endif |
/* after a second recalibrate, we still haven't |
* reached track 0. Probably no drive. Raise an |
* error, as failing immediately might upset |
* computers possessed by the Devil :-) */ |
cont->error(); |
cont->redo(); |
return; |
case NEED_2_RECAL: |
#ifdef DEBUGT |
debugt("recal interrupt need 2 recal:"); |
#endif |
/* If we already did a recalibrate, |
* and we are not at track 0, this |
* means we have moved. (The only way |
* not to move at recalibration is to |
* be already at track 0.) Clear the |
* new change flag */ |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("clearing NEWCHANGE flag because of second recalibrate\n"); |
} |
#endif |
|
CLEARF(FD_DISK_NEWCHANGE); |
DRS->select_date = jiffies; |
/* fall through */ |
default: |
#ifdef DEBUGT |
debugt("recal interrupt default:"); |
#endif |
/* Recalibrate moves the head by at |
* most 80 steps. If after one |
* recalibrate we don't have reached |
* track 0, this might mean that we |
* started beyond track 80. Try |
* again. */ |
DRS->track = NEED_1_RECAL; |
break; |
} |
} else |
DRS->track = ST1; |
floppy_ready(); |
} |
|
static void print_result(char *message, int inr) |
{ |
int i; |
|
DPRINT("%s ", message); |
if (inr >= 0) |
for (i=0; i<inr; i++) |
printk("repl[%d]=%x ", i, reply_buffer[i]); |
printk("\n"); |
} |
|
/* interrupt handler */ |
void floppy_interrupt(int irq, void *dev_id, struct pt_regs * regs) |
{ |
void (*handler)(void) = DEVICE_INTR; |
int do_print; |
|
lasthandler = handler; |
interruptjiffies = jiffies; |
|
fd_disable_dma(); |
floppy_enable_hlt(); |
CLEAR_INTR; |
if (fdc >= N_FDC || FDCS->address == -1){ |
/* we don't even know which FDC is the culprit */ |
printk("DOR0=%x\n", (unsigned int)fdc_state[0].dor); |
printk("floppy interrupt on bizarre fdc %d\n",fdc); |
printk("handler=%p\n", handler); |
is_alive("bizarre fdc"); |
return; |
} |
|
FDCS->reset = 0; |
/* We have to clear the reset flag here, because apparently on boxes |
* with level triggered interrupts (PS/2, Sparc, ...), it is needed to |
* emit SENSEI's to clear the interrupt line. And FDCS->reset blocks the |
* emission of the SENSEI's. |
* It is OK to emit floppy commands because we are in an interrupt |
* handler here, and thus we have to fear no interference of other |
* activity. |
*/ |
|
do_print = !handler && print_unex && !initialising; |
|
inr = result(); |
if(do_print) |
print_result("unexpected interrupt", inr); |
if (inr == 0){ |
do { |
output_byte(FD_SENSEI); |
inr = result(); |
if(do_print) |
print_result("sensei", inr); |
} while ((ST0 & 0x83) != UNIT(current_drive) && inr == 2); |
} |
if (handler) { |
if(intr_count >= 2) { |
/* expected interrupt */ |
floppy_tq.routine = (void *)(void *) handler; |
queue_task_irq(&floppy_tq, &tq_immediate); |
mark_bh(IMMEDIATE_BH); |
} else |
handler(); |
} else |
FDCS->reset = 1; |
is_alive("normal interrupt end"); |
} |
|
static void recalibrate_floppy(void) |
{ |
#ifdef DEBUGT |
debugt("recalibrate floppy:"); |
#endif |
SET_INTR(recal_interrupt); |
output_byte(FD_RECALIBRATE); |
LAST_OUT(UNIT(current_drive)); |
} |
|
/* |
* Must do 4 FD_SENSEIs after reset because of ``drive polling''. |
*/ |
static void reset_interrupt(void) |
{ |
#ifdef DEBUGT |
debugt("reset interrupt:"); |
#endif |
result(); /* get the status ready for set_fdc */ |
if (FDCS->reset) { |
printk("reset set in interrupt, calling %p\n", cont->error); |
cont->error(); /* a reset just after a reset. BAD! */ |
} |
cont->redo(); |
} |
|
/* |
* reset is done by pulling bit 2 of DOR low for a while (old FDCs), |
* or by setting the self clearing bit 7 of STATUS (newer FDCs) |
*/ |
static void reset_fdc(void) |
{ |
SET_INTR(reset_interrupt); |
FDCS->reset = 0; |
reset_fdc_info(0); |
|
/* Pseudo-DMA may intercept 'reset finished' interrupt. */ |
/* Irrelevant for systems with true DMA (i386). */ |
fd_disable_dma(); |
|
if (FDCS->version >= FDC_82072A) |
fd_outb(0x80 | (FDCS->dtr &3), FD_STATUS); |
else { |
fd_setdor(FDCS->dor & ~0x04); |
udelay(FD_RESET_DELAY); |
fd_setdor(FDCS->dor); |
} |
} |
|
void show_floppy(void) |
{ |
int i; |
|
printk("\n"); |
printk("floppy driver state\n"); |
printk("-------------------\n"); |
printk("now=%ld last interrupt=%d last called handler=%p\n", |
jiffies, interruptjiffies, lasthandler); |
|
|
#ifdef FLOPPY_SANITY_CHECK |
printk("timeout_message=%s\n", timeout_message); |
printk("last output bytes:\n"); |
for (i=0; i < OLOGSIZE; i++) |
printk("%2x %2x %ld\n", |
output_log[(i+output_log_pos) % OLOGSIZE].data, |
output_log[(i+output_log_pos) % OLOGSIZE].status, |
output_log[(i+output_log_pos) % OLOGSIZE].jiffies); |
printk("last result at %d\n", resultjiffies); |
printk("last redo_fd_request at %d\n", lastredo); |
for (i=0; i<resultsize; i++){ |
printk("%2x ", reply_buffer[i]); |
} |
printk("\n"); |
#endif |
|
printk("status=%x\n", fd_inb(FD_STATUS)); |
printk("fdc_busy=%d\n", fdc_busy); |
if (DEVICE_INTR) |
printk("DEVICE_INTR=%p\n", DEVICE_INTR); |
if (floppy_tq.sync) |
printk("floppy_tq.routine=%p\n", floppy_tq.routine); |
if (fd_timer.prev) |
printk("fd_timer.function=%p\n", fd_timer.function); |
if (fd_timeout.prev){ |
printk("timer_table=%p\n",fd_timeout.function); |
printk("expires=%ld\n",fd_timeout.expires-jiffies); |
printk("now=%ld\n",jiffies); |
} |
printk("cont=%p\n", cont); |
printk("CURRENT=%p\n", CURRENT); |
printk("command_status=%d\n", command_status); |
printk("\n"); |
} |
|
static void floppy_shutdown(void) |
{ |
if (!initialising) |
show_floppy(); |
cancel_activity(); |
sti(); |
|
floppy_enable_hlt(); |
fd_disable_dma(); |
/* avoid dma going to a random drive after shutdown */ |
|
if (!initialising) |
DPRINT("floppy timeout called\n"); |
FDCS->reset = 1; |
if (cont){ |
cont->done(0); |
cont->redo(); /* this will recall reset when needed */ |
} else { |
printk("no cont in shutdown!\n"); |
process_fd_request(); |
} |
is_alive("floppy shutdown"); |
} |
/*typedef void (*timeout_fn)(unsigned long);*/ |
|
/* start motor, check media-changed condition and write protection */ |
static int start_motor(void (*function)(void) ) |
{ |
int mask, data; |
|
mask = 0xfc; |
data = UNIT(current_drive); |
if (!(raw_cmd->flags & FD_RAW_NO_MOTOR)){ |
if (!(FDCS->dor & (0x10 << UNIT(current_drive)))){ |
set_debugt(); |
/* no read since this drive is running */ |
DRS->first_read_date = 0; |
/* note motor start time if motor is not yet running */ |
DRS->spinup_date = jiffies; |
data |= (0x10 << UNIT(current_drive)); |
} |
} else |
if (FDCS->dor & (0x10 << UNIT(current_drive))) |
mask &= ~(0x10 << UNIT(current_drive)); |
|
/* starts motor and selects floppy */ |
del_timer(motor_off_timer + current_drive); |
set_dor(fdc, mask, data); |
|
/* wait_for_completion also schedules reset if needed. */ |
return(wait_for_completion(DRS->select_date+DP->select_delay, |
(timeout_fn) function)); |
} |
|
static void floppy_ready(void) |
{ |
CHECK_RESET; |
if (start_motor(floppy_ready)) return; |
if (fdc_dtr()) return; |
|
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("calling disk change from floppy_ready\n"); |
} |
#endif |
|
if (!(raw_cmd->flags & FD_RAW_NO_MOTOR) && |
disk_change(current_drive) && |
!DP->select_delay) |
twaddle(); /* this clears the dcl on certain drive/controller |
* combinations */ |
|
if (raw_cmd->flags & (FD_RAW_NEED_SEEK | FD_RAW_NEED_DISK)){ |
perpendicular_mode(); |
fdc_specify(); /* must be done here because of hut, hlt ... */ |
seek_floppy(); |
} else |
setup_rw_floppy(); |
} |
|
static void floppy_start(void) |
{ |
reschedule_timeout(CURRENTD, "floppy start", 0); |
|
scandrives(); |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("setting NEWCHANGE in floppy_start\n"); |
} |
#endif |
SETF(FD_DISK_NEWCHANGE); |
floppy_ready(); |
} |
|
/* |
* ======================================================================== |
* here ends the bottom half. Exported routines are: |
* floppy_start, floppy_off, floppy_ready, lock_fdc, unlock_fdc, set_fdc, |
* start_motor, reset_fdc, reset_fdc_info, interpret_errors. |
* Initialization also uses output_byte, result, set_dor, floppy_interrupt |
* and set_dor. |
* ======================================================================== |
*/ |
/* |
* General purpose continuations. |
* ============================== |
*/ |
|
static void do_wakeup(void) |
{ |
reschedule_timeout(MAXTIMEOUT, "do wakeup", 0); |
cont = 0; |
command_status += 2; |
wake_up(&command_done); |
} |
|
static struct cont_t wakeup_cont={ |
empty, |
do_wakeup, |
empty, |
(done_f)empty |
}; |
|
|
static struct cont_t intr_cont={ |
empty, |
process_fd_request, |
empty, |
(done_f) empty |
}; |
|
static int wait_til_done(void (*handler)(void), int interruptible) |
{ |
int ret; |
unsigned long flags; |
|
floppy_tq.routine = (void *)(void *) handler; |
queue_task(&floppy_tq, &tq_immediate); |
mark_bh(IMMEDIATE_BH); |
INT_OFF; |
while(command_status < 2 && NO_SIGNAL){ |
is_alive("wait_til_done"); |
if (interruptible) |
interruptible_sleep_on(&command_done); |
else |
sleep_on(&command_done); |
} |
if (command_status < 2){ |
cancel_activity(); |
cont = &intr_cont; |
reset_fdc(); |
INT_ON; |
return -EINTR; |
} |
INT_ON; |
|
if (FDCS->reset) |
command_status = FD_COMMAND_ERROR; |
if (command_status == FD_COMMAND_OKAY) |
ret=0; |
else |
ret=-EIO; |
command_status = FD_COMMAND_NONE; |
return ret; |
} |
|
static void generic_done(int result) |
{ |
command_status = result; |
cont = &wakeup_cont; |
} |
|
static void generic_success(void) |
{ |
cont->done(1); |
} |
|
static void generic_failure(void) |
{ |
cont->done(0); |
} |
|
static void success_and_wakeup(void) |
{ |
generic_success(); |
cont->redo(); |
} |
|
|
/* |
* formatting and rw support. |
* ========================== |
*/ |
|
static int next_valid_format(void) |
{ |
int probed_format; |
|
probed_format = DRS->probed_format; |
while(1){ |
if (probed_format >= 8 || |
!DP->autodetect[probed_format]){ |
DRS->probed_format = 0; |
return 1; |
} |
if (floppy_type[DP->autodetect[probed_format]].sect){ |
DRS->probed_format = probed_format; |
return 0; |
} |
probed_format++; |
} |
} |
|
static void bad_flp_intr(void) |
{ |
if (probing){ |
DRS->probed_format++; |
if (!next_valid_format()) |
return; |
} |
(*errors)++; |
INFBOUND(DRWE->badness, *errors); |
if (*errors > DP->max_errors.abort) |
cont->done(0); |
if (*errors > DP->max_errors.reset) |
FDCS->reset = 1; |
else if (*errors > DP->max_errors.recal) |
DRS->track = NEED_2_RECAL; |
} |
|
static void set_floppy(kdev_t device) |
{ |
if (TYPE(device)) |
_floppy = TYPE(device) + floppy_type; |
else |
_floppy = current_type[ DRIVE(device) ]; |
} |
|
/* |
* formatting support. |
* =================== |
*/ |
static void format_interrupt(void) |
{ |
switch (interpret_errors()){ |
case 1: |
cont->error(); |
case 2: |
break; |
case 0: |
cont->done(1); |
} |
cont->redo(); |
} |
|
#define CODE2SIZE (ssize = ((1 << SIZECODE) + 3) >> 2) |
#define FM_MODE(x,y) ((y) & ~(((x)->rate & 0x80) >>1)) |
#define CT(x) ((x) | 0x40) |
static void setup_format_params(int track) |
{ |
struct fparm { |
unsigned char track,head,sect,size; |
} *here = (struct fparm *)floppy_track_buffer; |
int il,n; |
int count,head_shift,track_shift; |
|
raw_cmd = &default_raw_cmd; |
raw_cmd->track = track; |
|
raw_cmd->flags = FD_RAW_WRITE | FD_RAW_INTR | FD_RAW_SPIN | |
FD_RAW_NEED_DISK | FD_RAW_NEED_SEEK; |
raw_cmd->rate = _floppy->rate & 0x43; |
raw_cmd->cmd_count = NR_F; |
COMMAND = FM_MODE(_floppy,FD_FORMAT); |
DR_SELECT = UNIT(current_drive) + PH_HEAD(_floppy,format_req.head); |
F_SIZECODE = FD_SIZECODE(_floppy); |
F_SECT_PER_TRACK = _floppy->sect << 2 >> F_SIZECODE; |
F_GAP = _floppy->fmt_gap; |
F_FILL = FD_FILL_BYTE; |
|
raw_cmd->kernel_data = floppy_track_buffer; |
raw_cmd->length = 4 * F_SECT_PER_TRACK; |
|
/* allow for about 30ms for data transport per track */ |
head_shift = (F_SECT_PER_TRACK + 5) / 6; |
|
/* a ``cylinder'' is two tracks plus a little stepping time */ |
track_shift = 2 * head_shift + 3; |
|
/* position of logical sector 1 on this track */ |
n = (track_shift * format_req.track + head_shift * format_req.head) |
% F_SECT_PER_TRACK; |
|
/* determine interleave */ |
il = 1; |
if (_floppy->fmt_gap < 0x22) |
il++; |
|
/* initialize field */ |
for (count = 0; count < F_SECT_PER_TRACK; ++count) { |
here[count].track = format_req.track; |
here[count].head = format_req.head; |
here[count].sect = 0; |
here[count].size = F_SIZECODE; |
} |
/* place logical sectors */ |
for (count = 1; count <= F_SECT_PER_TRACK; ++count) { |
here[n].sect = count; |
n = (n+il) % F_SECT_PER_TRACK; |
if (here[n].sect) { /* sector busy, find next free sector */ |
++n; |
if (n>= F_SECT_PER_TRACK) { |
n-=F_SECT_PER_TRACK; |
while (here[n].sect) ++n; |
} |
} |
} |
} |
|
static void redo_format(void) |
{ |
buffer_track = -1; |
setup_format_params(format_req.track << STRETCH(_floppy)); |
floppy_start(); |
#ifdef DEBUGT |
debugt("queue format request"); |
#endif |
} |
|
static struct cont_t format_cont={ |
format_interrupt, |
redo_format, |
bad_flp_intr, |
generic_done }; |
|
static int do_format(kdev_t device, struct format_descr *tmp_format_req) |
{ |
int ret; |
int drive=DRIVE(device); |
|
LOCK_FDC(drive,1); |
set_floppy(device); |
if (!_floppy || |
_floppy->track > DP->tracks || |
tmp_format_req->track >= _floppy->track || |
tmp_format_req->head >= _floppy->head || |
(_floppy->sect << 2) % (1 << FD_SIZECODE(_floppy)) || |
!_floppy->fmt_gap) { |
process_fd_request(); |
return -EINVAL; |
} |
format_req = *tmp_format_req; |
format_errors = 0; |
cont = &format_cont; |
errors = &format_errors; |
IWAIT(redo_format); |
process_fd_request(); |
return ret; |
} |
|
/* |
* Buffer read/write and support |
* ============================= |
*/ |
|
/* new request_done. Can handle physical sectors which are smaller than a |
* logical buffer */ |
static void request_done(int uptodate) |
{ |
int block; |
|
probing = 0; |
reschedule_timeout(MAXTIMEOUT, "request done %d", uptodate); |
|
if (!CURRENT){ |
DPRINT("request list destroyed in floppy request done\n"); |
return; |
} |
|
if (uptodate){ |
/* maintain values for invalidation on geometry |
* change */ |
block = current_count_sectors + CURRENT->sector; |
INFBOUND(DRS->maxblock, block); |
if (block > _floppy->sect) |
DRS->maxtrack = 1; |
|
/* unlock chained buffers */ |
while (current_count_sectors && CURRENT && |
current_count_sectors >= CURRENT->current_nr_sectors){ |
current_count_sectors -= CURRENT->current_nr_sectors; |
CURRENT->nr_sectors -= CURRENT->current_nr_sectors; |
CURRENT->sector += CURRENT->current_nr_sectors; |
end_request(1); |
} |
if (current_count_sectors && CURRENT){ |
/* "unlock" last subsector */ |
CURRENT->buffer += current_count_sectors <<9; |
CURRENT->current_nr_sectors -= current_count_sectors; |
CURRENT->nr_sectors -= current_count_sectors; |
CURRENT->sector += current_count_sectors; |
return; |
} |
|
if (current_count_sectors && !CURRENT) |
DPRINT("request list destroyed in floppy request done\n"); |
|
} else { |
if (CURRENT->cmd == WRITE) { |
/* record write error information */ |
DRWE->write_errors++; |
if (DRWE->write_errors == 1) { |
DRWE->first_error_sector = CURRENT->sector; |
DRWE->first_error_generation = DRS->generation; |
} |
DRWE->last_error_sector = CURRENT->sector; |
DRWE->last_error_generation = DRS->generation; |
} |
end_request(0); |
} |
} |
|
/* Interrupt handler evaluating the result of the r/w operation */ |
static void rw_interrupt(void) |
{ |
int nr_sectors, ssize, eoc; |
|
if (!DRS->first_read_date) |
DRS->first_read_date = jiffies; |
|
nr_sectors = 0; |
CODE2SIZE; |
|
if(ST1 & ST1_EOC) |
eoc = 1; |
else |
eoc = 0; |
nr_sectors = ((R_TRACK-TRACK)*_floppy->head+R_HEAD-HEAD) * |
_floppy->sect + ((R_SECTOR-SECTOR+eoc) << SIZECODE >> 2) - |
(sector_t % _floppy->sect) % ssize; |
|
#ifdef FLOPPY_SANITY_CHECK |
if (nr_sectors > current_count_sectors + ssize - |
(current_count_sectors + sector_t) % ssize + |
sector_t % ssize){ |
DPRINT("long rw: %x instead of %lx\n", |
nr_sectors, current_count_sectors); |
printk("rs=%d s=%d\n", R_SECTOR, SECTOR); |
printk("rh=%d h=%d\n", R_HEAD, HEAD); |
printk("rt=%d t=%d\n", R_TRACK, TRACK); |
printk("spt=%d st=%d ss=%d\n", SECT_PER_TRACK, |
sector_t, ssize); |
} |
#endif |
INFBOUND(nr_sectors,0); |
SUPBOUND(current_count_sectors, nr_sectors); |
|
switch (interpret_errors()){ |
case 2: |
cont->redo(); |
return; |
case 1: |
if (!current_count_sectors){ |
cont->error(); |
cont->redo(); |
return; |
} |
break; |
case 0: |
if (!current_count_sectors){ |
cont->redo(); |
return; |
} |
current_type[current_drive] = _floppy; |
floppy_sizes[TOMINOR(current_drive) ]= _floppy->size>>1; |
break; |
} |
|
if (probing) { |
if (DP->flags & FTD_MSG) |
DPRINT("Auto-detected floppy type %s in fd%d\n", |
_floppy->name,current_drive); |
current_type[current_drive] = _floppy; |
floppy_sizes[TOMINOR(current_drive)] = _floppy->size >> 1; |
probing = 0; |
} |
|
if (CT(COMMAND) != FD_READ || |
raw_cmd->kernel_data == CURRENT->buffer){ |
/* transfer directly from buffer */ |
cont->done(1); |
} else if (CT(COMMAND) == FD_READ){ |
buffer_track = raw_cmd->track; |
buffer_drive = current_drive; |
INFBOUND(buffer_max, nr_sectors + sector_t); |
} |
cont->redo(); |
} |
|
/* Compute maximal contiguous buffer size. */ |
static int buffer_chain_size(void) |
{ |
struct buffer_head *bh; |
int size; |
char *base; |
|
base = CURRENT->buffer; |
size = CURRENT->current_nr_sectors << 9; |
bh = CURRENT->bh; |
|
if (bh){ |
bh = bh->b_reqnext; |
while (bh && bh->b_data == base + size){ |
size += bh->b_size; |
bh = bh->b_reqnext; |
} |
} |
return size >> 9; |
} |
|
/* Compute the maximal transfer size */ |
static int transfer_size(int ssize, int max_sector, int max_size) |
{ |
SUPBOUND(max_sector, sector_t + max_size); |
|
/* alignment */ |
max_sector -= (max_sector % _floppy->sect) % ssize; |
|
/* transfer size, beginning not aligned */ |
current_count_sectors = max_sector - sector_t ; |
|
return max_sector; |
} |
|
/* |
* Move data from/to the track buffer to/from the buffer cache. |
*/ |
static void copy_buffer(int ssize, int max_sector, int max_sector_2) |
{ |
int remaining; /* number of transferred 512-byte sectors */ |
struct buffer_head *bh; |
char *buffer, *dma_buffer; |
int size; |
|
max_sector = transfer_size(ssize, |
minimum(max_sector, max_sector_2), |
CURRENT->nr_sectors); |
|
if (current_count_sectors <= 0 && CT(COMMAND) == FD_WRITE && |
buffer_max > sector_t + CURRENT->nr_sectors) |
current_count_sectors = minimum(buffer_max - sector_t, |
CURRENT->nr_sectors); |
|
remaining = current_count_sectors << 9; |
#ifdef FLOPPY_SANITY_CHECK |
if ((remaining >> 9) > CURRENT->nr_sectors && |
CT(COMMAND) == FD_WRITE){ |
DPRINT("in copy buffer\n"); |
printk("current_count_sectors=%ld\n", current_count_sectors); |
printk("remaining=%d\n", remaining >> 9); |
printk("CURRENT->nr_sectors=%ld\n",CURRENT->nr_sectors); |
printk("CURRENT->current_nr_sectors=%ld\n", |
CURRENT->current_nr_sectors); |
printk("max_sector=%d\n", max_sector); |
printk("ssize=%d\n", ssize); |
} |
#endif |
|
buffer_max = maximum(max_sector, buffer_max); |
|
dma_buffer = floppy_track_buffer + ((sector_t - buffer_min) << 9); |
|
bh = CURRENT->bh; |
size = CURRENT->current_nr_sectors << 9; |
buffer = CURRENT->buffer; |
|
while (remaining > 0){ |
SUPBOUND(size, remaining); |
#ifdef FLOPPY_SANITY_CHECK |
if (dma_buffer + size > |
floppy_track_buffer + (max_buffer_sectors << 10) || |
dma_buffer < floppy_track_buffer){ |
DPRINT("buffer overrun in copy buffer %d\n", |
(int) ((floppy_track_buffer - dma_buffer) >>9)); |
printk("sector_t=%d buffer_min=%d\n", |
sector_t, buffer_min); |
printk("current_count_sectors=%ld\n", |
current_count_sectors); |
if (CT(COMMAND) == FD_READ) |
printk("read\n"); |
if (CT(COMMAND) == FD_READ) |
printk("write\n"); |
break; |
} |
if (((unsigned long)buffer) % 512) |
DPRINT("%p buffer not aligned\n", buffer); |
#endif |
if (CT(COMMAND) == FD_READ) |
memcpy(buffer, dma_buffer, size); |
else |
memcpy(dma_buffer, buffer, size); |
remaining -= size; |
if (!remaining) |
break; |
|
dma_buffer += size; |
bh = bh->b_reqnext; |
#ifdef FLOPPY_SANITY_CHECK |
if (!bh){ |
DPRINT("bh=null in copy buffer after copy\n"); |
break; |
} |
#endif |
size = bh->b_size; |
buffer = bh->b_data; |
} |
#ifdef FLOPPY_SANITY_CHECK |
if (remaining){ |
if (remaining > 0) |
max_sector -= remaining >> 9; |
DPRINT("weirdness: remaining %d\n", remaining>>9); |
} |
#endif |
} |
|
/* |
* Formulate a read/write request. |
* this routine decides where to load the data (directly to buffer, or to |
* tmp floppy area), how much data to load (the size of the buffer, the whole |
* track, or a single sector) |
* All floppy_track_buffer handling goes in here. If we ever add track buffer |
* allocation on the fly, it should be done here. No other part should need |
* modification. |
*/ |
|
static int make_raw_rw_request(void) |
{ |
int aligned_sector_t; |
int max_sector, max_size, tracksize, ssize; |
|
set_fdc(DRIVE(CURRENT->rq_dev)); |
|
raw_cmd = &default_raw_cmd; |
raw_cmd->flags = FD_RAW_SPIN | FD_RAW_NEED_DISK | FD_RAW_NEED_DISK | |
FD_RAW_NEED_SEEK; |
raw_cmd->cmd_count = NR_RW; |
if (CURRENT->cmd == READ){ |
raw_cmd->flags |= FD_RAW_READ; |
COMMAND = FM_MODE(_floppy,FD_READ); |
} else if (CURRENT->cmd == WRITE){ |
raw_cmd->flags |= FD_RAW_WRITE; |
COMMAND = FM_MODE(_floppy,FD_WRITE); |
} else { |
DPRINT("make_raw_rw_request: unknown command\n"); |
return 0; |
} |
|
max_sector = _floppy->sect * _floppy->head; |
|
TRACK = CURRENT->sector / max_sector; |
sector_t = CURRENT->sector % max_sector; |
if (_floppy->track && TRACK >= _floppy->track) |
return 0; |
HEAD = sector_t / _floppy->sect; |
|
if (((_floppy->stretch & FD_SWAPSIDES) || TESTF(FD_NEED_TWADDLE)) && |
sector_t < _floppy->sect) |
max_sector = _floppy->sect; |
|
/* 2M disks have phantom sectors on the first track */ |
if ((_floppy->rate & FD_2M) && (!TRACK) && (!HEAD)){ |
max_sector = 2 * _floppy->sect / 3; |
if (sector_t >= max_sector){ |
current_count_sectors = minimum(_floppy->sect - sector_t, |
CURRENT->nr_sectors); |
return 1; |
} |
SIZECODE = 2; |
} else |
SIZECODE = FD_SIZECODE(_floppy); |
raw_cmd->rate = _floppy->rate & 0x43; |
if ((_floppy->rate & FD_2M) && |
(TRACK || HEAD) && |
raw_cmd->rate == 2) |
raw_cmd->rate = 1; |
|
if (SIZECODE) |
SIZECODE2 = 0xff; |
else |
SIZECODE2 = 0x80; |
raw_cmd->track = TRACK << STRETCH(_floppy); |
DR_SELECT = UNIT(current_drive) + PH_HEAD(_floppy,HEAD); |
GAP = _floppy->gap; |
CODE2SIZE; |
SECT_PER_TRACK = _floppy->sect << 2 >> SIZECODE; |
SECTOR = ((sector_t % _floppy->sect) << 2 >> SIZECODE) + 1; |
tracksize = _floppy->sect - _floppy->sect % ssize; |
if (tracksize < _floppy->sect){ |
SECT_PER_TRACK ++; |
if (tracksize <= sector_t % _floppy->sect) |
SECTOR--; |
while (tracksize <= sector_t % _floppy->sect){ |
while(tracksize + ssize > _floppy->sect){ |
SIZECODE--; |
ssize >>= 1; |
} |
SECTOR++; SECT_PER_TRACK ++; |
tracksize += ssize; |
} |
max_sector = HEAD * _floppy->sect + tracksize; |
} else if (!TRACK && !HEAD && !(_floppy->rate & FD_2M) && probing) |
max_sector = _floppy->sect; |
|
aligned_sector_t = sector_t - (sector_t % _floppy->sect) % ssize; |
max_size = CURRENT->nr_sectors; |
if ((raw_cmd->track == buffer_track) && |
(current_drive == buffer_drive) && |
(sector_t >= buffer_min) && (sector_t < buffer_max)) { |
/* data already in track buffer */ |
if (CT(COMMAND) == FD_READ) { |
copy_buffer(1, max_sector, buffer_max); |
return 1; |
} |
} else if (aligned_sector_t != sector_t || CURRENT->nr_sectors < ssize){ |
if (CT(COMMAND) == FD_WRITE){ |
if (sector_t + CURRENT->nr_sectors > ssize && |
sector_t + CURRENT->nr_sectors < ssize + ssize) |
max_size = ssize + ssize; |
else |
max_size = ssize; |
} |
raw_cmd->flags &= ~FD_RAW_WRITE; |
raw_cmd->flags |= FD_RAW_READ; |
COMMAND = FM_MODE(_floppy,FD_READ); |
} else if ((unsigned long)CURRENT->buffer < MAX_DMA_ADDRESS) { |
unsigned long dma_limit; |
int direct, indirect; |
|
indirect= transfer_size(ssize,max_sector,max_buffer_sectors*2) - |
sector_t; |
|
/* |
* Do NOT use minimum() here---MAX_DMA_ADDRESS is 64 bits wide |
* on a 64 bit machine! |
*/ |
max_size = buffer_chain_size(); |
#ifndef CONFIG_ARM |
dma_limit = (MAX_DMA_ADDRESS - ((unsigned long) CURRENT->buffer)) >> 9; |
if ((unsigned long) max_size > dma_limit) { |
max_size = dma_limit; |
} |
/* 64 kb boundaries */ |
if (CROSS_64KB(CURRENT->buffer, max_size << 9)) |
max_size = (K_64 - ((long) CURRENT->buffer) % K_64)>>9; |
#endif |
direct = transfer_size(ssize,max_sector,max_size) - sector_t; |
/* |
* We try to read tracks, but if we get too many errors, we |
* go back to reading just one sector at a time. |
* |
* This means we should be able to read a sector even if there |
* are other bad sectors on this track. |
*/ |
if (!direct || |
(indirect * 2 > direct * 3 && |
*errors < DP->max_errors.read_track && |
/*!TESTF(FD_NEED_TWADDLE) &&*/ |
((!probing || (DP->read_track&(1<<DRS->probed_format)))))){ |
max_size = CURRENT->nr_sectors; |
} else { |
raw_cmd->kernel_data = CURRENT->buffer; |
raw_cmd->length = current_count_sectors << 9; |
if (raw_cmd->length == 0){ |
DPRINT("zero dma transfer attempted from make_raw_request\n"); |
DPRINT("indirect=%d direct=%d sector_t=%d", |
indirect, direct, sector_t); |
return 0; |
} |
return 2; |
} |
} |
|
if (CT(COMMAND) == FD_READ) |
max_size = max_sector; /* unbounded */ |
|
/* claim buffer track if needed */ |
if (buffer_track != raw_cmd->track || /* bad track */ |
buffer_drive !=current_drive || /* bad drive */ |
sector_t > buffer_max || |
sector_t < buffer_min || |
((CT(COMMAND) == FD_READ || |
(aligned_sector_t == sector_t && CURRENT->nr_sectors >= ssize))&& |
max_sector > 2 * max_buffer_sectors + buffer_min && |
max_size + sector_t > 2 * max_buffer_sectors + buffer_min) |
/* not enough space */){ |
buffer_track = -1; |
buffer_drive = current_drive; |
buffer_max = buffer_min = aligned_sector_t; |
} |
raw_cmd->kernel_data = floppy_track_buffer + |
((aligned_sector_t-buffer_min)<<9); |
|
if (CT(COMMAND) == FD_WRITE){ |
/* copy write buffer to track buffer. |
* if we get here, we know that the write |
* is either aligned or the data already in the buffer |
* (buffer will be overwritten) */ |
#ifdef FLOPPY_SANITY_CHECK |
if (sector_t != aligned_sector_t && buffer_track == -1) |
DPRINT("internal error offset !=0 on write\n"); |
#endif |
buffer_track = raw_cmd->track; |
buffer_drive = current_drive; |
copy_buffer(ssize, max_sector, 2*max_buffer_sectors+buffer_min); |
} else |
transfer_size(ssize, max_sector, |
2*max_buffer_sectors+buffer_min-aligned_sector_t); |
|
/* round up current_count_sectors to get dma xfer size */ |
raw_cmd->length = sector_t+current_count_sectors-aligned_sector_t; |
raw_cmd->length = ((raw_cmd->length -1)|(ssize-1))+1; |
raw_cmd->length <<= 9; |
#ifdef FLOPPY_SANITY_CHECK |
if ((raw_cmd->length < current_count_sectors << 9) || |
(raw_cmd->kernel_data != CURRENT->buffer && |
CT(COMMAND) == FD_WRITE && |
(aligned_sector_t + (raw_cmd->length >> 9) > buffer_max || |
aligned_sector_t < buffer_min)) || |
raw_cmd->length % (128 << SIZECODE) || |
raw_cmd->length <= 0 || current_count_sectors <= 0){ |
DPRINT("fractionary current count b=%lx s=%lx\n", |
raw_cmd->length, current_count_sectors); |
if (raw_cmd->kernel_data != CURRENT->buffer) |
printk("addr=%d, length=%ld\n", |
(int) ((raw_cmd->kernel_data - |
floppy_track_buffer) >> 9), |
current_count_sectors); |
printk("st=%d ast=%d mse=%d msi=%d\n", |
sector_t, aligned_sector_t, max_sector, max_size); |
printk("ssize=%x SIZECODE=%d\n", ssize, SIZECODE); |
printk("command=%x SECTOR=%d HEAD=%d, TRACK=%d\n", |
COMMAND, SECTOR, HEAD, TRACK); |
printk("buffer drive=%d\n", buffer_drive); |
printk("buffer track=%d\n", buffer_track); |
printk("buffer_min=%d\n", buffer_min); |
printk("buffer_max=%d\n", buffer_max); |
return 0; |
} |
|
if (raw_cmd->kernel_data != CURRENT->buffer){ |
if (raw_cmd->kernel_data < floppy_track_buffer || |
current_count_sectors < 0 || |
raw_cmd->length < 0 || |
raw_cmd->kernel_data + raw_cmd->length > |
floppy_track_buffer + (max_buffer_sectors << 10)){ |
DPRINT("buffer overrun in schedule dma\n"); |
printk("sector_t=%d buffer_min=%d current_count=%ld\n", |
sector_t, buffer_min, |
raw_cmd->length >> 9); |
printk("current_count_sectors=%ld\n", |
current_count_sectors); |
if (CT(COMMAND) == FD_READ) |
printk("read\n"); |
if (CT(COMMAND) == FD_READ) |
printk("write\n"); |
return 0; |
} |
} else if (raw_cmd->length > CURRENT->nr_sectors << 9 || |
current_count_sectors > CURRENT->nr_sectors){ |
DPRINT("buffer overrun in direct transfer\n"); |
return 0; |
} else if (raw_cmd->length < current_count_sectors << 9){ |
DPRINT("more sectors than bytes\n"); |
printk("bytes=%ld\n", raw_cmd->length >> 9); |
printk("sectors=%ld\n", current_count_sectors); |
} |
if (raw_cmd->length == 0){ |
DPRINT("zero dma transfer attempted from make_raw_request\n"); |
return 0; |
} |
#endif |
return 2; |
} |
|
static void redo_fd_request(void) |
{ |
#define REPEAT {request_done(0); continue; } |
kdev_t device; |
int tmp; |
|
lastredo = jiffies; |
if (current_drive < N_DRIVE) |
floppy_off(current_drive); |
|
if (CURRENT && CURRENT->rq_status == RQ_INACTIVE){ |
CLEAR_INTR; |
unlock_fdc(); |
return; |
} |
|
while(1){ |
if (!CURRENT) { |
CLEAR_INTR; |
unlock_fdc(); |
return; |
} |
if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) |
panic(DEVICE_NAME ": request list destroyed"); |
if (CURRENT->bh && !buffer_locked(CURRENT->bh)) |
panic(DEVICE_NAME ": block not locked"); |
|
device = CURRENT->rq_dev; |
set_fdc(DRIVE(device)); |
reschedule_timeout(CURRENTD, "redo fd request", 0); |
|
set_floppy(device); |
raw_cmd = & default_raw_cmd; |
raw_cmd->flags = 0; |
if (start_motor(redo_fd_request)) return; |
disk_change(current_drive); |
if (test_bit(current_drive, &fake_change) || |
TESTF(FD_DISK_CHANGED)){ |
DPRINT("disk absent or changed during operation\n"); |
REPEAT; |
} |
if (!_floppy) { /* Autodetection */ |
if (!probing){ |
DRS->probed_format = 0; |
if (next_valid_format()){ |
DPRINT("no autodetectable formats\n"); |
_floppy = NULL; |
REPEAT; |
} |
} |
probing = 1; |
_floppy = floppy_type+DP->autodetect[DRS->probed_format]; |
} else |
probing = 0; |
errors = & (CURRENT->errors); |
tmp = make_raw_rw_request(); |
if (tmp < 2){ |
request_done(tmp); |
continue; |
} |
|
if (TESTF(FD_NEED_TWADDLE)) |
twaddle(); |
floppy_tq.routine = (void *)(void *) floppy_start; |
queue_task(&floppy_tq, &tq_immediate); |
mark_bh(IMMEDIATE_BH); |
#ifdef DEBUGT |
debugt("queue fd request"); |
#endif |
return; |
} |
#undef REPEAT |
} |
|
static struct cont_t rw_cont={ |
rw_interrupt, |
redo_fd_request, |
bad_flp_intr, |
request_done }; |
|
static struct tq_struct request_tq = |
{ 0, 0, (void *) (void *) redo_fd_request, 0 }; |
|
static void process_fd_request(void) |
{ |
cont = &rw_cont; |
queue_task(&request_tq, &tq_immediate); |
mark_bh(IMMEDIATE_BH); |
} |
|
static void do_fd_request(void) |
{ |
sti(); |
if (fdc_busy){ |
/* fdc busy, this new request will be treated when the |
current one is done */ |
is_alive("do fd request, old request running"); |
return; |
} |
lock_fdc(MAXTIMEOUT,0); |
process_fd_request(); |
is_alive("do fd request"); |
} |
|
static struct cont_t poll_cont={ |
success_and_wakeup, |
floppy_ready, |
generic_failure, |
generic_done }; |
|
static int poll_drive(int interruptible, int flag) |
{ |
int ret; |
/* no auto-sense, just clear dcl */ |
raw_cmd = &default_raw_cmd; |
raw_cmd->flags= flag; |
raw_cmd->track=0; |
raw_cmd->cmd_count=0; |
cont = &poll_cont; |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("setting NEWCHANGE in poll_drive\n"); |
} |
#endif |
SETF(FD_DISK_NEWCHANGE); |
WAIT(floppy_ready); |
return ret; |
} |
|
/* |
* User triggered reset |
* ==================== |
*/ |
|
static void reset_intr(void) |
{ |
printk("weird, reset interrupt called\n"); |
} |
|
static struct cont_t reset_cont={ |
reset_intr, |
success_and_wakeup, |
generic_failure, |
generic_done }; |
|
static int user_reset_fdc(int drive, int arg, int interruptible) |
{ |
int ret; |
|
ret=0; |
LOCK_FDC(drive,interruptible); |
if (arg == FD_RESET_ALWAYS) |
FDCS->reset=1; |
if (FDCS->reset){ |
cont = &reset_cont; |
WAIT(reset_fdc); |
} |
process_fd_request(); |
return ret; |
} |
|
/* |
* Misc Ioctl's and support |
* ======================== |
*/ |
static int fd_copyout(void *param, const void *address, int size) |
{ |
int ret; |
|
ECALL(verify_area(VERIFY_WRITE,param,size)); |
memcpy_tofs(param,(void *) address, size); |
return 0; |
} |
|
static int fd_copyin(void *param, void *address, int size) |
{ |
int ret; |
|
ECALL(verify_area(VERIFY_READ,param,size)); |
memcpy_fromfs((void *) address, param, size); |
return 0; |
} |
|
#define COPYOUT(x) ECALL(fd_copyout((void *)param, &(x), sizeof(x))) |
#define COPYIN(x) ECALL(fd_copyin((void *)param, &(x), sizeof(x))) |
|
static inline const char *drive_name(int type, int drive) |
{ |
struct floppy_struct *floppy; |
|
if (type) |
floppy = floppy_type + type; |
else { |
if (UDP->native_format) |
floppy = floppy_type + UDP->native_format; |
else |
return "(null)"; |
} |
if (floppy->name) |
return floppy->name; |
else |
return "(null)"; |
} |
|
|
/* raw commands */ |
static void raw_cmd_done(int flag) |
{ |
int i; |
|
if (!flag) { |
raw_cmd->flags |= FD_RAW_FAILURE; |
raw_cmd->flags |= FD_RAW_HARDFAILURE; |
} else { |
raw_cmd->reply_count = inr; |
for (i=0; i< raw_cmd->reply_count; i++) |
raw_cmd->reply[i] = reply_buffer[i]; |
|
if (raw_cmd->flags & (FD_RAW_READ | FD_RAW_WRITE)) |
raw_cmd->length = fd_get_dma_residue(); |
|
if ((raw_cmd->flags & FD_RAW_SOFTFAILURE) && |
(!raw_cmd->reply_count || (raw_cmd->reply[0] & 0xc0))) |
raw_cmd->flags |= FD_RAW_FAILURE; |
|
if (disk_change(current_drive)) |
raw_cmd->flags |= FD_RAW_DISK_CHANGE; |
else |
raw_cmd->flags &= ~FD_RAW_DISK_CHANGE; |
if (raw_cmd->flags & FD_RAW_NO_MOTOR_AFTER) |
motor_off_callback(current_drive); |
|
if (raw_cmd->next && |
(!(raw_cmd->flags & FD_RAW_FAILURE) || |
!(raw_cmd->flags & FD_RAW_STOP_IF_FAILURE)) && |
((raw_cmd->flags & FD_RAW_FAILURE) || |
!(raw_cmd->flags &FD_RAW_STOP_IF_SUCCESS))) { |
raw_cmd = raw_cmd->next; |
return; |
} |
} |
generic_done(flag); |
} |
|
|
static struct cont_t raw_cmd_cont={ |
success_and_wakeup, |
floppy_start, |
generic_failure, |
raw_cmd_done |
}; |
|
static inline int raw_cmd_copyout(int cmd, char *param, |
struct floppy_raw_cmd *ptr) |
{ |
struct old_floppy_raw_cmd old_raw_cmd; |
int ret; |
|
while(ptr) { |
if (cmd == OLDFDRAWCMD) { |
old_raw_cmd.flags = ptr->flags; |
old_raw_cmd.data = ptr->data; |
old_raw_cmd.length = ptr->length; |
old_raw_cmd.rate = ptr->rate; |
old_raw_cmd.reply_count = ptr->reply_count; |
memcpy(old_raw_cmd.reply, ptr->reply, 7); |
COPYOUT(old_raw_cmd); |
param += sizeof(old_raw_cmd); |
} else { |
COPYOUT(*ptr); |
param += sizeof(struct floppy_raw_cmd); |
} |
|
if ((ptr->flags & FD_RAW_READ) && ptr->buffer_length){ |
if (ptr->length>=0 && ptr->length<=ptr->buffer_length) |
ECALL(fd_copyout(ptr->data, |
ptr->kernel_data, |
ptr->buffer_length - |
ptr->length)); |
} |
ptr = ptr->next; |
} |
return 0; |
} |
|
|
static void raw_cmd_free(struct floppy_raw_cmd **ptr) |
{ |
struct floppy_raw_cmd *next,*this; |
|
this = *ptr; |
*ptr = 0; |
while(this) { |
if (this->buffer_length) { |
fd_dma_mem_free((unsigned long)this->kernel_data, |
this->buffer_length); |
this->buffer_length = 0; |
} |
next = this->next; |
kfree(this); |
this = next; |
} |
} |
|
|
static inline int raw_cmd_copyin(int cmd, char *param, |
struct floppy_raw_cmd **rcmd) |
{ |
struct floppy_raw_cmd *ptr; |
struct old_floppy_raw_cmd old_raw_cmd; |
int ret; |
int i; |
|
*rcmd = 0; |
while(1) { |
ptr = (struct floppy_raw_cmd *) |
kmalloc(sizeof(struct floppy_raw_cmd), GFP_USER); |
if (!ptr) |
return -ENOMEM; |
*rcmd = ptr; |
if (cmd == OLDFDRAWCMD){ |
COPYIN(old_raw_cmd); |
ptr->flags = old_raw_cmd.flags; |
ptr->data = old_raw_cmd.data; |
ptr->length = old_raw_cmd.length; |
ptr->rate = old_raw_cmd.rate; |
ptr->cmd_count = old_raw_cmd.cmd_count; |
ptr->track = old_raw_cmd.track; |
ptr->phys_length = 0; |
ptr->next = 0; |
ptr->buffer_length = 0; |
memcpy(ptr->cmd, old_raw_cmd.cmd, 9); |
param += sizeof(struct old_floppy_raw_cmd); |
if (ptr->cmd_count > 9) |
return -EINVAL; |
} else { |
COPYIN(*ptr); |
ptr->next = 0; |
ptr->buffer_length = 0; |
param += sizeof(struct floppy_raw_cmd); |
if (ptr->cmd_count > 33) |
/* the command may now also take up the space |
* initially intended for the reply & the |
* reply count. Needed for long 82078 commands |
* such as RESTORE, which takes ... 17 command |
* bytes. Murphy's law #137: When you reserve |
* 16 bytes for a structure, you'll one day |
* discover that you really need 17... |
*/ |
return -EINVAL; |
} |
|
for (i=0; i< 16; i++) |
ptr->reply[i] = 0; |
ptr->resultcode = 0; |
ptr->kernel_data = 0; |
|
if (ptr->flags & (FD_RAW_READ | FD_RAW_WRITE)) { |
if (ptr->length <= 0) |
return -EINVAL; |
ptr->kernel_data =(char*)fd_dma_mem_alloc(ptr->length); |
if (!ptr->kernel_data) |
return -ENOMEM; |
ptr->buffer_length = ptr->length; |
} |
if ( ptr->flags & FD_RAW_READ ) |
ECALL( verify_area( VERIFY_WRITE, ptr->data, |
ptr->length )); |
if (ptr->flags & FD_RAW_WRITE) |
ECALL(fd_copyin(ptr->data, ptr->kernel_data, |
ptr->length)); |
rcmd = & (ptr->next); |
if (!(ptr->flags & FD_RAW_MORE)) |
return 0; |
ptr->rate &= 0x43; |
} |
} |
|
|
static int raw_cmd_ioctl(int cmd, void *param) |
{ |
int drive, ret, ret2; |
struct floppy_raw_cmd *my_raw_cmd; |
|
if (FDCS->rawcmd <= 1) |
FDCS->rawcmd = 1; |
for (drive= 0; drive < N_DRIVE; drive++){ |
if (FDC(drive) != fdc) |
continue; |
if (drive == current_drive){ |
if (UDRS->fd_ref > 1){ |
FDCS->rawcmd = 2; |
break; |
} |
} else if (UDRS->fd_ref){ |
FDCS->rawcmd = 2; |
break; |
} |
} |
|
if (FDCS->reset) |
return -EIO; |
|
ret = raw_cmd_copyin(cmd, param, &my_raw_cmd); |
if (ret) { |
raw_cmd_free(&my_raw_cmd); |
return ret; |
} |
|
raw_cmd = my_raw_cmd; |
cont = &raw_cmd_cont; |
ret=wait_til_done(floppy_start,1); |
#ifdef DCL_DEBUG |
if (DP->flags & FD_DEBUG){ |
DPRINT("calling disk change from raw_cmd ioctl\n"); |
} |
#endif |
|
if (ret != -EINTR && FDCS->reset) |
ret = -EIO; |
|
DRS->track = NO_TRACK; |
|
ret2 = raw_cmd_copyout(cmd, param, my_raw_cmd); |
if (!ret) |
ret = ret2; |
raw_cmd_free(&my_raw_cmd); |
return ret; |
} |
|
static int invalidate_drive(kdev_t rdev) |
{ |
/* invalidate the buffer track to force a reread */ |
set_bit(DRIVE(rdev), &fake_change); |
process_fd_request(); |
check_disk_change(rdev); |
return 0; |
} |
|
|
static inline void clear_write_error(int drive) |
{ |
CLEARSTRUCT(UDRWE); |
} |
|
static inline int set_geometry(unsigned int cmd, struct floppy_struct *g, |
int drive, int type, kdev_t device) |
{ |
int cnt; |
|
/* sanity checking for parameters.*/ |
if (g->sect <= 0 || |
g->head <= 0 || |
g->track <= 0 || |
g->track > UDP->tracks>>STRETCH(g) || |
/* check if reserved bits are set */ |
(g->stretch&~(FD_STRETCH|FD_SWAPSIDES)) != 0) |
return -EINVAL; |
if (type){ |
if (!suser()) |
return -EPERM; |
LOCK_FDC(drive,1); |
for (cnt = 0; cnt < N_DRIVE; cnt++){ |
if (ITYPE(drive_state[cnt].fd_device) == type && |
drive_state[cnt].fd_ref) |
set_bit(drive, &fake_change); |
} |
floppy_type[type] = *g; |
floppy_type[type].name="user format"; |
for (cnt = type << 2; cnt < (type << 2) + 4; cnt++) |
floppy_sizes[cnt]= floppy_sizes[cnt+0x80]= |
floppy_type[type].size>>1; |
process_fd_request(); |
for (cnt = 0; cnt < N_DRIVE; cnt++){ |
if (ITYPE(drive_state[cnt].fd_device) == type && |
drive_state[cnt].fd_ref) |
check_disk_change( |
MKDEV(FLOPPY_MAJOR, |
drive_state[cnt].fd_device)); |
} |
} else { |
LOCK_FDC(drive,1); |
if (cmd != FDDEFPRM) |
/* notice a disk change immediately, else |
* we loose our settings immediately*/ |
CALL(poll_drive(1, FD_RAW_NEED_DISK)); |
user_params[drive] = *g; |
if (buffer_drive == drive) |
SUPBOUND(buffer_max, user_params[drive].sect); |
current_type[drive] = &user_params[drive]; |
floppy_sizes[drive] = user_params[drive].size >> 1; |
if (cmd == FDDEFPRM) |
DRS->keep_data = -1; |
else |
DRS->keep_data = 1; |
/* invalidation. Invalidate only when needed, i.e. |
* when there are already sectors in the buffer cache |
* whose number will change. This is useful, because |
* mtools often changes the geometry of the disk after |
* looking at the boot block */ |
if (DRS->maxblock > user_params[drive].sect || DRS->maxtrack) |
invalidate_drive(device); |
else |
process_fd_request(); |
} |
return 0; |
} |
|
/* handle obsolete ioctl's */ |
static struct translation_entry { |
int newcmd; |
int oldcmd; |
int oldsize; /* size of 0x00xx-style ioctl. Reflects old structures, thus |
* use numeric values. NO SIZEOFS */ |
} translation_table[]= { |
{FDCLRPRM, 0, 0}, |
{FDSETPRM, 1, 28}, |
{FDDEFPRM, 2, 28}, |
{FDGETPRM, 3, 28}, |
{FDMSGON, 4, 0}, |
{FDMSGOFF, 5, 0}, |
{FDFMTBEG, 6, 0}, |
{FDFMTTRK, 7, 12}, |
{FDFMTEND, 8, 0}, |
{FDSETEMSGTRESH, 10, 0}, |
{FDFLUSH, 11, 0}, |
{FDSETMAXERRS, 12, 20}, |
{OLDFDRAWCMD, 30, 0}, |
{FDGETMAXERRS, 14, 20}, |
{FDGETDRVTYP, 16, 16}, |
{FDSETDRVPRM, 20, 88}, |
{FDGETDRVPRM, 21, 88}, |
{FDGETDRVSTAT, 22, 52}, |
{FDPOLLDRVSTAT, 23, 52}, |
{FDRESET, 24, 0}, |
{FDGETFDCSTAT, 25, 40}, |
{FDWERRORCLR, 27, 0}, |
{FDWERRORGET, 28, 24}, |
{FDRAWCMD, 0, 0}, |
{FDEJECT, 0, 0}, |
{FDTWADDLE, 40, 0} }; |
|
static inline int normalize_0x02xx_ioctl(int *cmd, int *size) |
{ |
int i; |
|
for (i=0; i < ARRAY_SIZE(translation_table); i++) { |
if ((*cmd & 0xffff) == (translation_table[i].newcmd & 0xffff)){ |
*size = _IOC_SIZE(*cmd); |
*cmd = translation_table[i].newcmd; |
if (*size > _IOC_SIZE(*cmd)) { |
printk("ioctl not yet supported\n"); |
return -EFAULT; |
} |
return 0; |
} |
} |
return -EINVAL; |
} |
|
static inline int xlate_0x00xx_ioctl(int *cmd, int *size) |
{ |
int i; |
/* old ioctls' for kernels <= 1.3.33 */ |
/* When the next even release will come around, we'll start |
* warning against these. |
* When the next odd release will come around, we'll fail with |
* -EINVAL */ |
if(strcmp(system_utsname.version, "1.4.0") >= 0) |
printk("obsolete floppy ioctl %x\n", *cmd); |
if((system_utsname.version[0] == '1' && |
strcmp(system_utsname.version, "1.5.0") >= 0) || |
(system_utsname.version[0] >= '2' && |
strcmp(system_utsname.version, "2.1.0") >= 0)) |
return -EINVAL; |
for (i=0; i < ARRAY_SIZE(translation_table); i++) { |
if (*cmd == translation_table[i].oldcmd) { |
*size = translation_table[i].oldsize; |
*cmd = translation_table[i].newcmd; |
return 0; |
} |
} |
return -EINVAL; |
} |
|
static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, |
unsigned long param) |
{ |
#define IOCTL_MODE_BIT 8 |
#define OPEN_WRITE_BIT 16 |
#define IOCTL_ALLOWED (filp && (filp->f_mode & IOCTL_MODE_BIT)) |
#define OUT(c,x) case c: outparam = (const char *) (x); break |
#define IN(c,x,tag) case c: *(x) = inparam. tag ; return 0 |
|
int i,drive,type; |
kdev_t device; |
int ret; |
int size; |
union inparam { |
struct floppy_struct g; /* geometry */ |
struct format_descr f; |
struct floppy_max_errors max_errors; |
struct floppy_drive_params dp; |
} inparam; /* parameters coming from user space */ |
const char *outparam; /* parameters passed back to user space */ |
|
device = inode->i_rdev; |
switch (cmd) { |
RO_IOCTLS(device,param); |
} |
type = TYPE(device); |
drive = DRIVE(device); |
|
/* convert compatibility eject ioctls into floppy eject ioctl. |
* We do this in order to provide a means to eject floppy disks before |
* installing the new fdutils package */ |
if(cmd == CDROMEJECT || /* CD-ROM eject */ |
cmd == 0x6470 /* SunOS floppy eject */) { |
DPRINT("obsolete eject ioctl\n"); |
DPRINT("please use floppycontrol --eject\n"); |
cmd = FDEJECT; |
} |
|
/* convert the old style command into a new style command */ |
if ((cmd & 0xff00) == 0x0200) { |
ECALL(normalize_0x02xx_ioctl(&cmd, &size)); |
} else if ((cmd & 0xff00) == 0x0000) { |
ECALL(xlate_0x00xx_ioctl(&cmd, &size)); |
} else |
return -EINVAL; |
|
/* permission checks */ |
if (((cmd & 0x80) && !suser()) || |
((cmd & 0x40) && !IOCTL_ALLOWED)) |
return -EPERM; |
|
/* verify writability of result, and fail early */ |
if (_IOC_DIR(cmd) & _IOC_READ) |
ECALL(verify_area(VERIFY_WRITE,(void *) param, size)); |
|
/* copyin */ |
CLEARSTRUCT(&inparam); |
if (_IOC_DIR(cmd) & _IOC_WRITE) |
ECALL(fd_copyin((void *)param, &inparam, size)) |
|
switch (cmd) { |
case FDEJECT: |
if(UDRS->fd_ref != 1) |
/* somebody else has this drive open */ |
return -EBUSY; |
LOCK_FDC(drive,1); |
|
/* do the actual eject. Fails on |
* non-Sparc architectures */ |
ret=fd_eject(UNIT(drive)); |
|
USETF(FD_DISK_CHANGED); |
USETF(FD_VERIFY); |
process_fd_request(); |
return ret; |
case FDCLRPRM: |
LOCK_FDC(drive,1); |
current_type[drive] = NULL; |
floppy_sizes[drive] = MAX_DISK_SIZE; |
UDRS->keep_data = 0; |
return invalidate_drive(device); |
case FDSETPRM: |
case FDDEFPRM: |
return set_geometry(cmd, & inparam.g, |
drive, type, device); |
case FDGETPRM: |
LOCK_FDC(drive,1); |
CALL(poll_drive(1,0)); |
process_fd_request(); |
if (type) |
outparam = (char *) &floppy_type[type]; |
else |
outparam = (char *) current_type[drive]; |
if(!outparam) |
return -ENODEV; |
break; |
|
case FDMSGON: |
UDP->flags |= FTD_MSG; |
return 0; |
case FDMSGOFF: |
UDP->flags &= ~FTD_MSG; |
return 0; |
|
case FDFMTBEG: |
LOCK_FDC(drive,1); |
CALL(poll_drive(1, FD_RAW_NEED_DISK)); |
ret = UDRS->flags; |
process_fd_request(); |
if(ret & FD_VERIFY) |
return -ENODEV; |
if(!(ret & FD_DISK_WRITABLE)) |
return -EROFS; |
return 0; |
case FDFMTTRK: |
if (UDRS->fd_ref != 1) |
return -EBUSY; |
return do_format(device, &inparam.f); |
case FDFMTEND: |
case FDFLUSH: |
LOCK_FDC(drive,1); |
return invalidate_drive(device); |
|
case FDSETEMSGTRESH: |
UDP->max_errors.reporting = |
(unsigned short) (param & 0x0f); |
return 0; |
OUT(FDGETMAXERRS, &UDP->max_errors); |
IN(FDSETMAXERRS, &UDP->max_errors, max_errors); |
|
case FDGETDRVTYP: |
outparam = drive_name(type,drive); |
SUPBOUND(size,strlen(outparam)+1); |
break; |
|
IN(FDSETDRVPRM, UDP, dp); |
OUT(FDGETDRVPRM, UDP); |
|
case FDPOLLDRVSTAT: |
LOCK_FDC(drive,1); |
CALL(poll_drive(1, FD_RAW_NEED_DISK)); |
process_fd_request(); |
/* fall through */ |
OUT(FDGETDRVSTAT, UDRS); |
|
case FDRESET: |
return user_reset_fdc(drive, (int)param, 1); |
|
OUT(FDGETFDCSTAT,UFDCS); |
|
case FDWERRORCLR: |
CLEARSTRUCT(UDRWE); |
return 0; |
OUT(FDWERRORGET,UDRWE); |
|
case OLDFDRAWCMD: |
case FDRAWCMD: |
if (type) |
return -EINVAL; |
LOCK_FDC(drive,1); |
set_floppy(device); |
CALL(i = raw_cmd_ioctl(cmd,(void *) param)); |
process_fd_request(); |
return i; |
|
case FDTWADDLE: |
LOCK_FDC(drive,1); |
twaddle(); |
process_fd_request(); |
return 0; |
|
default: |
return -EINVAL; |
} |
|
if (_IOC_DIR(cmd) & _IOC_READ) |
return fd_copyout((void *)param, outparam, size); |
else |
return 0; |
#undef IOCTL_ALLOWED |
#undef OUT |
#undef IN |
} |
|
static void config_types(void) |
{ |
int first=1; |
int drive; |
|
/* read drive info out of physical CMOS */ |
drive=0; |
if (!UDP->cmos) |
UDP->cmos= FLOPPY0_TYPE; |
drive=1; |
if (!UDP->cmos && FLOPPY1_TYPE) |
UDP->cmos = FLOPPY1_TYPE; |
|
/* XXX */ |
/* additional physical CMOS drive detection should go here */ |
|
for (drive=0; drive < N_DRIVE; drive++){ |
if (UDP->cmos >= 16) |
UDP->cmos = 0; |
if (UDP->cmos >= 0 && UDP->cmos <= NUMBER(default_drive_params)) |
memcpy((char *) UDP, |
(char *) (&default_drive_params[(int)UDP->cmos].params), |
sizeof(struct floppy_drive_params)); |
if (UDP->cmos){ |
if (first) |
printk(KERN_INFO "Floppy drive(s): "); |
else |
printk(", "); |
first=0; |
if (UDP->cmos > 0){ |
allowed_drive_mask |= 1 << drive; |
printk("fd%d is %s", drive, |
default_drive_params[(int)UDP->cmos].name); |
} else |
printk("fd%d is unknown type %d",drive, |
(unsigned int)UDP->cmos); |
} |
} |
if (!first) |
printk("\n"); |
} |
|
static int floppy_read(struct inode * inode, struct file * filp, |
char * buf, int count) |
{ |
int drive = DRIVE(inode->i_rdev); |
|
check_disk_change(inode->i_rdev); |
if (UTESTF(FD_DISK_CHANGED)) |
return -ENXIO; |
return block_read(inode, filp, buf, count); |
} |
|
static int floppy_write(struct inode * inode, struct file * filp, |
const char * buf, int count) |
{ |
int block; |
int ret; |
int drive = DRIVE(inode->i_rdev); |
|
if (!UDRS->maxblock) |
UDRS->maxblock=1;/* make change detectable */ |
check_disk_change(inode->i_rdev); |
if (UTESTF(FD_DISK_CHANGED)) |
return -ENXIO; |
if (!UTESTF(FD_DISK_WRITABLE)) |
return -EROFS; |
block = (filp->f_pos + count) >> 9; |
INFBOUND(UDRS->maxblock, block); |
ret= block_write(inode, filp, buf, count); |
return ret; |
} |
|
static void floppy_release(struct inode * inode, struct file * filp) |
{ |
int drive; |
|
drive = DRIVE(inode->i_rdev); |
|
if (!filp || (filp->f_mode & (2 | OPEN_WRITE_BIT))) |
/* if the file is mounted OR (writable now AND writable at |
* open time) Linus: Does this cover all cases? */ |
block_fsync(inode,filp); |
|
if (UDRS->fd_ref < 0) |
UDRS->fd_ref=0; |
else if (!UDRS->fd_ref--) { |
DPRINT("floppy_release with fd_ref == 0"); |
UDRS->fd_ref = 0; |
} |
floppy_release_irq_and_dma(); |
} |
|
/* |
* 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. |
*/ |
#define RETERR(x) do{floppy_release(inode,filp); return -(x);}while(0) |
|
static int floppy_open(struct inode * inode, struct file * filp) |
{ |
int drive; |
int old_dev; |
int try; |
char *tmp; |
|
if (!filp) { |
DPRINT("Weird, open called with filp=0\n"); |
return -EIO; |
} |
|
drive = DRIVE(inode->i_rdev); |
if (drive >= N_DRIVE || |
!(allowed_drive_mask & (1 << drive)) || |
fdc_state[FDC(drive)].version == FDC_NONE) |
return -ENXIO; |
|
if (TYPE(inode->i_rdev) >= NUMBER(floppy_type)) |
return -ENXIO; |
old_dev = UDRS->fd_device; |
if (UDRS->fd_ref && old_dev != MINOR(inode->i_rdev)) |
return -EBUSY; |
|
if (!UDRS->fd_ref && (UDP->flags & FD_BROKEN_DCL)){ |
USETF(FD_DISK_CHANGED); |
USETF(FD_VERIFY); |
} |
|
if (UDRS->fd_ref == -1 || |
(UDRS->fd_ref && (filp->f_flags & O_EXCL))) |
return -EBUSY; |
|
if (floppy_grab_irq_and_dma()) |
return -EBUSY; |
|
if (filp->f_flags & O_EXCL) |
UDRS->fd_ref = -1; |
else |
UDRS->fd_ref++; |
|
if (!floppy_track_buffer){ |
/* if opening an ED drive, reserve a big buffer, |
* else reserve a small one */ |
if ((UDP->cmos == 6) || (UDP->cmos == 5)) |
try = 64; /* Only 48 actually useful */ |
else |
try = 32; /* Only 24 actually useful */ |
|
tmp=(char *)fd_dma_mem_alloc(1024 * try); |
if (!tmp) { |
try >>= 1; /* buffer only one side */ |
INFBOUND(try, 16); |
tmp= (char *)fd_dma_mem_alloc(1024*try); |
} |
if (!tmp) { |
DPRINT("Unable to allocate DMA memory\n"); |
RETERR(ENXIO); |
} |
if (floppy_track_buffer) |
fd_dma_mem_free((unsigned long)tmp,try*1024); |
else { |
buffer_min = buffer_max = -1; |
floppy_track_buffer = tmp; |
max_buffer_sectors = try; |
} |
} |
|
UDRS->fd_device = MINOR(inode->i_rdev); |
if (old_dev != -1 && old_dev != MINOR(inode->i_rdev)) { |
if (buffer_drive == drive) |
buffer_track = -1; |
invalidate_buffers(MKDEV(FLOPPY_MAJOR,old_dev)); |
} |
|
/* Allow ioctls if we have write-permissions even if read-only open */ |
if ((filp->f_mode & 2) || (permission(inode,2) == 0)) |
filp->f_mode |= IOCTL_MODE_BIT; |
if (filp->f_mode & 2) |
filp->f_mode |= OPEN_WRITE_BIT; |
|
if (UFDCS->rawcmd == 1) |
UFDCS->rawcmd = 2; |
|
if (filp->f_flags & O_NDELAY) |
return 0; |
if (filp->f_mode & 3) { |
UDRS->last_checked = 0; |
check_disk_change(inode->i_rdev); |
if (UTESTF(FD_DISK_CHANGED)) |
RETERR(ENXIO); |
} |
if ((filp->f_mode & 2) && !(UTESTF(FD_DISK_WRITABLE))) |
RETERR(EROFS); |
return 0; |
#undef RETERR |
} |
|
/* |
* Check if the disk has been changed or if a change has been faked. |
*/ |
static int check_floppy_change(kdev_t dev) |
{ |
int drive = DRIVE(dev); |
|
if (MAJOR(dev) != MAJOR_NR) { |
DPRINT("check_floppy_change: not a floppy\n"); |
return 0; |
} |
|
if (UTESTF(FD_DISK_CHANGED) || UTESTF(FD_VERIFY)) |
return 1; |
|
if (UDP->checkfreq < jiffies - UDRS->last_checked){ |
lock_fdc(drive,0); |
poll_drive(0,0); |
process_fd_request(); |
} |
|
if (UTESTF(FD_DISK_CHANGED) || |
UTESTF(FD_VERIFY) || |
test_bit(drive, &fake_change) || |
(!TYPE(dev) && !current_type[drive])) |
return 1; |
return 0; |
} |
|
/* revalidate the floppy disk, i.e. trigger format autodetection by reading |
* the bootblock (block 0). "Autodetection" is also needed to check whether |
* there is a disk in the drive at all... Thus we also do it for fixed |
* geometry formats */ |
static int floppy_revalidate(kdev_t dev) |
{ |
#define NO_GEOM (!current_type[drive] && !TYPE(dev)) |
struct buffer_head * bh; |
int drive=DRIVE(dev); |
int cf; |
|
if (UTESTF(FD_DISK_CHANGED) || |
UTESTF(FD_VERIFY) || |
test_bit(drive, &fake_change) || |
NO_GEOM){ |
lock_fdc(drive,0); |
cf = UTESTF(FD_DISK_CHANGED) || UTESTF(FD_VERIFY); |
if (!(cf || test_bit(drive, &fake_change) || NO_GEOM)){ |
process_fd_request(); /*already done by another thread*/ |
return 0; |
} |
UDRS->maxblock = 0; |
UDRS->maxtrack = 0; |
if (buffer_drive == drive) |
buffer_track = -1; |
clear_bit(drive, &fake_change); |
UCLEARF(FD_DISK_CHANGED); |
if (cf) |
UDRS->generation++; |
if (NO_GEOM){ |
/* auto-sensing */ |
int size = floppy_blocksizes[MINOR(dev)]; |
if (!size) |
size = 1024; |
if (!(bh = getblk(dev,0,size))){ |
process_fd_request(); |
return 1; |
} |
if (bh && !buffer_uptodate(bh)) |
ll_rw_block(READ, 1, &bh); |
process_fd_request(); |
wait_on_buffer(bh); |
brelse(bh); |
return 0; |
} |
if (cf) |
poll_drive(0, FD_RAW_NEED_DISK); |
process_fd_request(); |
} |
return 0; |
} |
|
static struct file_operations floppy_fops = { |
NULL, /* lseek - default */ |
floppy_read, /* read - general block-dev read */ |
floppy_write, /* write - general block-dev write */ |
NULL, /* readdir - bad */ |
NULL, /* select */ |
fd_ioctl, /* ioctl */ |
NULL, /* mmap */ |
floppy_open, /* open */ |
floppy_release, /* release */ |
block_fsync, /* fsync */ |
NULL, /* fasync */ |
check_floppy_change, /* media_change */ |
floppy_revalidate, /* revalidate */ |
}; |
|
/* |
* Floppy Driver initialization |
* ============================= |
*/ |
|
/* Determine the floppy disk controller type */ |
/* This routine was written by David C. Niemi */ |
static char get_fdc_version(void) |
{ |
int r; |
|
output_byte(FD_DUMPREGS); /* 82072 and better know DUMPREGS */ |
if (FDCS->reset) |
return FDC_NONE; |
if ((r = result()) <= 0x00) |
return FDC_NONE; /* No FDC present ??? */ |
if ((r==1) && (reply_buffer[0] == 0x80)){ |
printk(KERN_INFO "FDC %d is an 8272A\n",fdc); |
return FDC_8272A; /* 8272a/765 don't know DUMPREGS */ |
} |
if (r != 10) { |
printk("FDC %d init: DUMPREGS: unexpected return of %d bytes.\n", |
fdc, r); |
return FDC_UNKNOWN; |
} |
|
if(!fdc_configure()) { |
printk(KERN_INFO "FDC %d is an 82072\n",fdc); |
return FDC_82072; /* 82072 doesn't know CONFIGURE */ |
} |
|
output_byte(FD_PERPENDICULAR); |
if(need_more_output() == MORE_OUTPUT) { |
output_byte(0); |
} else { |
printk(KERN_INFO "FDC %d is an 82072A\n", fdc); |
return FDC_82072A; /* 82072A as found on Sparcs. */ |
} |
|
output_byte(FD_UNLOCK); |
r = result(); |
if ((r == 1) && (reply_buffer[0] == 0x80)){ |
printk(KERN_INFO "FDC %d is a pre-1991 82077\n", fdc); |
return FDC_82077_ORIG; /* Pre-1991 82077, doesn't know |
* LOCK/UNLOCK */ |
} |
if ((r != 1) || (reply_buffer[0] != 0x00)) { |
printk("FDC %d init: UNLOCK: unexpected return of %d bytes.\n", |
fdc, r); |
return FDC_UNKNOWN; |
} |
output_byte(FD_PARTID); |
r = result(); |
if (r != 1) { |
printk("FDC %d init: PARTID: unexpected return of %d bytes.\n", |
fdc, r); |
return FDC_UNKNOWN; |
} |
if (reply_buffer[0] == 0x80) { |
printk(KERN_INFO "FDC %d is a post-1991 82077\n",fdc); |
return FDC_82077; /* Revised 82077AA passes all the tests */ |
} |
switch (reply_buffer[0] >> 5) { |
case 0x0: |
/* Either a 82078-1 or a 82078SL running at 5Volt */ |
printk(KERN_INFO "FDC %d is an 82078.\n",fdc); |
return FDC_82078; |
case 0x1: |
printk(KERN_INFO "FDC %d is a 44pin 82078\n",fdc); |
return FDC_82078; |
case 0x2: |
printk(KERN_INFO "FDC %d is a S82078B\n", fdc); |
return FDC_S82078B; |
case 0x3: |
printk(KERN_INFO "FDC %d is a National Semiconductor PC87306\n", fdc); |
return FDC_87306; |
default: |
printk(KERN_INFO "FDC %d init: 82078 variant with unknown PARTID=%d.\n", |
fdc, reply_buffer[0] >> 5); |
return FDC_82078_UNKN; |
} |
} /* get_fdc_version */ |
|
/* lilo configuration */ |
|
/* we make the invert_dcl function global. One day, somebody might |
* want to centralize all thinkpad related options into one lilo option, |
* there are just so many thinkpad related quirks! */ |
void floppy_invert_dcl(int *ints,int param) |
{ |
int i; |
|
for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ |
if (param) |
default_drive_params[i].params.flags |= 0x80; |
else |
default_drive_params[i].params.flags &= ~0x80; |
} |
DPRINT("Configuring drives for inverted dcl\n"); |
} |
|
static void daring(int *ints,int param) |
{ |
int i; |
|
for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ |
if (param){ |
default_drive_params[i].params.select_delay = 0; |
default_drive_params[i].params.flags |= FD_SILENT_DCL_CLEAR; |
} else { |
default_drive_params[i].params.select_delay = 2*HZ/100; |
default_drive_params[i].params.flags &= ~FD_SILENT_DCL_CLEAR; |
} |
} |
DPRINT("Assuming %s floppy hardware\n", param ? "standard" : "broken"); |
} |
|
static void set_cmos(int *ints, int dummy) |
{ |
int current_drive=0; |
|
if (ints[0] != 2){ |
DPRINT("wrong number of parameter for cmos\n"); |
return; |
} |
current_drive = ints[1]; |
if (current_drive < 0 || current_drive >= 8){ |
DPRINT("bad drive for set_cmos\n"); |
return; |
} |
if (current_drive >= 4 && !FDC2) |
FDC2 = 0x370; |
if (ints[2] <= 0 || |
(ints[2] >= NUMBER(default_drive_params) && ints[2] != 16)){ |
DPRINT("bad cmos code %d\n", ints[2]); |
return; |
} |
DP->cmos = ints[2]; |
DPRINT("setting cmos code to %d\n", ints[2]); |
} |
|
static struct param_table { |
const char *name; |
void (*fn)(int *ints, int param); |
int *var; |
int def_param; |
} config_params[]={ |
{ "allowed_drive_mask", 0, &allowed_drive_mask, 0xff }, |
{ "all_drives", 0, &allowed_drive_mask, 0xff }, |
{ "asus_pci", 0, &allowed_drive_mask, 0x33 }, |
|
{ "daring", daring, 0, 1}, |
|
{ "two_fdc", 0, &FDC2, 0x370 }, |
{ "one_fdc", 0, &FDC2, 0 }, |
|
{ "thinkpad", floppy_invert_dcl, 0, 1 }, |
|
{ "nodma", 0, &use_virtual_dma, 1 }, |
{ "omnibook", 0, &use_virtual_dma, 1 }, |
{ "dma", 0, &use_virtual_dma, 0 }, |
|
{ "fifo_depth", 0, &fifo_depth, 0xa }, |
{ "nofifo", 0, &no_fifo, 0x20 }, |
{ "usefifo", 0, &no_fifo, 0 }, |
|
{ "cmos", set_cmos, 0, 0 }, |
|
{ "unexpected_interrupts", 0, &print_unex, 1 }, |
{ "no_unexpected_interrupts", 0, &print_unex, 0 }, |
{ "L40SX", 0, &print_unex, 0 } }; |
|
#define FLOPPY_SETUP |
void floppy_setup(char *str, int *ints) |
{ |
int i; |
int param; |
if (str) |
for (i=0; i< ARRAY_SIZE(config_params); i++){ |
if (strcmp(str,config_params[i].name) == 0){ |
if (ints[0]) |
param = ints[1]; |
else |
param = config_params[i].def_param; |
if(config_params[i].fn) |
config_params[i].fn(ints,param); |
if(config_params[i].var) { |
DPRINT("%s=%d\n", str, param); |
*config_params[i].var = param; |
} |
return; |
} |
} |
if (str) { |
DPRINT("unknown floppy option [%s]\n", str); |
|
DPRINT("allowed options are:"); |
for (i=0; i< ARRAY_SIZE(config_params); i++) |
printk(" %s",config_params[i].name); |
printk("\n"); |
} else |
DPRINT("botched floppy option\n"); |
DPRINT("Read linux/arch/arm/drivers/block/README.fd\n"); |
} |
|
int floppy_init(void) |
{ |
int i,unit,drive; |
int have_no_fdc= -EIO; |
|
raw_cmd = NULL; |
|
if (register_blkdev(MAJOR_NR,"fd",&floppy_fops)) { |
printk("Unable to get major %d for floppy\n",MAJOR_NR); |
return -EBUSY; |
} |
|
for (i=0; i<256; i++) |
if (ITYPE(i)) |
floppy_sizes[i] = floppy_type[ITYPE(i)].size >> 1; |
else |
floppy_sizes[i] = MAX_DISK_SIZE; |
|
blk_size[MAJOR_NR] = floppy_sizes; |
blksize_size[MAJOR_NR] = floppy_blocksizes; |
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; |
reschedule_timeout(MAXTIMEOUT, "floppy init", MAXTIMEOUT); |
config_types(); |
|
for (i = 0; i < N_FDC; i++) { |
fdc = i; |
CLEARSTRUCT(FDCS); |
FDCS->dtr = -1; |
FDCS->dor = 0x4; |
#ifdef __sparc__ |
/*sparcs don't have a DOR reset which we can fall back on to*/ |
FDCS->version = FDC_82072A; |
#endif |
} |
|
fdc_state[0].address = FDC1; |
#if N_FDC > 1 |
fdc_state[1].address = FDC2; |
#endif |
|
if (floppy_grab_irq_and_dma()){ |
unregister_blkdev(MAJOR_NR,"fd"); |
return -EBUSY; |
} |
|
/* initialise drive state */ |
for (drive = 0; drive < N_DRIVE; drive++) { |
CLEARSTRUCT(UDRS); |
CLEARSTRUCT(UDRWE); |
UDRS->flags = FD_VERIFY | FD_DISK_NEWCHANGE | FD_DISK_CHANGED; |
UDRS->fd_device = -1; |
floppy_track_buffer = NULL; |
max_buffer_sectors = 0; |
} |
|
for (i = 0; i < N_FDC; i++) { |
fdc = i; |
FDCS->driver_version = FD_DRIVER_VERSION; |
for (unit=0; unit<4; unit++) |
FDCS->track[unit] = 0; |
if (FDCS->address == -1) |
continue; |
FDCS->rawcmd = 2; |
if (user_reset_fdc(-1,FD_RESET_ALWAYS,0)){ |
FDCS->address = -1; |
FDCS->version = FDC_NONE; |
continue; |
} |
/* Try to determine the floppy controller type */ |
FDCS->version = get_fdc_version(); |
if (FDCS->version == FDC_NONE){ |
FDCS->address = -1; |
continue; |
} |
|
request_region(FDCS->address, 6, "floppy"); |
request_region(FDCS->address+7, 1, "floppy DIR"); |
/* address + 6 is reserved, and may be taken by IDE. |
* Unfortunately, Adaptec doesn't know this :-(, */ |
|
have_no_fdc = 0; |
/* Not all FDCs seem to be able to handle the version command |
* properly, so force a reset for the standard FDC clones, |
* to avoid interrupt garbage. |
*/ |
user_reset_fdc(-1,FD_RESET_ALWAYS,0); |
#ifdef CONFIG_ARM |
fd_scandrives(); |
#endif |
} |
fdc=0; |
del_timer(&fd_timeout); |
current_drive = 0; |
floppy_release_irq_and_dma(); |
initialising=0; |
if (have_no_fdc) { |
DPRINT("no floppy controllers found\n"); |
unregister_blkdev(MAJOR_NR,"fd"); |
} |
return have_no_fdc; |
} |
|
static int floppy_grab_irq_and_dma(void) |
{ |
int i; |
unsigned long flags; |
|
INT_OFF; |
if (usage_count++){ |
INT_ON; |
return 0; |
} |
INT_ON; |
MOD_INC_USE_COUNT; |
for (i=0; i< N_FDC; i++){ |
if (fdc_state[i].address != -1){ |
fdc = i; |
reset_fdc_info(1); |
fd_setdor(FDCS->dor); |
} |
} |
fdc = 0; |
set_dor(0, ~0, 8); /* avoid immediate interrupt */ |
|
if (fd_request_irq()) { |
DPRINT("Unable to grab IRQ%d for the floppy driver\n", |
FLOPPY_IRQ); |
MOD_DEC_USE_COUNT; |
usage_count--; |
return -1; |
} |
if (fd_request_dma()) { |
DPRINT("Unable to grab DMA%d for the floppy driver\n", |
FLOPPY_DMA); |
fd_free_irq(); |
MOD_DEC_USE_COUNT; |
usage_count--; |
return -1; |
} |
for (fdc = 0; fdc < N_FDC; fdc++) |
if (FDCS->address != -1) |
fd_setdor(FDCS->dor); |
fdc = 0; |
fd_enable_irq(); |
return 0; |
} |
|
static void floppy_release_irq_and_dma(void) |
{ |
#ifdef FLOPPY_SANITY_CHECK |
int drive; |
#endif |
long tmpsize; |
unsigned long tmpaddr; |
unsigned long flags; |
|
INT_OFF; |
if (--usage_count){ |
INT_ON; |
return; |
} |
INT_ON; |
fd_disable_dma(); |
fd_free_dma(); |
fd_disable_irq(); |
fd_free_irq(); |
|
set_dor(0, ~0, 8); |
#if N_FDC > 1 |
set_dor(1, ~8, 0); |
#endif |
floppy_enable_hlt(); |
|
if (floppy_track_buffer && max_buffer_sectors) { |
tmpsize = max_buffer_sectors*1024; |
tmpaddr = (unsigned long)floppy_track_buffer; |
floppy_track_buffer = NULL; |
max_buffer_sectors = 0; |
buffer_min = buffer_max = -1; |
fd_dma_mem_free(tmpaddr, tmpsize); |
} |
|
#ifdef FLOPPY_SANITY_CHECK |
for (drive=0; drive < N_FDC * 4; drive++) |
if (motor_off_timer[drive].next) |
printk("motor off timer %d still active\n", drive); |
|
if (fd_timeout.next) |
printk("floppy timer still active:%s\n", timeout_message); |
if (fd_timer.next) |
printk("auxiliary floppy timer still active\n"); |
if (floppy_tq.sync) |
printk("task queue still active\n"); |
#endif |
MOD_DEC_USE_COUNT; |
} |
|
|
#ifdef MODULE |
|
extern char *get_options(char *str, int *ints); |
|
char *floppy=NULL; |
|
static void parse_floppy_cfg_string(char *cfg) |
{ |
char *ptr; |
int ints[11]; |
|
while(*cfg) { |
for(ptr = cfg;*cfg && *cfg != ' ' && *cfg != '\t'; cfg++); |
if(*cfg) { |
*cfg = '\0'; |
cfg++; |
} |
if(*ptr) |
floppy_setup(get_options(ptr,ints),ints); |
} |
} |
|
static void mod_setup(char *pattern, void (*setup)(char *, int *)) |
{ |
unsigned long i; |
char c; |
int j; |
int match; |
char buffer[100]; |
int ints[11]; |
int length = strlen(pattern)+1; |
|
match=0; |
j=1; |
|
for (i=current->mm->env_start; i< current->mm->env_end; i ++){ |
c= get_fs_byte(i); |
if (match){ |
if (j==99) |
c='\0'; |
buffer[j] = c; |
if (!c || c == ' ' || c == '\t'){ |
if (j){ |
buffer[j] = '\0'; |
setup(get_options(buffer,ints),ints); |
} |
j=0; |
} else |
j++; |
if (!c) |
break; |
continue; |
} |
if ((!j && !c) || (j && c == pattern[j-1])) |
j++; |
else |
j=0; |
if (j==length){ |
match=1; |
j=0; |
} |
} |
} |
|
|
#ifdef __cplusplus |
extern "C" { |
#endif |
int init_module(void) |
{ |
printk(KERN_INFO "inserting floppy driver for %s\n", kernel_version); |
|
if(floppy) |
parse_floppy_cfg_string(floppy); |
else |
mod_setup("floppy=", floppy_setup); |
|
return floppy_init(); |
} |
|
void cleanup_module(void) |
{ |
int fdc, dummy; |
|
for (fdc=0; fdc<2; fdc++) |
if (FDCS->address != -1){ |
release_region(FDCS->address, 6); |
release_region(FDCS->address+7, 1); |
} |
|
unregister_blkdev(MAJOR_NR, "fd"); |
|
blk_dev[MAJOR_NR].request_fn = 0; |
/* eject disk, if any */ |
dummy = fd_eject(0); |
} |
|
#ifdef __cplusplus |
} |
#endif |
|
#else |
/* eject the boot floppy (if we need the drive for a different root floppy) */ |
/* This should only be called at boot time when we're sure that there's no |
* resource contention. */ |
void floppy_eject(void) |
{ |
int dummy; |
floppy_grab_irq_and_dma(); |
lock_fdc(MAXTIMEOUT,0); |
dummy=fd_eject(0); |
process_fd_request(); |
floppy_release_irq_and_dma(); |
} |
#endif |
/fd1772.c
0,0 → 1,1671
/* |
* linux/kernel/arch/arm/drivers/block/fd1772.c |
* Based on ataflop.c in the m68k Linux |
* Copyright (C) 1993 Greg Harp |
* Atari Support by Bjoern Brauel, Roman Hodek |
* Archimedes Support by Dave Gilbert (gilbertd@cs.man.ac.uk) |
* |
* 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 FDC1772CMDADD_H flag since |
* the FDC1772 will otherwise wait forever when no disk is inserted... |
* |
* Things left to do: |
* - Formatting |
* - Maybe a better strategy for disk change detection (does anyone |
* know one?) |
* - There are some strange problems left: The strangest one is |
* that, at least on my TT (4+4MB), the first 2 Bytes of the last |
* page of the TT-Ram (!) change their contents (some bits get |
* set) while a floppy DMA is going on. But there are no accesses |
* to these memory locations from the kernel... (I tested that by |
* making the page read-only). I cannot explain what's going on... |
* - Sometimes the drive-change-detection stops to work. The |
* function is still called, but the WP bit always reads as 0... |
* Maybe a problem with the status reg mode or a timing problem. |
* Note 10/12/94: The change detection now seems to work reliably. |
* There is no proof, but I've seen no hang for a long time... |
* |
* ARCHIMEDES changes: (gilbertd@cs.man.ac.uk) |
* 26/12/95 - Changed all names starting with FDC to FDC1772 |
* Removed all references to clock speed of FDC - we're stuck with 8MHz |
* Modified disk_type structure to remove HD formats |
* |
* 7/ 1/96 - Wrote FIQ code, removed most remaining atariisms |
* |
* 13/ 1/96 - Well I think its read a single sector; but there is a problem |
* fd_rwsec_done which is called in FIQ mode starts another transfer |
* off (in fd_rwsec) while still in FIQ mode. Because its still in |
* FIQ mode it can't service the DMA and loses data. So need to |
* heavily restructure. |
* 14/ 1/96 - Found that the definitions of the register numbers of the |
* FDC were multiplied by 2 in the header for the 16bit words |
* of the atari so half the writes were going in the wrong place. |
* Also realised that the FIQ entry didn't make any attempt to |
* preserve registers or return correctly; now in assembler. |
* |
* 11/ 2/96 - Hmm - doesn't work on real machine. Auto detect doesn't |
* and hacking that past seems to wait forever - check motor |
* being turned on. |
* |
* 17/ 2/96 - still having problems - forcing track to -1 when selecting |
* new drives seems to allow it to read first few sectors |
* but then we get solid hangs at apparently random places |
* which change depending what is happening. |
* |
* 9/ 3/96 - Fiddled a lot of stuff around to move to kernel 1.3.35 |
* A lot of fiddling in DMA stuff. Having problems with it |
* constnatly thinking its timeing out. Ah - its timeout |
* was set to (6*HZ) rather than jiffies+(6*HZ). Now giving |
* duff data! |
* |
* 5/ 4/96 - Made it use the new IOC_ macros rather than *ioc |
* Hmm - giving unexpected FIQ and then timeouts |
* 18/ 8/96 - Ran through indent -kr -i8 |
* Some changes to disc change detect; don't know how well it |
* works. |
* 24/ 8/96 - Put all the track buffering code back in from the atari |
* code - I wonder if it will still work... No :-) |
* Still works if I turn off track buffering. |
* 25/ 8/96 - Changed the timer expires that I'd added back to be |
* jiffies + ....; and it all sprang to life! Got 2.8K/sec |
* off a cp -r of a 679K disc (showed 94% cpu usage!) |
* (PC gets 14.3K/sec - 0% CPU!) Hmm - hard drive corrupt! |
* Also perhaps that compile was with cache off. |
* changed cli in fd_readtrack_check to cliIF |
* changed vmallocs to kmalloc (whats the difference!!) |
* Removed the busy wait loop in do_fd_request and replaced |
* by a routine on tq_immediate; only 11% cpu on a dd off the |
* raw disc - but the speed is the same. |
* 1/ 9/96 - Idea (failed!) - set the 'disable spin-up seqeunce' |
* when we read the track if we know the motor is on; didn't |
* help - perhaps we have to do it in stepping as well. |
* Nope. Still doesn't help. |
* Hmm - what seems to be happening is that fd_readtrack_check |
* is never getting called. Its job is to terminate the read |
* just after we think we should have got the data; otherwise |
* the fdc takes 1 second to timeout; which is what's happening |
* Now I can see 'readtrack_timer' being set (which should do the |
* call); but it never seems to be called - hmm! |
* OK - I've moved the check to my tq_immediate code - |
* and it WORKS! 13.95K/second at 19% CPU. |
* I wish I knew why that timer didn't work..... |
* |
* 16/11/96 - Fiddled and frigged for 2.0.18 |
* |
* 4/ 9/98 - Cleaned up a few compiler warnings |
*/ |
|
#include <linux/config.h> |
#include <linux/sched.h> |
#include <linux/fs.h> |
#include <linux/fcntl.h> |
#include <linux/kernel.h> |
#include <linux/interrupt.h> |
#include <linux/timer.h> |
#include <linux/tqueue.h> |
#include <linux/fd.h> |
#include <linux/fd1772.h> |
#include <linux/errno.h> |
#include <linux/types.h> |
#include <linux/delay.h> |
#include <linux/mm.h> |
|
#include <asm/arch/oldlatches.h> |
#include <asm/system.h> |
#include <asm/bitops.h> |
#include <asm/dma.h> |
#include <asm/hardware.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/pgtable.h> |
#include <asm/segment.h> |
|
#define MAJOR_NR FLOPPY_MAJOR |
#define FLOPPY_DMA 0 |
#include "blk.h" |
|
/* Note: FD_MAX_UNITS could be redefined to 2 for the Atari (with |
* little additional rework in this file). But I'm not yet sure if |
* some other code depends on the number of floppies... (It is defined |
* in a public header!) |
*/ |
#if 0 |
#undef FD_MAX_UNITS |
#define FD_MAX_UNITS 2 |
#endif |
|
/* Ditto worries for Arc - DAG */ |
#define FD_MAX_UNITS 4 |
#define TRACKBUFFER 0 |
/*#define DEBUG*/ |
|
#ifdef DEBUG |
#define DPRINT(a) printk a |
#else |
#define DPRINT(a) |
#endif |
|
/* Disk types: DD */ |
static struct archy_disk_type { |
const char *name; |
unsigned spt; /* sectors per track */ |
unsigned blocks; /* total number of blocks */ |
unsigned stretch; /* track doubling ? */ |
} disk_type[] = { |
|
{ "d360", 9, 720, 0 }, /* 360kB diskette */ |
{ "D360", 9, 720, 1 }, /* 360kb in 720kb drive */ |
{ "D720", 9, 1440, 0 }, /* 720kb diskette (DD) */ |
/*{ "D820", 10,1640, 0}, *//* DD disk with 82 tracks/10 sectors |
- DAG - can't see how type detect can distinguish this |
from 720K until it reads block 4 by which time its too late! */ |
}; |
|
#define NUM_DISK_TYPES (sizeof(disk_type)/sizeof(*disk_type)) |
|
/* |
* Maximum disk size (in kilobytes). This default is used whenever the |
* current disk size is unknown. |
*/ |
#define MAX_DISK_SIZE 720 |
|
static int floppy_sizes[256]; |
static int floppy_blocksizes[256] = {0,}; |
|
/* current info on each unit */ |
static struct archy_floppy_struct { |
int connected; /* !=0 : drive is connected */ |
int autoprobe; /* !=0 : do autoprobe */ |
|
struct archy_disk_type *disktype; /* current type of disk */ |
|
int track; /* current head position or -1 |
* if unknown */ |
unsigned int steprate; /* steprate setting */ |
unsigned int wpstat; /* current state of WP signal |
* (for disk change detection) */ |
} unit[FD_MAX_UNITS]; |
|
/* DAG: On Arc we spin on a flag being cleared by fdc1772_comendhandler which |
is an assembler routine */ |
extern void fdc1772_comendhandler(void); /* Actually doens't have these parameters - see fd1772.S */ |
extern volatile int fdc1772_comendstatus; |
extern volatile int fdc1772_fdc_int_done; |
|
#define FDC1772BASE ((0x210000>>2)|0x80000000) |
|
#define FDC1772_READ(reg) inb(FDC1772BASE+(reg/2)) |
|
/* DAG: You wouldn't be silly to ask why FDC1772_WRITE is a function rather |
than the #def below - well simple - the #def won't compile - and I |
don't understand why (__outwc not defined) */ |
/* NOTE: Reg is 0,2,4,6 as opposed to 0,1,2,3 or 0,4,8,12 to keep compatibility |
with the ST version of fd1772.h */ |
/*#define FDC1772_WRITE(reg,val) outw(val,(reg+FDC1772BASE)); */ |
void FDC1772_WRITE(int reg, unsigned char val) |
{ |
if (reg == FDC1772REG_CMD) { |
DPRINT(("FDC1772_WRITE new command 0x%x @ %d\n", val,jiffies)); |
if (fdc1772_fdc_int_done) { |
DPRINT(("FDC1772_WRITE: Hmm fdc1772_fdc_int_done true - resetting\n")); |
fdc1772_fdc_int_done = 0; |
}; |
}; |
outb(val, (reg / 2) + FDC1772BASE); |
}; |
|
#define MAX_SECTORS 22 |
|
unsigned char *DMABuffer; /* buffer for writes */ |
/*static unsigned long PhysDMABuffer; *//* physical address */ |
/* DAG: On Arc we just go straight for the DMA buffer */ |
#define PhysDMABuffer DMABuffer |
|
#ifdef TRACKBUFFER |
unsigned char *TrackBuffer; /* buffer for reads */ |
#define PhysTrackBuffer TrackBuffer /* physical address */ |
static int BufferDrive, BufferSide, BufferTrack; |
static int read_track; /* non-zero if we are reading whole tracks */ |
|
#define SECTOR_BUFFER(sec) (TrackBuffer + ((sec)-1)*512) |
#define IS_BUFFERED(drive,side,track) \ |
(BufferDrive == (drive) && BufferSide == (side) && BufferTrack == (track)) |
#endif |
|
/* |
* These are global variables, as that's the easiest way to give |
* information to interrupts. They are the data used for the current |
* request. |
*/ |
static int SelectedDrive = 0; |
static int ReqCmd, ReqBlock; |
static int ReqSide, ReqTrack, ReqSector, ReqCnt; |
static int HeadSettleFlag = 0; |
static unsigned char *ReqData, *ReqBuffer; |
static int MotorOn = 0, MotorOffTrys; |
|
/* Synchronization of FDC1772 access. */ |
static volatile int fdc_busy = 0; |
static struct wait_queue *fdc_wait = NULL; |
|
|
static unsigned int changed_floppies = 0xff, fake_change = 0; |
#define CHECK_CHANGE_DELAY HZ/2 |
|
/* DAG - increased to 30*HZ - not sure if this is the correct thing to do */ |
#define FD_MOTOR_OFF_DELAY (10*HZ) |
#define FD_MOTOR_OFF_MAXTRY (10*20) |
|
#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. */ |
|
|
#define START_MOTOR_OFF_TIMER(delay) \ |
do { \ |
motor_off_timer.expires = jiffies + (delay); \ |
add_timer( &motor_off_timer ); \ |
MotorOffTrys = 0; \ |
} while(0) |
|
#define START_CHECK_CHANGE_TIMER(delay) \ |
do { \ |
timer_table[FLOPPY_TIMER].expires = jiffies + (delay); \ |
timer_active |= (1 << FLOPPY_TIMER); \ |
} while(0) |
|
#define START_TIMEOUT() \ |
do { \ |
del_timer( &timeout_timer ); \ |
timeout_timer.expires = jiffies + FLOPPY_TIMEOUT; \ |
add_timer( &timeout_timer ); \ |
} while(0) |
|
#define STOP_TIMEOUT() \ |
do { \ |
del_timer( &timeout_timer ); \ |
} while(0) |
|
#define ENABLE_IRQ() enable_irq(FIQ_FD1772+64); |
|
#define DISABLE_IRQ() disable_irq(FIQ_FD1772+64); |
|
static void fd1772_checkint(void); |
|
struct tq_struct fd1772_tq = |
{ 0,0, (void *)fd1772_checkint, 0 }; |
/* |
* The driver is trying to determine the correct media format |
* while Probing is set. fd_rwsec_done() clears it after a |
* successful access. |
*/ |
static int Probing = 0; |
|
/* This flag is set when a dummy seek is necesary to make the WP |
* status bit accessible. |
*/ |
static int NeedSeek = 0; |
|
|
/***************************** Prototypes *****************************/ |
|
static void fd_select_side(int side); |
static void fd_select_drive(int drive); |
static void fd_deselect(void); |
static void fd_motor_off_timer(unsigned long dummy); |
static void check_change(void); |
static __inline__ void set_head_settle_flag(void); |
static __inline__ int get_head_settle_flag(void); |
static void floppy_irqconsequencehandler(void); |
static void fd_error(void); |
static void do_fd_action(int drive); |
static void fd_calibrate(void); |
static void fd_calibrate_done(int status); |
static void fd_seek(void); |
static void fd_seek_done(int status); |
static void fd_rwsec(void); |
#ifdef TRACKBUFFER |
static void fd_readtrack_check( unsigned long dummy ); |
#endif |
static void fd_rwsec_done(int status); |
static void fd_times_out(unsigned long dummy); |
static void finish_fdc(void); |
static void finish_fdc_done(int dummy); |
static void floppy_off(unsigned int nr); |
static __inline__ void copy_buffer(void *from, void *to); |
static void setup_req_params(int drive); |
static void redo_fd_request(void); |
static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int |
cmd, unsigned long param); |
static void fd_probe(int drive); |
static int fd_test_drive_present(int drive); |
static void config_types(void); |
static int floppy_open(struct inode *inode, struct file *filp); |
static void floppy_release(struct inode *inode, struct file *filp); |
|
/************************* End of Prototypes **************************/ |
|
static struct timer_list motor_off_timer = |
{NULL, NULL, 0, 0, fd_motor_off_timer}; |
#ifdef TRACKBUFFER |
static struct timer_list readtrack_timer = |
{ NULL, NULL, 0, 0, fd_readtrack_check }; |
#endif |
static struct timer_list timeout_timer = |
{NULL, NULL, 0, 0, fd_times_out}; |
|
/* DAG: Haven't got a clue what this is? */ |
int stdma_islocked(void) |
{ |
return 0; |
}; |
|
/* Select the side to use. */ |
|
static void fd_select_side(int side) |
{ |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
oldlatch_aupdate(LATCHA_SIDESEL, side ? 0 : LATCHA_SIDESEL); |
restore_flags(flags); |
} |
|
|
/* Select a drive, update the FDC1772's track register |
*/ |
|
static void fd_select_drive(int drive) |
{ |
unsigned long flags; |
|
#ifdef DEBUG |
printk("fd_select_drive:%d\n", drive); |
#endif |
/* Hmm - nowhere do we seem to turn the motor on - I'm going to do it here! */ |
oldlatch_aupdate(LATCHA_MOTOR | LATCHA_INUSE, 0); |
|
if (drive == SelectedDrive) |
return; |
|
save_flags(flags); |
cli(); |
oldlatch_aupdate(LATCHA_FDSELALL, 0xf - (1 << drive)); |
restore_flags(flags); |
|
/* restore track register to saved value */ |
FDC1772_WRITE(FDC1772REG_TRACK, unit[drive].track); |
udelay(25); |
|
SelectedDrive = drive; |
} |
|
|
/* Deselect both drives. */ |
|
static void fd_deselect(void) |
{ |
unsigned long flags; |
|
DPRINT(("fd_deselect\n")); |
|
save_flags(flags); |
cli(); |
oldlatch_aupdate(LATCHA_FDSELALL | LATCHA_MOTOR | LATCHA_INUSE, 0xf | LATCHA_MOTOR | LATCHA_INUSE); |
restore_flags(flags); |
|
SelectedDrive = -1; |
} |
|
|
/* This timer function deselects the drives when the FDC1772 switched the |
* motor off. The deselection cannot happen earlier because the FDC1772 |
* counts the index signals, which arrive only if one drive is selected. |
*/ |
|
static void fd_motor_off_timer(unsigned long dummy) |
{ |
unsigned long flags; |
unsigned char status; |
int delay; |
|
del_timer(&motor_off_timer); |
|
if (SelectedDrive < 0) |
/* no drive selected, needn't deselect anyone */ |
return; |
|
save_flags(flags); |
cli(); |
|
if (fdc_busy) /* was stdma_islocked */ |
goto retry; |
|
status = FDC1772_READ(FDC1772REG_STATUS); |
|
if (!(status & 0x80)) { |
/* motor already turned off by FDC1772 -> deselect drives */ |
/* In actual fact its this deselection which turns the motor off on the |
Arc, since the motor control is actually on Latch A */ |
DPRINT(("fdc1772: deselecting in fd_motor_off_timer\n")); |
fd_deselect(); |
MotorOn = 0; |
restore_flags(flags); |
return; |
} |
/* not yet off, try again */ |
|
retry: |
restore_flags(flags); |
/* Test again later; if tested too often, it seems there is no disk |
* in the drive and the FDC1772 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... |
*/ |
delay = (MotorOffTrys < FD_MOTOR_OFF_MAXTRY) ? |
(++MotorOffTrys, HZ / 20) : HZ / 2; |
START_MOTOR_OFF_TIMER(delay); |
} |
|
|
/* This function is repeatedly called to detect disk changes (as good |
* as possible) and keep track of the current state of the write protection. |
*/ |
|
static void check_change(void) |
{ |
static int drive = 0; |
|
unsigned long flags; |
int stat; |
|
if (fdc_busy) |
return; /* Don't start poking about if the fdc is busy */ |
|
return; /* lets just forget it for the mo DAG */ |
|
if (++drive > 1 || !unit[drive].connected) |
drive = 0; |
|
save_flags(flags); |
cli(); |
|
if (!stdma_islocked()) { |
stat = !!(FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_WPROT); |
|
/* The idea here is that if the write protect line has changed then |
the disc must have changed */ |
if (stat != unit[drive].wpstat) { |
DPRINT(("wpstat[%d] = %d\n", drive, stat)); |
unit[drive].wpstat = stat; |
set_bit(drive, &changed_floppies); |
} |
} |
restore_flags(flags); |
|
START_CHECK_CHANGE_TIMER(CHECK_CHANGE_DELAY); |
} |
|
|
/* Handling of the Head Settling Flag: This flag should be set after each |
* seek operation, because we dont't use seeks with verify. |
*/ |
|
static __inline__ void set_head_settle_flag(void) |
{ |
HeadSettleFlag = FDC1772CMDADD_E; |
} |
|
static __inline__ int get_head_settle_flag(void) |
{ |
int tmp = HeadSettleFlag; |
HeadSettleFlag = 0; |
return (tmp); |
} |
|
|
|
|
/* General Interrupt Handling */ |
|
static void (*FloppyIRQHandler) (int status) = NULL; |
|
static void floppy_irqconsequencehandler(void) |
{ |
unsigned char status; |
void (*handler) (int); |
|
fdc1772_fdc_int_done = 0; |
|
handler = FloppyIRQHandler; |
FloppyIRQHandler = NULL; |
|
if (handler) { |
nop(); |
status = (unsigned char) fdc1772_comendstatus; |
DPRINT(("FDC1772 irq, status = %02x handler = %08lx\n", (unsigned int) status, (unsigned long) handler)); |
handler(status); |
} else { |
DPRINT(("FDC1772 irq, no handler status=%02x\n", fdc1772_comendstatus)); |
} |
DPRINT(("FDC1772 irq: end of floppy_irq\n")); |
} |
|
|
/* Error handling: If some error happened, retry some times, then |
* recalibrate, then try again, and fail after MAX_ERRORS. |
*/ |
|
static void fd_error(void) |
{ |
printk("FDC1772: fd_error\n"); |
/*panic("fd1772: fd_error"); *//* DAG tmp */ |
if (!CURRENT) |
return; |
CURRENT->errors++; |
if (CURRENT->errors >= MAX_ERRORS) { |
printk("fd%d: too many errors.\n", SelectedDrive); |
end_request(0); |
} else if (CURRENT->errors == RECALIBRATE_ERRORS) { |
printk("fd%d: recalibrating\n", SelectedDrive); |
if (SelectedDrive != -1) |
unit[SelectedDrive].track = -1; |
} |
redo_fd_request(); |
} |
|
|
|
#define SET_IRQ_HANDLER(proc) do { FloppyIRQHandler = (proc); } while(0) |
|
|
/* 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(). |
*/ |
|
static void do_fd_action(int drive) |
{ |
DPRINT(("do_fd_action unit[drive].track=%d\n", unit[drive].track)); |
|
#ifdef TRACKBUFFER |
repeat: |
|
if (IS_BUFFERED( drive, ReqSide, ReqTrack )) { |
if (ReqCmd == READ) { |
copy_buffer( SECTOR_BUFFER(ReqSector), ReqData ); |
if (++ReqCnt < CURRENT->current_nr_sectors) { |
/* read next sector */ |
setup_req_params( drive ); |
goto repeat; |
} |
else { |
/* all sectors finished */ |
CURRENT->nr_sectors -= CURRENT->current_nr_sectors; |
CURRENT->sector += CURRENT->current_nr_sectors; |
end_request( 1 ); |
redo_fd_request(); |
return; |
} |
} |
else { |
/* cmd == WRITE, pay attention to track buffer |
* consistency! */ |
copy_buffer( ReqData, SECTOR_BUFFER(ReqSector) ); |
} |
} |
#endif |
|
if (SelectedDrive != drive) { |
/*unit[drive].track = -1; DAG */ |
fd_select_drive(drive); |
}; |
|
|
if (unit[drive].track == -1) |
fd_calibrate(); |
else if (unit[drive].track != ReqTrack << unit[drive].disktype->stretch) |
fd_seek(); |
else |
fd_rwsec(); |
} |
|
|
/* Seek to track 0 if the current track is unknown */ |
|
static void fd_calibrate(void) |
{ |
DPRINT(("fd_calibrate\n")); |
if (unit[SelectedDrive].track >= 0) { |
fd_calibrate_done(0); |
return; |
} |
DPRINT(("fd_calibrate (after track compare)\n")); |
SET_IRQ_HANDLER(fd_calibrate_done); |
/* we can't verify, since the speed may be incorrect */ |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | unit[SelectedDrive].steprate); |
|
NeedSeek = 1; |
MotorOn = 1; |
START_TIMEOUT(); |
/* wait for IRQ */ |
} |
|
|
static void fd_calibrate_done(int status) |
{ |
DPRINT(("fd_calibrate_done()\n")); |
STOP_TIMEOUT(); |
|
/* set the correct speed now */ |
if (status & FDC1772STAT_RECNF) { |
printk("fd%d: restore failed\n", SelectedDrive); |
fd_error(); |
} else { |
unit[SelectedDrive].track = 0; |
fd_seek(); |
} |
} |
|
|
/* Seek the drive to the requested track. The drive must have been |
* calibrated at some point before this. |
*/ |
|
static void fd_seek(void) |
{ |
unsigned long flags; |
DPRINT(("fd_seek() to track %d (unit[SelectedDrive].track=%d)\n", ReqTrack, |
unit[SelectedDrive].track)); |
if (unit[SelectedDrive].track == ReqTrack << |
unit[SelectedDrive].disktype->stretch) { |
fd_seek_done(0); |
return; |
} |
FDC1772_WRITE(FDC1772REG_DATA, ReqTrack << |
unit[SelectedDrive].disktype->stretch); |
udelay(25); |
save_flags(flags); |
cliIF(); |
SET_IRQ_HANDLER(fd_seek_done); |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK | unit[SelectedDrive].steprate | |
/* DAG */ |
(MotorOn?FDC1772CMDADD_H:0)); |
|
restore_flags(flags); |
MotorOn = 1; |
set_head_settle_flag(); |
START_TIMEOUT(); |
/* wait for IRQ */ |
} |
|
|
static void fd_seek_done(int status) |
{ |
DPRINT(("fd_seek_done()\n")); |
STOP_TIMEOUT(); |
|
/* set the correct speed */ |
if (status & FDC1772STAT_RECNF) { |
printk("fd%d: seek error (to track %d)\n", |
SelectedDrive, ReqTrack); |
/* we don't know exactly which track we are on now! */ |
unit[SelectedDrive].track = -1; |
fd_error(); |
} else { |
unit[SelectedDrive].track = ReqTrack << |
unit[SelectedDrive].disktype->stretch; |
NeedSeek = 0; |
fd_rwsec(); |
} |
} |
|
|
/* This does the actual reading/writing after positioning the head |
* over the correct track. |
*/ |
|
#ifdef TRACKBUFFER |
static int MultReadInProgress = 0; |
#endif |
|
|
static void fd_rwsec(void) |
{ |
unsigned long paddr, flags; |
unsigned int rwflag, old_motoron; |
unsigned int track; |
|
DPRINT(("fd_rwsec(), Sec=%d, Access=%c\n", ReqSector, ReqCmd == WRITE ? 'w' : 'r')); |
if (ReqCmd == WRITE) { |
/*cache_push( (unsigned long)ReqData, 512 ); */ |
paddr = (unsigned long) ReqData; |
rwflag = 0x100; |
} else { |
#ifdef TRACKBUFFER |
if (read_track) |
paddr = (unsigned long)PhysTrackBuffer; |
else |
paddr =(unsigned long)PhysDMABuffer; |
#else |
paddr = (unsigned long)PhysDMABuffer; |
#endif |
rwflag = 0; |
} |
|
DPRINT(("fd_rwsec() before sidesel rwflag=%d sec=%d trk=%d\n", rwflag, |
ReqSector, FDC1772_READ(FDC1772REG_TRACK))); |
fd_select_side(ReqSide); |
|
/*DPRINT(("fd_rwsec() before start sector \n")); */ |
/* Start sector of this operation */ |
#ifdef TRACKBUFFER |
FDC1772_WRITE( FDC1772REG_SECTOR, !read_track ? ReqSector : 1 ); |
#else |
FDC1772_WRITE( FDC1772REG_SECTOR, ReqSector ); |
#endif |
|
/* Cheat for track if stretch != 0 */ |
if (unit[SelectedDrive].disktype->stretch) { |
track = FDC1772_READ(FDC1772REG_TRACK); |
FDC1772_WRITE(FDC1772REG_TRACK, track >> |
unit[SelectedDrive].disktype->stretch); |
} |
udelay(25); |
|
DPRINT(("fd_rwsec() before setup DMA \n")); |
/* Setup DMA - Heavily modified by DAG */ |
save_flags(flags); |
cliIF(); |
disable_dma(FLOPPY_DMA); |
set_dma_mode(FLOPPY_DMA, rwflag ? DMA_MODE_WRITE : DMA_MODE_READ); |
set_dma_addr(FLOPPY_DMA, (long) paddr); /* DAG - changed from Atari specific */ |
#ifdef TRACKBUFFER |
set_dma_count(FLOPPY_DMA,(!read_track ? 1 : unit[SelectedDrive].disktype->spt)*512); |
#else |
set_dma_count(FLOPPY_DMA, 512); /* Block/sector size - going to have to change */ |
#endif |
SET_IRQ_HANDLER(fd_rwsec_done); |
/* Turn on dma int */ |
enable_dma(FLOPPY_DMA); |
/* Now give it something to do */ |
FDC1772_WRITE(FDC1772REG_CMD, (rwflag ? (FDC1772CMD_WRSEC | FDC1772CMDADD_P) : |
#ifdef TRACKBUFFER |
(FDC1772CMD_RDSEC | (read_track ? FDC1772CMDADD_M : 0) | |
/* Hmm - the idea here is to stop the FDC spinning the disc |
up when we know that we already still have it spinning */ |
(MotorOn?FDC1772CMDADD_H:0)) |
#else |
FDC1772CMD_RDSEC |
#endif |
)); |
|
restore_flags(flags); |
DPRINT(("fd_rwsec() after DMA setup flags=0x%08x\n", flags)); |
/*sti(); *//* DAG - Hmm */ |
/* Hmm - should do something DAG */ |
old_motoron = MotorOn; |
MotorOn = 1; |
NeedSeek = 1; |
|
/* wait for interrupt */ |
|
#ifdef TRACKBUFFER |
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-existant sector and need 1 sec to |
* recognise that it isn't present :-( |
*/ |
del_timer( &readtrack_timer ); |
readtrack_timer.function = fd_readtrack_check; |
readtrack_timer.expires = jiffies + HZ/5 + (old_motoron ? 0 : HZ); |
/* 1 rot. + 5 rot.s if motor was off */ |
DPRINT(("Setting readtrack_timer to %d @ %d\n",readtrack_timer.expires,jiffies)); |
add_timer( &readtrack_timer ); |
MultReadInProgress = 1; |
} |
#endif |
|
/*DPRINT(("fd_rwsec() before START_TIMEOUT \n")); */ |
START_TIMEOUT(); |
/*DPRINT(("fd_rwsec() after START_TIMEOUT \n")); */ |
} |
|
|
#ifdef TRACKBUFFER |
|
static void fd_readtrack_check( unsigned long dummy ) |
|
{ unsigned long flags, addr; |
extern unsigned char *fdc1772_dataaddr; |
|
DPRINT(("fd_readtrack_check @ %d\n",jiffies)); |
|
save_flags(flags); |
cliIF(); |
|
del_timer( &readtrack_timer ); |
|
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 control flow |
* gets here. |
*/ |
restore_flags(flags); |
return; |
} |
|
/* get the current DMA address */ |
addr=fdc1772_dataaddr; /* DAG - ? */ |
DPRINT(("fd_readtrack_check: addr=%x PhysTrackBuffer=%p\n",addr,PhysTrackBuffer)); |
|
if (addr >= (unsigned long)PhysTrackBuffer + unit[SelectedDrive].disktype->spt*512) { |
/* already read enough data, force an FDC interrupt to stop |
* the read operation |
*/ |
SET_IRQ_HANDLER( NULL ); |
restore_flags(flags); |
DPRINT(("fd_readtrack_check(): done\n")); |
FDC1772_WRITE( FDC1772REG_CMD, FDC1772CMD_FORCI ); |
udelay(25); |
|
/* No error until now -- the FDC would have interrupted |
* otherwise! |
*/ |
fd_rwsec_done( 0 ); |
} |
else { |
/* not yet finished, wait another tenth rotation */ |
restore_flags(flags); |
DPRINT(("fd_readtrack_check(): not yet finished\n")); |
readtrack_timer.expires = jiffies + HZ/5/10; |
add_timer( &readtrack_timer ); |
} |
} |
|
#endif |
|
static void fd_rwsec_done(int status) |
{ |
unsigned int track; |
|
DPRINT(("fd_rwsec_done() status=%d @ %d\n", status,jiffies)); |
|
#ifdef TRACKBUFFER |
if (read_track && !MultReadInProgress) return; |
MultReadInProgress = 0; |
|
STOP_TIMEOUT(); |
|
if (read_track) |
del_timer( &readtrack_timer ); |
#endif |
|
|
/* Correct the track if stretch != 0 */ |
if (unit[SelectedDrive].disktype->stretch) { |
track = FDC1772_READ(FDC1772REG_TRACK); |
FDC1772_WRITE(FDC1772REG_TRACK, track << |
unit[SelectedDrive].disktype->stretch); |
} |
if (ReqCmd == WRITE && (status & FDC1772STAT_WPROT)) { |
printk("fd%d: is write protected\n", SelectedDrive); |
goto err_end; |
} |
if ((status & FDC1772STAT_RECNF) |
#ifdef TRACKBUFFER |
/* RECNF is no error after a multiple read when the FDC |
* searched for a non-existant sector! |
*/ |
&& !(read_track && |
FDC1772_READ(FDC1772REG_SECTOR) > unit[SelectedDrive].disktype->spt) |
#endif |
) { |
if (Probing) { |
if (unit[SelectedDrive].disktype > disk_type) { |
/* try another disk type */ |
unit[SelectedDrive].disktype--; |
floppy_sizes[SelectedDrive] |
= unit[SelectedDrive].disktype->blocks >> 1; |
} else |
Probing = 0; |
} else { |
/* record not found, but not probing. Maybe stretch wrong ? Restart probing */ |
if (unit[SelectedDrive].autoprobe) { |
unit[SelectedDrive].disktype = disk_type + NUM_DISK_TYPES - 1; |
floppy_sizes[SelectedDrive] |
= unit[SelectedDrive].disktype->blocks >> 1; |
Probing = 1; |
} |
} |
if (Probing) { |
setup_req_params(SelectedDrive); |
#ifdef TRACKBUFFER |
BufferDrive = -1; |
#endif |
do_fd_action(SelectedDrive); |
return; |
} |
printk("fd%d: sector %d not found (side %d, track %d)\n", |
SelectedDrive, FDC1772_READ(FDC1772REG_SECTOR), ReqSide, ReqTrack); |
goto err_end; |
} |
if (status & FDC1772STAT_CRC) { |
printk("fd%d: CRC error (side %d, track %d, sector %d)\n", |
SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); |
goto err_end; |
} |
if (status & FDC1772STAT_LOST) { |
printk("fd%d: lost data (side %d, track %d, sector %d)\n", |
SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); |
goto err_end; |
} |
Probing = 0; |
|
if (ReqCmd == READ) { |
#ifdef TRACKBUFFER |
if (!read_track) |
{ |
/*cache_clear (PhysDMABuffer, 512);*/ |
copy_buffer (DMABuffer, ReqData); |
} |
else |
{ |
/*cache_clear (PhysTrackBuffer, MAX_SECTORS * 512);*/ |
BufferDrive = SelectedDrive; |
BufferSide = ReqSide; |
BufferTrack = ReqTrack; |
copy_buffer (SECTOR_BUFFER (ReqSector), ReqData); |
} |
#else |
/*cache_clear( PhysDMABuffer, 512 ); */ |
copy_buffer(DMABuffer, ReqData); |
#endif |
} |
if (++ReqCnt < CURRENT->current_nr_sectors) { |
/* read next sector */ |
setup_req_params(SelectedDrive); |
do_fd_action(SelectedDrive); |
} else { |
/* all sectors finished */ |
CURRENT->nr_sectors -= CURRENT->current_nr_sectors; |
CURRENT->sector += CURRENT->current_nr_sectors; |
end_request(1); |
redo_fd_request(); |
} |
return; |
|
err_end: |
#ifdef TRACKBUFFER |
BufferDrive = -1; |
#endif |
|
fd_error(); |
} |
|
|
static void fd_times_out(unsigned long dummy) |
{ |
SET_IRQ_HANDLER(NULL); |
/* If the timeout occured while the readtrack_check timer was |
* active, we need to cancel it, else bad things will happen */ |
del_timer( &readtrack_timer ); |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); |
udelay(25); |
|
printk("floppy timeout\n"); |
STOP_TIMEOUT(); /* hmm - should we do this ? */ |
fd_error(); |
} |
|
|
/* The (noop) seek operation here is needed to make the WP bit in the |
* FDC1772 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. |
*/ |
|
static void finish_fdc(void) |
{ |
/* DAG - just try without this dummy seek! */ |
finish_fdc_done(0); |
return; |
|
if (!NeedSeek) { |
finish_fdc_done(0); |
} else { |
DPRINT(("finish_fdc: dummy seek started\n")); |
FDC1772_WRITE(FDC1772REG_DATA, unit[SelectedDrive].track); |
SET_IRQ_HANDLER(finish_fdc_done); |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); |
MotorOn = 1; |
START_TIMEOUT(); |
/* we must wait for the IRQ here, because the ST-DMA is |
* released immediatly afterwards and the interrupt may be |
* delivered to the wrong driver. |
*/ |
} |
} |
|
|
static void finish_fdc_done(int dummy) |
{ |
unsigned long flags; |
|
DPRINT(("finish_fdc_done entered\n")); |
STOP_TIMEOUT(); |
NeedSeek = 0; |
|
if ((timer_active & (1 << FLOPPY_TIMER)) && |
timer_table[FLOPPY_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 :-(( |
*/ |
timer_table[FLOPPY_TIMER].expires = jiffies + 5; |
else { |
/* START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ |
}; |
del_timer(&motor_off_timer); |
START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); |
|
save_flags(flags); |
cli(); |
/* stdma_release(); - not sure if I should do something DAG */ |
fdc_busy = 0; |
wake_up(&fdc_wait); |
restore_flags(flags); |
|
DPRINT(("finish_fdc() finished\n")); |
} |
|
|
/* Prevent "aliased" accesses. */ |
static fd_ref[4] = |
{0, 0, 0, 0}; |
static fd_device[4] = |
{0, 0, 0, 0}; |
|
/* |
* Current device number. Taken either from the block header or from the |
* format request descriptor. |
*/ |
#define CURRENT_DEVICE (CURRENT->rq_dev) |
|
/* Current error count. */ |
#define CURRENT_ERRORS (CURRENT->errors) |
|
|
/* dummy for blk.h */ |
static void floppy_off(unsigned int nr) |
{ |
} |
|
|
/* On the old arcs write protect depends on the particular model |
of machine. On the A310, R140, and A440 there is a disc changed |
detect, however on the A4x0/1 range there is not. There |
is nothing to tell you which machine your on. |
At the moment I'm just marking changed always. I've |
left the Atari's 'change on write protect change' code in this |
part (but nothing sets it). |
RiscOS apparently checks the disc serial number etc. to detect changes |
- but if it sees a disc change line go high (?) it flips to using |
it. Well maybe I'll add that in the future (!?) |
*/ |
static int check_floppy_change(dev_t dev) |
{ |
unsigned int drive = (dev & 0x03); |
|
if (MAJOR(dev) != MAJOR_NR) { |
printk("floppy_changed: not a floppy\n"); |
return 0; |
} |
if (test_bit(drive, &fake_change)) { |
/* simulated change (e.g. after formatting) */ |
return 1; |
} |
if (test_bit(drive, &changed_floppies)) { |
/* surely changed (the WP signal changed at least once) */ |
return 1; |
} |
if (unit[drive].wpstat) { |
/* WP is on -> could be changed: to be sure, buffers should be |
* invalidated... |
*/ |
return 1; |
} |
return 1; /* DAG - was 0 */ |
} |
|
static int floppy_revalidate(dev_t dev) |
{ |
int drive = dev & 3; |
|
if (test_bit(drive, &changed_floppies) || test_bit(drive, &fake_change) |
|| unit[drive].disktype == 0) { |
#ifdef TRACKBUFFER |
BufferDrive = -1; |
#endif |
clear_bit(drive, &fake_change); |
clear_bit(drive, &changed_floppies); |
unit[drive].disktype = 0; |
} |
return 0; |
} |
|
static __inline__ void copy_buffer(void *from, void *to) |
{ |
ulong *p1 = (ulong *) from, *p2 = (ulong *) to; |
int cnt; |
|
for (cnt = 512 / 4; cnt; cnt--) |
*p2++ = *p1++; |
} |
|
|
/* This sets up the global variables describing the current request. */ |
|
static void setup_req_params(int drive) |
{ |
int block = ReqBlock + ReqCnt; |
|
ReqTrack = block / unit[drive].disktype->spt; |
ReqSector = block - ReqTrack * unit[drive].disktype->spt + 1; |
ReqSide = ReqTrack & 1; |
ReqTrack >>= 1; |
ReqData = ReqBuffer + 512 * ReqCnt; |
|
#ifdef TRACKBUFFER |
read_track = (ReqCmd == READ && CURRENT_ERRORS == 0); |
#endif |
|
DPRINT(("Request params: Si=%d Tr=%d Se=%d Data=%08lx\n", ReqSide, |
ReqTrack, ReqSector, (unsigned long) ReqData)); |
} |
|
|
static void redo_fd_request(void) |
{ |
int device, drive, type; |
struct archy_floppy_struct *floppy; |
|
DPRINT(("redo_fd_request: CURRENT=%08lx CURRENT->rq_dev=%04x CURRENT->sector=%ld\n", |
(unsigned long) CURRENT, CURRENT ? CURRENT->rq_dev : 0, |
CURRENT ? CURRENT->sector : 0)); |
|
if (CURRENT && CURRENT->rq_status == RQ_INACTIVE) |
goto the_end; |
|
repeat: |
|
if (!CURRENT) |
goto the_end; |
|
if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) |
panic(DEVICE_NAME ": request list destroyed"); |
|
if (CURRENT->bh) { |
if (!buffer_locked(CURRENT->bh)) |
panic(DEVICE_NAME ": block not locked"); |
} |
device = MINOR(CURRENT_DEVICE); |
drive = device & 3; |
type = device >> 2; |
floppy = &unit[drive]; |
|
if (!floppy->connected) { |
/* drive not connected */ |
printk("Unknown Device: fd%d\n", drive); |
end_request(0); |
goto repeat; |
} |
if (type == 0) { |
if (!floppy->disktype) { |
Probing = 1; |
floppy->disktype = disk_type + NUM_DISK_TYPES - 1; |
floppy_sizes[drive] = floppy->disktype->blocks >> 1; |
floppy->autoprobe = 1; |
} |
} else { |
/* user supplied disk type */ |
--type; |
if (type >= NUM_DISK_TYPES) { |
printk("fd%d: invalid disk format", drive); |
end_request(0); |
goto repeat; |
} |
floppy->disktype = &disk_type[type]; |
floppy_sizes[drive] = disk_type[type].blocks >> 1; |
floppy->autoprobe = 0; |
} |
|
if (CURRENT->sector + 1 > floppy->disktype->blocks) { |
end_request(0); |
goto repeat; |
} |
/* stop deselect timer */ |
del_timer(&motor_off_timer); |
|
ReqCnt = 0; |
ReqCmd = CURRENT->cmd; |
ReqBlock = CURRENT->sector; |
ReqBuffer = CURRENT->buffer; |
setup_req_params(drive); |
do_fd_action(drive); |
|
return; |
|
the_end: |
finish_fdc(); |
} |
|
static void fd1772_checkint(void) |
{ |
extern int fdc1772_bytestogo; |
|
/*printk("fd1772_checkint %d\n",fdc1772_fdc_int_done);*/ |
if (fdc1772_fdc_int_done) |
floppy_irqconsequencehandler(); |
if ((MultReadInProgress) && (fdc1772_bytestogo==0)) fd_readtrack_check(0); |
if (fdc_busy) { |
queue_task(&fd1772_tq,&tq_immediate); |
mark_bh(IMMEDIATE_BH); |
}; |
}; |
|
void do_fd_request(void) |
{ |
unsigned long flags; |
|
DPRINT(("do_fd_request for pid %d\n", current->pid)); |
if (fdc_busy) return; |
save_flags(flags); |
cli(); |
while (fdc_busy) |
sleep_on(&fdc_wait); |
fdc_busy = 1; |
ENABLE_IRQ(); |
restore_flags(flags); |
|
fdc1772_fdc_int_done = 0; |
|
redo_fd_request(); |
|
queue_task(&fd1772_tq,&tq_immediate); |
mark_bh(IMMEDIATE_BH); |
} |
|
|
static int invalidate_drive(int rdev) |
{ |
/* invalidate the buffer track to force a reread */ |
#ifdef TRACKBUFFER |
BufferDrive = -1; |
#endif |
|
set_bit(rdev & 3, &fake_change); |
check_disk_change(rdev); |
return 0; |
} |
|
static int fd_ioctl(struct inode *inode, struct file *filp, |
unsigned int cmd, unsigned long param) |
{ |
#define IOCTL_MODE_BIT 8 |
#define OPEN_WRITE_BIT 16 |
#define IOCTL_ALLOWED (filp && (filp->f_mode & IOCTL_MODE_BIT)) |
|
int drive, device; |
|
device = inode->i_rdev; |
switch (cmd) { |
RO_IOCTLS(inode->i_rdev, param); |
} |
drive = MINOR(device); |
if (!IOCTL_ALLOWED) |
return -EPERM; |
switch (cmd) { |
case FDFMTBEG: |
return 0; |
/* case FDC1772LRPRM: ??? DAG what does this do?? |
unit[drive].disktype = NULL; |
floppy_sizes[drive] = MAX_DISK_SIZE; |
return invalidate_drive (device); */ |
case FDFMTEND: |
case FDFLUSH: |
return invalidate_drive(drive); |
} |
if (!suser()) |
return -EPERM; |
if (drive < 0 || drive > 3) |
return -EINVAL; |
switch (cmd) { |
default: |
return -EINVAL; |
} |
return 0; |
} |
|
|
/* Initialize the 'unit' variable for drive 'drive' */ |
|
static void fd_probe(int drive) |
{ |
unit[drive].connected = 0; |
unit[drive].disktype = NULL; |
|
if (!fd_test_drive_present(drive)) |
return; |
|
unit[drive].connected = 1; |
unit[drive].track = -1; /* If we put the auto detect back in this can go to 0 */ |
unit[drive].steprate = FDC1772STEP_6; |
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. |
*/ |
|
static int fd_test_drive_present(int drive) |
{ |
unsigned long timeout; |
unsigned char status; |
int ok; |
|
printk("fd_test_drive_present %d\n", drive); |
if (drive > 1) |
return (0); |
return (1); /* Simple hack for the moment - the autodetect doesn't seem to work on arc */ |
fd_select_drive(drive); |
|
/* disable interrupt temporarily */ |
DISABLE_IRQ(); |
FDC1772_WRITE(FDC1772REG_TRACK, 0x00); /* was ff00 why? */ |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | FDC1772CMDADD_H | FDC1772STEP_6); |
|
/*printk("fd_test_drive_present: Going into timeout loop\n"); */ |
for (ok = 0, timeout = jiffies + 2 * HZ + HZ / 2; jiffies < timeout;) { |
/* What does this piece of atariism do? - query for an interrupt? */ |
/* if (!(mfp.par_dt_reg & 0x20)) |
break; */ |
/* Well this is my nearest guess - quit when we get an FDC interrupt */ |
if (IOC_FIQSTAT & 2) |
break; |
} |
|
/*printk("fd_test_drive_present: Coming out of timeout loop\n"); */ |
status = FDC1772_READ(FDC1772REG_STATUS); |
ok = (status & FDC1772STAT_TR00) != 0; |
|
/*printk("fd_test_drive_present: ok=%d\n",ok); */ |
/* force interrupt to abort restore operation (FDC1772 would try |
* about 50 seconds!) */ |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); |
udelay(500); |
status = FDC1772_READ(FDC1772REG_STATUS); |
udelay(20); |
/*printk("fd_test_drive_present: just before OK code %d\n",ok); */ |
|
if (ok) { |
/* dummy seek command to make WP bit accessible */ |
FDC1772_WRITE(FDC1772REG_DATA, 0); |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); |
printk("fd_test_drive_present: just before wait for int\n"); |
/* DAG: Guess means wait for interrupt */ |
while (!(IOC_FIQSTAT & 2)); |
printk("fd_test_drive_present: just after wait for int\n"); |
status = FDC1772_READ(FDC1772REG_STATUS); |
} |
printk("fd_test_drive_present: just before ENABLE_IRQ\n"); |
ENABLE_IRQ(); |
printk("fd_test_drive_present: about to return\n"); |
return (ok); |
} |
|
|
/* Look how many and which kind of drives are connected. If there are |
* floppies, additionally start the disk-change and motor-off timers. |
*/ |
|
static void config_types(void) |
{ |
int drive, cnt = 0; |
|
printk("Probing floppy drive(s):\n"); |
for (drive = 0; drive < FD_MAX_UNITS; drive++) { |
fd_probe(drive); |
if (unit[drive].connected) { |
printk("fd%d\n", drive); |
++cnt; |
} |
} |
|
if (FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_BUSY) { |
/* If FDC1772 is still busy from probing, give it another FORCI |
* command to abort the operation. If this isn't done, the FDC1772 |
* 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 :-( |
*/ |
FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); |
udelay(500); |
FDC1772_READ(FDC1772REG_STATUS); |
udelay(20); |
} |
if (cnt > 0) { |
START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); |
if (cnt == 1) |
fd_select_drive(0); |
/*START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ |
} |
} |
|
/* |
* 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. |
*/ |
|
static int floppy_open(struct inode *inode, struct file *filp) |
{ |
int drive; |
int old_dev; |
|
if (!filp) { |
DPRINT(("Weird, open called with filp=0\n")); |
return -EIO; |
} |
drive = MINOR(inode->i_rdev) & 3; |
if ((MINOR(inode->i_rdev) >> 2) > NUM_DISK_TYPES) |
return -ENXIO; |
|
old_dev = fd_device[drive]; |
|
if (fd_ref[drive]) |
if (old_dev != inode->i_rdev) |
return -EBUSY; |
|
if (fd_ref[drive] == -1 || (fd_ref[drive] && filp->f_flags & O_EXCL)) |
return -EBUSY; |
|
if (filp->f_flags & O_EXCL) |
fd_ref[drive] = -1; |
else |
fd_ref[drive]++; |
|
fd_device[drive] = inode->i_rdev; |
|
if (old_dev && old_dev != inode->i_rdev) |
invalidate_buffers(old_dev); |
|
/* Allow ioctls if we have write-permissions even if read-only open */ |
if (filp->f_mode & 2 || permission(inode, 2) == 0) |
filp->f_mode |= IOCTL_MODE_BIT; |
if (filp->f_mode & 2) |
filp->f_mode |= OPEN_WRITE_BIT; |
|
if (filp->f_flags & O_NDELAY) |
return 0; |
|
if (filp->f_mode & 3) { |
check_disk_change(inode->i_rdev); |
if (filp->f_mode & 2) { |
if (unit[drive].wpstat) { |
floppy_release(inode, filp); |
return -EROFS; |
} |
} |
} |
return 0; |
} |
|
|
static void floppy_release(struct inode *inode, struct file *filp) |
{ |
int drive; |
|
drive = inode->i_rdev & 3; |
|
if (!filp || (filp->f_mode & (2 | OPEN_WRITE_BIT))) |
/* if the file is mounted OR (writable now AND writable at open |
time) Linus: Does this cover all cases? */ |
block_fsync(inode, filp); |
|
if (fd_ref[drive] < 0) |
fd_ref[drive] = 0; |
else if (!fd_ref[drive]--) { |
printk("floppy_release with fd_ref == 0"); |
fd_ref[drive] = 0; |
} |
} |
|
static struct file_operations floppy_fops = |
{ |
NULL, /* lseek - default */ |
block_read, /* read - general block-dev read */ |
block_write, /* write - general block-dev write */ |
NULL, /* readdir - bad */ |
NULL, /* select */ |
fd_ioctl, /* ioctl */ |
NULL, /* mmap */ |
floppy_open, /* open */ |
floppy_release, /* release */ |
block_fsync, /* fsync */ |
NULL, /* fasync */ |
check_floppy_change, /* media_change */ |
floppy_revalidate, /* revalidate */ |
}; |
|
|
int floppy_init(void) |
{ |
int i; |
|
if (register_blkdev(MAJOR_NR, "fd", &floppy_fops)) { |
printk("Unable to get major %d for floppy\n", MAJOR_NR); |
return 1; |
} |
|
if (request_dma(FLOPPY_DMA, "fd1772")) { |
printk("Unable to grab DMA%d for the floppy (1772) driver\n", FLOPPY_DMA); |
return 1; |
}; |
|
if (request_dma(FIQ_FD1772, "fd1772 end")) { |
printk("Unable to grab DMA%d for the floppy (1772) driver\n", FIQ_FD1772); |
free_dma(FLOPPY_DMA); |
return 1; |
}; |
enable_dma(FIQ_FD1772); /* This inserts a call to our command end routine */ |
|
/* initialize variables */ |
SelectedDrive = -1; |
#ifdef TRACKBUFFER |
BufferDrive = -1; |
#endif |
|
/* initialize check_change timer */ |
timer_table[FLOPPY_TIMER].fn = check_change; |
timer_active &= ~(1 << FLOPPY_TIMER); |
|
|
#ifdef TRACKBUFFER |
DMABuffer = (char *)kmalloc((MAX_SECTORS+1)*512,GFP_KERNEL); /* Atari uses 512 - I want to eventually cope with 1K sectors */ |
TrackBuffer = DMABuffer + 512; |
#else |
/* Allocate memory for the DMAbuffer - on the Atari this takes it |
out of some special memory... */ |
DMABuffer = (char *) kmalloc(2048); /* Copes with pretty large sectors */ |
#endif |
#ifdef TRACKBUFFER |
BufferDrive = BufferSide = BufferTrack = -1; |
#endif |
|
for (i = 0; i < FD_MAX_UNITS; i++) { |
unit[i].track = -1; |
} |
|
for (i = 0; i < 256; i++) |
if ((i >> 2) > 0 && (i >> 2) <= NUM_DISK_TYPES) |
floppy_sizes[i] = disk_type[(i >> 2) - 1].blocks >> 1; |
else |
floppy_sizes[i] = MAX_DISK_SIZE; |
|
blk_size[MAJOR_NR] = floppy_sizes; |
blksize_size[MAJOR_NR] = floppy_blocksizes; |
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; |
|
config_types(); |
|
return 0; |
} |
|
/* Just a dummy at the moment */ |
void floppy_setup(char *str, int *ints) |
{ |
} |
|
void floppy_eject(void) { |
} |
/fd1772dma.S
0,0 → 1,100
#include <asm/hardware.h> |
|
@ Code for DMA with the 1772 fdc |
.text |
|
|
.global _fdc1772_dataaddr |
_fdc1772_fiqdata: |
@ Number of bytes left to DMA |
.global _fdc1772_bytestogo |
_fdc1772_bytestogo: |
.word 0 |
@ Place to put/get data from in DMA |
.global _fdc1772_dataaddr |
_fdc1772_dataaddr: |
.word 0 |
|
.global _fdc1772_fdc_int_done |
_fdc1772_fdc_int_done: |
.word 0 |
.global _fdc1772_comendstatus |
_fdc1772_comendstatus: |
.word 0 |
|
@ We hang this off DMA channel 1 |
.global _fdc1772_comendhandler |
_fdc1772_comendhandler: |
mov r8,#IOC_BASE |
ldrb r9,[r8,#0x34] @ IOC FIQ status |
tst r9,#2 |
subeqs pc,r14,#4 @ should I leave a space here |
orr r9,r8,#0x10000 @ FDC base |
adr r8,_fdc1772_fdc_int_done |
ldrb r10,[r9,#0] @ FDC status |
mov r9,#1 @ Got a FIQ flag |
stmia r8,{r9,r10} |
subs pc,r14,#4 |
|
|
.global _fdc1772_dma_read |
_fdc1772_dma_read: |
mov r8,#IOC_BASE |
ldrb r9,[r8,#0x34] @ IOC FIQ status |
tst r9,#1 |
beq _fdc1772_dma_read_notours |
orr r8,r8,#0x10000 @ FDC base |
ldrb r10,[r8,#0xc] @ Read from FDC data reg (also clears interrupt) |
ldmia r11,{r8,r9} |
subs r8,r8,#1 @ One less byte to go |
@ If there was somewhere for this data to go then store it and update pointers |
strplb r10,[r9],#1 @ Store the data and increment the pointer |
stmplia r11,{r8,r9} @ Update count/pointers |
@ Handle any other interrupts if there are any |
_fdc1772_dma_read_notours: |
@ Cant branch because this code has been copied down to the FIQ vector |
ldr pc,[pc,#-4] |
.word _fdc1772_comendhandler |
.global _fdc1772_dma_read_end |
_fdc1772_dma_read_end: |
|
.global _fdc1772_dma_write |
_fdc1772_dma_write: |
mov r8,#IOC_BASE |
ldrb r9,[r8,#0x34] @ IOC FIQ status |
tst r9,#1 |
beq _fdc1772_dma_write_notours |
orr r8,r8,#0x10000 @ FDC base |
ldmia r11,{r9,r10} |
subs r9,r9,#1 @ One less byte to go |
@ If there really is some data then get it, store it and update count |
ldrplb r12,[r10],#1 |
strplb r12,[r8,#0xc] @ write it to FDC data reg |
stmplia r11,{r9,r10} @ Update count and pointer - should clear interrupt |
@ Handle any other interrupts |
_fdc1772_dma_write_notours: |
@ Cant branch because this code has been copied down to the FIQ vector |
ldr pc,[pc,#-4] |
.word _fdc1772_comendhandler |
|
.global _fdc1772_dma_write_end |
_fdc1772_dma_write_end: |
|
|
@ Setup the FIQ R11 to point to the data and store the count, address |
@ for this dma |
@ R0=count |
@ R1=address |
.global _fdc1772_setupdma |
_fdc1772_setupdma: |
@ The big job is flipping in and out of FIQ mode |
adr r2,_fdc1772_fiqdata @ This is what we really came here for |
stmia r2,{r0,r1} |
mov r3, pc |
teqp pc,#0x0c000001 @ Disable FIQs, IRQs and switch to FIQ mode |
mov r0,r0 @ NOP |
mov r11,r2 |
teqp r3,#0 @ Normal mode |
mov r0,r0 @ NOP |
mov pc,r14 |
|
/blk.h
0,0 → 1,438
#ifndef _BLK_H |
#define _BLK_H |
|
#include <linux/blkdev.h> |
#include <linux/locks.h> |
#include <linux/config.h> |
|
/* |
* NR_REQUEST is the number of entries in the request-queue. |
* NOTE that writes may use only the low 2/3 of these: reads |
* take precedence. |
*/ |
#define NR_REQUEST 64 |
|
/* |
* This is used in the elevator algorithm. We don't prioritise reads |
* over writes any more --- although reads are more time-critical than |
* writes, by treating them equally we increase filesystem throughput. |
* This turns out to give better overall performance. -- sct |
*/ |
#define IN_ORDER(s1,s2) \ |
((s1)->rq_dev < (s2)->rq_dev || (((s1)->rq_dev == (s2)->rq_dev && \ |
(s1)->sector < (s2)->sector))) |
|
/* |
* These will have to be changed to be aware of different buffer |
* sizes etc.. It actually needs a major cleanup. |
*/ |
#if defined(IDE_DRIVER) || defined(MD_DRIVER) |
#define SECTOR_MASK ((BLOCK_SIZE >> 9) - 1) |
#else |
#define SECTOR_MASK (blksize_size[MAJOR_NR] && \ |
blksize_size[MAJOR_NR][MINOR(CURRENT->rq_dev)] ? \ |
((blksize_size[MAJOR_NR][MINOR(CURRENT->rq_dev)] >> 9) - 1) : \ |
((BLOCK_SIZE >> 9) - 1)) |
#endif /* IDE_DRIVER */ |
|
#define SUBSECTOR(block) (CURRENT->current_nr_sectors > 0) |
|
#ifdef CONFIG_CDU31A |
extern int cdu31a_init(void); |
#endif CONFIG_CDU31A |
#ifdef CONFIG_MCD |
extern int mcd_init(void); |
#endif CONFIG_MCD |
#ifdef CONFIG_MCDX |
extern int mcdx_init(void); |
#endif CONFIG_MCDX |
#ifdef CONFIG_SBPCD |
extern int sbpcd_init(void); |
#endif CONFIG_SBPCD |
#ifdef CONFIG_AZTCD |
extern int aztcd_init(void); |
#endif CONFIG_AZTCD |
#ifdef CONFIG_CDU535 |
extern int sony535_init(void); |
#endif CONFIG_CDU535 |
#ifdef CONFIG_GSCD |
extern int gscd_init(void); |
#endif CONFIG_GSCD |
#ifdef CONFIG_CM206 |
extern int cm206_init(void); |
#endif CONFIG_CM206 |
#ifdef CONFIG_OPTCD |
extern int optcd_init(void); |
#endif CONFIG_OPTCD |
#ifdef CONFIG_SJCD |
extern int sjcd_init(void); |
#endif CONFIG_SJCD |
#ifdef CONFIG_BLK_DEV_HD |
extern int hd_init(void); |
#endif |
#ifdef CONFIG_BLK_DEV_IDE |
extern int ide_init(void); |
#endif |
#ifdef CONFIG_BLK_DEV_XD |
extern int xd_init(void); |
#endif |
#ifdef CONFIG_BLK_DEV_LOOP |
extern int loop_init(void); |
#endif |
#ifdef CONFIG_BLK_DEV_MD |
extern int md_init(void); |
#endif CONFIG_BLK_DEV_MD |
|
extern void set_device_ro(kdev_t dev,int flag); |
void add_blkdev_randomness(int major); |
|
extern int floppy_init(void); |
extern void rd_load(void); |
extern int rd_init(void); |
extern int rd_doload; /* 1 = load ramdisk, 0 = don't load */ |
extern int rd_prompt; /* 1 = prompt for ramdisk, 0 = don't prompt */ |
extern int rd_image_start; /* starting block # of image */ |
|
#ifdef CONFIG_BLK_DEV_INITRD |
|
#define INITRD_MINOR 250 /* shouldn't collide with /dev/ram* too soon ... */ |
|
extern unsigned long initrd_start,initrd_end; |
extern int mount_initrd; /* zero if initrd should not be mounted */ |
void initrd_init(void); |
|
#endif |
|
#define RO_IOCTLS(dev,where) \ |
case BLKROSET: { int __err; if (!suser()) return -EACCES; \ |
__err = verify_area(VERIFY_READ, (void *) (where), sizeof(long)); \ |
if (!__err) set_device_ro((dev),get_fs_long((long *) (where))); return __err; } \ |
case BLKROGET: { int __err = verify_area(VERIFY_WRITE, (void *) (where), sizeof(long)); \ |
if (!__err) put_fs_long(0!=is_read_only(dev),(long *) (where)); return __err; } |
|
#if defined(MAJOR_NR) || defined(IDE_DRIVER) |
|
/* |
* Add entries as needed. |
*/ |
|
#ifdef IDE_DRIVER |
|
#define DEVICE_NR(device) (MINOR(device) >> PARTN_BITS) |
#define DEVICE_ON(device) /* nothing */ |
#define DEVICE_OFF(device) /* nothing */ |
|
#elif (MAJOR_NR == RAMDISK_MAJOR) |
|
/* ram disk */ |
#define DEVICE_NAME "ramdisk" |
#define DEVICE_REQUEST rd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
#define DEVICE_NO_RANDOM |
|
#elif (MAJOR_NR == FLOPPY_MAJOR) |
|
static void floppy_off(unsigned int nr); |
|
#define DEVICE_NAME "floppy" |
#define DEVICE_INTR do_floppy |
#define DEVICE_REQUEST do_fd_request |
#define DEVICE_NR(device) ( (MINOR(device) & 3) | ((MINOR(device) & 0x80 ) >> 5 )) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) floppy_off(DEVICE_NR(device)) |
|
#elif (MAJOR_NR == HD_MAJOR) |
|
/* harddisk: timeout is 6 seconds.. */ |
#define DEVICE_NAME "harddisk" |
#define DEVICE_INTR do_hd |
#define DEVICE_TIMEOUT HD_TIMER |
#define TIMEOUT_VALUE (6*HZ) |
#define DEVICE_REQUEST do_hd_request |
#define DEVICE_NR(device) (MINOR(device)>>6) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == SCSI_DISK_MAJOR) |
|
#define DEVICE_NAME "scsidisk" |
#define DEVICE_INTR do_sd |
#define TIMEOUT_VALUE (2*HZ) |
#define DEVICE_REQUEST do_sd_request |
#define DEVICE_NR(device) (MINOR(device) >> 4) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
/* Kludge to use the same number for both char and block major numbers */ |
#elif (MAJOR_NR == MD_MAJOR) && defined(MD_DRIVER) |
|
#define DEVICE_NAME "Multiple devices driver" |
#define DEVICE_REQUEST do_md_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == SCSI_TAPE_MAJOR) |
|
#define DEVICE_NAME "scsitape" |
#define DEVICE_INTR do_st |
#define DEVICE_NR(device) (MINOR(device) & 0x7f) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == SCSI_CDROM_MAJOR) |
|
#define DEVICE_NAME "CD-ROM" |
#define DEVICE_INTR do_sr |
#define DEVICE_REQUEST do_sr_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == XT_DISK_MAJOR) |
|
#define DEVICE_NAME "xt disk" |
#define DEVICE_REQUEST do_xd_request |
#define DEVICE_NR(device) (MINOR(device) >> 6) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif defined(MFM_DISK_MAJOR) && (MAJOR_NR == MFM_DISK_MAJOR) |
|
#define DEVICE_NAME "mfm disk" |
#define DEVICE_INTR do_mfm |
#define DEVICE_REQUEST do_mfm_request |
#define DEVICE_NR(device) (MINOR(device) >> 6) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == CDU31A_CDROM_MAJOR) |
|
#define DEVICE_NAME "CDU31A" |
#define DEVICE_REQUEST do_cdu31a_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MITSUMI_CDROM_MAJOR) |
|
#define DEVICE_NAME "Mitsumi CD-ROM" |
/* #define DEVICE_INTR do_mcd */ |
#define DEVICE_REQUEST do_mcd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MITSUMI_X_CDROM_MAJOR) |
|
#define DEVICE_NAME "Mitsumi CD-ROM" |
/* #define DEVICE_INTR do_mcdx */ |
#define DEVICE_REQUEST do_mcdx_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MATSUSHITA_CDROM_MAJOR) |
|
#define DEVICE_NAME "Matsushita CD-ROM controller #1" |
#define DEVICE_REQUEST do_sbpcd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MATSUSHITA_CDROM2_MAJOR) |
|
#define DEVICE_NAME "Matsushita CD-ROM controller #2" |
#define DEVICE_REQUEST do_sbpcd2_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MATSUSHITA_CDROM3_MAJOR) |
|
#define DEVICE_NAME "Matsushita CD-ROM controller #3" |
#define DEVICE_REQUEST do_sbpcd3_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == MATSUSHITA_CDROM4_MAJOR) |
|
#define DEVICE_NAME "Matsushita CD-ROM controller #4" |
#define DEVICE_REQUEST do_sbpcd4_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == AZTECH_CDROM_MAJOR) |
|
#define DEVICE_NAME "Aztech CD-ROM" |
#define DEVICE_REQUEST do_aztcd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == CDU535_CDROM_MAJOR) |
|
#define DEVICE_NAME "SONY-CDU535" |
#define DEVICE_INTR do_cdu535 |
#define DEVICE_REQUEST do_cdu535_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == GOLDSTAR_CDROM_MAJOR) |
|
#define DEVICE_NAME "Goldstar R420" |
#define DEVICE_REQUEST do_gscd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == CM206_CDROM_MAJOR) |
#define DEVICE_NAME "Philips/LMS cd-rom cm206" |
#define DEVICE_REQUEST do_cm206_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == OPTICS_CDROM_MAJOR) |
|
#define DEVICE_NAME "DOLPHIN 8000AT CD-ROM" |
#define DEVICE_REQUEST do_optcd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#elif (MAJOR_NR == SANYO_CDROM_MAJOR) |
|
#define DEVICE_NAME "Sanyo H94A CD-ROM" |
#define DEVICE_REQUEST do_sjcd_request |
#define DEVICE_NR(device) (MINOR(device)) |
#define DEVICE_ON(device) |
#define DEVICE_OFF(device) |
|
#endif /* MAJOR_NR == whatever */ |
|
#if (MAJOR_NR != SCSI_TAPE_MAJOR) |
#if !defined(IDE_DRIVER) |
|
#ifndef CURRENT |
#define CURRENT (blk_dev[MAJOR_NR].current_request) |
#endif |
|
#define CURRENT_DEV DEVICE_NR(CURRENT->rq_dev) |
|
#ifdef DEVICE_INTR |
static void (*DEVICE_INTR)(void) = NULL; |
#endif |
#ifdef DEVICE_TIMEOUT |
|
#define SET_TIMER \ |
((timer_table[DEVICE_TIMEOUT].expires = jiffies + TIMEOUT_VALUE), \ |
(timer_active |= 1<<DEVICE_TIMEOUT)) |
|
#define CLEAR_TIMER \ |
timer_active &= ~(1<<DEVICE_TIMEOUT) |
|
#define SET_INTR(x) \ |
if ((DEVICE_INTR = (x)) != NULL) \ |
SET_TIMER; \ |
else \ |
CLEAR_TIMER; |
|
#else |
|
#define SET_INTR(x) (DEVICE_INTR = (x)) |
|
#endif /* DEVICE_TIMEOUT */ |
|
static void (DEVICE_REQUEST)(void); |
|
#ifdef DEVICE_INTR |
#define CLEAR_INTR SET_INTR(NULL) |
#else |
#define CLEAR_INTR |
#endif |
|
#define INIT_REQUEST \ |
if (!CURRENT) {\ |
CLEAR_INTR; \ |
return; \ |
} \ |
if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) \ |
panic(DEVICE_NAME ": request list destroyed"); \ |
if (CURRENT->bh) { \ |
if (!buffer_locked(CURRENT->bh)) \ |
panic(DEVICE_NAME ": block not locked"); \ |
} |
|
#endif /* !defined(IDE_DRIVER) */ |
|
/* end_request() - SCSI devices have their own version */ |
/* - IDE drivers have their own copy too */ |
|
#if ! SCSI_BLK_MAJOR(MAJOR_NR) |
|
#if defined(IDE_DRIVER) && !defined(_IDE_C) /* shared copy for IDE modules */ |
void ide_end_request(byte uptodate, ide_hwgroup_t *hwgroup); |
#else |
|
#ifdef IDE_DRIVER |
void ide_end_request(byte uptodate, ide_hwgroup_t *hwgroup) { |
struct request *req = hwgroup->rq; |
#else |
static void end_request(int uptodate) { |
struct request *req = CURRENT; |
#endif /* IDE_DRIVER */ |
struct buffer_head * bh; |
|
req->errors = 0; |
if (!uptodate) { |
printk("end_request: I/O error, dev %s, sector %lu\n", |
kdevname(req->rq_dev), req->sector); |
req->nr_sectors--; |
req->nr_sectors &= ~SECTOR_MASK; |
req->sector += (BLOCK_SIZE / 512); |
req->sector &= ~SECTOR_MASK; |
} |
|
if ((bh = req->bh) != NULL) { |
req->bh = bh->b_reqnext; |
bh->b_reqnext = NULL; |
mark_buffer_uptodate(bh, uptodate); |
unlock_buffer(bh); |
if ((bh = req->bh) != NULL) { |
req->current_nr_sectors = bh->b_size >> 9; |
if (req->nr_sectors < req->current_nr_sectors) { |
req->nr_sectors = req->current_nr_sectors; |
printk("end_request: buffer-list destroyed\n"); |
} |
req->buffer = bh->b_data; |
return; |
} |
} |
#ifndef DEVICE_NO_RANDOM |
add_blkdev_randomness(MAJOR(req->rq_dev)); |
#endif |
#ifdef IDE_DRIVER |
blk_dev[MAJOR(req->rq_dev)].current_request = req->next; |
hwgroup->rq = NULL; |
#else |
DEVICE_OFF(req->rq_dev); |
CURRENT = req->next; |
#endif /* IDE_DRIVER */ |
if (req->sem != NULL) |
up(req->sem); |
req->rq_status = RQ_INACTIVE; |
wake_up(&wait_for_request); |
} |
#endif /* defined(IDE_DRIVER) && !defined(_IDE_C) */ |
#endif /* ! SCSI_BLK_MAJOR(MAJOR_NR) */ |
#endif /* (MAJOR_NR != SCSI_TAPE_MAJOR) */ |
|
#endif /* defined(MAJOR_NR) || defined(IDE_DRIVER) */ |
|
#endif /* _BLK_H */ |
/ide-ics.c
0,0 → 1,297
/* |
* linux/arch/arm/drivers/block/ide-ics.c |
* |
* Copyright (c) 1996,1997 Russell King. |
* |
* Changelog: |
* 08-06-1996 RMK Created |
* 12-09-1997 RMK Added interrupt enable/disable |
*/ |
|
#include <linux/module.h> |
#include <linux/malloc.h> |
#include <linux/blkdev.h> |
#include <linux/errno.h> |
|
#include <asm/dma.h> |
#include <asm/ecard.h> |
#include <asm/io.h> |
|
#include "ide.h" |
|
/* |
* Maximum number of interfaces per card |
*/ |
#define MAX_IFS 2 |
|
#define ICS_IDENT_OFFSET 0x8a0 |
|
#define ICS_ARCIN_V5_INTRSTAT 0x000 |
#define ICS_ARCIN_V5_INTROFFSET 0x001 |
#define ICS_ARCIN_V5_IDEOFFSET 0xa00 |
#define ICS_ARCIN_V5_IDEALTOFFSET 0xae0 |
#define ICS_ARCIN_V5_IDESTEPPING 4 |
|
#define ICS_ARCIN_V6_IDEOFFSET_1 0x800 |
#define ICS_ARCIN_V6_INTROFFSET_1 0x880 |
#define ICS_ARCIN_V6_INTRSTAT_1 0x8a4 |
#define ICS_ARCIN_V6_IDEALTOFFSET_1 0x8e0 |
#define ICS_ARCIN_V6_IDEOFFSET_2 0xc00 |
#define ICS_ARCIN_V6_INTROFFSET_2 0xc80 |
#define ICS_ARCIN_V6_INTRSTAT_2 0xca4 |
#define ICS_ARCIN_V6_IDEALTOFFSET_2 0xce0 |
#define ICS_ARCIN_V6_IDESTEPPING 4 |
|
static const card_ids icside_cids[] = { |
{ MANU_ICS, PROD_ICS_IDE }, |
{ MANU_ICS2, PROD_ICS2_IDE }, |
{ 0xffff, 0xffff } |
}; |
|
typedef enum { |
ics_if_unknown, |
ics_if_arcin_v5, |
ics_if_arcin_v6 |
} iftype_t; |
|
static struct expansion_card *ec[MAX_ECARDS]; |
static int result[MAX_ECARDS][MAX_IFS]; |
|
|
/* ---------------- Version 5 PCB Support Functions --------------------- */ |
/* Prototype: icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr) |
* Purpose : enable interrupts from card |
*/ |
static void icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr) |
{ |
unsigned int memc_port = (unsigned int)ec->irq_data; |
outb (0, memc_port + ICS_ARCIN_V5_INTROFFSET); |
} |
|
/* Prototype: icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr) |
* Purpose : disable interrupts from card |
*/ |
static void icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr) |
{ |
unsigned int memc_port = (unsigned int)ec->irq_data; |
inb (memc_port + ICS_ARCIN_V5_INTROFFSET); |
} |
|
static const expansioncard_ops_t icside_ops_arcin_v5 = { |
icside_irqenable_arcin_v5, |
icside_irqdisable_arcin_v5, |
NULL, |
NULL, |
NULL, |
NULL |
}; |
|
|
/* ---------------- Version 6 PCB Support Functions --------------------- */ |
/* Prototype: icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr) |
* Purpose : enable interrupts from card |
*/ |
static void icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr) |
{ |
unsigned int ide_base_port = (unsigned int)ec->irq_data; |
outb (0, ide_base_port + ICS_ARCIN_V6_INTROFFSET_1); |
outb (0, ide_base_port + ICS_ARCIN_V6_INTROFFSET_2); |
} |
|
/* Prototype: icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr) |
* Purpose : disable interrupts from card |
*/ |
static void icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr) |
{ |
unsigned int ide_base_port = (unsigned int)ec->irq_data; |
inb (ide_base_port + ICS_ARCIN_V6_INTROFFSET_1); |
inb (ide_base_port + ICS_ARCIN_V6_INTROFFSET_2); |
} |
|
/* Prototype: icside_irqprobe(struct expansion_card *ec) |
* Purpose : detect an active interrupt from card |
*/ |
static int icside_irqprobe_arcin_v6(struct expansion_card *ec) |
{ |
unsigned int ide_base_port = (unsigned int)ec->irq_data; |
|
return inb(ide_base_port + ICS_ARCIN_V6_INTRSTAT_1) & 1 || |
inb(ide_base_port + ICS_ARCIN_V6_INTRSTAT_2) & 1; |
} |
|
static const expansioncard_ops_t icside_ops_arcin_v6 = { |
icside_irqenable_arcin_v6, |
icside_irqdisable_arcin_v6, |
icside_irqprobe_arcin_v6, |
NULL, |
NULL, |
NULL |
}; |
|
|
|
/* Prototype: icside_identifyif (struct expansion_card *ec) |
* Purpose : identify IDE interface type |
* Notes : checks the description string |
*/ |
static iftype_t icside_identifyif (struct expansion_card *ec) |
{ |
unsigned int addr; |
iftype_t iftype; |
int id = 0; |
|
iftype = ics_if_unknown; |
|
addr = ecard_address (ec, ECARD_IOC, ECARD_FAST) + ICS_IDENT_OFFSET; |
|
id = inb (addr) & 1; |
id |= (inb (addr + 1) & 1) << 1; |
id |= (inb (addr + 2) & 1) << 2; |
id |= (inb (addr + 3) & 1) << 3; |
|
switch (id) { |
case 0: /* A3IN */ |
printk ("icside: A3IN unsupported\n"); |
break; |
|
case 1: /* A3USER */ |
printk ("icside: A3USER unsupported\n"); |
break; |
|
case 3: /* ARCIN V6 */ |
printk ("icside: detected ARCIN V6 in slot %d\n", ec->slot_no); |
iftype = ics_if_arcin_v6; |
break; |
|
case 15:/* ARCIN V5 (no id) */ |
printk ("icside: detected ARCIN V5 in slot %d\n", ec->slot_no); |
iftype = ics_if_arcin_v5; |
break; |
|
default:/* we don't know - complain very loudly */ |
printk ("icside: ***********************************\n"); |
printk ("icside: *** UNKNOWN ICS INTERFACE id=%d ***\n", id); |
printk ("icside: ***********************************\n"); |
printk ("icside: please report this to: linux@arm.linux.org.uk\n"); |
break; |
} |
|
return iftype; |
} |
|
/* Prototype: icside_register (struct expansion_card *ec) |
* Purpose : register an ICS IDE card with the IDE driver |
* Notes : we make sure that interrupts are disabled from the card |
*/ |
static inline void icside_register (struct expansion_card *ec, int index) |
{ |
unsigned long port = 0, latch; |
|
result[index][0] = -1; |
result[index][1] = -1; |
|
switch (icside_identifyif (ec)) { |
case ics_if_unknown: |
default: |
printk ("icside: *** Warning: ICS IDE Interface unrecognised! ***\n"); |
break; |
|
case ics_if_arcin_v5: |
port = ecard_address (ec, ECARD_MEMC, 0); |
ec->irqaddr = (unsigned char *)ioaddr(port + ICS_ARCIN_V5_INTRSTAT); |
ec->irqmask = 1; |
ec->irq_data = (void *)port; |
ec->ops = (expansioncard_ops_t *)&icside_ops_arcin_v5; |
|
/* |
* Be on the safe side - disable interrupts |
*/ |
inb (port + ICS_ARCIN_V5_INTROFFSET); |
result[index][0] = |
ide_register_port(port + ICS_ARCIN_V5_IDEOFFSET, |
port + ICS_ARCIN_V5_IDEALTOFFSET, |
ICS_ARCIN_V5_IDESTEPPING, |
ec->irq); |
break; |
|
case ics_if_arcin_v6: |
latch = ecard_address (ec, ECARD_IOC, ECARD_FAST); |
if (ec->dma == NO_DMA) |
port = ecard_address (ec, ECARD_EASI, ECARD_FAST); |
if (port) |
outb(0x20, latch); |
else { |
port = latch; |
outb(0, port); |
} |
ec->irq_data = (void *)port; |
ec->ops = (expansioncard_ops_t *)&icside_ops_arcin_v6; |
|
/* |
* Be on the safe side - disable interrupts |
*/ |
inb (port + ICS_ARCIN_V6_INTROFFSET_1); |
inb (port + ICS_ARCIN_V6_INTROFFSET_2); |
result[index][0] = |
ide_register_port(port + ICS_ARCIN_V6_IDEOFFSET_1, |
port + ICS_ARCIN_V6_IDEALTOFFSET_1, |
ICS_ARCIN_V6_IDESTEPPING, |
ec->irq); |
result[index][1] = |
ide_register_port(port + ICS_ARCIN_V6_IDEOFFSET_2, |
port + ICS_ARCIN_V6_IDEALTOFFSET_2, |
ICS_ARCIN_V6_IDESTEPPING, |
ec->irq); |
break; |
} |
} |
|
int icside_init (void) |
{ |
int i; |
|
for (i = 0; i < MAX_ECARDS; i++) |
ec[i] = NULL; |
|
ecard_startfind (); |
|
for (i = 0; ; i++) { |
if ((ec[i] = ecard_find (0, icside_cids)) == NULL) |
break; |
|
ecard_claim (ec[i]); |
icside_register (ec[i], i); |
} |
|
for (i = 0; i < MAX_ECARDS; i++) |
if (ec[i] && result[i][0] < 0 && result[i][1] < 0) { |
ecard_release (ec[i]); |
ec[i] = NULL; |
} |
return 0; |
} |
|
#ifdef MODULE |
int init_module (void) |
{ |
return icside_init(); |
} |
|
void cleanup_module (void) |
{ |
int i; |
|
for (i = 0; i < MAX_ECARDS; i++) |
if (ec[i]) { |
if (result[i][0] >= 0) |
ide_unregister (result[i][0]); |
|
if (result[i][1] >= 0) |
ide_unregister (result[i][1]); |
|
ecard_release (ec[i]); |
ec[i] = NULL; |
} |
} |
#endif |
|
/genhd.c
0,0 → 1,445
/* |
* Code extracted from |
* linux/kernel/hd.c |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
* |
* |
* Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug |
* in the early extended-partition checks and added DM partitions |
* |
* Support for DiskManager v6.0x added by Mark Lord, |
* with information provided by OnTrack. This now works for linux fdisk |
* and LILO, as well as loadlin and bootln. Note that disks other than |
* /dev/hda *must* have a "DOS" type 0x51 partition in the first slot (hda1). |
* |
* More flexible handling of extended partitions - aeb, 950831 |
* |
* Modifications Copyright (C) 1995, 1996 Russell King |
* |
* Origional from linux/drivers/genhd.c |
* |
* Altered to find image files on ADFS-formatted hard drives. |
*/ |
|
#include <linux/config.h> |
#include <linux/fs.h> |
#include <linux/genhd.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/string.h> |
#ifdef CONFIG_BLK_DEV_INITRD |
#include <linux/blk.h> |
#endif |
|
#include <asm/system.h> |
|
/* |
* Many architectures don't like unaligned accesses, which is |
* frequently the case with the nr_sects and start_sect partition |
* table entries. |
*/ |
#include <asm/unaligned.h> |
|
#define SYS_IND(p) get_unaligned(&p->sys_ind) |
#define NR_SECTS(p) get_unaligned(&p->nr_sects) |
#define START_SECT(p) get_unaligned(&p->start_sect) |
|
|
struct gendisk *gendisk_head; |
|
static int current_minor; |
extern int *blk_size[]; |
extern void rd_load(void); |
extern void initrd_load(void); |
|
extern int chr_dev_init(void); |
extern int blk_dev_init(void); |
extern int scsi_dev_init(void); |
extern int net_dev_init(void); |
|
/* |
* disk_name() is used by genhd.c and md.c. |
* It formats the devicename of the indicated disk |
* into the supplied buffer, and returns a pointer |
* to that same buffer (for convenience). |
*/ |
char *disk_name (struct gendisk *hd, int minor, char *buf) |
{ |
unsigned int part; |
const char *maj = hd->major_name; |
char unit = (minor >> hd->minor_shift) + 'a'; |
|
#ifdef CONFIG_BLK_DEV_IDE |
/* |
* IDE devices use multiple major numbers, but the drives |
* are named as: {hda,hdb}, {hdc,hdd}, {hde,hdf}, {hdg,hdh}.. |
* This requires special handling here. |
*/ |
switch (hd->major) { |
case IDE3_MAJOR: |
unit += 2; |
case IDE2_MAJOR: |
unit += 2; |
case IDE1_MAJOR: |
unit += 2; |
case IDE0_MAJOR: |
maj = "hd"; |
} |
#endif |
part = minor & ((1 << hd->minor_shift) - 1); |
if (part) |
sprintf(buf, "%s%c%d", maj, unit, part); |
else |
sprintf(buf, "%s%c", maj, unit); |
return buf; |
} |
|
void add_partition (struct gendisk *hd, int minor, int start, int size) |
{ |
char buf[8]; |
hd->part[minor].start_sect = start; |
hd->part[minor].nr_sects = size; |
printk(" %s", disk_name(hd, minor, buf)); |
} |
|
static inline int is_extended_partition(struct partition *p) |
{ |
return (SYS_IND(p) == DOS_EXTENDED_PARTITION || |
SYS_IND(p) == LINUX_EXTENDED_PARTITION); |
} |
|
#ifdef CONFIG_MSDOS_PARTITION |
/* |
* Create devices for each logical partition in an extended partition. |
* The logical partitions form a linked list, with each entry being |
* a partition table with two entries. The first entry |
* is the real data partition (with a start relative to the partition |
* table start). The second is a pointer to the next logical partition |
* (with a start relative to the entire extended partition). |
* We do not create a Linux partition for the partition tables, but |
* only for the actual data partitions. |
*/ |
|
static void extended_partition(struct gendisk *hd, kdev_t dev) |
{ |
struct buffer_head *bh; |
struct partition *p; |
unsigned long first_sector, first_size, this_sector, this_size; |
int mask = (1 << hd->minor_shift) - 1; |
int i; |
|
first_sector = hd->part[MINOR(dev)].start_sect; |
first_size = hd->part[MINOR(dev)].nr_sects; |
this_sector = first_sector; |
|
while (1) { |
if ((current_minor & mask) == 0) |
return; |
if (!(bh = bread(dev,0,1024))) |
return; |
/* |
* This block is from a device that we're about to stomp on. |
* So make sure nobody thinks this block is usable. |
*/ |
bh->b_state = 0; |
|
if (*(unsigned short *) (bh->b_data+510) != 0xAA55) |
goto done; |
|
p = (struct partition *) (0x1BE + bh->b_data); |
|
this_size = hd->part[MINOR(dev)].nr_sects; |
|
/* |
* Usually, the first entry is the real data partition, |
* the 2nd entry is the next extended partition, or empty, |
* and the 3rd and 4th entries are unused. |
* However, DRDOS sometimes has the extended partition as |
* the first entry (when the data partition is empty), |
* and OS/2 seems to use all four entries. |
*/ |
|
/* |
* First process the data partition(s) |
*/ |
for (i=0; i<4; i++, p++) { |
if (!NR_SECTS(p) || is_extended_partition(p)) |
continue; |
|
/* Check the 3rd and 4th entries - |
these sometimes contain random garbage */ |
if (i >= 2 |
&& START_SECT(p) + NR_SECTS(p) > this_size |
&& (this_sector + START_SECT(p) < first_sector || |
this_sector + START_SECT(p) + NR_SECTS(p) > |
first_sector + first_size)) |
continue; |
|
add_partition(hd, current_minor, this_sector+START_SECT(p), NR_SECTS(p)); |
current_minor++; |
if ((current_minor & mask) == 0) |
goto done; |
} |
/* |
* Next, process the (first) extended partition, if present. |
* (So far, there seems to be no reason to make |
* extended_partition() recursive and allow a tree |
* of extended partitions.) |
* It should be a link to the next logical partition. |
* Create a minor for this just long enough to get the next |
* partition table. The minor will be reused for the next |
* data partition. |
*/ |
p -= 4; |
for (i=0; i<4; i++, p++) |
if(NR_SECTS(p) && is_extended_partition(p)) |
break; |
if (i == 4) |
goto done; /* nothing left to do */ |
|
hd->part[current_minor].nr_sects = NR_SECTS(p); |
hd->part[current_minor].start_sect = first_sector + START_SECT(p); |
this_sector = first_sector + START_SECT(p); |
dev = MKDEV(hd->major, current_minor); |
brelse(bh); |
} |
done: |
brelse(bh); |
} |
|
#ifdef CONFIG_BSD_DISKLABEL |
/* |
* Create devices for BSD partitions listed in a disklabel, under a |
* dos-like partition. See extended_partition() for more information. |
*/ |
static void bsd_disklabel_partition(struct gendisk *hd, kdev_t dev) |
{ |
struct buffer_head *bh; |
struct bsd_disklabel *l; |
struct bsd_partition *p; |
int mask = (1 << hd->minor_shift) - 1; |
|
if (!(bh = bread(dev,0,1024))) |
return; |
bh->b_state = 0; |
l = (struct bsd_disklabel *) (bh->b_data+512); |
if (l->d_magic != BSD_DISKMAGIC) { |
brelse(bh); |
return; |
} |
|
p = &l->d_partitions[0]; |
while (p - &l->d_partitions[0] <= BSD_MAXPARTITIONS) { |
if ((current_minor & mask) >= (4 + hd->max_p)) |
break; |
|
if (p->p_fstype != BSD_FS_UNUSED) { |
add_partition(hd, current_minor, p->p_offset, p->p_size); |
current_minor++; |
} |
p++; |
} |
brelse(bh); |
|
} |
#endif |
|
static int msdos_partition(struct gendisk *hd, kdev_t dev, unsigned long first_sector) |
{ |
int i, minor = current_minor; |
struct buffer_head *bh; |
struct partition *p; |
unsigned char *data; |
int mask = (1 << hd->minor_shift) - 1; |
|
if (!(bh = bread(dev,0,1024))) { |
printk(" unable to read partition table\n"); |
return -1; |
} |
data = bh->b_data; |
/* In some cases we modify the geometry */ |
/* of the drive (below), so ensure that */ |
/* nobody else tries to re-use this data. */ |
bh->b_state = 0; |
|
if (*(unsigned short *) (0x1fe + data) != 0xAA55) { |
brelse(bh); |
return 0; |
} |
printk (" [DOS]"); |
p = (struct partition *) (0x1be + data); |
|
current_minor += 4; /* first "extra" minor (for extended partitions) */ |
for (i=1 ; i<=4 ; minor++,i++,p++) { |
if (!NR_SECTS(p)) |
continue; |
add_partition(hd, minor, first_sector+START_SECT(p), NR_SECTS(p)); |
if (is_extended_partition(p)) { |
printk(" <"); |
/* |
* If we are rereading the partition table, we need |
* to set the size of the partition so that we will |
* be able to bread the block containing the extended |
* partition info. |
*/ |
hd->sizes[minor] = hd->part[minor].nr_sects |
>> (BLOCK_SIZE_BITS - 9); |
extended_partition(hd, MKDEV(hd->major, minor)); |
printk(" >"); |
/* prevent someone doing mkfs or mkswap on an |
extended partition, but leave room for LILO */ |
if (hd->part[minor].nr_sects > 2) |
hd->part[minor].nr_sects = 2; |
} |
#ifdef CONFIG_BSD_DISKLABEL |
if (SYS_IND(p) == BSD_PARTITION) { |
printk(" <"); |
bsd_disklabel_partition(hd, MKDEV(hd->major, minor)); |
printk(" >"); |
} |
#endif |
} |
/* |
* Check for old-style Disk Manager partition table |
*/ |
if (*(unsigned short *) (data+0xfc) == 0x55AA) { |
p = (struct partition *) (0x1be + data); |
for (i = 4 ; i < 16 ; i++, current_minor++) { |
p--; |
if ((current_minor & mask) == 0) |
break; |
if (!(START_SECT(p) && NR_SECTS(p))) |
continue; |
add_partition(hd, current_minor, START_SECT(p), NR_SECTS(p)); |
} |
} |
printk("\n"); |
brelse(bh); |
return 1; |
} |
|
#endif /* CONFIG_MSDOS_PARTITION */ |
|
extern int adfspart_initdev (struct gendisk *hd, kdev_t dev, int first_sector, int minor); |
|
static void check_partition(struct gendisk *hd, kdev_t dev) |
{ |
static int first_time = 1; |
unsigned long first_sector; |
char buf[8]; |
|
if (first_time) |
printk("Partition check:\n"); |
first_time = 0; |
first_sector = hd->part[MINOR(dev)].start_sect; |
|
/* |
* This is a kludge to allow the partition check to be |
* skipped for specific drives (e.g. IDE cd-rom drives) |
*/ |
if ((int)first_sector == -1) { |
hd->part[MINOR(dev)].start_sect = 0; |
return; |
} |
|
printk(" %s:", disk_name(hd, MINOR(dev), buf)); |
#ifdef CONFIG_BLK_DEV_PART |
if (adfspart_initdev (hd, dev, first_sector, current_minor)) |
return; |
#endif |
#ifdef CONFIG_MSDOS_PARTITION |
if (msdos_partition(hd, dev, first_sector)) |
return; |
#endif |
printk(" unknown partition table\n"); |
} |
|
/* |
* This function is used to re-read partition tables for removable disks. |
* Much of the cleanup from the old partition tables should have already been |
* done |
*/ |
|
/* |
* This function will re-read the partition tables for a given device, |
* and set things back up again. There are some important caveats, |
* however. You must ensure that no one is using the device, and no one |
* can start using the device while this function is being executed. |
*/ |
void resetup_one_dev(struct gendisk *dev, int drive) |
{ |
int i; |
int first_minor = drive << dev->minor_shift; |
int end_minor = first_minor + dev->max_p; |
|
blk_size[dev->major] = NULL; |
current_minor = 1 + first_minor; |
check_partition(dev, MKDEV(dev->major, first_minor)); |
|
/* |
* We need to set the sizes array before we will be able to access |
* any of the partitions on this device. |
*/ |
if (dev->sizes != NULL) { /* optional safeguard in ll_rw_blk.c */ |
for (i = first_minor; i < end_minor; i++) |
dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9); |
blk_size[dev->major] = dev->sizes; |
} |
} |
|
static void setup_dev(struct gendisk *dev) |
{ |
int i, drive; |
int end_minor = dev->max_nr * dev->max_p; |
|
blk_size[dev->major] = NULL; |
for (i = 0 ; i < end_minor; i++) { |
dev->part[i].start_sect = 0; |
dev->part[i].nr_sects = 0; |
} |
dev->init(dev); |
for (drive = 0 ; drive < dev->nr_real ; drive++) { |
int first_minor = drive << dev->minor_shift; |
current_minor = 1 + first_minor; |
check_partition(dev, MKDEV(dev->major, first_minor)); |
} |
if (dev->sizes != NULL) { /* optional safeguard in ll_rw_blk.c */ |
for (i = 0; i < end_minor; i++) |
dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9); |
blk_size[dev->major] = dev->sizes; |
} |
} |
|
void device_setup(void) |
{ |
extern void console_map_init(void); |
extern void adfsimg_init (void); |
struct gendisk *p; |
int nr=0; |
|
chr_dev_init(); |
blk_dev_init(); |
sti(); |
#ifdef CONFIG_SCSI |
scsi_dev_init(); |
#endif |
#ifdef CONFIG_INET |
net_dev_init(); |
#endif |
console_map_init(); |
#ifdef CONFIG_BLK_DEV_IMG |
adfsimg_init (); |
#endif |
for (p = gendisk_head ; p ; p=p->next) { |
setup_dev(p); |
nr += p->nr_real; |
} |
#ifdef CONFIG_BLK_DEV_RAM |
#ifdef CONFIG_BLK_DEV_INITRD |
if (initrd_start && mount_initrd) initrd_load(); |
else |
#endif |
rd_load(); |
#endif |
} |
/Config.in
0,0 → 1,53
# |
# Block device driver configuration |
# |
mainmenu_option next_comment |
comment 'Floppy, IDE, and other block devices' |
|
tristate 'Normal floppy disk support' CONFIG_BLK_DEV_FD |
bool 'Enhanced IDE disk/cdrom/tape support' CONFIG_BLK_DEV_IDE |
comment 'Please see Documentation/ide.txt for help/info on IDE drives' |
if [ "$CONFIG_BLK_DEV_IDE" = "n" ]; then |
bool 'Old harddisk (IDE) driver' CONFIG_BLK_DEV_HD_ONLY |
else |
bool ' Use old disk-only driver on primary interface' CONFIG_BLK_DEV_HD_IDE |
bool ' Include IDE/ATAPI CDROM support' CONFIG_BLK_DEV_IDECD |
bool ' Include IDE/ATAPI TAPE support' CONFIG_BLK_DEV_IDETAPE |
bool ' Include IDE/ATAPI FLOPPY support (new)' CONFIG_BLK_DEV_IDEFLOPPY |
bool ' SCSI emulation support' CONFIG_BLK_DEV_IDESCSI |
bool ' Support removable IDE interfaces (PCMCIA)' CONFIG_BLK_DEV_IDE_PCMCIA |
bool ' Support expansion card IDE interfaces' CONFIG_BLK_DEV_IDE_CARDS |
if [ "$CONFIG_BLK_DEV_IDE_CARDS" = "y" ]; then |
tristate ' ICS IDE interface support' CONFIG_BLK_DEV_IDE_ICSIDE |
tristate ' RapIDE interface support' CONFIG_BLK_DEV_IDE_RAPIDE |
fi |
fi |
|
tristate 'MFM harddisk support' CONFIG_BLK_DEV_XD |
|
comment 'Additional Block Devices' |
|
tristate 'Loopback device support' CONFIG_BLK_DEV_LOOP |
bool 'Multiple devices driver support' CONFIG_BLK_DEV_MD |
if [ "$CONFIG_BLK_DEV_MD" = "y" ]; then |
tristate ' Linear (append) mode' CONFIG_MD_LINEAR |
tristate ' RAID-0 (striping) mode' CONFIG_MD_STRIPED |
tristate ' RAID-1 (mirroring) mode' CONFIG_MD_MIRRORING |
tristate ' RAID-4/RAID-5 mode' CONFIG_MD_RAID5 |
fi |
tristate 'RAM disk support' CONFIG_BLK_DEV_RAM |
if [ "$CONFIG_BLK_DEV_RAM" = "y" ]; then |
bool ' Initial RAM disk (initrd) support' CONFIG_BLK_DEV_INITRD |
fi |
bool 'ADFS partition support' CONFIG_BLK_DEV_PART |
#if [ "$CONFIG_BLK_DEV_PART" = "y" -a "$CONFIG_EXPERIMENTAL" = "y" ]; then |
# bool 'ADFS image file support (EXPERIMENTAL)' CONFIG_BLK_DEV_IMG |
#fi |
|
if [ "$CONFIG_BLK_DEV_HD_IDE" = "y" -o "$CONFIG_BLK_DEV_HD_ONLY" = "y" ]; then |
define_bool CONFIG_BLK_DEV_HD y |
else |
define_bool CONFIG_BLK_DEV_HD n |
fi |
|
endmenu |
/hd.c
0,0 → 1,1134
/* |
* linux/arch/arm/drivers/block/hd.c |
* [ origional file: linux/drivers/block/hd.c ] |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
* Modified 1995 Russell King for ARM processor. |
*/ |
|
/* |
* This is the low-level hd interrupt support. It traverses the |
* request-list, using interrupts to jump between functions. As |
* all the functions are called within interrupts, we may not |
* sleep. Special care is recommended. |
* |
* modified by Drew Eckhardt to check nr of hd's from the CMOS. |
* |
* Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug |
* in the early extended-partition checks and added DM partitions |
* |
* IRQ-unmask, drive-id, multiple-mode, support for ">16 heads", |
* and general streamlining by Mark Lord. |
*/ |
|
#define DEFAULT_MULT_COUNT 0 /* set to 0 to disable multiple mode at boot */ |
#define DEFAULT_UNMASK_INTR 0 /* set to 0 to *NOT* unmask irq's more often */ |
|
#include <asm/irq.h> |
#include <linux/errno.h> |
#include <linux/signal.h> |
#include <linux/sched.h> |
#include <linux/timer.h> |
#include <linux/fs.h> |
#include <linux/kernel.h> |
#include <linux/hdreg.h> |
#include <linux/genhd.h> |
#include <linux/malloc.h> |
#include <linux/string.h> |
#include <linux/ioport.h> |
|
#define REALLY_SLOW_IO |
#include <asm/system.h> |
#include <asm/io.h> |
#include <asm/segment.h> |
|
#define MAJOR_NR HD_MAJOR |
#include <linux/blk.h> |
|
#undef HD_IRQ |
#define HD_IRQ 11 |
|
static int revalidate_hddisk(kdev_t, int); |
|
#define HD_DELAY 0 |
|
#define MAX_ERRORS 16 /* Max read/write errors/sector */ |
#define RESET_FREQ 8 /* Reset controller every 8th retry */ |
#define RECAL_FREQ 4 /* Recalibrate every 4th retry */ |
#define MAX_HD 2 |
|
#define STAT_OK (READY_STAT|SEEK_STAT) |
#define OK_STATUS(s) (((s)&(STAT_OK|(BUSY_STAT|WRERR_STAT|ERR_STAT)))==STAT_OK) |
|
static void recal_intr(void); |
static void bad_rw_intr(void); |
|
static char recalibrate[MAX_HD]; |
static char special_op[MAX_HD]; |
static int access_count[MAX_HD]; |
static char busy[MAX_HD]; |
static struct wait_queue * busy_wait; |
|
static int reset; |
static int hd_error; |
|
/* |
* This struct defines the HD's and their types. |
*/ |
struct hd_i_struct { |
unsigned int head,sect,cyl,wpcom,lzone,ctl; |
}; |
static struct hd_driveid *hd_ident_info[MAX_HD]; |
|
#ifdef HD_TYPE |
static struct hd_i_struct hd_info[] = { HD_TYPE }; |
struct hd_i_struct bios_info[] = { HD_TYPE }; |
static int NR_HD = ((sizeof (hd_info))/(sizeof (struct hd_i_struct))); |
#else |
static struct hd_i_struct hd_info[MAX_HD]; |
struct hd_i_struct bios_info[MAX_HD]; |
static int NR_HD; |
#endif |
|
static struct hd_struct hd[MAX_HD<<6]; |
static int hd_sizes[MAX_HD<<6]; |
static int hd_blocksizes[MAX_HD<<6]; |
static int hd_hardsectsizes[MAX_HD<<6]; |
|
#if (HD_DELAY > 0) |
unsigned long last_req; |
|
unsigned long read_timer(void) |
{ |
unsigned long t, flags; |
int i; |
|
save_flags_cli (flags); |
t = jiffies * 11932; |
outb_p(0, 0x43); |
i = inb_p(0x40); |
i |= inb(0x40) << 8; |
restore_flags(flags); |
return(t - i); |
} |
#endif |
|
void hd_setup(char *str, int *ints) |
{ |
int hdind = 0; |
|
if (ints[0] != 3) |
return; |
if (bios_info[0].head != 0) |
hdind=1; |
bios_info[hdind].head = hd_info[hdind].head = ints[2]; |
bios_info[hdind].sect = hd_info[hdind].sect = ints[3]; |
bios_info[hdind].cyl = hd_info[hdind].cyl = ints[1]; |
bios_info[hdind].wpcom = hd_info[hdind].wpcom = 0; |
bios_info[hdind].lzone = hd_info[hdind].lzone = ints[1]; |
bios_info[hdind].ctl = hd_info[hdind].ctl = (ints[2] > 8 ? 8 : 0); |
NR_HD = hdind+1; |
} |
|
static void dump_status (const char *msg, unsigned int stat) |
{ |
unsigned long flags; |
char devc; |
|
devc = CURRENT ? 'a' + DEVICE_NR(CURRENT->rq_dev) : '?'; |
save_flags (flags); |
sti(); |
printk("hd%c: %s: status=0x%02x { ", devc, msg, stat & 0xff); |
if (stat & BUSY_STAT) printk("Busy "); |
if (stat & READY_STAT) printk("DriveReady "); |
if (stat & WRERR_STAT) printk("WriteFault "); |
if (stat & SEEK_STAT) printk("SeekComplete "); |
if (stat & DRQ_STAT) printk("DataRequest "); |
if (stat & ECC_STAT) printk("CorrectedError "); |
if (stat & INDEX_STAT) printk("Index "); |
if (stat & ERR_STAT) printk("Error "); |
printk("}\n"); |
if ((stat & ERR_STAT) == 0) { |
hd_error = 0; |
} else { |
hd_error = inb(HD_ERROR); |
printk("hd%c: %s: error=0x%02x { ", devc, msg, hd_error & 0xff); |
if (hd_error & BBD_ERR) printk("BadSector "); |
if (hd_error & ECC_ERR) printk("UncorrectableError "); |
if (hd_error & ID_ERR) printk("SectorIdNotFound "); |
if (hd_error & ABRT_ERR) printk("DriveStatusError "); |
if (hd_error & TRK0_ERR) printk("TrackZeroNotFound "); |
if (hd_error & MARK_ERR) printk("AddrMarkNotFound "); |
printk("}"); |
if (hd_error & (BBD_ERR|ECC_ERR|ID_ERR|MARK_ERR)) { |
printk(", CHS=%d/%d/%d", (inb(HD_HCYL)<<8) + inb(HD_LCYL), |
inb(HD_CURRENT) & 0xf, inb(HD_SECTOR)); |
if (CURRENT) |
printk(", sector=%ld", CURRENT->sector); |
} |
printk("\n"); |
} |
restore_flags (flags); |
} |
|
void check_status(void) |
{ |
int i = inb_p(HD_STATUS); |
|
if (!OK_STATUS(i)) { |
dump_status("check_status", i); |
bad_rw_intr(); |
} |
} |
|
static int controller_busy(void) |
{ |
int retries = 100000; |
unsigned char status; |
|
do { |
status = inb_p(HD_STATUS); |
} while ((status & BUSY_STAT) && --retries); |
return status; |
} |
|
static int status_ok(void) |
{ |
unsigned char status = inb_p(HD_STATUS); |
|
if (status & BUSY_STAT) |
return 1; /* Ancient, but does it make sense??? */ |
if (status & WRERR_STAT) |
return 0; |
if (!(status & READY_STAT)) |
return 0; |
if (!(status & SEEK_STAT)) |
return 0; |
return 1; |
} |
|
static int controller_ready(unsigned int drive, unsigned int head) |
{ |
int retry = 100; |
|
do { |
if (controller_busy() & BUSY_STAT) |
return 0; |
outb_p(0xA0 | (drive<<4) | head, HD_CURRENT); |
if (status_ok()) |
return 1; |
} while (--retry); |
return 0; |
} |
|
static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect, |
unsigned int head,unsigned int cyl,unsigned int cmd, |
void (*intr_addr)(void)) |
{ |
unsigned short port; |
|
#if (HD_DELAY > 0) |
while (read_timer() - last_req < HD_DELAY) |
/* nothing */; |
#endif |
if (reset) |
return; |
if (!controller_ready(drive, head)) { |
reset = 1; |
return; |
} |
SET_INTR(intr_addr); |
outb_p(hd_info[drive].ctl,HD_CMD); |
port=HD_DATA; |
outb_p(hd_info[drive].wpcom>>2,++port); |
outb_p(nsect,++port); |
outb_p(sect,++port); |
outb_p(cyl,++port); |
outb_p(cyl>>8,++port); |
outb_p(0xA0|(drive<<4)|head,++port); |
outb_p(cmd,++port); |
} |
|
static void hd_request (void); |
static unsigned int identified [MAX_HD]; /* 1 = drive ID already displayed */ |
static unsigned int unmask_intr [MAX_HD]; /* 1 = unmask IRQs during I/O */ |
static unsigned int max_mult [MAX_HD]; /* max sectors for MultMode */ |
static unsigned int mult_req [MAX_HD]; /* requested MultMode count */ |
static unsigned int mult_count [MAX_HD]; /* currently enabled MultMode count */ |
static struct request WCURRENT; |
|
static void fixstring (unsigned char *s, int bytecount) |
{ |
unsigned char *p, *end = &s[bytecount &= ~1]; /* bytecount must be even */ |
|
/* convert from big-endian to little-endian */ |
for (p = end ; p != s;) { |
unsigned short *pp = (unsigned short *) (p -= 2); |
*pp = (*pp >> 8) | (*pp << 8); |
} |
|
/* strip leading blanks */ |
while (s != end && *s == ' ') |
++s; |
|
/* compress internal blanks and strip trailing blanks */ |
while (s != end && *s) { |
if (*s++ != ' ' || (s != end && *s && *s != ' ')) |
*p++ = *(s-1); |
} |
|
/* wipe out trailing garbage */ |
while (p != end) |
*p++ = '\0'; |
} |
|
static void identify_intr(void) |
{ |
unsigned int dev = DEVICE_NR(CURRENT->rq_dev); |
unsigned short stat = inb_p(HD_STATUS); |
struct hd_driveid *id = hd_ident_info[dev]; |
|
if (unmask_intr[dev]) |
sti(); |
if (stat & (BUSY_STAT|ERR_STAT)) { |
printk (" hd%c: non-IDE device, %dMB, CHS=%d/%d/%d\n", dev+'a', |
hd_info[dev].cyl*hd_info[dev].head*hd_info[dev].sect / 2048, |
hd_info[dev].cyl, hd_info[dev].head, hd_info[dev].sect); |
if (id != NULL) { |
hd_ident_info[dev] = NULL; |
kfree_s (id, 512); |
} |
} else { |
insw(HD_DATA, id, 256); /* get ID info */ |
max_mult[dev] = id->max_multsect; |
if ((id->field_valid&1) && id->cur_cyls && id->cur_heads && (id->cur_heads <= 16) && id->cur_sectors) { |
/* |
* Extract the physical drive geometry for our use. |
* Note that we purposely do *not* update the bios_info. |
* This way, programs that use it (like fdisk) will |
* still have the same logical view as the BIOS does, |
* which keeps the partition table from being screwed. |
*/ |
hd_info[dev].cyl = id->cur_cyls; |
hd_info[dev].head = id->cur_heads; |
hd_info[dev].sect = id->cur_sectors; |
} |
fixstring (id->serial_no, sizeof(id->serial_no)); |
fixstring (id->fw_rev, sizeof(id->fw_rev)); |
fixstring (id->model, sizeof(id->model)); |
printk (" hd%c: %.40s, %dMB w/%dKB Cache, CHS=%d/%d/%d, MaxMult=%d\n", |
dev+'a', id->model, id->cyls*id->heads*id->sectors/2048, |
id->buf_size/2, bios_info[dev].cyl, bios_info[dev].head, |
bios_info[dev].sect, id->max_multsect); |
/* |
* Early model Quantum drives go weird at this point, |
* but doing a recalibrate seems to "fix" them. |
* (Doing a full reset confuses some other model Quantums) |
*/ |
if (!strncmp(id->model, "QUANTUM", 7)) |
special_op[dev] = recalibrate[dev] = 1; |
} |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
hd_request(); |
return; |
} |
|
static void set_multmode_intr(void) |
{ |
unsigned int dev = DEVICE_NR(CURRENT->rq_dev), stat = inb_p(HD_STATUS); |
|
if (unmask_intr[dev]) |
sti(); |
if (stat & (BUSY_STAT|ERR_STAT)) { |
mult_req[dev] = mult_count[dev] = 0; |
dump_status("set multmode failed", stat); |
} else { |
if ((mult_count[dev] = mult_req[dev])) |
printk (" hd%c: enabled %d-sector multiple mode\n", |
dev+'a', mult_count[dev]); |
else |
printk (" hd%c: disabled multiple mode\n", dev+'a'); |
} |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
hd_request(); |
return; |
} |
|
static int drive_busy(void) |
{ |
unsigned int i; |
unsigned char c; |
|
for (i = 0; i < 500000 ; i++) { |
c = inb_p(HD_STATUS); |
if ((c & (BUSY_STAT | READY_STAT | SEEK_STAT)) == STAT_OK) |
return 0; |
} |
dump_status("reset timed out", c); |
return 1; |
} |
|
static void reset_controller(void) |
{ |
int i; |
|
outb_p(4,HD_CMD); |
for(i = 0; i < 1000; i++) barrier(); |
outb_p(hd_info[0].ctl & 0x0f,HD_CMD); |
for(i = 0; i < 1000; i++) barrier(); |
if (drive_busy()) |
printk("hd: controller still busy\n"); |
else if ((hd_error = inb(HD_ERROR)) != 1) |
printk("hd: controller reset failed: %02x\n",hd_error); |
} |
|
static void reset_hd(void) |
{ |
static int i; |
|
repeat: |
if (reset) { |
reset = 0; |
i = -1; |
reset_controller(); |
} else { |
check_status(); |
if (reset) |
goto repeat; |
} |
if (++i < NR_HD) { |
special_op[i] = recalibrate[i] = 1; |
if (unmask_intr[i]) { |
unmask_intr[i] = DEFAULT_UNMASK_INTR; |
printk("hd%c: reset irq-unmasking to %d\n",i+'a', |
DEFAULT_UNMASK_INTR); |
} |
if (mult_req[i] || mult_count[i]) { |
mult_count[i] = 0; |
mult_req[i] = DEFAULT_MULT_COUNT; |
printk("hd%c: reset multiple mode to %d\n",i+'a', |
DEFAULT_MULT_COUNT); |
} |
hd_out(i,hd_info[i].sect,hd_info[i].sect,hd_info[i].head-1, |
hd_info[i].cyl,WIN_SPECIFY,&reset_hd); |
if (reset) |
goto repeat; |
} else |
hd_request(); |
} |
|
/* |
* Ok, don't know what to do with the unexpected interrupts: on some machines |
* doing a reset and a retry seems to result in an eternal loop. Right now I |
* ignore it, and just set the timeout. |
* |
* On laptops (and "green" PCs), an unexpected interrupt occurs whenever the |
* drive enters "idle", "standby", or "sleep" mode, so if the status looks |
* "good", we just ignore the interrupt completely. |
*/ |
void unexpected_hd_interrupt(void) |
{ |
unsigned int stat = inb_p(HD_STATUS); |
|
if (stat & (BUSY_STAT|DRQ_STAT|ECC_STAT|ERR_STAT)) { |
dump_status ("unexpected interrupt", stat); |
SET_TIMER; |
} |
} |
|
/* |
* bad_rw_intr() now tries to be a bit smarter and does things |
* according to the error returned by the controller. |
* -Mika Liljeberg (liljeber@cs.Helsinki.FI) |
*/ |
static void bad_rw_intr(void) |
{ |
int dev; |
|
if (!CURRENT) |
return; |
dev = DEVICE_NR(CURRENT->rq_dev); |
if (++CURRENT->errors >= MAX_ERRORS || (hd_error & BBD_ERR)) { |
end_request(0); |
special_op[dev] = recalibrate[dev] = 1; |
} else if (CURRENT->errors % RESET_FREQ == 0) |
reset = 1; |
else if ((hd_error & TRK0_ERR) || CURRENT->errors % RECAL_FREQ == 0) |
special_op[dev] = recalibrate[dev] = 1; |
/* Otherwise just retry */ |
} |
|
static inline int wait_DRQ(void) |
{ |
int retries = 100000, stat; |
|
while (--retries > 0) |
if ((stat = inb_p(HD_STATUS)) & DRQ_STAT) |
return 0; |
dump_status("wait_DRQ", stat); |
return -1; |
} |
|
static void read_intr(void) |
{ |
unsigned int dev = DEVICE_NR(CURRENT->rq_dev); |
int i, retries = 100000, msect = mult_count[dev], nsect; |
|
if (unmask_intr[dev]) |
sti(); /* permit other IRQs during xfer */ |
do { |
i = (unsigned) inb_p(HD_STATUS); |
if (i & BUSY_STAT) |
continue; |
if (!OK_STATUS(i)) |
break; |
if (i & DRQ_STAT) |
goto ok_to_read; |
} while (--retries > 0); |
dump_status("read_intr", i); |
bad_rw_intr(); |
hd_request(); |
return; |
ok_to_read: |
if (msect) { |
if ((nsect = CURRENT->current_nr_sectors) > msect) |
nsect = msect; |
msect -= nsect; |
} else |
nsect = 1; |
insw(HD_DATA,CURRENT->buffer,nsect<<8); |
CURRENT->sector += nsect; |
CURRENT->buffer += nsect<<9; |
CURRENT->errors = 0; |
i = (CURRENT->nr_sectors -= nsect); |
|
#ifdef DEBUG |
printk("hd%c: read: sectors(%ld-%ld), remaining=%ld, buffer=0x%08lx\n", |
dev+'a', CURRENT->sector, CURRENT->sector+nsect, |
CURRENT->nr_sectors, (unsigned long) CURRENT->buffer+(nsect<<9)); |
#endif |
if ((CURRENT->current_nr_sectors -= nsect) <= 0) |
end_request(1); |
if (i > 0) { |
if (msect) |
goto ok_to_read; |
SET_INTR(&read_intr); |
return; |
} |
(void) inb_p(HD_STATUS); |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
if (CURRENT) |
hd_request(); |
return; |
} |
|
static inline void multwrite (unsigned int dev) |
{ |
unsigned int mcount = mult_count[dev]; |
|
while (mcount--) { |
outsw(HD_DATA,WCURRENT.buffer,256); |
if (!--WCURRENT.nr_sectors) |
return; |
WCURRENT.buffer += 512; |
if (!--WCURRENT.current_nr_sectors) { |
WCURRENT.bh = WCURRENT.bh->b_reqnext; |
if (WCURRENT.bh == NULL) |
panic("buffer list corrupted\n"); |
WCURRENT.current_nr_sectors = WCURRENT.bh->b_size>>9; |
WCURRENT.buffer = WCURRENT.bh->b_data; |
} |
} |
} |
|
static void multwrite_intr(void) |
{ |
int i; |
unsigned int dev = DEVICE_NR(WCURRENT.rq_dev); |
|
if (unmask_intr[dev]) |
sti(); |
if (OK_STATUS(i=inb_p(HD_STATUS))) { |
if (i & DRQ_STAT) { |
if (WCURRENT.nr_sectors) { |
multwrite(dev); |
SET_INTR(&multwrite_intr); |
return; |
} |
} else { |
if (!WCURRENT.nr_sectors) { /* all done? */ |
for (i = CURRENT->nr_sectors; i > 0;){ |
i -= CURRENT->current_nr_sectors; |
end_request(1); |
} |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
if (CURRENT) |
hd_request(); |
return; |
} |
} |
} |
dump_status("multwrite_intr", i); |
bad_rw_intr(); |
hd_request(); |
} |
|
static void write_intr(void) |
{ |
int i; |
int retries = 100000; |
|
if (unmask_intr[DEVICE_NR(WCURRENT.rq_dev)]) |
sti(); |
do { |
i = (unsigned) inb_p(HD_STATUS); |
if (i & BUSY_STAT) |
continue; |
if (!OK_STATUS(i)) |
break; |
if ((CURRENT->nr_sectors <= 1) || (i & DRQ_STAT)) |
goto ok_to_write; |
} while (--retries > 0); |
dump_status("write_intr", i); |
bad_rw_intr(); |
hd_request(); |
return; |
ok_to_write: |
CURRENT->sector++; |
i = --CURRENT->nr_sectors; |
--CURRENT->current_nr_sectors; |
CURRENT->buffer += 512; |
if (!i || (CURRENT->bh && !SUBSECTOR(i))) |
end_request(1); |
if (i > 0) { |
SET_INTR(&write_intr); |
outsw(HD_DATA,CURRENT->buffer,256); |
sti(); |
} else { |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
hd_request(); |
} |
return; |
} |
|
static void recal_intr(void) |
{ |
check_status(); |
#if (HD_DELAY > 0) |
last_req = read_timer(); |
#endif |
hd_request(); |
} |
|
/* |
* This is another of the error-routines I don't know what to do with. The |
* best idea seems to just set reset, and start all over again. |
*/ |
static void hd_times_out(void) |
{ |
unsigned int dev; |
|
DEVICE_INTR = NULL; |
if (!CURRENT) |
return; |
disable_irq(HD_IRQ); |
sti(); |
reset = 1; |
dev = DEVICE_NR(CURRENT->rq_dev); |
printk("hd%c: timeout\n", dev+'a'); |
if (++CURRENT->errors >= MAX_ERRORS) { |
#ifdef DEBUG |
printk("hd%c: too many errors\n", dev+'a'); |
#endif |
end_request(0); |
} |
cli(); |
hd_request(); |
enable_irq(HD_IRQ); |
} |
|
int do_special_op (unsigned int dev) |
{ |
if (recalibrate[dev]) { |
recalibrate[dev] = 0; |
hd_out(dev,hd_info[dev].sect,0,0,0,WIN_RESTORE,&recal_intr); |
return reset; |
} |
if (!identified[dev]) { |
identified[dev] = 1; |
unmask_intr[dev] = DEFAULT_UNMASK_INTR; |
mult_req[dev] = DEFAULT_MULT_COUNT; |
hd_out(dev,0,0,0,0,WIN_IDENTIFY,&identify_intr); |
return reset; |
} |
if (mult_req[dev] != mult_count[dev]) { |
hd_out(dev,mult_req[dev],0,0,0,WIN_SETMULT,&set_multmode_intr); |
return reset; |
} |
if (hd_info[dev].head > 16) { |
printk ("hd%c: cannot handle device with more than 16 heads - giving up\n", dev+'a'); |
end_request(0); |
} |
special_op[dev] = 0; |
return 1; |
} |
|
/* |
* The driver enables interrupts as much as possible. In order to do this, |
* (a) the device-interrupt is disabled before entering hd_request(), |
* and (b) the timeout-interrupt is disabled before the sti(). |
* |
* Interrupts are still masked (by default) whenever we are exchanging |
* data/cmds with a drive, because some drives seem to have very poor |
* tolerance for latency during I/O. For devices which don't suffer from |
* that problem (most don't), the unmask_intr[] flag can be set to unmask |
* other interrupts during data/cmd transfers (by defining DEFAULT_UNMASK_INTR |
* to 1, or by using "hdparm -u1 /dev/hd?" from the shell). |
*/ |
static void hd_request(void) |
{ |
unsigned int dev, block, nsect, sec, track, head, cyl; |
|
if (CURRENT && CURRENT->rq_status == RQ_INACTIVE) return; |
if (DEVICE_INTR) |
return; |
repeat: |
timer_active &= ~(1<<HD_TIMER); |
sti(); |
INIT_REQUEST; |
if (reset) { |
cli(); |
reset_hd(); |
return; |
} |
dev = MINOR(CURRENT->rq_dev); |
block = CURRENT->sector; |
nsect = CURRENT->nr_sectors; |
if (dev >= (NR_HD<<6) || block >= hd[dev].nr_sects || ((block+nsect) > hd[dev].nr_sects)) { |
#ifdef DEBUG |
if (dev >= (NR_HD<<6)) |
printk("hd: bad minor number: device=%s\n", |
kdevname(CURRENT->rq_dev)); |
else |
printk("hd%c: bad access: block=%d, count=%d\n", |
(MINOR(CURRENT->rq_dev)>>6)+'a', block, nsect); |
#endif |
end_request(0); |
goto repeat; |
} |
block += hd[dev].start_sect; |
dev >>= 6; |
if (special_op[dev]) { |
if (do_special_op(dev)) |
goto repeat; |
return; |
} |
sec = block % hd_info[dev].sect + 1; |
track = block / hd_info[dev].sect; |
head = track % hd_info[dev].head; |
cyl = track / hd_info[dev].head; |
#ifdef DEBUG |
printk("hd%c: %sing: CHS=%d/%d/%d, sectors=%d, buffer=0x%08lx\n", |
dev+'a', (CURRENT->cmd == READ)?"read":"writ", |
cyl, head, sec, nsect, (unsigned long) CURRENT->buffer); |
#endif |
if (!unmask_intr[dev]) |
cli(); |
if (CURRENT->cmd == READ) { |
unsigned int cmd = mult_count[dev] > 1 ? WIN_MULTREAD : WIN_READ; |
hd_out(dev,nsect,sec,head,cyl,cmd,&read_intr); |
if (reset) |
goto repeat; |
return; |
} |
if (CURRENT->cmd == WRITE) { |
if (mult_count[dev]) |
hd_out(dev,nsect,sec,head,cyl,WIN_MULTWRITE,&multwrite_intr); |
else |
hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr); |
if (reset) |
goto repeat; |
if (wait_DRQ()) { |
bad_rw_intr(); |
goto repeat; |
} |
if (mult_count[dev]) { |
WCURRENT = *CURRENT; |
multwrite(dev); |
} else |
outsw(HD_DATA,CURRENT->buffer,256); |
return; |
} |
panic("unknown hd-command"); |
} |
|
static void do_hd_request (void) |
{ |
disable_irq(HD_IRQ); |
hd_request(); |
enable_irq(HD_IRQ); |
} |
|
static int hd_ioctl(struct inode * inode, struct file * file, |
unsigned int cmd, unsigned long arg) |
{ |
struct hd_geometry *loc = (struct hd_geometry *) arg; |
int dev, err; |
unsigned long flags; |
|
if ((!inode) || !(inode->i_rdev)) |
return -EINVAL; |
dev = DEVICE_NR(inode->i_rdev); |
if (dev >= NR_HD) |
return -EINVAL; |
switch (cmd) { |
case HDIO_GETGEO: |
if (!loc) return -EINVAL; |
err = verify_area(VERIFY_WRITE, loc, sizeof(*loc)); |
if (err) |
return err; |
put_user(bios_info[dev].head, |
(char *) &loc->heads); |
put_user(bios_info[dev].sect, |
(char *) &loc->sectors); |
put_user(bios_info[dev].cyl, |
(short *) &loc->cylinders); |
put_user(hd[MINOR(inode->i_rdev)].start_sect, |
(long *) &loc->start); |
return 0; |
case BLKRASET: |
if(!suser()) return -EACCES; |
if(arg > 0xff) return -EINVAL; |
read_ahead[MAJOR(inode->i_rdev)] = arg; |
return 0; |
case BLKRAGET: |
if (!arg) return -EINVAL; |
err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long)); |
if (err) |
return err; |
put_user(read_ahead[MAJOR(inode->i_rdev)],(long *) arg); |
return 0; |
case BLKGETSIZE: /* Return device size */ |
if (!arg) return -EINVAL; |
err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long)); |
if (err) |
return err; |
put_user(hd[MINOR(inode->i_rdev)].nr_sects, (long *) arg); |
return 0; |
case BLKFLSBUF: |
if(!suser()) return -EACCES; |
fsync_dev(inode->i_rdev); |
invalidate_buffers(inode->i_rdev); |
return 0; |
|
case BLKRRPART: /* Re-read partition tables */ |
return revalidate_hddisk(inode->i_rdev, 1); |
|
case HDIO_SET_UNMASKINTR: |
if (!suser()) return -EACCES; |
if ((arg > 1) || (MINOR(inode->i_rdev) & 0x3F)) |
return -EINVAL; |
unmask_intr[dev] = arg; |
return 0; |
|
case HDIO_GET_UNMASKINTR: |
if (!arg) return -EINVAL; |
err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long)); |
if (err) |
return err; |
put_user(unmask_intr[dev], (long *) arg); |
return 0; |
|
case HDIO_GET_MULTCOUNT: |
if (!arg) return -EINVAL; |
err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long)); |
if (err) |
return err; |
put_user(mult_count[dev], (long *) arg); |
return 0; |
|
case HDIO_SET_MULTCOUNT: |
if (!suser()) return -EACCES; |
if (MINOR(inode->i_rdev) & 0x3F) return -EINVAL; |
save_flags(flags); |
cli(); /* a prior request might still be in progress */ |
if (arg > max_mult[dev]) |
err = -EINVAL; /* out of range for device */ |
else if (mult_req[dev] != mult_count[dev]) { |
special_op[dev] = 1; |
err = -EBUSY; /* busy, try again */ |
} else { |
mult_req[dev] = arg; |
special_op[dev] = 1; |
err = 0; |
} |
restore_flags(flags); |
return err; |
|
case HDIO_GET_IDENTITY: |
if (!arg) return -EINVAL; |
if (MINOR(inode->i_rdev) & 0x3F) return -EINVAL; |
if (hd_ident_info[dev] == NULL) return -ENOMSG; |
err = verify_area(VERIFY_WRITE, (char *) arg, sizeof(struct hd_driveid)); |
if (err) |
return err; |
memcpy_tofs((char *)arg, (char *) hd_ident_info[dev], sizeof(struct hd_driveid)); |
return 0; |
|
RO_IOCTLS(inode->i_rdev,arg); |
default: |
return -EINVAL; |
} |
} |
|
static int hd_open(struct inode * inode, struct file * filp) |
{ |
int target; |
target = DEVICE_NR(inode->i_rdev); |
|
if (target >= NR_HD) |
return -ENODEV; |
while (busy[target]) |
sleep_on(&busy_wait); |
access_count[target]++; |
return 0; |
} |
|
/* |
* Releasing a block device means we sync() it, so that it can safely |
* be forgotten about... |
*/ |
static void hd_release(struct inode * inode, struct file * file) |
{ |
int target; |
sync_dev(inode->i_rdev); |
|
target = DEVICE_NR(inode->i_rdev); |
access_count[target]--; |
|
} |
|
static void hd_geninit(struct gendisk *); |
|
static struct gendisk hd_gendisk = { |
MAJOR_NR, /* Major number */ |
"hd", /* Major name */ |
6, /* Bits to shift to get real from partition */ |
1 << 6, /* Number of partitions per real */ |
MAX_HD, /* maximum number of real */ |
hd_geninit, /* init function */ |
hd, /* hd struct */ |
hd_sizes, /* block sizes */ |
0, /* number */ |
(void *) bios_info, /* internal */ |
NULL /* next */ |
}; |
|
static void hd_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
{ |
void (*handler)(void) = DEVICE_INTR; |
|
DEVICE_INTR = NULL; |
timer_active &= ~(1<<HD_TIMER); |
if (!handler) |
handler = unexpected_hd_interrupt; |
handler(); |
sti(); |
} |
|
/* |
* Since we find out the physical drive geometry, we don't touch that. |
* We only alter the logical disk geometry that is passed to user programs. |
* [as per PC Linux]. |
*/ |
void hd_set_geometry (kdev_t dev, unsigned char secspertrack, unsigned char heads, |
unsigned long discsize, unsigned int secsize) |
{ |
int minor = MINOR(dev); |
int drv = minor >> 6; |
|
if (bios_info[drv].cyl == 1) { |
bios_info[drv].cyl = discsize / (secspertrack * heads * secsize); |
bios_info[drv].head = heads; |
bios_info[drv].wpcom = -1; |
bios_info[drv].ctl = 8; |
bios_info[drv].lzone = bios_info[drv].cyl - 1; |
bios_info[drv].sect = secspertrack; |
} |
hd[minor].start_sect = 0; |
hd[minor].nr_sects = discsize / secsize; |
} |
|
/* |
* This is the harddisk IRQ description. The SA_INTERRUPT in sa_flags |
* means we run the IRQ-handler with interrupts disabled: this is bad for |
* interrupt latency, but anything else has led to problems on some |
* machines... |
* |
* We enable interrupts in some of the routines after making sure it's |
* safe. |
*/ |
static void hd_geninit(struct gendisk *ignored) |
{ |
int i; |
|
if (!NR_HD) { |
int drive; |
extern int number_ide_drives; |
/* |
* Default settings |
* |
* If we don't know anything about the drive, then set it |
* so that we have enough to read the boot sector of the |
* ADFS drive. This means that you *MUST* specify the |
* drive parameters of *all* drives if you have one IDE |
* drive that is not ADFS formatted. |
*/ |
for (drive=0 ; drive<2 ; drive++) { |
bios_info[drive].cyl = hd_info[drive].cyl = 1; |
bios_info[drive].head = hd_info[drive].head = 1; |
bios_info[drive].wpcom = hd_info[drive].wpcom = -1; |
bios_info[drive].ctl = hd_info[drive].ctl = 8; |
bios_info[drive].lzone = hd_info[drive].lzone = 1; |
bios_info[drive].sect = hd_info[drive].sect = 17; |
} |
|
/* |
* We only set this to the one that the host OS gave us |
* if the user has not defined any types. |
*/ |
NR_HD = number_ide_drives; |
} |
|
i = NR_HD; |
while (i-- > 0) { |
/* |
* The newer E-IDE BIOSs handle drives larger than 1024 |
* cylinders by increasing the number of logical heads |
* to keep the number of logical cylinders below the |
* sacred INT13 limit of 1024 (10 bits). If that is |
* what's happening here, we'll find out and correct |
* it later when "identifying" the drive. |
*/ |
hd[i<<6].nr_sects = bios_info[i].head * |
bios_info[i].sect * bios_info[i].cyl; |
hd_ident_info[i] = (struct hd_driveid *) kmalloc(512,GFP_KERNEL); |
special_op[i] = 1; |
} |
if (NR_HD) { |
if (request_irq(HD_IRQ, hd_interrupt, SA_INTERRUPT, "hd", NULL)) { |
printk("hd: unable to get IRQ%d for the harddisk driver\n",HD_IRQ); |
NR_HD = 0; |
} else { |
request_region(HD_DATA, 8, "hd"); |
request_region(HD_CMD, 1, "hd(cmd)"); |
} |
} |
hd_gendisk.nr_real = NR_HD; |
|
for (i = 0; i < (MAX_HD << 6); i++) { |
hd_blocksizes[i] = 1024; |
hd_hardsectsizes[i] = 512; |
} |
blksize_size[MAJOR_NR] = hd_blocksizes; |
hardsect_size[MAJOR_NR] = hd_hardsectsizes; |
} |
|
static struct file_operations hd_fops = { |
NULL, /* lseek - default */ |
block_read, /* read - general block-dev read */ |
block_write, /* write - general block-dev write */ |
NULL, /* readdir - bad */ |
NULL, /* select */ |
hd_ioctl, /* ioctl */ |
NULL, /* mmap */ |
hd_open, /* open */ |
hd_release, /* release */ |
block_fsync /* fsync */ |
}; |
|
int hd_init(void) |
{ |
if (register_blkdev(MAJOR_NR,"hd",&hd_fops)) { |
printk("hd: unable to get major %d for harddisk\n",MAJOR_NR); |
return -1; |
} |
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; |
read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */ |
hd_gendisk.next = gendisk_head; |
gendisk_head = &hd_gendisk; |
timer_table[HD_TIMER].fn = hd_times_out; |
return 0; |
} |
|
#define DEVICE_BUSY busy[target] |
#define USAGE access_count[target] |
#define CAPACITY (bios_info[target].head*bios_info[target].sect*bios_info[target].cyl) |
/* We assume that the the bios parameters do not change, so the disk capacity |
will not change */ |
#undef MAYBE_REINIT |
#define GENDISK_STRUCT hd_gendisk |
|
/* |
* This routine is called to flush all partitions and partition tables |
* for a changed scsi disk, and then re-read the new partition table. |
* If we are revalidating a disk because of a media change, then we |
* enter with usage == 0. If we are using an ioctl, we automatically have |
* usage == 1 (we need an open channel to use an ioctl :-), so this |
* is our limit. |
*/ |
static int revalidate_hddisk(kdev_t dev, int maxusage) |
{ |
int target; |
struct gendisk * gdev; |
int max_p; |
int start; |
int i; |
long flags; |
|
target = DEVICE_NR(dev); |
gdev = &GENDISK_STRUCT; |
|
save_flags_cli (flags); |
if (DEVICE_BUSY || USAGE > maxusage) { |
restore_flags(flags); |
return -EBUSY; |
}; |
DEVICE_BUSY = 1; |
restore_flags(flags); |
|
max_p = gdev->max_p; |
start = target << gdev->minor_shift; |
|
for (i=max_p - 1; i >=0 ; i--) { |
int minor = start + i; |
kdev_t devi = MKDEV(MAJOR_NR, minor); |
sync_dev(devi); |
invalidate_inodes(devi); |
invalidate_buffers(devi); |
gdev->part[minor].start_sect = 0; |
gdev->part[minor].nr_sects = 0; |
}; |
|
#ifdef MAYBE_REINIT |
MAYBE_REINIT; |
#endif |
|
gdev->part[start].nr_sects = CAPACITY; |
resetup_one_dev(gdev, target); |
|
DEVICE_BUSY = 0; |
wake_up(&busy_wait); |
return 0; |
} |
|
/mfmhd.c
0,0 → 1,1539
/* |
* linux/arch/arm/drivers/block/mfmhd.c |
* |
* Copyright (C) 1995, 1996 Russell King, Dave Alan Gilbert (linux@treblig.org) |
* |
* MFM hard drive code [experimental] |
*/ |
|
/* |
* Change list: |
* |
* 3/2/96:DAG: Started a change list :-) |
* Set the hardsect_size pointers up since we are running 256 byte |
* sectors |
* Added DMA code, put it into the rw_intr |
* Moved RCAL out of generic interrupt code - don't want to do it |
* while DMA'ing - its now in individual handlers. |
* Took interrupt handlers off task queue lists and called |
* directly - not sure of implications. |
* |
* 18/2/96:DAG: Well its reading OK I think, well enough for image file code |
* to find the image file; but now I've discovered that I actually |
* have to put some code in for image files. |
* |
* Added stuff for image files; seems to work, but I've not |
* got a multisegment image file (I don't think!). |
* Put in a hack (yep a real hack) for multiple cylinder reads. |
* Not convinced its working. |
* |
* 5/4/96:DAG: Added asm/hardware.h and use IOC_ macros |
* Rewrote dma code in mfm.S (again!) - now takes a word at a time |
* from main RAM for speed; still doesn't feel speedy! |
* |
* 20/4/96:DAG: After rewriting mfm.S a heck of a lot of times and speeding |
* things up, I've finally figured out why its so damn slow. |
* Linux is only reading a block at a time, and so you never |
* get more than 1K per disc revoloution ~=60K/second. |
* |
* 27/4/96:DAG: On Russell's advice I change ll_rw_blk.c to ask it to |
* join adjacent blocks together. Everything falls flat on its |
* face. |
* Four hours of debugging later; I hadn't realised that |
* ll_rw_blk would be so generous as to join blocks whose |
* results aren't going into consecutive buffers. |
* |
* OK; severe rehacking of mfm_rw_interrupt; now end_request's |
* as soon as its DMA'd each request. Odd thing is that |
* we are sometimes getting interrupts where we are not transferring |
* any data; why? Is that what happens when you miss? I doubt |
* it; are we too fast? No - its just at command ends. Got 240K/s |
* better than before, but RiscOS hits 480K/s |
* |
* 25/6/96:RMK: Fixed init code to allow the MFM podule to work. Increased the |
* number of errors for my Miniscribe drive (8425). |
* |
* 30/6/96:DAG: Russell suggested that a check drive 0 might turn the LEDs off |
* - so in request_done just before it clears Busy it sends a |
* check drive 0 - and the LEDs go off!!!! |
* |
* Added test for mainboard controller. - Removes need for separate |
* define. |
* |
* 13/7/96:DAG: Changed hardware sectore size to 512 in attempt to make |
* IM drivers work. |
* 21/7/96:DAG: Took out old image file stuff (accessing it now produces an IO |
* error.) |
* |
* 17/8/96:DAG: Ran through indent -kr -i8; evil - all my nice 2 character indents |
* gone :-( Hand modified afterwards. |
* Took out last remains of the older image map system. |
* |
* 22/9/96:DAG: Changed mfm.S so it will carry on DMA'ing til; BSY is dropped |
* Changed mfm_rw_intr so that it doesn't follow the error |
* code until BSY is dropped. Nope - still broke. Problem |
* may revolve around when it reads the results for the error |
* number? |
* |
*16/11/96:DAG: Modified for 2.0.18; request_irq changed |
* |
*17/12/96:RMK: Various cleanups, reorganisation, and the changes for new IO system. |
* Improved probe for onboard MFM chip - it was hanging on my A5k. |
* Added autodetect CHS code such that we don't rely on the presence |
* of an ADFS boot block. Added ioport resource manager calls so |
* that we don't clash with already-running hardware (eg. RiscPC Ether |
* card slots if someone tries this)! |
*/ |
|
/* |
* Possible enhancements: |
* Multi-thread the code so that it is possible that while one drive |
* is seeking, the other one can be reading data/seeking as well. |
* This would be a performance boost with dual drive systems. |
*/ |
|
#include <linux/module.h> |
#include <linux/config.h> |
#include <linux/sched.h> |
#include <linux/fs.h> |
#include <linux/interrupt.h> |
#include <linux/kernel.h> |
#include <linux/timer.h> |
#include <linux/tqueue.h> |
#include <linux/mm.h> |
#include <linux/errno.h> |
#include <linux/genhd.h> |
#include <linux/major.h> |
#include <linux/ioport.h> |
|
#include <asm/system.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/segment.h> |
#include <asm/delay.h> |
#include <asm/dma.h> |
#include <asm/hardware.h> |
#include <asm/ecard.h> |
|
#define MFM_DISK_MAJOR 13 |
#undef XT_DISK_MAJOR |
#define XT_DISK_MAJOR -1 |
#define MAJOR_NR MFM_DISK_MAJOR |
#include "blk.h" |
|
/* |
* This sort of stuff should be in a header file shared with ide.c, hd.c, xd.c etc |
*/ |
#ifndef HDIO_GETGEO |
#define HDIO_GETGEO 0x301 |
struct hd_geometry { |
unsigned char heads; |
unsigned char sectors; |
unsigned short cylinders; |
unsigned long start; |
}; |
#endif |
|
|
/* |
* Configuration section |
* |
* Enable MFM_AUTODETECT_DRIVES to allow the code to find out the real CHS |
* for the drive. |
*/ |
#define MFM_AUTODETECT_DRIVES |
/* |
* This is the maximum number of drives that we accept |
*/ |
#define MFM_MAXDRIVES 2 |
/* |
* Linux I/O address of onboard MFM controller or 0 to disable this |
*/ |
#define ONBOARD_MFM_ADDRESS ((0x002d0000 >> 2) | 0x80000000) |
/* |
* Uncomment this to enable debugging in the MFM driver... |
*/ |
#ifndef DEBUG |
/*#define DEBUG */ |
#endif |
/* |
* List of card types that we recognise |
*/ |
static const card_ids mfm_cids[] = { |
{ MANU_ACORN, PROD_ACORN_MFM }, |
{ 0xffff, 0xffff } |
}; |
/* |
* End of configuration |
*/ |
|
|
/* |
* This structure contains all information to do with a particular physical |
* device. |
*/ |
struct mfm_info { |
unsigned char sectors; |
unsigned char heads; |
unsigned short cylinders; |
unsigned short lowcurrent; |
unsigned short precomp; |
#define NO_TRACK -1 |
#define NEED_1_RECAL -2 |
#define NEED_2_RECAL -3 |
int cylinder; |
unsigned int access_count; |
unsigned int busy; |
struct { |
char recal; |
char report; |
char abort; |
} errors; |
} mfm_info[MFM_MAXDRIVES]; |
|
#define MFM_DRV_INFO mfm_info[raw_cmd.dev] |
|
static struct hd_struct mfm[MFM_MAXDRIVES << 6]; |
static int mfm_sizes[MFM_MAXDRIVES << 6]; |
static int mfm_blocksizes[MFM_MAXDRIVES << 6]; |
static int mfm_sectsizes[MFM_MAXDRIVES << 6]; |
static struct wait_queue *mfm_wait_open = NULL; |
|
/* Stuff from the assembly routines */ |
extern unsigned int hdc63463_baseaddress; /* Controller base address */ |
extern unsigned int hdc63463_irqpolladdress; /* Address to read to test for int */ |
extern unsigned int hdc63463_irqpollmask; /* Mask for irq register */ |
extern unsigned int hdc63463_dataptr; /* Pointer to kernel data space to DMA */ |
extern int hdc63463_dataleft; /* Number of bytes left to transfer */ |
|
|
|
|
static int lastspecifieddrive; |
static unsigned Busy; |
|
static unsigned int PartFragRead; /* The number of sectors which have been read |
during a partial read split over two |
cylinders. If 0 it means a partial |
read did not occur. */ |
|
static unsigned int PartFragRead_RestartBlock; /* Where to restart on a split access */ |
static unsigned int PartFragRead_SectorsLeft; /* Where to restart on a split access */ |
|
static int Sectors256LeftInCurrent; /* i.e. 256 byte sectors left in current */ |
static int SectorsLeftInRequest; /* i.e. blocks left in the thing mfm_request was called for */ |
static int Copy_Sector; /* The 256 byte sector we are currently at - fragments need to know |
where to take over */ |
static char *Copy_buffer; |
|
|
static void mfm_seek(void); |
static void mfm_rerequest(void); |
static void mfm_request(void); |
static int mfm_reread_partitions(kdev_t dev); |
static void mfm_specify (void); |
static void issue_request(int dev, unsigned int block, unsigned int nsect, |
struct request *req); |
|
#define mfm_init xd_init |
#define mfm_setup xd_setup |
|
static unsigned int mfm_addr; /* Controller address */ |
static unsigned int mfm_IRQPollLoc; /* Address to read for IRQ information */ |
static unsigned int mfm_irqenable; /* Podule IRQ enable location */ |
static unsigned char mfm_irq; /* Interrupt number */ |
static int mfm_drives = 0; /* drives available */ |
static int mfm_status = 0; /* interrupt status */ |
static int *errors; |
|
static struct rawcmd { |
unsigned int dev; |
unsigned int cylinder; |
unsigned int head; |
unsigned int sector; |
unsigned int cmdtype; |
unsigned int cmdcode; |
unsigned char cmddata[16]; |
unsigned int cmdlen; |
} raw_cmd; |
|
static unsigned char result[16]; |
|
static struct cont { |
void (*interrupt) (void); /* interrupt handler */ |
void (*error) (void); /* error handler */ |
void (*redo) (void); /* redo handler */ |
void (*done) (int st); /* done handler */ |
} *cont = NULL; |
|
static struct tq_struct mfm_tq = {0, 0, (void (*)(void *)) NULL, 0}; |
|
int number_mfm_drives = 1; |
|
/* ------------------------------------------------------------------------------------------ */ |
/* |
* From the HD63463 data sheet from Hitachi Ltd. |
*/ |
|
#define MFM_COMMAND (mfm_addr + 0) |
#define MFM_DATAOUT (mfm_addr + 1) |
#define MFM_STATUS (mfm_addr + 8) |
#define MFM_DATAIN (mfm_addr + 9) |
|
#define CMD_ABT 0xF0 /* Abort */ |
#define CMD_SPC 0xE8 /* Specify */ |
#define CMD_TST 0xE0 /* Test */ |
#define CMD_RCLB 0xC8 /* Recalibrate */ |
#define CMD_SEK 0xC0 /* Seek */ |
#define CMD_WFS 0xAB /* Write Format Skew */ |
#define CMD_WFM 0xA3 /* Write Format */ |
#define CMD_MTB 0x90 /* Memory to buffer */ |
#define CMD_CMPD 0x88 /* Compare data */ |
#define CMD_WD 0x87 /* Write data */ |
#define CMD_RED 0x70 /* Read erroneous data */ |
#define CMD_RIS 0x68 /* Read ID skew */ |
#define CMD_FID 0x61 /* Find ID */ |
#define CMD_RID 0x60 /* Read ID */ |
#define CMD_BTM 0x50 /* Buffer to memory */ |
#define CMD_CKD 0x48 /* Check data */ |
#define CMD_RD 0x40 /* Read data */ |
#define CMD_OPBW 0x38 /* Open buffer write */ |
#define CMD_OPBR 0x30 /* Open buffer read */ |
#define CMD_CKV 0x28 /* Check drive */ |
#define CMD_CKE 0x20 /* Check ECC */ |
#define CMD_POD 0x18 /* Polling disable */ |
#define CMD_POL 0x10 /* Polling enable */ |
#define CMD_RCAL 0x08 /* Recall */ |
|
#define STAT_BSY 0x8000 /* Busy */ |
#define STAT_CPR 0x4000 /* Command Parameter Rejection */ |
#define STAT_CED 0x2000 /* Command end */ |
#define STAT_SED 0x1000 /* Seek end */ |
#define STAT_DER 0x0800 /* Drive error */ |
#define STAT_ABN 0x0400 /* Abnormal end */ |
#define STAT_POL 0x0200 /* Polling */ |
|
/* ------------------------------------------------------------------------------------------ */ |
#ifdef DEBUG |
static void console_printf(const char *fmt,...) |
{ |
static char buffer[2048]; /* Arbitary! */ |
extern void console_print(const char *); |
unsigned long flags; |
va_list ap; |
|
save_flags_cli(flags); |
|
va_start(ap, fmt); |
vsprintf(buffer, fmt, ap); |
console_print(buffer); |
va_end(fmt); |
|
restore_flags(flags); |
}; /* console_printf */ |
|
#define DBG(x...) console_printf(x) |
#else |
#define DBG(x...) |
#endif |
|
static void print_status(void) |
{ |
char *error; |
static char *errors[] = { |
"no error", |
"command aborted", |
"invalid command", |
"parameter error", |
"not initialised", |
"rejected TEST", |
"no useld", |
"write fault", |
"not ready", |
"no scp", |
"in seek", |
"invalid NCA", |
"invalid step rate", |
"seek error", |
"over run", |
"invalid PHA", |
"data field EEC error", |
"data field CRC error", |
"error corrected", |
"data field fatal error", |
"no data am", |
"not hit", |
"ID field CRC error", |
"time over", |
"no ID am", |
"not writable" |
}; |
if (result[1] < 0x65) |
error = errors[result[1] >> 2]; |
else |
error = "unknown"; |
printk("("); |
if (mfm_status & STAT_BSY) printk("BSY "); |
if (mfm_status & STAT_CPR) printk("CPR "); |
if (mfm_status & STAT_CED) printk("CED "); |
if (mfm_status & STAT_SED) printk("SED "); |
if (mfm_status & STAT_DER) printk("DER "); |
if (mfm_status & STAT_ABN) printk("ABN "); |
if (mfm_status & STAT_POL) printk("POL "); |
printk(") SSB = %X (%s)\n", result[1], error); |
|
} |
|
/* ------------------------------------------------------------------------------------- */ |
|
static void issue_command(int command, unsigned char *cmdb, int len) |
{ |
int status; |
#ifdef DEBUG |
int i; |
console_printf("issue_command: %02X: ", command); |
for (i = 0; i < len; i++) |
console_printf("%02X ", cmdb[i]); |
console_printf("\n"); |
#endif |
|
do { |
status = inw(MFM_STATUS); |
} while (status & (STAT_BSY | STAT_POL)); |
DBG("issue_command: status after pol/bsy loop: %02X:\n ", status >> 8); |
|
if (status & (STAT_CPR | STAT_CED | STAT_SED | STAT_DER | STAT_ABN)) { |
outw(CMD_RCAL, MFM_COMMAND); |
while (inw(MFM_STATUS) & STAT_BSY); |
} |
status = inw(MFM_STATUS); |
DBG("issue_command: status before parameter issue: %02X:\n ", status >> 8); |
|
while (len > 0) { |
outw(cmdb[1] | (cmdb[0] << 8), MFM_DATAOUT); |
len -= 2; |
cmdb += 2; |
} |
status = inw(MFM_STATUS); |
DBG("issue_command: status before command issue: %02X:\n ", status >> 8); |
|
outw(command, MFM_COMMAND); |
status = inw(MFM_STATUS); |
DBG("issue_command: status immediatly after command issue: %02X:\n ", status >> 8); |
} |
|
static void wait_for_completion(void) |
{ |
while ((mfm_status = inw(MFM_STATUS)) & STAT_BSY); |
} |
|
static void wait_for_command_end(void) |
{ |
int i; |
|
while (!((mfm_status = inw(MFM_STATUS)) & STAT_CED)); |
|
for (i = 0; i < 16;) { |
int in; |
in = inw(MFM_DATAIN); |
result[i++] = in >> 8; |
result[i++] = in; |
} |
outw (CMD_RCAL, MFM_COMMAND); |
} |
|
/* ------------------------------------------------------------------------------------- */ |
|
static void mfm_rw_intr(void) |
{ |
int old_status; /* Holds status on entry, we read to see if the command just finished */ |
#ifdef DEBUG |
console_printf("mfm_rw_intr...dataleft=%d\n", hdc63463_dataleft); |
print_status(); |
#endif |
|
/* Now don't handle the error until BSY drops */ |
if ((mfm_status & (STAT_DER | STAT_ABN)) && ((mfm_status&STAT_BSY)==0)) { |
/* Something has gone wrong - lets try that again */ |
outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ |
if (cont) { |
DBG("mfm_rw_intr: DER/ABN err\n"); |
cont->error(); |
cont->redo(); |
}; |
return; |
}; |
|
/* OK so what ever happend its not an error, now I reckon we are left between |
a choice of command end or some data which is ready to be collected */ |
/* I think we have to transfer data while the interrupt line is on and its |
not any other type of interrupt */ |
if (CURRENT->cmd == WRITE) { |
extern void hdc63463_writedma(void); |
if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { |
printk("mfm_rw_intr: Apparent DMA write request when no more to DMA\n"); |
if (cont) { |
cont->error(); |
cont->redo(); |
}; |
return; |
}; |
hdc63463_writedma(); |
} else { |
extern void hdc63463_readdma(void); |
if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { |
printk("mfm_rw_intr: Apparent DMA read request when no more to DMA\n"); |
if (cont) { |
cont->error(); |
cont->redo(); |
}; |
return; |
}; |
DBG("Going to try read dma..............status=0x%x, buffer=%p\n", mfm_status, hdc63463_dataptr); |
hdc63463_readdma(); |
}; /* Read */ |
|
if (hdc63463_dataptr != ((unsigned int) Copy_buffer + 256)) { |
/* If we didn't actually manage to get any data on this interrupt - but why? We got the interrupt */ |
/* Ah - well looking at the status its just when we get command end; so no problem */ |
/*console_printf("mfm: dataptr mismatch. dataptr=0x%08x Copy_buffer+256=0x%08p\n", |
hdc63463_dataptr,Copy_buffer+256); |
print_status(); */ |
} else { |
Sectors256LeftInCurrent--; |
Copy_buffer += 256; |
Copy_Sector++; |
|
/* We have come to the end of this request */ |
if (!Sectors256LeftInCurrent) { |
DBG("mfm: end_request for CURRENT=0x%p CURRENT(sector=%d current_nr_sectors=%d nr_sectors=%d)\n", |
CURRENT, CURRENT->sector, CURRENT->current_nr_sectors, CURRENT->nr_sectors); |
|
CURRENT->nr_sectors -= CURRENT->current_nr_sectors; |
CURRENT->sector += CURRENT->current_nr_sectors; |
SectorsLeftInRequest -= CURRENT->current_nr_sectors; |
|
end_request(1); |
if (SectorsLeftInRequest) { |
hdc63463_dataptr = (unsigned int) CURRENT->buffer; |
Copy_buffer = CURRENT->buffer; |
Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; |
errors = &(CURRENT->errors); |
/* These should match the present calculations of the next logical sector |
on the device |
Copy_Sector=CURRENT->sector*2; */ |
|
if (Copy_Sector != CURRENT->sector * 2) |
#ifdef DEBUG |
/*console_printf*/printk("mfm: Copy_Sector mismatch. Copy_Sector=%d CURRENT->sector*2=%d\n", |
Copy_Sector, CURRENT->sector * 2); |
#else |
printk("mfm: Copy_Sector mismatch! Eek!\n"); |
#endif |
}; /* CURRENT */ |
}; /* Sectors256LeftInCurrent */ |
}; |
|
old_status = mfm_status; |
mfm_status = inw(MFM_STATUS); |
if (mfm_status & (STAT_DER | STAT_ABN)) { |
/* Something has gone wrong - lets try that again */ |
if (cont) { |
DBG("mfm_rw_intr: DER/ABN error\n"); |
cont->error(); |
cont->redo(); |
}; |
return; |
}; |
|
/* If this code wasn't entered due to command_end but there is |
now a command end we must read the command results out. If it was |
entered like this then mfm_interrupt_handler would have done the |
job. */ |
if ((!((old_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) && |
((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) { |
int len = 0; |
while (len < 16) { |
int in; |
in = inw(MFM_DATAIN); |
result[len++] = in >> 8; |
result[len++] = in; |
}; |
}; /* Result read */ |
|
/*console_printf ("mfm_rw_intr nearexit [%02X]\n", inb(mfm_IRQPollLoc)); */ |
|
/* If end of command move on */ |
if (mfm_status & (STAT_CED)) { |
outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ |
/* End of command - trigger the next command */ |
if (cont) { |
cont->done(1); |
} |
DBG("mfm_rw_intr: returned from cont->done\n"); |
} else { |
/* Its going to generate another interrupt */ |
SET_INTR(mfm_rw_intr); |
}; |
} |
|
static void mfm_setup_rw(void) |
{ |
DBG("setting up for rw...\n"); |
|
SET_INTR(mfm_rw_intr); |
issue_command(raw_cmd.cmdcode, raw_cmd.cmddata, raw_cmd.cmdlen); |
} |
|
static void mfm_recal_intr(void) |
{ |
#ifdef DEBUG |
console_printf("recal intr - status = "); |
print_status(); |
#endif |
outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ |
if (mfm_status & (STAT_DER | STAT_ABN)) { |
printk("recal failed\n"); |
MFM_DRV_INFO.cylinder = NEED_2_RECAL; |
if (cont) { |
cont->error(); |
cont->redo(); |
} |
return; |
} |
/* Thats seek end - we are finished */ |
if (mfm_status & STAT_SED) { |
issue_command(CMD_POD, NULL, 0); |
MFM_DRV_INFO.cylinder = 0; |
mfm_seek(); |
return; |
} |
/* Command end without seek end (see data sheet p.20) for parallel seek |
- we have to send a POL command to wait for the seek */ |
if (mfm_status & STAT_CED) { |
SET_INTR(mfm_recal_intr); |
issue_command(CMD_POL, NULL, 0); |
return; |
} |
printk("recal: unknown status\n"); |
} |
|
static void mfm_seek_intr(void) |
{ |
#ifdef DEBUG |
console_printf("seek intr - status = "); |
print_status(); |
#endif |
outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ |
if (mfm_status & (STAT_DER | STAT_ABN)) { |
printk("seek failed\n"); |
MFM_DRV_INFO.cylinder = NEED_2_RECAL; |
if (cont) { |
cont->error(); |
cont->redo(); |
} |
return; |
} |
if (mfm_status & STAT_SED) { |
issue_command(CMD_POD, NULL, 0); |
MFM_DRV_INFO.cylinder = raw_cmd.cylinder; |
mfm_seek(); |
return; |
} |
if (mfm_status & STAT_CED) { |
SET_INTR(mfm_seek_intr); |
issue_command(CMD_POL, NULL, 0); |
return; |
} |
printk("seek: unknown status\n"); |
} |
|
/* IDEA2 seems to work better - its what RiscOS sets my |
* disc to - on its SECOND call to specify! |
*/ |
#define IDEA2 |
#ifndef IDEA2 |
#define SPEC_SL 0x16 |
#define SPEC_SH 0xa9 /* Step pulse high=21, Record Length=001 (256 bytes) */ |
#else |
#define SPEC_SL 0x00 /* OM2 - SL - step pulse low */ |
#define SPEC_SH 0x21 /* Step pulse high=4, Record Length=001 (256 bytes) */ |
#endif |
|
static void mfm_setupspecify (int drive, unsigned char *cmdb) |
{ |
cmdb[0] = 0x1f; /* OM0 - !SECT,!MOD,!DIF,PADP,ECD,CRCP,CRCI,ACOR */ |
cmdb[1] = 0xc3; /* OM1 - DTM,BRST,!CEDM,!SEDM,!DERM,0,AMEX,PSK */ |
cmdb[2] = SPEC_SL; /* OM2 - SL - step pulse low */ |
cmdb[3] = (number_mfm_drives == 1) ? 0x02 : 0x06; /* 1 or 2 drives */ |
cmdb[4] = 0xfc | ((mfm_info[drive].cylinders - 1) >> 8);/* RW time over/high part of number of cylinders */ |
cmdb[5] = mfm_info[drive].cylinders - 1; /* low part of number of cylinders */ |
cmdb[6] = mfm_info[drive].heads - 1; /* Number of heads */ |
cmdb[7] = mfm_info[drive].sectors - 1; /* Number of sectors */ |
cmdb[8] = SPEC_SH; |
cmdb[9] = 0x0a; /* gap length 1 */ |
cmdb[10] = 0x0d; /* gap length 2 */ |
cmdb[11] = 0x0c; /* gap length 3 */ |
cmdb[12] = (mfm_info[drive].precomp - 1) >> 8; /* pre comp cylinder */ |
cmdb[13] = mfm_info[drive].precomp - 1; |
cmdb[14] = (mfm_info[drive].lowcurrent - 1) >> 8; /* Low current cylinder */ |
cmdb[15] = mfm_info[drive].lowcurrent - 1; |
} |
|
static void mfm_specify (void) |
{ |
unsigned char cmdb[16]; |
|
DBG("specify...dev=%d lastspecified=%d\n", raw_cmd.dev, lastspecifieddrive); |
mfm_setupspecify (raw_cmd.dev, cmdb); |
|
issue_command (CMD_SPC, cmdb, 16); |
/* Ensure that we will do another specify if we move to the other drive */ |
lastspecifieddrive = raw_cmd.dev; |
wait_for_completion(); |
} |
|
static void mfm_seek(void) |
{ |
unsigned char cmdb[4]; |
|
DBG("seeking...\n"); |
if (MFM_DRV_INFO.cylinder < 0) { |
SET_INTR(mfm_recal_intr); |
DBG("mfm_seek: about to call specify\n"); |
mfm_specify (); /* DAG added this */ |
|
cmdb[0] = raw_cmd.dev + 1; |
cmdb[1] = 0; |
|
issue_command(CMD_RCLB, cmdb, 2); |
return; |
} |
if (MFM_DRV_INFO.cylinder != raw_cmd.cylinder) { |
cmdb[0] = raw_cmd.dev + 1; |
cmdb[1] = 0; /* raw_cmd.head; DAG: My data sheet says this should be 0 */ |
cmdb[2] = raw_cmd.cylinder >> 8; |
cmdb[3] = raw_cmd.cylinder; |
|
SET_INTR(mfm_seek_intr); |
issue_command(CMD_SEK, cmdb, 4); |
} else |
mfm_setup_rw(); |
} |
|
static void mfm_initialise(void) |
{ |
DBG("init...\n"); |
mfm_seek(); |
} |
|
static void request_done(int uptodate) |
{ |
DBG("mfm:request_done\n"); |
if (uptodate) { |
unsigned char block[2] = {0, 0}; |
|
/* Apparently worked - lets check bytes left to DMA */ |
if (hdc63463_dataleft != (PartFragRead_SectorsLeft * 256)) { |
printk("mfm: request_done - dataleft=%d - should be %d - Eek!\n", hdc63463_dataleft, PartFragRead_SectorsLeft * 256); |
end_request(0); |
Busy = 0; |
}; |
/* Potentially this means that we've done; but we might be doing |
a partial access, (over two cylinders) or we may have a number |
of fragments in an image file. First lets deal with partial accesss |
*/ |
if (PartFragRead) { |
/* Yep - a partial access */ |
|
/* and issue the remainder */ |
issue_request(MINOR(CURRENT->rq_dev), PartFragRead_RestartBlock, PartFragRead_SectorsLeft, CURRENT); |
return; |
} |
|
/* ah well - perhaps there is another fragment to go */ |
|
/* Increment pointers/counts to start of next fragment */ |
if (SectorsLeftInRequest > 0) printk("mfm: SectorsLeftInRequest>0 - Eek! Shouldn't happen!\n"); |
|
/* No - its the end of the line */ |
/* end_request's should have happened at the end of sector DMAs */ |
/* Turns Drive LEDs off - may slow it down? */ |
if (!CURRENT) |
issue_command(CMD_CKV, block, 2); |
|
Busy = 0; |
DBG("request_done: About to mfm_request\n"); |
/* Next one please */ |
mfm_request(); /* Moved from mfm_rw_intr */ |
DBG("request_done: returned from mfm_request\n"); |
} else { |
printk("mfm:request_done: update=0\n"); |
end_request(0); |
Busy = 0; |
} |
} |
|
static void error_handler(void) |
{ |
printk("error detected... status = "); |
print_status(); |
(*errors)++; |
if (*errors > MFM_DRV_INFO.errors.abort) |
cont->done(0); |
if (*errors > MFM_DRV_INFO.errors.recal) |
MFM_DRV_INFO.cylinder = NEED_2_RECAL; |
} |
|
static void rw_interrupt(void) |
{ |
printk("rw_interrupt\n"); |
} |
|
static struct cont rw_cont = |
{ |
rw_interrupt, |
error_handler, |
mfm_rerequest, |
request_done |
}; |
|
/* |
* Actually gets round to issuing the request - note everything at this |
* point is in 256 byte sectors not Linux 512 byte blocks |
*/ |
static void issue_request(int dev, unsigned int block, unsigned int nsect, |
struct request *req) |
{ |
int track, start_head, start_sector; |
int sectors_to_next_cyl; |
|
dev >>= 6; |
|
track = block / mfm_info[dev].sectors; |
start_sector = block % mfm_info[dev].sectors; |
start_head = track % mfm_info[dev].heads; |
|
/* First get the number of whole tracks which are free before the next |
track */ |
sectors_to_next_cyl = (mfm_info[dev].heads - (start_head + 1)) * mfm_info[dev].sectors; |
/* Then add in the number of sectors left on this track */ |
sectors_to_next_cyl += (mfm_info[dev].sectors - start_sector); |
|
DBG("issue_request: mfm_info[dev].sectors=%d track=%d\n", mfm_info[dev].sectors, track); |
|
raw_cmd.dev = dev; |
raw_cmd.sector = start_sector; |
raw_cmd.head = start_head; |
raw_cmd.cylinder = track / mfm_info[dev].heads; |
raw_cmd.cmdtype = CURRENT->cmd; |
raw_cmd.cmdcode = CURRENT->cmd == WRITE ? CMD_WD : CMD_RD; |
raw_cmd.cmddata[0] = dev + 1; /* DAG: +1 to get US */ |
raw_cmd.cmddata[1] = raw_cmd.head; |
raw_cmd.cmddata[2] = raw_cmd.cylinder >> 8; |
raw_cmd.cmddata[3] = raw_cmd.cylinder; |
raw_cmd.cmddata[4] = raw_cmd.head; |
raw_cmd.cmddata[5] = raw_cmd.sector; |
|
/* Was == and worked - how the heck??? */ |
if (lastspecifieddrive != raw_cmd.dev) |
mfm_specify (); |
|
if (nsect <= sectors_to_next_cyl) { |
raw_cmd.cmddata[6] = nsect >> 8; |
raw_cmd.cmddata[7] = nsect; |
PartFragRead = 0; /* All in one */ |
PartFragRead_SectorsLeft = 0; /* Must set this - used in DMA calcs */ |
} else { |
raw_cmd.cmddata[6] = sectors_to_next_cyl >> 8; |
raw_cmd.cmddata[7] = sectors_to_next_cyl; |
PartFragRead = sectors_to_next_cyl; /* only do this many this time */ |
PartFragRead_RestartBlock = block + sectors_to_next_cyl; /* Where to restart from */ |
PartFragRead_SectorsLeft = nsect - sectors_to_next_cyl; |
} |
raw_cmd.cmdlen = 8; |
|
/* Setup DMA pointers */ |
hdc63463_dataptr = (unsigned int) Copy_buffer; |
hdc63463_dataleft = nsect * 256; /* Better way? */ |
|
DBG("mfm%c: %sing: CHS=%d/%d/%d, sectors=%d, buffer=0x%08lx (%p)\n", |
raw_cmd.dev + 'a', (CURRENT->cmd == READ) ? "read" : "writ", |
raw_cmd.cylinder, |
raw_cmd.head, |
raw_cmd.sector, nsect, (unsigned long) Copy_buffer, CURRENT); |
|
cont = &rw_cont; |
errors = &(CURRENT->errors); |
#if 0 |
mfm_tq.routine = (void (*)(void *)) mfm_initialise; |
queue_task(&mfm_tq, &tq_immediate); |
mark_bh(IMMEDIATE_BH); |
#else |
mfm_initialise(); |
#endif |
} /* issue_request */ |
|
/* |
* Called when an error has just happened - need to trick mfm_request |
* into thinking we weren't busy |
* |
* Turn off ints - mfm_request expects them this way |
*/ |
static void mfm_rerequest(void) |
{ |
DBG("mfm_rerequest\n"); |
cli(); |
Busy = 0; |
mfm_request(); |
} |
|
static void mfm_request(void) |
{ |
DBG("mfm_request CURRENT=%p Busy=%d\n", CURRENT, Busy); |
|
if (!CURRENT) { |
DBG("mfm_request: Exited due to NULL Current 1\n"); |
return; |
} |
|
if (CURRENT->rq_status == RQ_INACTIVE) { |
/* Hmm - seems to be happening a lot on 1.3.45 */ |
/*console_printf("mfm_request: Exited due to INACTIVE Current\n"); */ |
return; |
} |
|
/* If we are still processing then return; we will get called again */ |
if (Busy) { |
/* Again seems to be common in 1.3.45 */ |
/*DBG*/printk("mfm_request: Exiting due to busy\n"); |
return; |
} |
Busy = 1; |
|
while (1) { |
unsigned int dev, block, nsect; |
|
DBG("mfm_request: loop start\n"); |
sti(); |
|
DBG("mfm_request: before INIT_REQUEST\n"); |
|
if (!CURRENT) { |
printk("mfm_request: Exiting due to !CURRENT (pre)\n"); |
CLEAR_INTR; |
Busy = 0; |
return; |
}; |
|
INIT_REQUEST; |
|
DBG("mfm_request: before arg extraction\n"); |
|
dev = MINOR(CURRENT->rq_dev); |
block = CURRENT->sector; |
nsect = CURRENT->nr_sectors; |
#ifdef DEBUG |
/*if ((dev>>6)==1) */ console_printf("mfm_request: raw vals: dev=%d (block=512 bytes) block=%d nblocks=%d\n", dev, block, nsect); |
#endif |
if (dev >= (mfm_drives << 6) || |
block >= mfm[dev].nr_sects || ((block+nsect) > mfm[dev].nr_sects)) { |
if (dev >= (mfm_drives << 6)) |
printk("mfm: bad minor number: device=%s\n", kdevname(CURRENT->rq_dev)); |
else |
printk("mfm%c: bad access: block=%d, count=%d, nr_sects=%ld\n", (dev >> 6)+'a', |
block, nsect, mfm[dev].nr_sects); |
printk("mfm: continue 1\n"); |
end_request(0); |
Busy = 0; |
continue; |
} |
|
block += mfm[dev].start_sect; |
|
/* DAG: Linux doesn't cope with this - even though it has an array telling |
it the hardware block size - silly */ |
block <<= 1; /* Now in 256 byte sectors */ |
nsect <<= 1; /* Ditto */ |
|
SectorsLeftInRequest = nsect >> 1; |
Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; |
Copy_buffer = CURRENT->buffer; |
Copy_Sector = CURRENT->sector << 1; |
|
DBG("mfm_request: block after offset=%d\n", block); |
|
if (CURRENT->cmd != READ && CURRENT->cmd != WRITE) { |
printk("unknown mfm-command %d\n", CURRENT->cmd); |
end_request(0); |
Busy = 0; |
printk("mfm: continue 4\n"); |
continue; |
} |
issue_request(dev, block, nsect, CURRENT); |
|
break; |
} |
DBG("mfm_request: Dropping out bottom\n"); |
} |
|
static void do_mfm_request(void) |
{ |
DBG("do_mfm_request: about to mfm_request\n"); |
mfm_request(); |
} |
|
static void mfm_interrupt_handler(int unused, void *dev_id, struct pt_regs *regs) |
{ |
void (*handler) (void) = DEVICE_INTR; |
|
CLEAR_INTR; |
|
DBG("mfm_interrupt_handler (handler=0x%p)\n", handler); |
|
mfm_status = inw(MFM_STATUS); |
|
/* If CPR (Command Parameter Reject) and not busy it means that the command |
has some return message to give us */ |
if ((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR) { |
int len = 0; |
while (len < 16) { |
int in; |
in = inw(MFM_DATAIN); |
result[len++] = in >> 8; |
result[len++] = in; |
} |
} |
if (handler) { |
handler(); |
return; |
} |
outw (CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ |
printk ("mfm: unexpected interrupt - status = "); |
print_status (); |
while (1); |
} |
|
|
|
|
|
/* |
* Tell the user about the drive if we decided it exists. Also, |
* set the size of the drive. |
*/ |
static void mfm_geometry (int drive) |
{ |
if (mfm_info[drive].cylinders) |
printk ("mfm%c: %dMB CHS=%d/%d/%d LCC=%d RECOMP=%d\n", 'a' + drive, |
mfm_info[drive].cylinders * mfm_info[drive].heads * mfm_info[drive].sectors / 4096, |
mfm_info[drive].cylinders, mfm_info[drive].heads, mfm_info[drive].sectors, |
mfm_info[drive].lowcurrent, mfm_info[drive].precomp); |
mfm[drive << 6].start_sect = 0; |
mfm[drive << 6].nr_sects = mfm_info[drive].cylinders * mfm_info[drive].heads * mfm_info[drive].sectors / 2; |
} |
|
#ifdef MFM_AUTODETECT_DRIVES |
/* |
* Attempt to detect a drive and find its geometry. The drive has already been |
* specified... |
* |
* We first recalibrate the disk, then try to probe sectors, heads and then |
* cylinders. NOTE! the cylinder probe may break drives. The xd disk driver |
* does something along these lines, so I assume that most drives are up to |
* this mistreatment... |
*/ |
static int mfm_detectdrive (int drive) |
{ |
unsigned int mingeo[3], maxgeo[3]; |
unsigned int attribute, need_recal = 1; |
unsigned char cmdb[8]; |
|
memset (mingeo, 0, sizeof (mingeo)); |
maxgeo[0] = mfm_info[drive].sectors; |
maxgeo[1] = mfm_info[drive].heads; |
maxgeo[2] = mfm_info[drive].cylinders; |
|
cmdb[0] = drive + 1; |
cmdb[6] = 0; |
cmdb[7] = 1; |
for (attribute = 0; attribute < 3; attribute++) { |
while (mingeo[attribute] != maxgeo[attribute]) { |
unsigned int variable; |
|
variable = (maxgeo[attribute] + mingeo[attribute]) >> 1; |
cmdb[1] = cmdb[2] = cmdb[3] = cmdb[4] = cmdb[5] = 0; |
|
if (need_recal) { |
int tries = 5; |
|
do { |
issue_command (CMD_RCLB, cmdb, 2); |
wait_for_completion (); |
wait_for_command_end (); |
if (result[1] == 0x20) |
break; |
} while (result[1] && --tries); |
if (result[1]) { |
outw (CMD_RCAL, MFM_COMMAND); |
return 0; |
} |
need_recal = 0; |
} |
|
switch (attribute) { |
case 0: |
cmdb[5] = variable; |
issue_command (CMD_CMPD, cmdb, 8); |
break; |
case 1: |
cmdb[1] = variable; |
cmdb[4] = variable; |
issue_command (CMD_CMPD, cmdb, 8); |
break; |
case 2: |
cmdb[2] = variable >> 8; |
cmdb[3] = variable; |
issue_command (CMD_SEK, cmdb, 4); |
break; |
} |
wait_for_completion (); |
wait_for_command_end (); |
|
switch (result[1]) { |
case 0x00: |
case 0x50: |
mingeo[attribute] = variable + 1; |
break; |
|
case 0x20: |
outw (CMD_RCAL, MFM_COMMAND); |
return 0; |
|
case 0x24: |
need_recal = 1; |
default: |
maxgeo[attribute] = variable; |
break; |
} |
} |
} |
mfm_info[drive].cylinders = mingeo[2]; |
mfm_info[drive].lowcurrent = mingeo[2]; |
mfm_info[drive].precomp = mingeo[2] / 2; |
mfm_info[drive].heads = mingeo[1]; |
mfm_info[drive].sectors = mingeo[0]; |
outw (CMD_RCAL, MFM_COMMAND); |
return 1; |
} |
#endif |
|
/* |
* Initialise all drive information for this controller. |
*/ |
static int mfm_initdrives(void) |
{ |
int drive; |
|
if (number_mfm_drives > MFM_MAXDRIVES) { |
number_mfm_drives = MFM_MAXDRIVES; |
printk("No. of ADFS MFM drives is greater than MFM_MAXDRIVES - you can't have that many!\n"); |
} |
|
for (drive = 0; drive < number_mfm_drives; drive++) { |
mfm_info[drive].lowcurrent = 1; |
mfm_info[drive].precomp = 1; |
mfm_info[drive].cylinder = -1; |
mfm_info[drive].errors.recal = 0; |
mfm_info[drive].errors.report = 0; |
mfm_info[drive].errors.abort = 4; |
|
#ifdef MFM_AUTODETECT_DRIVES |
mfm_info[drive].cylinders = 1024; |
mfm_info[drive].heads = 8; |
mfm_info[drive].sectors = 64; |
{ |
unsigned char cmdb[16]; |
|
mfm_setupspecify (drive, cmdb); |
cmdb[1] &= ~0x81; |
issue_command (CMD_SPC, cmdb, 16); |
wait_for_completion (); |
if (!mfm_detectdrive (drive)) { |
mfm_info[drive].cylinders = 0; |
mfm_info[drive].heads = 0; |
mfm_info[drive].sectors = 0; |
} |
cmdb[0] = cmdb[1] = 0; |
issue_command (CMD_CKV, cmdb, 2); |
} |
#else |
mfm_info[drive].cylinders = 1; /* its going to have to figure it out from the partition info */ |
mfm_info[drive].heads = 4; |
mfm_info[drive].sectors = 32; |
#endif |
} |
return number_mfm_drives; |
} |
|
|
|
/* |
* The 'front' end of the mfm driver follows... |
*/ |
|
static int mfm_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) |
{ |
struct hd_geometry *geo = (struct hd_geometry *) arg; |
int dev, err; |
|
if (!inode || !inode->i_rdev) |
return -EINVAL; |
dev = DEVICE_NR(MINOR(inode->i_rdev)), err; |
if (dev >= mfm_drives) |
return -EINVAL; |
|
switch (cmd) { |
case HDIO_GETGEO: |
if (!arg) |
return -EINVAL; |
err = verify_area (VERIFY_WRITE, geo, sizeof(*geo)); |
if (err) |
return err; |
put_user (mfm_info[dev].heads, &geo->heads); |
put_user (mfm_info[dev].sectors, &geo->sectors); |
put_user (mfm_info[dev].cylinders, &geo->cylinders); |
put_user (mfm[MINOR(inode->i_rdev)].start_sect, &geo->start); |
return 0; |
|
case BLKRASET: |
if (!suser()) |
return -EACCES; |
if (arg > 0xff) |
return -EINVAL; |
read_ahead[MAJOR(inode->i_rdev)] = arg; |
return 0; |
|
case BLKRAGET: |
if (!arg) |
return -EINVAL; |
err = verify_area (VERIFY_WRITE, (long *) arg, sizeof (long)); |
if (err) |
return err; |
put_user(read_ahead[MAJOR(inode->i_rdev)], (long *)arg); |
return 0; |
|
case BLKGETSIZE: |
if (!arg) |
return -EINVAL; |
err = verify_area (VERIFY_WRITE, (long *) arg, sizeof (long)); |
if (err) |
return err; |
put_user (mfm[MINOR(inode->i_rdev)].nr_sects, (long *)arg); |
return 0; |
|
case BLKFLSBUF: |
if (!suser()) |
return -EACCES; |
if (!inode->i_rdev) |
return -EINVAL; |
fsync_dev(inode->i_rdev); |
invalidate_buffers(inode->i_rdev); |
return 0; |
|
case BLKRRPART: |
return mfm_reread_partitions(inode->i_rdev); |
|
RO_IOCTLS(inode->i_rdev, arg); |
default: |
return -EINVAL; |
} |
} |
|
static int mfm_open(struct inode *inode, struct file *file) |
{ |
int dev = DEVICE_NR(MINOR(inode->i_rdev)); |
|
if (dev >= mfm_drives) |
return -ENODEV; |
while (mfm_info[dev].busy) |
sleep_on (&mfm_wait_open); |
mfm_info[dev].access_count++; |
return 0; |
} |
|
/* |
* Releasing a block device means we sync() it, so that it can safely |
* be forgotten about... |
*/ |
static void mfm_release(struct inode *inode, struct file *file) |
{ |
sync_dev(inode->i_rdev); |
mfm_info[DEVICE_NR(MINOR(inode->i_rdev))].access_count--; |
} |
|
/* |
* This is to handle various kernel command line parameters |
* specific to this driver. |
*/ |
void mfm_setup(char *str, int *ints) |
{ |
return; |
} |
|
/* |
* Set the CHS from the ADFS boot block if it is present. This is not ideal |
* since if there are any non-ADFS partitions on the disk, this won't work! |
* Hence, I want to get rid of this... |
*/ |
void xd_set_geometry(kdev_t dev, unsigned char secsptrack, unsigned char heads, |
unsigned long discsize, unsigned int secsize) |
{ |
int drive = MINOR(dev) >> 6; |
|
if (mfm_info[drive].cylinders == 1) { |
mfm_info[drive].sectors = secsptrack; |
mfm_info[drive].heads = heads; |
mfm_info[drive].cylinders = discsize / (secsptrack * heads * secsize); |
|
if ((heads < 1) || (mfm_info[drive].cylinders > 1024)) { |
printk("mfm%c: Insane disc shape! Setting to 512/4/32\n",'a' + (dev >> 6)); |
|
/* These values are fairly arbitary, but are there so that if your |
* lucky you can pick apart your disc to find out what is going on - |
* I reckon these figures won't hurt MOST drives |
*/ |
mfm_info[drive].sectors = 32; |
mfm_info[drive].heads = 4; |
mfm_info[drive].cylinders = 512; |
} |
if (raw_cmd.dev == drive) |
mfm_specify (); |
mfm_geometry (drive); |
} |
} |
|
static void mfm_geninit (struct gendisk *gdev); |
|
static struct gendisk mfm_gendisk = { |
MAJOR_NR, /* Major number */ |
"mfm", /* Major name */ |
6, /* Bits to shift to get real from partition */ |
1 << 6, /* Number of partitions per real */ |
MFM_MAXDRIVES, /* maximum number of real */ |
mfm_geninit, /* init function */ |
mfm, /* hd struct */ |
mfm_sizes, /* block sizes */ |
0, /* number */ |
(void *) mfm_info, /* internal */ |
NULL /* next */ |
}; |
|
static void mfm_geninit (struct gendisk *gdev) |
{ |
int i; |
|
mfm_drives = mfm_initdrives(); |
|
printk("mfm: detected %d hard drive%s\n", mfm_drives, mfm_drives == 1 ? "" : "s"); |
gdev->nr_real = mfm_drives; |
|
for (i = 0; i < mfm_drives; i++) |
mfm_geometry (i); |
|
if (request_irq(mfm_irq, mfm_interrupt_handler, SA_INTERRUPT, "MFM harddisk", NULL)) |
printk("mfm: unable to get IRQ%d\n", mfm_irq); |
|
if (mfm_irqenable) |
outw(0x80, mfm_irqenable); /* Required to enable IRQs from MFM podule */ |
|
for (i = 0; i < (MFM_MAXDRIVES << 6); i++) { |
mfm_blocksizes[i] = 1024; /* Can't increase this - if you do all hell breaks loose */ |
mfm_sectsizes[i] = 512; |
} |
blksize_size[MAJOR_NR] = mfm_blocksizes; |
hardsect_size[MAJOR_NR] = mfm_sectsizes; |
} |
|
void xd_manual_geo_init (char *command,int *integers) { |
printk("MFM:Manual setting of geometry not presently supported\n"); |
} |
|
static struct file_operations mfm_fops = |
{ |
NULL, /* lseek - default */ |
block_read, /* read - general block-dev read */ |
block_write, /* write - general block-dev write */ |
NULL, /* readdir - bad */ |
NULL, /* select */ |
mfm_ioctl, /* ioctl */ |
NULL, /* mmap */ |
mfm_open, /* open */ |
mfm_release, /* release */ |
block_fsync /* fsync */ |
}; |
|
|
static struct expansion_card *ecs; |
|
/* |
* See if there is a controller at the address presently at mfm_addr |
* |
* We check to see if the controller is busy - if it is, we abort it first, |
* and check that the chip is no longer busy after at least 180 clock cycles. |
* We then issue a command and check that the BSY or CPR bits are set. |
*/ |
static int mfm_probecontroller (unsigned int mfm_addr) |
{ |
if (check_region (mfm_addr, 10)) |
return 0; |
|
if (inw (MFM_STATUS) & STAT_BSY) { |
outw (CMD_ABT, MFM_COMMAND); |
udelay (50); |
if (inw (MFM_STATUS) & STAT_BSY) |
return 0; |
} |
|
if (inw (MFM_STATUS) & STAT_CED) |
outw (CMD_RCAL, MFM_COMMAND); |
|
outw (CMD_SEK, MFM_COMMAND); |
|
if (inw (MFM_STATUS) & (STAT_BSY | STAT_CPR)) { |
unsigned int count = 2000; |
while (inw (MFM_STATUS) & STAT_BSY) { |
udelay (500); |
if (!--count) |
return 0; |
} |
|
outw (CMD_RCAL, MFM_COMMAND); |
} |
return 1; |
} |
|
/* |
* Look for a MFM controller - first check the motherboard, then the podules |
* The podules have an extra interrupt enable that needs to be played with |
* |
* The HDC is accessed at MEDIUM IOC speeds. |
*/ |
int mfm_init (void) |
{ |
unsigned char irqmask; |
|
if (register_blkdev(MAJOR_NR, "mfm", &mfm_fops)) { |
printk("mfm_init: unable to get major number %d\n", MAJOR_NR); |
return -1; |
} |
|
if (mfm_probecontroller(ONBOARD_MFM_ADDRESS)) { |
mfm_addr = ONBOARD_MFM_ADDRESS; |
mfm_IRQPollLoc = IOC_IRQSTATB; |
mfm_irqenable = 0; |
mfm_irq = IRQ_HARDDISK; |
irqmask = 0x08; /* IL3 pin */ |
} else { |
ecs = ecard_find(0, mfm_cids); |
if (!ecs) { |
mfm_addr = 0; |
return -1; |
} |
|
mfm_addr = ecard_address(ecs, ECARD_IOC, ECARD_MEDIUM) + 0x800; |
mfm_IRQPollLoc = mfm_addr + 0x400; |
mfm_irqenable = mfm_IRQPollLoc; |
mfm_irq = ecs->irq; |
irqmask = 0x08; |
|
ecard_claim(ecs); |
} |
|
printk("mfm: found at address %08X, interrupt %d\n", mfm_addr, mfm_irq); |
request_region (mfm_addr, 10, "mfm"); |
|
/* Stuff for the assembler routines to get to */ |
hdc63463_baseaddress = ioaddr(mfm_addr); |
hdc63463_irqpolladdress = ioaddr(mfm_IRQPollLoc); |
hdc63463_irqpollmask = irqmask; |
|
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; |
read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB?) read ahread */ |
|
#ifndef MODULE |
mfm_gendisk.next = gendisk_head; |
gendisk_head = &mfm_gendisk; |
#endif |
|
Busy = 0; |
lastspecifieddrive = -1; |
|
return 0; |
} |
|
/* |
* This routine is called to flush all partitions and partition tables |
* for a changed MFM disk, and then re-read the new partition table. |
* If we are revalidating due to an ioctl, we have USAGE == 1. |
*/ |
static int mfm_reread_partitions(kdev_t dev) |
{ |
unsigned int start, i, maxp, target = DEVICE_NR(MINOR(dev)); |
unsigned long flags; |
|
save_flags_cli(flags); |
if (mfm_info[target].busy || mfm_info[target].access_count > 1) { |
restore_flags (flags); |
return -EBUSY; |
} |
mfm_info[target].busy = 1; |
restore_flags (flags); |
|
maxp = mfm_gendisk.max_p; |
start = target << mfm_gendisk.minor_shift; |
|
for (i = maxp - 1; i >= 0; i--) { |
int minor = start + i; |
kdev_t devi = MKDEV(MAJOR_NR, minor); |
|
sync_dev (devi); |
invalidate_inodes (devi); |
invalidate_buffers (devi); |
|
mfm_gendisk.part[minor].start_sect = 0; |
mfm_gendisk.part[minor].nr_sects = 0; |
} |
|
mfm_gendisk.part[start].nr_sects = mfm_info[target].heads * |
mfm_info[target].cylinders * mfm_info[target].sectors / 2; |
|
resetup_one_dev(&mfm_gendisk, target); |
|
mfm_info[target].busy = 0; |
wake_up (&mfm_wait_open); |
return 0; |
} |
|
#ifdef MODULE |
int init_module(void) |
{ |
int ret; |
ret = mfm_init(); |
if (!ret) |
mfm_geninit(&mfm_gendisk); |
return ret; |
} |
|
void cleanup_module(void) |
{ |
if (ecs && mfm_irqenable) |
outw (0, mfm_irqenable); /* Required to enable IRQs from MFM podule */ |
free_irq(mfm_irq, NULL); |
unregister_blkdev(MAJOR_NR, "mfm"); |
if (ecs) |
ecard_release(ecs); |
if (mfm_addr) |
release_region(mfm_addr, 10); |
} |
#endif |
/Makefile
0,0 → 1,185
# |
# Makefile for the kernel block device drivers. |
# |
# Note! Dependencies are done automagically by 'make dep', which also |
# removes any old dependencies. DON'T put your own dependencies here |
# unless it's something special (ie not a .c file). |
# |
# Note 2! The CFLAGS definition is now inherited from the |
# parent makefile. |
# |
|
all: links first_rule |
|
L_TARGET := block.a |
L_OBJS := ll_rw_blk.o genhd.o |
M_OBJS := |
MOD_LIST_NAME := BLOCK_MODULES |
|
LK = README.fd README.hd README.ide README.md \ |
ide_modes.h ide-cd.c ide-tape.c ide-tape.h \ |
linear.c loop.c md.c raid0.c raid1.c raid5.c rd.c |
|
# Architecture dependencies |
|
ifeq ($(MACHINE),arc) |
FLOPPY = fd1772.o fd1772dma.o |
FLOPPYMOD = fd1772_mod.o |
endif |
|
ifeq ($(MACHINE),a5k) |
FLOPPY = floppy.o |
FLOPPYMOD = floppy.o |
endif |
|
ifeq ($(MACHINE),rpc) |
FLOPPY = floppy.o |
FLOPPYMOD = floppy.o |
endif |
|
# Common dependencies |
|
ifdef FLOPPY |
ifeq ($(CONFIG_BLK_DEV_FD),y) |
L_OBJS += $(FLOPPY) |
else |
ifeq ($(CONFIG_BLK_DEV_FD),m) |
M_OBJS += $(FLOPPYMOD) |
endif |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_PART),y) |
L_OBJS += adfspart.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_RAM),y) |
L_OBJS += rd.o |
else |
ifeq ($(CONFIG_BLK_DEV_RAM),m) |
M_OBJS += rd.o |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_LOOP),y) |
L_OBJS += loop.o |
else |
ifeq ($(CONFIG_BLK_DEV_LOOP),m) |
M_OBJS += loop.o |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_HD),y) |
L_OBJS += hd.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDE),y) |
L_OBJS += ide.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDE_ICSIDE),y) |
L_OBJS += ide-ics.o |
else |
ifeq ($(CONFIG_BLK_DEV_IDE_ICSIDE),m) |
M_OBJS += ide-ics.o |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDE_RAPIDE),y) |
L_OBJS += ide-rapide.o |
else |
ifeq ($(CONFIG_BLK_DEV_IDE_RAPIDE),m) |
M_OBJS += ide-rapide.o |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDECD),y) |
L_OBJS += ide-cd.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDETAPE),y) |
L_OBJS += ide-tape.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_IDEFLOPPY),y) |
L_OBJS += ide-floppy.o |
endif |
|
ifeq ($(CONFIG_BLK_DEV_XD),y) |
L_OBJS += mfmhd.o mfm.o |
else |
ifeq ($(CONFIG_BLK_DEV_XD),m) |
M_OBJS += mfmhd_mod.o |
endif |
endif |
|
ifeq ($(CONFIG_BLK_DEV_MD),y) |
LX_OBJS += md.o |
|
ifeq ($(CONFIG_MD_LINEAR),y) |
L_OBJS += linear.o |
else |
ifeq ($(CONFIG_MD_LINEAR),m) |
M_OBJS += linear.o |
endif |
endif |
|
ifeq ($(CONFIG_MD_STRIPED),y) |
L_OBJS += raid0.o |
else |
ifeq ($(CONFIG_MD_STRIPED),m) |
M_OBJS += raid0.o |
endif |
endif |
|
ifeq ($(CONFIG_MD_MIRRORING),y) |
L_OBJS += raid1.o |
else |
ifeq ($(CONFIG_MD_MIRRORING),m) |
M_OBJS += raid1.o |
endif |
endif |
|
ifeq ($(CONFIG_MD_RAID5),y) |
L_OBJS += raid5.o |
else |
ifeq ($(CONFIG_MD_RAID5),m) |
M_OBJS += raid5.o |
endif |
endif |
|
endif |
|
include $(TOPDIR)/Rules.make |
|
fastdep: links |
|
fd1772_mod.o: $(FLOPPY) |
ld -r -o $@ $(FLOPPY) |
|
mfmhd_mod.o: mfmhd.o mfm.o |
ld -r -o $@ mfmhd.o mfm.o |
|
.PHONY: links |
links: |
-@for f in $(LK); do \ |
if [ ! -e $$f ]; then \ |
echo "ln -s ../../../../drivers/block/$$f ."; \ |
ln -s ../../../../drivers/block/$$f .; \ |
fi; \ |
done |
|
mrproper: |
-@for f in $(LK); do \ |
if [ -L $$f ]; then \ |
echo $(RM) $$f; \ |
$(RM) $$f; \ |
elif [ -f $$f ]; then \ |
echo not removing $$f; \ |
fi; \ |
done |
|
%.o: %.S |
$(CC) $(CFLAGS) -E $< | tr ';$$' '\n#' > ..tmp.s |
$(CC) $(CFLAGS) -c -o $@ ..tmp.s |
$(RM) ..tmp.s |