I'm trying to get Minix 1.3 running on an IBM PS/2 Model 80
with not too much luck. With Dr. Tanenbaum, Stephen Ligett, and
Gary Craig's help, I've been trying different methods of attack.
Right now, I have an AT&T 6300 with Minix 1.3 connected via serial
to the Model 80. I installed Gary Craig's floppy.c diffs and I'm
working from there. First, some semi-hard results (semi-hard because
I don't want to put my foot in my mouth! :-) ):
1) It appears that there will be a need for an MCA type
variable since the MODEL 80 crashes sooner with ps = 1
than it does with ps = 0
2) I can't see any noticeable difference between the old floppy.c
and the new floppy.c (as yet) - meaning I think that
there is another problem besides the ones the new floppy.c fixes.
3) There is no difference (as yet) with pc_at = 1, and pc_at = 0.
4) By putting printk's in the kernel/proc.c interrupt() function,
I've found that a MASSIVE amount of interrupts are
being received for the FLOPPY task. This is after the floppy
task starts the motor, issues a recalibrate command to
Floppy disk controller, and then does a
receive(HARDWARE). A printk placed right after the receive in
the Floppy task never gets executed. Leading me to
believe that the system appears to hang because all its
doing is servicing disk interrupts. Also, once the
the first disk interrupt is received, the message is sent to FS.
Thereafter, all the other messages fail on the call to
mini_send since the FS is marked as not waiting.
5) By placing this statement in interrupt():
/* TEST: mask any further disk interrupts
* Note: Gary Craig's code will undo this after receive(HARDWARE)
* in floppy task
*/
if (task == FLOPPY) port_out(INT_CTLMASK, 0x40);
I was able to continue execution until the system decided it
had an Invalid Root File System (even though it didn't). The
message "Type space to reboot" appeared. I did, and it received
the SPACE key interrupt and rebooted (in)correctly.
I think that the reason Minix came up with a bad file system
error was probably because the other receive(HARDWARE)s were
receiving the initial interrupt cause by the recalibrate command.
6) With masking the disk interrupt off, I can then hit F1/F2
and get a dump (meaning at least the KEYBOARD interrupt works).
However, I'm not sure the clock works. If my memory serves,
at first I forgot to do a test to see if the interrupt was
coming from the clock before I did a:
printk("Hello from interrupt! task = %d\n", task);
in the interrupt routine. I noticed this a while later -
certainly after I had tried the boot image on the Model 80
once or twice, and I don't remember being bombarded
with lots of: "Hello from interrupt! task = -3" meaning
a clock interrupt was received.
This brings me to my question. Recently, Scott Wiesner posted
this message:
>PS/2's with microchannel architectures use level triggered rather than
>edge triggered interrupts. The pic(s) much be programmed at initialization
>time for level triggered interrupts.
>
>The normal interrupt acknowledgment for a PC is not sufficient to lower
>the interrupt for a device. All the devices on a PS/2 have some operation
>that causes an interrupt to be cleared.. Most devices have their interrupt
>line lowered as a result of an operation that would normally be done at
>interrupt time anyway such as reading from the device. For the floppy
>drives, you must issue a sense interrupt status command to the controller.
>For the clock, you've got to set the high bit at io address 0x61. (Don't
>remember what this is. Some kind of misc. control register I think. I
>believe this is a read/modify/write operation.) I think these were the
>only two that required anything special.
Does anyone (maybe Scott??) know what the value of the "sense interrupt
status" command is? I would greatly appreciate it if some one could
post that information so I can attempt sending a "sense interrupt status"
command to the FDC in interrupt().
Dr. Tanenbaum and I have concluded that the 8259A controller should
be set appropriately for level triggered interrupts by BIOS. Since
the keyboard interrupt is being detected correctly, I think the problem
lies mainly with the Floppy Disk Controller not "shutting up" after
sending one interrupt. If anyone has some more suggestions about
what may be wrong, please post! I need all the help I can get.
Thanks.
Steve
I completely agree with Steve's analysis. Since the operating system is read in
by bootblok.s, which makes BIOS calls, the hardware is presumably set up
properly, including the 8259A. For a PC, the 8259A is in edge-triggered mode.
After an interrupt, the device is re-enabled on line 1888 of the book. You
get exactly one interrupt this way.
There must be an analogous statement to line 1888 for the microchannel.
My hope is that by adding a line
if (mca) port_out(somewhere, something)
around there, we can get exactly one interrupt, no more and no less.
If anyone who knows the PS/2, or for that matter, the 8259A, has any idea,
please post it.
Andy Tanenbaum (a...@cs.vu.nl)
From "IBM PS/2 Model 50 & 60 Technical Reference, page 4-143" :
Sense Interrupt Status Command Format
Command Phase
7 6 5 4 3 2 1 0
Byte 0 0 0 0 0 1 0 0 0
Result Phase
Byte 0 Status Register 0 (ST 0)
Byte 1 Present Cylinder Number (PCN)
On Microchannel systems, every device driver definitely MUST DEACTIVATE
the corresponding IRQ line before it enables interrupts again. Otherwise
the interrupt routine will be reentered forever ...
However, I don't know whether the "Sense Interrupt Status Command" will
do this for the floppy controller. But I also didn't find any other
reference to "interrupt" in the floppy controller section of the
above mentioned manual.
I hope this helps and good luck
Thomas
-------------------------------------------------------------------
Thomas Ruf {uunet,mcvax}!unido!rsp!tom
RSP Datensysteme GmbH West-Germany
Scheffelstrasse 26
D-7590 Achern
I just searched thru a NEC 765A data sheet and found the following
information :
"..if the 765 is in NON-DMA Mode, then the receipt of each data byte
(if the 765 is reading data from FDD) is indicated by an Interrupt signal ...
The generation of a Read Signal or Write Signal will reset the Interrupt
as well as ouput the data on the bus ...
.. if the 765 is in the DMA mode, no interrupts are generated during the
execution phase... After the execution phase has completed (Terminal Count
has occured) or EOT sector was read/written, then an Interrupt will occur.
This signifies the beginning of the Result Phase. When the first byte of
data is read during the Result Phase, the Interrupt is automatically
reset.."
"Sense Interrupt Status
An Interrupt Signal is generated by the FDC for one of the following
reasons :
1. Upon entering the Result Phase of :
a. read data command
b. read a track command
c. read ID command
d. read deleted data command
e. write data command
f. format a cylinder command
g. rwite deleted data command
h scan command
2. Ready line of FDD changes state
3. End of seek or recalibrate command
4. During execution phase in the NON-DMA mode
Interrupts caused by reasons 1 and 4 above occur during normal command
operations and are easily discernible by the processor. During an
execution phase in NON-DMA mode, DB5 in main status register is high. Upon
entering result phase this bit gets clear. Reason 1 and 4 does not require
sense interrupt status command. The interrupt is cleared by
reading/writing data to FDC. Interrupts caused by reasons 2 and 3 above
may be uniquely identified with the aid of the sense interrupt status
command. This command when issued resets the interrupt signal ...
..Neither the seek or recalibrate command have a result phase. Therefore,
it is mandatory to use the sense interrupt status command after these
commands to effectively terminate them...
..Issuing sense interrupt status command without interrupt pending is
treated as and invalid command"
Maybe this helps you to solve the PS/2 puzzle
Thomas
-----------------------------------------------------------
Thomas Ruf RSP Datensysteme GmbH
{uunet,mcvax}!unido!rsp!tom Scheffelstrasse 26
West-Germany 7590 Achern
"Service routines must not attempt to end the interrupt sequence (EOI)
until it has reset the interrupt line of the device being serviced."
I didn't actually look up the bit pattern, but I assume the "ENABLE"
in line 1888 of the book is the 8259A's EOI command.
This could be a problem in the Minix environment where the interrupt
would not be cleared until the floppy task is dispatched.
Also, interestingly, the PS/2 will not allow you to set the PIC up in
edge-sensitive mode (even accidentally):
"The interrupt control circuitry of the system board prevents
setting the controller to the edge-sensitive mode by blocking positive
edge-sensitive commands to the interrupt controllers."
(Yes, I did look up the bit pattern "ENABLE" is EOI.)
That must be it! The EOI is issued in interrupt(), which is the *first*
stage of interrupt handling.
>[...] The floppy disk
>controller must be given some sort of command to tell it to negate the
>interrupt. Furthermore, every other device (clock, hard disk, network,
>etc) that can generate interrupts has the same problem.
The floppy task must not be getting a chance to do this, because the
processor is interrupted again as soon as it finishes the low-level
interrupt handling. This does not happen for edge-triggered interrupts
since the device interrupt request remains asserted.
So, the solution may be simply to remove all the EOI's in proc.c and put
them in floppy.c. Similarly for *wini.c. The other drivers are probably
OK. All except clock.c are low level and already do their own EOI's, and
I think the clock interrupt turns off automatically.
The EOI's never belonged in proc.c anyway, since they are machine-dependent
(pc, at, ps, ...) while the rest is not. I put them elsewhere ages ago,
centralized in save(). Even worse for level triggered interrupts :-{.
--
Bruce Evans ev...@ditsyda.oz.au
D
First, a number of people seem to agree with the theoretical analysis that
by sending a sense command to the floppy disk controller, it will negate
its interrupt. Could one of the people with a PS/2 give it a try and see
if we are on the right track.
Second, I think is is cleaner to have the EOI sent in a single place for
all device classes, rather than having every routine have to worry about it
on its own. Some day some naive person will write a driver for a new device
and not realize that he has to deal with this. I am perfectly happy to move
it anywhere else if someone can suggest a suitable place. If it can't be
done, it can't be done, but it would be cleaner to do that sort of nasty
stuff just once, not all over the place.
John Nall made a comment a couple of days ago about world-wide debugging.
With Bruce's comments on the PS/2, we now have three continents working on
this problem. When I was a kid, I always thought it was a pity that I hadn't
been born 50 years earlier, so I could have been a pioneer in, say,
amateur radio in the beginning. Nevertheless, trying to solve an obscure
software problem with the help of a score of people on three continents
(anybody in Japan have any suggestions?) is probably in the same spirit.
I think most people on the net take for granted something that was quite
inconceivably only 20 years ago. For instance, I never recall having read
any science fiction stories called "The Net." Three cheers for USENET.
Andy Tanenbaum (a...@cs.vu.nl)
I don't object to a single EOI statement done in interrupt(). But what is
done now defeats the point of this: it tests for the special cases
ps && task == FLOPPY
pc_at && task == WINCHESTER
to decide the appropiate place to send the EOI to. Since only CLOCK, FLOPPY,
PRINTER and WINCHESTER currently depend on the EOI done here, only two cases
are being handled generally. All the PS cases for models >= 50 will be
different again.
Interrupt() must be kept short since it runs with interrupts disabled. So I
don't want to have any port_out()s in it (a dozen or so instructions in C
compared with 2 in assembler) or lots of cases to check. Attention to this
detail was one of the things required to boost rs232 performance from 300
baud to 19200 baud on a 5MHz 8088.
>on its own. Some day some naive person will write a driver for a new device
>and not realize that he has to deal with this. I am perfectly happy to move
It only took me 2 days to realize in my 1st PC hardware driver, for rs232
4 years ago :-).
D
Good point. Perhaps it is indeed best to move all the EOI stuff to the
tasks instead of the interrupt handlers, since the tasks an run with
interrupts enabled.
This still leaves us with the question of how to exit from the interrupt
handler and not get another interrupt instantly. Whatever solution is
chosen should be efficient. Doing a port_out to the 8259 to change the
mask is as expensive as doing a port_out to the 8259 for EOI.
Andy Tanenbaum (a...@cs.vu.nl)
There is an amazingly simple answer. Do nothing. The reason we get another
interrupt now is that the EOI done in interrupt() incorrectly tells the
8259 that the interrupt has been handled. Look at the PC BIOS keyboard
driver. The first thing it does is an STI which is analogous to the task
switch or return done at the end interrupt(). I think most of the BIOS
drivers do this.
I implemented this for my PC and 386-AT today.
Of course an EOI must still be done in the high level task. It must now be
a specific EOI:
port_out(int_ctl_port, SPECIFIC_EOI | ((1 << irq_number) & 0x07));
where
int_ctl_port is the interrupt control port (usually a constant for
each driver, e.g., INT_CTL)
SPECIFIC_EOI is 0x60
irq_number is the interrupt number (not vector), i.e., 0 to 15,
8 on each controller, e.g., 5 for XT_WINI, 6 for FLOPPY.
I put these after all the fdc_results() and win_results() calls (actually
in a new function) for the disk drivers. The sense command really belongs
in the results function too. I'm using the 1.4a floppy driver.It works
well. Perhaps the wini drivers will need a sense command for PS/2's too.
One thing I didn't nail down is the PS/2's interrupt controller. There
is a define for it as 0x3C in the assembler code only. Is this an 8259
with a mask port at 0x3D?
The other drivers needed essentially no changes. The centralized EOI
had to be deleted (lest the disks do it) so all the other drivers have
to do it explicitly. It doesn't matter if it is done before or after
device service, since all non-disk drivers run with interrupts disabled
and the 8259 does not latch changes. (I'm fairly sure but don't believe
me for level sensitive interrupts without trying it :-).)
The clock is a special case in this. I actually use a low level handler,
but since the clock has no device registers, the hardware part of the
service routine is null and an immediate EOI does the job.
There may be one minor problem :-). The 8259 enforces a priority scheme so
while the XT_WINI interrupt is being handled at level 5, the FLOPPY interrupt
at level 6 is locked out. These 2 priorities are well chosen and cause
no trouble. The keyboard and rs232 priorities at 1 and 3-4 are not well
chosen but cause no trouble because the devices are serviced with
interrupts disabled anyway. I'm worried about the AT_WINI interrupt at
level 14. The catch is that it is chained through level 2 on the first
controller.
So the 8259 is after all fairly well matched to Minix's interrupt handling.
The main thing that is missing is a mode with no priorities.
> chosen should be efficient. Doing a port_out to the 8259 to change the
> mask is as expensive as doing a port_out to the 8259 for EOI.
It is far worse than that. I implemented this mask fiddling approach too.
It requires a port_in to get the current state, then a lock/restore to
protect everything. 5 instructions and 8 bytes in assembler, but about
50 instructions in C. It still has to do an EOI as well. I did it in
assembler to set the mask, in between the save() and interrupt() calls,
and in C for the task level code to clear the mask. I think that the
lock/restore approach is wasteful because the interrupt state is always
known at the task level.
I'm now investigating the "Special Mask Mode" which offers some hope of
improving one or both methods. The documentation (data sheet) is so obscure
that I'm using experimental methods.
Whatever I end up with will be in the 386 protected mode version to be
posted in a week or two (remember it will also run on PC's and provide
other improvements). I already had a lot of #define's for the interrupt
controller (to reprogram it at the start and to properly distinguish
between interrupt numbers and interrupt vectors), and these could be
used for the PS/2 code to avoid unnecessary diffs.
Bruce Evans ev...@ditsyda.oz.au
Sorry, (1 << irq_number) should be just irq_number. The bit power is only
appropiate for adjusting the mask bits.
> interrupts disabled anyway. I'm worried about the AT_WINI interrupt at
> level 14. The catch is that it is chained through level 2 on the first
> controller.
The problem is sidestepped by doing an EOI for level 2 in the low level
interrupt handler, leaving only the EOI for level 14 for the high level
task. This works well with the current devices (tested reading the hard
disk, the floppy, and doing rs232 concurrently. The printer at level 7
might not get much service with this activity). If future device drivers
are written for levels 8 to 15 (all cascading through 2), this may be
inadequate. So I am leaning towards the slightly more complicated but
more flexible method of fiddling the mask bits.
Bruce Evans ev...@ditsyda.oz.au
>Of course an EOI must still be done in the high level task. It must now be
>a specific EOI:
Why does it have to be specific now?
Andy Tanenbaum (a...@cs.vu.nl)
It blocks those on the current line and lower priority lines (== higher
interrupt numbers within the same 8259). I think Steve Ackerman needed
the mask fiddling because he did not remove the EOI.
>>Of course an EOI must still be done in the high level task. It must now be
>>a specific EOI:
> Why does it have to be specific now?
The generic EOI applies to the last in-service line (or maybe the highest
priority line - they are usually the same). The difference is like the
one between function returns and coroutine returns.
I used the "simple" method for a while but now prefer the mask fiddling
approach. The simple method depends too much on the native privilege
levels and may cause trouble in future. Currently it may cause unnecessary
(but probably negligible) delays in servicing floppy and printer interrupts.
I changed the wini drivers (back) to use mask fiddling but the floppy
driver still uses the simple method. They are compatible once the centralized
EOI is removed.
The overheads for the mask fiddling are just:
(1) Every interrupt handler (i.e. the part running with processor interrupts
disabled) which completely services its device must do an EOI at its start
or finish. This takes just 2 instructions each in assembler. The only devices
not covered by this are the disks. Here's what it looks like for tty.
---
_tty_int: | Interrupt routine for terminal input.
call save | save the machine state
call _keyboard | process a keyboard interrupt
movb al,#ENABLE | reenable int controller
out INT_CTL
jmp _restart | continue execution
---
(2) Other handlers must set a mask bit in the interrupt handler as well. This
takes 3 instructions in assembler. The high level handler must clear the mask
bit every time it clears the device interrupt and expects another. Here's
the wini code (slightly edited, may not run :-)). This would be cleaner if
the XT and AT routines were separated and the interrupt vector initialized to
the appropiate one by a decision in main().
---
_wini_int: | Interrupt routine for the winchester disk.
call save | save the machine state
mov ax,_pc_at | check for 2nd int controller on AT
test ax,ax
jnz at_wini_int
xt_wini_int:
in INT_CTLMASK | mask further wini interrupts using 8259
orb al,#XT_WINI_BITPOWER | 0x20
out INT_CTLMASK
movb al,#ENABLE | reenable all unmasked 8259 lines
out INT_CTL
jmp common_wini_int
at_wini_int:
in INT2_MASK | mask further wini interrupts using 8259
orb al,#AT_WINI_BITPOWER | 0x40
out INT2_MASK
movb al,#ENABLE | reenable both 8259's (all unmasked lines)
out INT2_CTL
out INT_CTL | CONNECT_IRQ is on this
common_wini_int:
mov _int_mess+2,*DISKINT| build message for winchester task
mov ax,#_int_mess | prepare to call interrupt(WINI, &intmess)
push ax | push second parameter
mov ax,*WINI | prepare to push first parameter
push ax | push first parameter
call _interrupt | this is the call
jmp _restart | continue execution
---
This is the high level code for the AT. It is exactly what I'm using.
---
PRIVATE int win_results()
{
/* Extract results from the controller after an operation, then reenable the
* (slave) interrupt controller.
*/
int old_mask;
int old_state;
int r;
r = old_win_results();
old_state = lock();
port_in(INT2_MASK, &old_mask);
port_out(INT2_MASK, old_mask & ~(1 << (AT_WINI_IRQ & 0x07)));
restore(old_state);
return(r);
}
---
Bruce Evans ev...@ditsyda.oz.au