Using a secondary AT-type controller under linux

Using a secondary AT-type controller under linux

Post by M. Sagg » Thu, 18 Feb 1993 06:10:39



If you want to use a drive connected to a secondary AT-type controller
(e.g. an MFM controller besides an IDE one) under linux, here are some
instructions. The 'instructions' are just a series of correspondences
I had with Pat, Mika, Linus, and Hubert, mostly over the KERNEL
channal, as well as some private ones. Diffs against 0.99.5 are also
included at the end of this document.

Basically, if your secondary controller can be set to use any IRQ
other than 14 (which is used by the primary controller), then you just
do that and skip the hardware section. Just apply the diffs (they
assume IRQ 10), edit hdreg.h with your controller port adrresses,
define your drive geometry in config.h as SHD_TYPE, and hope for the
best. Ah yes, also make the appropriate devices. I'm using the same
devices used by the XT driver (major number 13), which is not a good
idea, but I haven't had time to change that. If you're not using Pat's
XT driver, you have no problem. Otherwise edit blk.h and sec_hd.c to
use a different major number, and create the appropriate devices (same
minor numbers as hda...etc.).

If your controller cannot be set by jumpers to use an IRQ different
than 14, you have to hack it to use another IRQ. Make sure you choose
and IRQ not used by any other device, the diffs below use IRQ 10. If
you decide to hack your controller, you need a sharp exacto knife, a
solder iron and lead, and plenty of good luck.

Disclaimer: It worked for me, but there is a good chance you will
damage your controller, and possibly your motherboard, if you're not
careful enough. Neither I nor any of the people mentioned in this
document assume any resposibility for what you do. Please don't send
me or others hate mail if you fail. Also, there is a good chance your
controller won't work under DOS afterwards. Mine, by the way, is a
Seagate ST22M.

Oh another thing, everything I know is here, so I probably won't
return your mail if you send me any. Read the diffs carefully or make
your own if you encounter any problem. They are pretty straight
forward to make if you read hd.c in the kernel source.

------------------------------------------------------------
Muhammad:
I'm about to hack hd.c, so before I waste my time on a hack that
doesn't work, I'd like to consult the experts on wether I'm on the
right track.

Here is the situation: besides my IDE drive, I have an MFM drive
connected to an ST22 16-bit MFM controller. The ST22 has its own BIOS
and that drive bypasses CMOS. The two drives co-exist peacefully under
DOS, but linux of course doesn't know about the MFM drive since it
bypasses CMOS.

The MFM drive uses the drive I/O ports 170-177 and 376-377 since it is
a secondary controller (versus 1F0-1F7 and 3F6-3F7 that the primary
controller uses). After looking at the linux hd driver code, I have
the following plan:

The kernel first consult CMOS to learn about the drives present and
their geometries if HD_TYPE is not defined in config.h. This is easy
to overcome: I'll just define HD_TYPE to have the appropriate numbers.
A more general solution (in the future) would be to make the kernel
consult the MFM on-board BIOS (it has it's own BIOS, that's why the
two controllers can co-exist together).

Next step is to make linux talk to the other drive. The kernel assumes
that there is only one controller present, so it uses the same port
address for all AT drives (as defined in hdreg.h). This requires a bit
more surgery. Hdreg.h has to be duplicated with the other port
addresses and hd.c has be modified so that it uses different port
addresses for the different drives (this is grunt work and requires
knowledge of how hd.c does things).

Am I on the right track? Would the above do the trick? I'd really
appreciate any hints (or code) in this regard, and if somebody has
already done all or part of the above, please let me know.
------------------------------------------------------------
Mika:
I strongly suggest that you simply copy hd.c to sec_hd.c, give it a
new major number and modify it. Several reasons:

- hd.c needs (as you described) extensive modifications to support the
  second controller. This is rather bug prone, risks your data and
  probably makes Linus somewhat reluctant to add the code to the
  standard distribution.

- As hd.c is not really re-entrant and has a single request queue (one
  for each major number) you wouldn't be able to drive the two
  controllers simultaneously, without even more extensive changes.

- On the other hand, if you make the secondary controller a separate
  device, the modifications needed are fairly simple. The secondary
  driver can easily be configured out, if not needed. The two drivers
  would also work together with no problems.

I think that writing a driver for the secondary controller is a *good*
idea. Buying a cheap IDE disk is tempting (I have a couple of RLL:s
myself) and were I to buy one, I would set about writing a driver
immediately. I'm sure many others find themselves with some vintage
hard disks, which they don't want to part with, but which effectly
prevent them from upgrading to better hardware.
------------------------------------------------------------
Pat:
Just before you go and buy a new IDE drive, Mika, I think I'd better just
warn you about using two "generic AT-style" controllers in a single
machine. Although many AT MFM or RLL controllers can be set to use an
alternate set of I/O ports (ie: 0x170-0x17F), most of them cannot use an
alternate IRQ. Most of these boards are hard-wired to use IRQ14, which will
not allow them to work together. The first poster's (sorry, forgot your
name) board obviously _does_ support the alternate IRQ, but in my
experience this is not common...
------------------------------------------------------------
Muhammad:
Well, actually the ST22 uses IRQ14 as well. Don't ask me how they
co-exist under DOS, they do. The ST22 was recommended to me after I
posted to c.s.i.p.harware, apparently quite a few people are using it
mainly because of its ability to co-exist with IDE controllers, and
its manual states this point clearly (i.e. that it should work well
with IDE controllers).
------------------------------------------------------------
Mika:
Um, the IRQ is a bit of a problem. IRQ's can't be shared on the ISA
bus, because the IRQ lines are not tri-stated (or so I have been told,
frequently). I wouldn't be suprised, if the IRQ's weren't used at all
under DOS. The BIOS might do it by polling the status registers.

What you really need, is a controller that allows you to reconfigure
the IRQ, too. One alternative is to hack one of your controllers to
use a different IRQ (ie. cut IRQ line 14 and resolder it to one of the
other IRQ's). If DOS really does things by polling, this would pose no
problem there. If not, you wouldn't be able to use the hacked
controller under DOS.

I believe the problem is that two controllers simply cannot share the
same IRQ, no matter how the software is written. The limitation in
irqaction() is there for a reason. In this case, your best bet would
be to find a controller that allows you to reconfigure the IRQ line,
or, if you have the confidence, to fix the existing hardware. Of
course, you could write a polling hd driver, but that's hardly
desirable.
------------------------------------------------------------
Pat:
Perfectly correct, and I wouldn't be surprised if the Seagate's on-board
BIOS _had_ been specially written so as _not_ to use the IRQ... Sounds a
bit dumb, they really should have just made it jumper-configurable...

>What you really need, is a controller that allows you to reconfigure
>the IRQ, too. One alternative is to hack one of your controllers to
>use a different IRQ (ie. cut IRQ line 14 and resolder it to one of the
>other IRQ's). If DOS really does things by polling, this would pose no
>problem there. If not, you wouldn't be able to use the hacked
>controller under DOS.

I really think this is the best idea. It may sound a little brutal, but
it's really the best solution. One thing to watch out for, as I think I
said before, is that not all cards have all the necessary contacts on their
edge connectors. You can pretty safely attach your jumper to a board that
_does_ have the required contact, however... (if you don't mind two of your
boards being stuck together <grin>)
------------------------------------------------------------
Muhammad:
OK, I'm sold on the idea of hacking my controller to use a different
IRQ (this is a reversible process, right?). I'll change it to use IRQ
10 and see if I can use it with secondary driver. I'll post my
findings (either success or the announcement of dead controller
<grin>) to the channel.

Now how do I do that? I mean, this is a prited circuit, right? There
doesn't seem me anything I can *cut*. Would you please send me
instructions on how to do that? For example, what do you really mean
by the IRQ *line*? How does one identify that line? What do mean by
*cutting* the line? That sort of thing. A diagram would be most
helpful. Perhaps we can later use those instruction to write small
document on how to use a secondary controller in linux.
------------------------------------------------------------
Pat:

>OK, I'm sold on the idea of hacking my controller to use a different
>IRQ (this is a reversible process, right?). I'll change it to use IRQ

Ummm... not _easily_ reversible, but reversible, yes.

>Now how do I do that? I mean, this is a prited circuit, right? There

Yep.

>doesn't seem me anything I can *cut*. Would you please send me
>instructions on how to do that? For example, what do you really mean
>by the IRQ *line*? How does one identify that line? What do mean by

Ok. You need a book or something that shows the AT bus pinout (the ISA
bus)... I don't have one at school, but I can get back to you tonight (our
time) if you can't find one.

>*cutting* the line? That sort of thing. A diagram would be most
>helpful. Perhaps we can later use those instruction to write small
>document on how to use a secondary controller in linux.

Essentially, there are a whole bunch of "fingers" on your controller, that
actually make the electrical contact between the card and the motherboard.
One of these "fingers" transmits the IRQ10 signal. What you need to do is
get a trimming knife (you know, one of those _really_ sharp ones) and cut
the track on the circuit board leading to the IRQ14 contact (which you can
find by looking at the abovementioned book). Then you need to solder a
little jumper wire between the IRQ14 line (before the break) and an
available IRQ10 "finger" (on whatever board).

If you haven't done much soldering work before, I suggest you get someone
who has to "walk" you through it. The most important thing is that you make
a good cut to the old track, and a clean join to the new "finger".

If you're lucky, the controller will have a full complement of "fingers",
and you'll be able to put the jumper onto the same board. If not, you'll
have to "borrow" a "finger" off another board that _does_ have the IRQ10
contact.

If you trace the IRQ14 track back on the board, you should be able to
find the chip that it comes from. This will probably be the easiest place
to solder the new jumper on...

So, get a book that has the ISA bus pinout, and you should be able to
locate the desired tracks...
------------------------------------------------------------
Muhammad:
Thanks for the info. As a matter of fact I don't have access to a
document describing the ISA pinout, so I'd appreciate it if you could
supply me with that. Also, it seems my controller is one of those
cards that do not have a full set of fingers, so the task seems even
more daunting.

Here is my plan, please take a look at it and let me know if it seems
to you that it might work. First let's agree on some vocabulary.  Here
is my understanding of the different compononets of the card (excuse
my ignorance, I'm a geophysicist):

Finger: the little pin that actually gets inserted into the bus. There
are a number of those, and 16-bit cards have an extra set of them.

Track: the thin paths on the printed circuit electrically connecting
the diffrent chips of the card to each other.

Wire: just an ordinary wire.

Connection point: the little points on the printed circuit that have
some soldering in them and where two tracks meet. My understanding is
that one can connect (solder) a wire to such points, is that correct?

Ok, let's say the bottom of the card looks like this:
                       ______
  |                    |    |                                  |
  |_|_|_|________|_____|    |__________________________________|
   a         b

Let's say that point a above is the finger for IRQ 14, and point b is
the one for IRQ 10 (missing finger). Here is an enlarged diagram:

  |        c   |                        ______
  |   _____o___|                        |
  |  /                                  |
  |__|__|__|_______________|____________|
    a                b

A and b are as before, and c is a connection point. The first step is
to make a cut between points a and c so that IRQ 14 is not used, like
this:

  |    cut     |
  |   __  _o___|
  |  /
  |__|__|__|_______________|____________

Next a wire is soldered to connection point c and the other end of the
wire is inserted into an edge connector of an empty slot (one can make
that end of the wire a bit fat so that there is contact):

            _____________ wire __________________
  |        |                                    |
  |    cut |   |                                |
  |   __  _o___|                                |
  |  /                                          |
  |__|__|__|_______________|____________        |
     a           b                              |
                 _______________________________|
                 |
                 v

Is the above correct. If so, let me know (together with the pinout)
and I'll start hacking the card. I'll probably have to contact a
computer repair shop and convince them to let me use their tools.
------------------------------------------------------------
Pat:

>Finger: the little pin that actually gets inserted into the bus. There
>are a number of those, and 16-bit cards have an extra set of them.
>Track: the thin paths on the printed circuit electrically connecting
>the diffrent chips of the card to each other.
>Wire: just an ordinary wire.

Right so far.

>Connection point: the little points on the printed circuit that have
>some soldering in them and where two tracks meet. My understanding is
>that one can connect (solder) a wire to such points, is that correct?

This actually called a "plated through hole", but your definition will do.
You _can_ solder wires to these holes, but you must make sure you don't
interfere with it's original purpose, usually to connect a track on top of
the board with one on the other side (or in another layer).

>Next a wire is soldered to connection point c and the other end of the
>wire is inserted into an edge connector of an empty slot (one can make
>that end of the wire a bit fat so that there is contact):
>            _____________ wire __________________
>  |        |                                    |
>  |    cut |   |                                |
>  |   __  _o___|                                |
>  |  /                                          |
>  |__|__|__|_______________|____________        |
>     a           b                              |
>                 _______________________________|
>                 |
>                 v

Hmmm... The track cutting and connection of the wire are ok, but just
"stuffing" the wire into the right place is a little tricky. I think you'd
be better off connecting it to another card's (unused) IRQ10 finger... If
none of your other cards have a free one, you could look into buying a
so-called "prototyping" board at an electronics shop. They are basically
"empty" cards (ie: no components on them) and are equipped with all the
fingers on board... Of course, this means that your controller will be
"attached" to another card all the time, but that shouldn't really be a big
problem...

>Is the above correct. If so, let me know (together with the pinout)
>and I'll start hacking the card. I'll probably have to contact a
>computer repair shop and convince them to let me use their tools.

I'd say so. As I said, I personally think you'd do better to attach your
jumper wire to another card's IRQ10 finger, but just poking it into the
right place will probably work ok (until you remove the card, of course). I
will get back to you with the necessary data on the ISA bus sometime...

In fact, I might just run over to the library (I'm at Uni) and see if I can
find something there...

Ok, the IRQ14 finger is D7, the IRQ10 one is D3. What this means in english
is the they're the 7th and 3rd finger respectively on the 'D' part of the
ISA bus connector. The 'D' part is the "solder" side of the extended AT
part... So it's on the side of the card with all the solder on it, rather
than the components. Your motherboard may actually have the markings
printed on it, so they will help a bit. The extended AT bit has 18 pins, on
each side. The pin numbers increase as they go toward the front of the
computer...

Looking down on the motherboard, with the back of the computer toward the
top, this is what the AT part of the connector looks like:

        __
    D1 |  | C1
    D2 |  | C2
    D3 |  | C3          D3 is IRQ 10
       .  .
       .  .
       .  .
    D7 |  | C7          D7 is IRQ 14
       |  |
       |  |
       .  .
       .  .
       .  .
   D18 |  | C18
        --

Ok? So look on your motherboard and you might find similar info printed on
it. The IRQ14 pin on your card will be the seventh one along from the
"break" between the XT and AT halves, on the solder side of the board. If
that's not there, then something's _seriously_ wrong... <grin>
------------------------------------------------------------
Hubert:
        It sounds as if you aren't technician experienced.  I would not
recommend modifying a printed circuit card without some kit building
or other electronic tech experience.  Find a friend who recognizes
the instuctions and let him do it.  Background.  A printed circuit
card is laminate of alternating copper and insulator(usually fiber
glass).  The copper layers have the circuit paths (equivalent of
pieces of wire) printed on them(initially screen printing now usually)
a photo resist process therby the name "printed") as an etch resist.
The remaining copper is then chemically etched away from the insulator
layer leaving behind fine copper paths that carry the electic signals
between the components(IC's etc)on the board and to the edge connector.
Since these boards are designed to work in any slot on a PC buss the
edge connector contacts have the same function on each board.  You
will need a diagram of the buss conections to determine which one
is which interupt.  Your choice based on what oters are in use in
your system.  At this point determine the circuit path that carries
the signal to the current interupt connector and cut away (usually
with a knife unless it is internal to the laminate then use a drill)
a portion of the path so it no longer connects the IC's to the edge of
the board (edge connector).  Now carefully clean a portion of the
remaing path (expose the copper) so a small piece of copper wire
can be soldered to it(You must use a soldering iron liquid solder
doesn't work).  Then solder the other end of the wire to the edge
connector contact that connects to interupt you want to use.  For
anybody to give you more specific diagrams you would have to provide
us with a specific diagram of the controller card you wanted to use.
        I strongly recommend that you find someone who has made this
type of change before to assist you in this endeavor.
------------------------------------------------------------
Muhammad:
Thanks to Pat, Mika, Linus, and Hubert, I'm now able to use my MFM
controller as a secondary controller with the IDE one. In all, it
wasn't very difficult (thanks to Pat's hardware instructions). I'll
make a summary of what I did (basically the correspondence I had with
the people who helped me) and probably a patch for the dirver and post
to c.o.l.a later. A brief summary follows.

Hardware:
Since both controllers use IRQ- 14, and since the ST22 (the MFM)
doesn't allow one to set it to a different IRQ, I had to cut the
IRQ-14 line and connect instead to the IRQ-10 edge connector. My
controller also doesn't have a full set of edge connectors, so that
makes the work a bit more difficult, but its' quite doable. All you
need is a sharp cutting knife (to cut the IRQ-14 track), a solder
iron, and a jumper wire. In my case I just stuffed the free end of the
wire with the missing D3 (IRQ-10) edge connector -- kludgy, but works
for now. I soldered at the plated through hole (one of those little
connection points)

Software:
This is easy, I just did the follwoing:
- cloned hd.c to sec_hd.c renaming all the functions with the prefix
  sec.
- changed the definition of IRQ to 10 in sec_hd.c
- defined XD_TYPE in that file (one doesn't really need this, just use
  the alternate BIOS address).
- cloned hdreg.h to sec_hdreg.h with the alternate port address.
- made the appropriate call in ll_rw_blk.c.
- cloned the part about '#elif (MAJOR_NR == 3)' with the approriate
  (cloned) function names. I chose the major number to be 13, mainly
  because I already have the XT devices and was too lazy to make new
  ones. This should really be a different number since the XT driver
  uses major number 13.
- a few minor things (like declarations, where neccessary).

That's it. I have both drives mounted now and there doesn't seem to be
a problem so far (knock on wood). I have used it for only a few
miniutes so far though.
------------------------------------------------------------

Here are the diffs:
------------------------------------------------------------
*** test/linux/config.in        Tue Feb  9 20:07:51 1993
--- linux/config.in     Mon Feb 15 22:26:18 1993
***************
*** 4,9 ****
--- 4,11 ----
  CONFIG_MATH_EMULATION y/n n
  Normal harddisk support
  CONFIG_BLK_DEV_HD y/n y
+ Secondary AT controller support
+ CONFIG_BLK_DEV_SHD y/n n
  TCP/IP
  CONFIG_TCPIP y/n y
  Kernel profiling support
*** test/linux/kernel/blk_drv/Makefile  Fri Dec 11 20:55:14 1992
--- linux/kernel/blk_drv/Makefile       Mon Feb 15 22:26:19 1993
***************
*** 18,24 ****

  SUBDIRS       = scsi

! OBJS = hd.o ll_rw_blk.o floppy.o ramdisk.o genhd.o

  all: blk_drv.a scsisubdirs

--- 18,24 ----

  SUBDIRS       = scsi

! OBJS = hd.o ll_rw_blk.o floppy.o ramdisk.o genhd.o sec_hd.o

  all: blk_drv.a scsisubdirs

*** test/linux/kernel/blk_drv/blk.h     Sat Dec 19 19:05:00 1992
--- linux/kernel/blk_drv/blk.h  Mon Feb 15 22:26:19 1993
***************
*** 1,7 ****
  #ifndef _BLK_H
  #define _BLK_H

! #define NR_BLK_DEV    12
  /*
   * NR_REQUEST is the number of entries in the request-queue.
   * NOTE that writes may use only the low 2/3 of these: reads
--- 1,7 ----
  #ifndef _BLK_H
  #define _BLK_H

! #define NR_BLK_DEV    14
  /*
   * NR_REQUEST is the number of entries in the request-queue.
   * NOTE that writes may use only the low 2/3 of these: reads
***************
*** 70,75 ****
--- 70,77 ----
  extern int * blk_size[NR_BLK_DEV];

  extern unsigned long hd_init(unsigned long mem_start, unsigned long mem_end);
+ extern unsigned long sec_hd_init(unsigned long mem_start,
+                                                                unsigned long mem_end);
  extern int is_read_only(int dev);
  extern void set_device_ro(int dev,int flag);

***************
*** 143,148 ****
--- 145,161 ----
  #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 == 13)
+ /* harddisk: timeout is 6 seconds.. */
+ #define DEVICE_NAME "harddisk"
+ #define DEVICE_INTR sec_do_hd
+ #define DEVICE_TIMEOUT HD_TIMER
+ #define TIMEOUT_VALUE 600
+ #define DEVICE_REQUEST sec_do_hd_request
+ #define DEVICE_NR(device) (MINOR(device)>>6)
  #define DEVICE_ON(device)
  #define DEVICE_OFF(device)

*** test/linux/kernel/blk_drv/ll_rw_blk.c       Sat Dec 12 01:05:32 1992
--- linux/kernel/blk_drv/ll_rw_blk.c    Tue Feb 16 00:15:29 1993
***************
*** 386,391 ****
--- 386,394 ----
  #ifdef CONFIG_BLK_DEV_HD
        mem_start = hd_init(mem_start,mem_end);
  #endif
+ #ifdef CONFIG_BLK_DEV_SHD
+       mem_start = sec_hd_init(mem_start,mem_end);
+ #endif
        if (ramdisk_size)
                mem_start += rd_init(mem_start, ramdisk_size*1024);
        return mem_start;
*** test/linux/kernel/blk_drv/sec_hd.c  Tue Feb 16 15:19:37 1993
--- linux/kernel/blk_drv/sec_hd.c       Tue Feb 16 00:10:00 1993
***************
*** 0 ****
--- 1,763 ----
+ /*
+  *  linux/kernel/sec_hd.c
+  *
+  *  Copyright (C) 1991, 1992  Linus Torvalds
+  */
+
+ /*
+  * 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, lanke...@fwi.uva.nl, who found a bug
+  *  in the early extended-partition checks and added DM partitions
+  *
+  *  Modified by Muhammad M. Saggaf to accommodate a secondary AT-type
+  *  controller at an alternate port address -- 2/15/92.
+  */
+
+ #include <linux/config.h>
+ #ifdef CONFIG_BLK_DEV_SHD
+
+ #ifndef SHD_TYPE
+ # you have to define SHD_TYPE if you want secondary AT controller support
+ #endif
+
+ #define HD_IRQ 10
+
+ #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>
+
+ #define REALLY_SLOW_IO
+ #include <asm/system.h>
+ #include <asm/io.h>
+ #include <asm/segment.h>
+
+ #define MAJOR_NR 13
+ #include "blk.h"
+
+ extern void resetup_one_dev(struct gendisk *, unsigned int);
+ static int sec_revalidate_hddisk(int, int);
+
+ static inline unsigned char sec_CMOS_READ(unsigned char addr)
+ {
+       outb_p(0x80|addr,0x70);
+       return inb_p(0x71);
+ }
+
+ #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
+
+ static void sec_recal_intr(void);
+ static void sec_bad_rw_intr(void);
+
+ static char recalibrate[ MAX_HD ] = { 0, };
+ static int access_count[MAX_HD] = {0, };
+ static char busy[MAX_HD] = {0, };
+ static struct wait_queue * busy_wait = NULL;
+
+ static int reset = 0;
+ static int hd_error = 0;
+
+ #if (HD_DELAY > 0)
+ unsigned long last_req, sec_read_timer();
+ #endif
+
+ /*
+  *  This struct defines the HD's and their types.
+  */
+ struct hd_i_struct {
+       unsigned int head,sect,cyl,wpcom,lzone,ctl;
+       };
+ #ifdef SHD_TYPE
+ struct hd_i_struct sec_hd_info[] = { SHD_TYPE };
+ static int NR_HD = ((sizeof (sec_hd_info))/(sizeof (struct hd_i_struct)));
+ #else
+ struct hd_i_struct sec_hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
+ static int NR_HD = 0;
+ #endif
+
+ static struct hd_struct hd[MAX_HD<<6]={{0,0},};
+ static int hd_sizes[MAX_HD<<6] = {0, };
+
+ #define sec_port_read(port,buf,nr) \
+ __asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr):"cx","di")
+
+ #define sec_port_write(port,buf,nr) \
+ __asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr):"cx","si")
+
+ #if (HD_DELAY > 0)
+ unsigned long sec_read_timer(void)
+ {
+       unsigned long t;
+       int i;
+
+       cli();
+       outb_p(0xc2, 0x43);
+       t = jiffies * 11931 + (inb_p(0x40) & 0x80 ? 5966 : 11932);
+       i = inb_p(0x40);
+       i |= inb(0x40) << 8;
+       sti();
+       return(t - i / 2);
+ }
+ #endif
+
+ static int sec_win_result(void)
+ {
+       int i=inb_p(SHD_STATUS);
+
+       if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
+               == (READY_STAT | SEEK_STAT)) {
+               hd_error = 0;
+               return 0; /* ok */
+       }
+       printk("HD: sec_win_result: status = 0x%02x\n",i);
+       if (i&1) {
+               hd_error = inb(SHD_ERROR);
+               printk("HD: sec_win_result: error = 0x%02x\n",hd_error);
+       }      
+       return 1;
+ }
+
+ static int sec_controller_busy(void);
+ static int sec_status_ok(void);
+
+ static int sec_controller_ready(unsigned int drive, unsigned int head)
+ {
+       int retry = 100;
+
+       do {
+               if (sec_controller_busy() & BUSY_STAT)
+                       return 0;
+               outb_p(0xA0 | (drive<<4) | head, SHD_CURRENT);
+               if (sec_status_ok())
+                       return 1;
+       } while (--retry);
+       return 0;
+ }
+
+ static int sec_status_ok(void)
+ {
+       unsigned char status = inb_p(SHD_STATUS);
+
+       if (status & BUSY_STAT)
+               return 1;
+       if (status & WRERR_STAT)
+               return 0;
+       if (!(status & READY_STAT))
+               return 0;
+       if (!(status & SEEK_STAT))
+               return 0;
+       return 1;
+ }
+
+ static int sec_controller_busy(void)
+ {
+       int retries = 100000;
+       unsigned char status;
+
+       do {
+               status = inb_p(SHD_STATUS);
+       } while ((status & BUSY_STAT) && --retries);
+       return status;
+ }
+
+ static void sec_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 (drive>1 || head>15)
+               panic("Trying to write bad sector");
+ #if (HD_DELAY > 0)
+       while (sec_read_timer() - last_req < HD_DELAY)
+               /* nothing */;
+ #endif
+       if (reset)
+               return;
+       if (!sec_controller_ready(drive, head)) {
+               reset = 1;
+               return;
+       }
+       SET_INTR(intr_addr);
+       outb_p(sec_hd_info[drive].ctl,SHD_CMD);
+       port=SHD_DATA;
+       outb_p(sec_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 int sec_drive_busy(void)
+ {
+       unsigned int i;
+       unsigned char c;
+
+       for (i = 0; i < 500000 ; i++) {
+               c = inb_p(SHD_STATUS);
+               c &= (BUSY_STAT | READY_STAT | SEEK_STAT);
+               if (c == (READY_STAT | SEEK_STAT))
+                       return 0;
+       }
+       printk("HD controller times out, status = 0x%02x\n\r",c);
+       return 1;
+ }
+
+ static void sec_reset_controller(void)
+ {
+       int     i;
+
+       printk("HD-controller reset\r\n");
+       outb(4,SHD_CMD);
+       for(i = 0; i < 1000; i++) nop();
+       outb(sec_hd_info[0].ctl & 0x0f ,SHD_CMD);
+       if (sec_drive_busy())
+               printk("HD-controller still busy\n\r");
+       if ((hd_error = inb(SHD_ERROR)) != 1)
+               printk("HD-controller reset failed: %02x\n\r",hd_error);
+ }
+
+ static void sec_reset_intr(void)
+ {
+       static int i;
+
+ repeat:
+       if (reset) {
+               reset = 0;
+               i = -1;
+               sec_reset_controller();
+       } else if (sec_win_result()) {
+               sec_bad_rw_intr();
+               if (reset)
+                       goto repeat;
+       }
+       i++;
+       if (i < NR_HD) {
+               sec_hd_out(i,sec_hd_info[i].sect,sec_hd_info[i].sect,sec_hd_info[i].head-1,
+                       sec_hd_info[i].cyl,WIN_SPECIFY,&sec_reset_intr);
+               if (reset)
+                       goto repeat;
+       } else
+               sec_do_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.
+  */
+ void sec_unexpected_hd_interrupt(void)
+ {
+       sti();
+       printk("Unexpected HD interrupt\n\r");
+       SET_TIMER;
+ }
+
+ /*
+  * sec_bad_rw_intr() now tries to be a bit smarter and does things
+  * according to the error returned by the controller.
+  * -Mika Liljeberg (lilje...@cs.Helsinki.FI)
+  */
+ static void sec_bad_rw_intr(void)
+ {
+       int dev;
+
+       if (!CURRENT)
+               return;
+       dev = MINOR(CURRENT->dev) >> 6;
+       if (++CURRENT->errors >= MAX_ERRORS || (hd_error & BBD_ERR)) {
+               end_request(0);
+               recalibrate[dev] = 1;
+       } else if (CURRENT->errors % RESET_FREQ == 0)
+               reset = 1;
+       else if ((hd_error & TRK0_ERR) || CURRENT->errors % RECAL_FREQ == 0)
+               recalibrate[dev] = 1;
+       /* Otherwise just retry */
+ }
+
+ static inline int sec_wait_DRQ(void)
+ {
+       int retries = 100000;
+
+       while (--retries > 0)
+               if (inb_p(SHD_STATUS) & DRQ_STAT)
+                       return 0;
+       return -1;
+ }
+
+ #define STAT_MASK (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT)
+ #define STAT_OK (READY_STAT | SEEK_STAT)
+
+ static void sec_read_intr(void)
+ {
+       int i;
+       int retries = 100000;
+
+       do {
+               i = (unsigned) inb_p(SHD_STATUS);
+               if ((i & STAT_MASK) != STAT_OK)
+                       break;
+               if (i & DRQ_STAT)
+                       goto ok_to_read;
+       } while (--retries > 0);
+       sti();
+       printk("HD: sec_read_intr: status = 0x%02x\n",i);
+       if (i & ERR_STAT) {
+               hd_error = (unsigned) inb(SHD_ERROR);
+               printk("HD: sec_read_intr: error = 0x%02x\n",hd_error);
+       }
+       sec_bad_rw_intr();
+       cli();
+       sec_do_hd_request();
+       return;
+ ok_to_read:
+       sec_port_read(SHD_DATA,CURRENT->buffer,256);
+       CURRENT->errors = 0;
+       CURRENT->buffer += 512;
+       CURRENT->sector++;
+       i = --CURRENT->nr_sectors;
+       --CURRENT->current_nr_sectors;
+ #ifdef DEBUG
+       printk("hd%d : sector = %d, %d remaining to buffer = %08x\n",
+               MINOR(CURRENT->dev), CURRENT->sector, i, CURRENT->
+               buffer);
+ #endif
+       if (!i || (CURRENT->bh && !SUBSECTOR(i)))
+               end_request(1);
+       if (i > 0) {
+               SET_INTR(&sec_read_intr);
+               sti();
+               return;
+       }
+       (void) inb_p(SHD_STATUS);
+ #if (HD_DELAY > 0)
+       last_req = sec_read_timer();
+ #endif
+       sec_do_hd_request();
+       return;
+ }
+
+ static void sec_write_intr(void)
+ {
+       int i;
+       int retries = 100000;
+
+       do {
+               i = (unsigned) inb_p(SHD_STATUS);
+               if ((i & STAT_MASK) != STAT_OK)
+                       break;
+               if ((CURRENT->nr_sectors <= 1) || (i & DRQ_STAT))
+                       goto ok_to_write;
+       } while (--retries > 0);
+       sti();
+       printk("HD: sec_write_intr: status = 0x%02x\n",i);
+       if (i & ERR_STAT) {
+               hd_error = (unsigned) inb(SHD_ERROR);
+               printk("HD: sec_write_intr: error = 0x%02x\n",hd_error);
+       }
+       sec_bad_rw_intr();
+       cli();
+       sec_do_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(&sec_write_intr);
+               sec_port_write(SHD_DATA,CURRENT->buffer,256);
+               sti();
+       } else {
+ #if (HD_DELAY > 0)
+               last_req = sec_read_timer();
+ #endif
+               sec_do_hd_request();
+       }
+       return;
+ }
+
+ static void sec_recal_intr(void)
+ {
+       if (sec_win_result())
+               sec_bad_rw_intr();
+       sec_do_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 sec_hd_times_out(void)
+ {
+       DEVICE_INTR = NULL;
+       sti();
+       reset = 1;
+       if (!CURRENT)
+               return;
+       printk("HD timeout\n\r");
+       cli();
+       if (++CURRENT->errors >= MAX_ERRORS) {
+ #ifdef DEBUG
+               printk("hd : too many errors.\n");
+ #endif
+               end_request(0);
+       }
+
+       sec_do_hd_request();
+ }
+
+ /*
+  * The driver has been modified to enable interrupts a bit more: in order to
+  * do this we first (a) disable the timeout-interrupt and (b) clear the
+  * device-interrupt. This way the interrupts won't mess with out code (the
+  * worst that can happen is that an unexpected HD-interrupt comes in and
+  * sets the "reset" variable and starts the timer)
+  */
+ static void sec_do_hd_request(void)
+ {
+       unsigned int block,dev;
+       unsigned int sec,head,cyl,track;
+       unsigned int nsect;
+
+       if (CURRENT && CURRENT->dev < 0) return;
+
+       if (DEVICE_INTR)
+               return;
+ repeat:
+       timer_active &= ~(1<<HD_TIMER);
+       sti();
+       INIT_REQUEST;
+       dev = MINOR(CURRENT->dev);
+       block = CURRENT->sector;
+       nsect = CURRENT->nr_sectors;
+       if (dev >= (NR_HD<<6) || block >= hd[dev].nr_sects) {
+ #ifdef DEBUG
+               printk("hd%d : attempted read for sector %d past end of device at sector %d.\n",
+                       block, hd[dev].nr_sects);
+ #endif
+               end_request(0);
+               goto repeat;
+       }
+       block += hd[dev].start_sect;
+       dev >>= 6;
+       sec = block % sec_hd_info[dev].sect + 1;
+       track = block / sec_hd_info[dev].sect;
+       head = track % sec_hd_info[dev].head;
+       cyl = track / sec_hd_info[dev].head;
+ #ifdef DEBUG
+       printk("hd%d : cyl = %d, head = %d, sector = %d, buffer = %08x\n",
+               dev, cyl, head, sec, CURRENT->buffer);
+ #endif
+       cli();
+       if (reset) {
+               int i;
+
+               for (i=0; i < NR_HD; i++)
+                       recalibrate[i] = 1;
+               sec_reset_intr();
+               sti();
+               return;
+       }
+       if (recalibrate[dev]) {
+               recalibrate[dev] = 0;
+               sec_hd_out(dev,sec_hd_info[dev].sect,0,0,0,WIN_RESTORE,&sec_recal_intr);
+               if (reset)
+                       goto repeat;
+               sti();
+               return;
+       }      
+       if (CURRENT->cmd == WRITE) {
+               sec_hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&sec_write_intr);
+               if (reset)
+                       goto repeat;
+               if (sec_wait_DRQ()) {
+                       printk("HD: sec_do_hd_request: no DRQ\n");
+                       sec_bad_rw_intr();
+                       goto repeat;
+               }
+               sec_port_write(SHD_DATA,CURRENT->buffer,256);
+               sti();
+               return;
+       }
+       if (CURRENT->cmd == READ) {
+               sec_hd_out(dev,nsect,sec,head,cyl,WIN_READ,&sec_read_intr);
+               if (reset)
+                       goto repeat;
+               sti();
+               return;
+       }
+       panic("unknown hd-command");
+ }
+
+ static int sec_hd_ioctl(struct inode * inode, struct file * file,
+       unsigned int cmd, unsigned int arg)
+ {
+       struct hd_geometry *loc = (void *) arg;
+       int dev;
+
+       if (!inode)
+               return -EINVAL;
+       dev = MINOR(inode->i_rdev) >> 6;
+       if (dev >= NR_HD)
+               return -EINVAL;
+       switch (cmd) {
+               case HDIO_GETGEO:
+                       if (!loc)  return -EINVAL;
+                       verify_area(loc, sizeof(*loc));
+                       put_fs_byte(sec_hd_info[dev].head,
+                               (char *) &loc->heads);
+                       put_fs_byte(sec_hd_info[dev].sect,
+                               (char *) &loc->sectors);
+                       put_fs_word(sec_hd_info[dev].cyl,
+                               (short *) &loc->cylinders);
+                       put_fs_long(hd[MINOR(inode->i_rdev)].start_sect,
+                               (long *) &loc->start);
+                       return 0;
+               case BLKGETSIZE:   /* Return device size */
+                       if (!arg)  return -EINVAL;
+                       verify_area((long *) arg, sizeof(long));
+                       put_fs_long(hd[MINOR(inode->i_rdev)].nr_sects,
+                               (long *) arg);
+                       return 0;
+               case BLKRRPART: /* Re-read partition tables */
+                       return sec_revalidate_hddisk(inode->i_rdev, 1);
+               RO_IOCTLS(inode->i_rdev,arg);
+               default:
+                       return -EINVAL;
+       }
+ }
+
+ static int sec_hd_open(struct inode * inode, struct file * filp)
+ {
+       int target;
+       target =  DEVICE_NR(MINOR(inode->i_rdev));
+
+       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 sec_hd_release(struct inode * inode, struct file * file)
+ {
+         int target;
+       sync_dev(inode->i_rdev);
+
+       target =  DEVICE_NR(MINOR(inode->i_rdev));
+       access_count[target]--;
+
+ }
+
+ static void sec_hd_geninit();
+
+ 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 */
+       sec_hd_geninit, /* init function */
+       hd,             /* hd struct */
+       hd_sizes,       /* block sizes */
+       0,              /* number */
+       (void *) sec_hd_info,   /* internal */
+       NULL            /* next */
+ };
+      
+ static void sec_hd_interrupt(int unused)
+ {
+       void (*handler)(void) = DEVICE_INTR;
+
+       DEVICE_INTR = NULL;
+       timer_active &= ~(1<<HD_TIMER);
+       if (!handler)
+               handler = sec_unexpected_hd_interrupt;
+       handler();
+       sti();
+ }
+
+ /*
+  * 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 struct sigaction hd_sigaction = {
+       sec_hd_interrupt,
+       0,
+       SA_INTERRUPT,
+       NULL
+ };
+
+ static void sec_hd_geninit(void)
+ {
+       int i;
+ #ifndef SHD_TYPE
+       int drive;
+       extern struct drive_info drive_info;
+       void *BIOS = (void *) &drive_info;
+       int cmos_disks;
+          
+       for (drive=0 ; drive<2 ; drive++) {
+               sec_hd_info[drive].cyl = *(unsigned short *) BIOS;
+               sec_hd_info[drive].head = *(unsigned char *) (2+BIOS);
+               sec_hd_info[drive].wpcom = *(unsigned short *) (5+BIOS);
+               sec_hd_info[drive].ctl = *(unsigned char *) (8+BIOS);
+               sec_hd_info[drive].lzone = *(unsigned short *) (12+BIOS);
+               sec_hd_info[drive].sect = *(unsigned char *) (14+BIOS);
+               BIOS += 16;
+       }
+
+       /*
+               We querry CMOS about hard disks : it could be that
+               we have a SCSI/ESDI/etc controller that is BIOS
+               compatable with ST-506, and thus showing up in our
+               BIOS table, but not register compatable, and therefore
+               not present in CMOS.
+
+               Furthurmore, we will assume that our ST-506 drives
+               <if any> are the primary drives in the system, and
+               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.
+
+               Needless to say, a non-zero value means we have
+               an AT controller hard disk for that drive.
+
+              
+       */
+
+       if ((cmos_disks = sec_CMOS_READ(0x12)) & 0xf0)
+               if (cmos_disks & 0x0f)
+                       NR_HD = 2;
+               else
+                       NR_HD = 1;
+       else
+               NR_HD = 0;
+ #endif
+       if (NR_HD) {
+               if (irqaction(HD_IRQ,&hd_sigaction)) {
+                       printk("Sec: Unable to get IRQ%d for the harddisk driver\n",HD_IRQ);
+                       NR_HD = 0;
+               }
+       }
+       for (i = 0 ; i < NR_HD ; i++)
+               hd[i<<6].nr_sects = sec_hd_info[i].head*
+                               sec_hd_info[i].sect*sec_hd_info[i].cyl;
+
+       hd_gendisk.nr_real = NR_HD;
+ }
+
+ 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 */
+       sec_hd_ioctl,           /* ioctl */
+       NULL,                   /* mmap */
+       sec_hd_open,            /* open */
+       sec_hd_release          /* release */
+ };
+
+ unsigned long sec_hd_init(unsigned long mem_start, unsigned long mem_end)
+ {
+       blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+       blkdev_fops[MAJOR_NR] = &hd_fops;
+       read_ahead[MAJOR_NR] = 8;               /* 8 sector (4kB) read-ahead */
+       hd_gendisk.next = gendisk_head;
+       gendisk_head = &hd_gendisk;
+       timer_table[HD_TIMER].fn = sec_hd_times_out;
+       return mem_start;
+ }
+
+ #define DEVICE_BUSY busy[target]
+ #define USAGE access_count[target]
+ #define CAPACITY (sec_hd_info[target].head*sec_hd_info[target].sect*sec_hd_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 sec_revalidate_hddisk(int dev, int maxusage)
+ {
+       int target, major;
+       struct gendisk * gdev;
+       int max_p;
+       int start;
+       int i;
+
+       target =  DEVICE_NR(MINOR(dev));
+       gdev = &GENDISK_STRUCT;
+
+       cli();
+       if (DEVICE_BUSY || USAGE > maxusage) {
+               sti();
+               return -EBUSY;
+       };
+       DEVICE_BUSY = 1;
+       sti();
+
+       max_p = gdev->max_p;
+       start = target << gdev->minor_shift;
+       major = MAJOR_NR << 8;
+
+       for (i=max_p - 1; i >=0 ; i--) {
+               sync_dev(major | start | i);
+               invalidate_inodes(major | start | i);
+               invalidate_buffers(major | start | i);
+               gdev->part[start+i].start_sect = 0;
+               gdev->part[start+i].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;
+ }
+
+ #endif
*** test/linux/include/linux/config.h   Wed Jan 13 20:25:31 1993
--- linux/include/linux/config.h        Mon Feb 15 23:09:07 1993
***************
*** 72,77 ****
--- 72,79 ----
  */

  #undef HD_TYPE
+ #undef SHD_TYPE
+ #define SHD_TYPE { 6,17,807,128,807,0 }

  /*
        File type specific stuff goes into this.
*** test/linux/include/linux/hdreg.h    Wed Jan 13 20:25:31 1993
--- linux/include/linux/hdreg.h Mon Feb 15 22:26:20 1993
***************
*** 21,26 ****
--- 21,42 ----

  #define HD_CMD                0x3f6

+ #ifdef CONFIG_BLK_DEV_SHD
+ /* Secondary Hd controller regs. Edit according to your setup */
+ #define SHD_DATA      0x170   /* _CTL when writing */
+ #define SHD_ERROR     0x171   /* see err-bits */
+ #define SHD_NSECTOR   0x172   /* nr of sectors to read/write */
+ #define SHD_SECTOR    0x173   /* starting sector */
+ #define SHD_LCYL      0x174   /* starting cylinder */
+ #define SHD_HCYL      0x175   /* high byte of starting cyl */
+ #define SHD_CURRENT   0x176   /* 101dhhhh , d=drive, hhhh=head */
+ #define SHD_STATUS    0x177   /* see status-bits */
+ #define SHD_PRECOMP SHD_ERROR  /* same io address, read=error, write=precomp */
+ #define SHD_COMMAND SHD_STATUS /* same io address, read=status, write=cmd */
+
+ #define SHD_CMD               0x376
+ #endif /* CONFIG_BLK_DEV_SHD */
+
  /* Bits of HD_STATUS */
  #define ERR_STAT      0x01
  #define INDEX_STAT    0x02
------------------------------------------------------------

--
Send submissions for comp.os.linux.announce to: linux-annou...@tc.cornell.edu

 
 
 

1. Using a secondary AT-type controller underl linux

I hope I'm not being rude in re-iterating a suggestion I made in a posting
several months ago.  I modified a DTC7287 RLL controller to use IRQ11 vice
IRQ10 for the following [possibly obscene] reason:

I used stain glass copper foil to add an additional finger to the controller
card [yes, virginia, I am an electronic hardware engineer]
[no, the people at work didn't know I did this before this posting]

I choose IRQ11 which is D4 vice IRQ10 which is D3 for the very good reason
that D2 is signal I/O CS16. It should be noted that D5 is IRQ12 and hence
is not used on the card.

I only used the copper foiling "trick" because I knew the two contacts
on either side went to IRQ10 and IRQ12 --- hence if I shorted contacts
in the socket I would get an unexpected interrupt from IRQ10 or IRQ12.

This is what in the trade would be called a "Built-In Self Test".

Please don't try to add a finger to use IRQ10 as a short would be to
I/O CS 16.

I had trouble getting my own hd_sec code to run.  I ended up buying
a second IDE.  I plan on looking at the code in the c.o.l.a announcement
and giving my daughter an additional 66 MBs.

BTW: The maintenance technicians at work NEVER let me touch the hardware.
John

2. Please help :S Reorder eth numbers?

3. Secondary Gateway for Secondary Network Card using Secondary IP's

4. Create a DNS database from /etc/hosts

5. Performance Monitoring

6. 2nd AT-type Patch

7. Direct Backups

8. using secondary port for ide controller

9. Using a Large Drive on a secondary controller (solved)!

10. Using an IDE secondary controller card

11. Using a secondary (non-SCSI) HD controller?

12. aha1542b scsi controller as secondary controller