All of the PIC handling code is in the "PIC" subdirectory.
pic.c is a fairly complete emulation of both 8259 Priority Interrupt Controllers. It also includes provision for 16 lower level interrupts. This implementation supports the following i/o commands:
ICW1 bits 0 and 1 number of ICWs to expect ICW2 bits 3 - 7 base address of IRQs ICW3 no bits accepted but ignored ICW4 no bits accepted but ignored OCW1 all bits sets interrupt mask OCW2 bits 7,5-0 EOI commands only OCW3 bits 0,1,5,6 select read register, select special mask mode Reads of both pic ports are supported completely.
An important concept to understand in pic is the interrupt level. This is a value which represents the priority of the current interrupt. It is used to identify interrupts, and IRQs can be mapped to these levels(see pic.h ). The currently active interrupt level is maintained in pic_ilevel, which is globally available, A pic_ilevel of 32 means no interrupts are active; 0, the highest priority, represents the NMI. IRQs 0 through 15 are mapped, in priority order, to values of 1-15 (there is no IRQ2 in an AT). Values of 16 - 31 represent additional interrupt levels available for internal dosemu usage.
More detail is available in the file README.pic
A debug flag of +r or 1r will generate debug messages concerning reads and writes to pic ports. Many of these messages are not yet implemented. A debug flag of 2r will generate messages concerning activation, nesting, and completion of interrupts. A flag of -r or 0r will turn off debugging messages. Flags may be combined: to get both 1r and 2r, use a value of 3r.
These are the functions defined in base/dev/pic/pic.c.
This is the pic debug message printer. It writes out some basic information, followed by an informative message. The basic information consists of: interrupt nesting counter change flag (+, -, or blank) interrupt nesting count (pic_icount) interrupt level change flag (+, -, or blank) current interrupt level interrupt in-service register interrupt mask register interrupt request register message part one decimal data value message part two
If the message part 2 pointer is a null pointer, then only message part one (without the data value) is printed.
The change flags are there to facilitate grepping for changes in pic_ilevel and pic_icount
To avoid line wrap, the first seven values are printed without labels. Instead, a header line is printed every 15 messages.
write_pic_0() and write_pic1() implement dos writes to the pic ports. They are called by the code that emulates inb and outb instructions. Each function implements both ports for the pic: pic0 is on ports 0x20 and 0x21; pic1 is on ports 0xa0 and 0xa1. These functions take
read_pic0 and read_pic1 return the values for the interrupt mask register (port 1), or either the in service register or interrupt request register, as determined by the last OCW3 command (port 0). These functions take a single parameter, which is a port number (0 or 1). They are called by code that emulates the inb instruction.
The pic maintains an additional interrupt mask which is not visible to the DOS process. This is normally cleared (enabling an interrupt) when an interrupt is initialized, but dosemu code may choose to use this mask internally. One possible use is to implement the interrupt gate controlled by the OUT2 bit of the 16550A UART's Modem Control Register. This mask is cleared by pic_unmaski() and set by pic_maski()
pic_seti is used to initialize an interrupt for dosemu. It requires three parameters. The first parameter is the interrupt level, which man select the NMI, any of the IRQs, or any of the 16 extra levels (16 - 31). The second parameter is the dosemu function to be called when the interrupt is activated. This function should call do_irq() if the DOS interruptis really to be activated. If there is no special dosemu code to call, the second parameter can specify do_irq(), but see that description for some special considerations.
run_irqs, which is initiated via the macro pic_run, is the "brains" of the pic. It is called from the vm86() loop, checks for the highest priority interrupt requested, and executes it. This function is written in assembly language in order to take advantage of atomic (indivisible) instructions, so that it should be safe for a two process model, even in a multiple CPU machine. A c language version was started, but it became impossible, even with in-line assembly macros, because such macros can only return a single result. If I find a way to do it in c, I will, but don't hold your breath.
do_irq() calls the correct do_int(). It then executes a vm86 loop until an outb( end-of-interrupt) is found. For priority levels 0 and >15 (not real IRQs), vm86 executes once, then returns, since no outb20 will come. Returns: 0 = complete, 1 = interrupt not run because it directly calls our "bios" See run_timer_tick() in timer.c for an example To assure notification when the irq completes, we push flags, ip, and cs here and fake cs:ip to PIC_[SEG,OFF], where there is a hlt. This makes the irq generate a sigsegv, which calls pic_iret when it completes. pic_iret then pops the real cs:ip from the stack. This routine is RE-ENTRANT - it calls run_irqs, which may call an interrupt routine, which may call do_irq(). Be Careful! !!!!!!!!!!!!!!!!!! No single interrupt is ever re-entered.
Callers: base/misc/ioctl.c base/keyboard/serv_8042.c base/keyboard/keyboard-server.c base/serial/ser_irq.c dosext/sound/sound.c dosext/net/net/pktnew.c
pic_request triggers an interrupt. There is presently no way to "un-trigger" an interrupt. The interrupt will be initiated the next time pic_run is called, unless masked or superceded by a higher priority interrupt. pic_request takes one argument, an interrupt level, which specifies the interrupt to be triggered. If that interrupt is already active, the request will be queued until all active interrupts have been completed. The queue is only one request deep for each interrupt, so it is the responsibility of the interrupt code to retrigger itself if more interrupts are needed.
pic_iret is used to sense that all active interrupts are really complete, so that interrupts queued by pic_request can be triggered. Interrupts end when they issue an outb 0x20 to the pic, however it is not yet safe at that time to retrigger interrupts, since the stack has not been restored to its initial state by an iret. pic_iret is called whenever interrupts have been enabled by a popf, sti, or iret. It determines if an iret was the cause by comparing stack contents with cs and ip. If so, it decrements a count of interrupts on the stack (set by do_irq()). If the count is then zero, pic_iret moves all queued interrupts to the interrupt request register. It is possible for pic_iret to be fooled by dos code; for this reason active interrupts are checked, any queued interrupts that are also active will remain queued. Also, some programs fake an iret, so that it is possible for pic_iret to fail. See pic_watch for the watchdog timer that catches and fixes this event.
pic_watch is a watchdog timer for pending interrupts. If pic_iret somehow fails to activate a pending interrupt request for 2 consecutive timer ticks, pic_watch will activate them anyway. pic_watch is called ONLY by timer_tick, the interval timer signal handler, so the two functions will probably be merged.
This function returns a non-zero value if the designated interrupt has been requested and is not masked. In these circumstances, it is important for a hardware emulation to return a status which does *not* reflect the event(s) which caused the request, until the interrupt actually gets processed. This, in turn, hides the interrupt latency of pic from the dos software.
The single parameter ilevel is the interrupt level (see pic.h) of the interrupt of interest.
If the requested interrupt level is currently active, the returned status will depend upon whether the interrupt code has re-requested itself. If no re-request has occurred, a value of false (zero) will be returned.
pic_activate requests any interrupts whose scheduled time has arrived. anything after pic_dos_time and before pic_sys_time is activated. pic_dos_time is advanced to the earliest time scheduled.
pic_sched schedules an interrupt for activation after a designated time interval. The time measurement is in unis of 1193047/second, ( or if using MONOTON_MICRO_TIMING in units of PIT_TICK_RATE/second ) the same rate as the pit counters. This is convenient for timer emulation, but can also be used for pacing other functions, such as serial emulation, incoming keystrokes, or video updates. Some sample intervals:
rate/sec: 5 7.5 11 13.45 15 30 60 interval: 238608 159072 108459 88702 79536 39768 19884
rate/sec: 120 180 200 240 360 480 720 interval: 9942 6628 5965 4971 3314 2485 1657
rate/sec: 960 1440 1920 2880 3840 5760 11520 interval: 1243 829 621 414 311 207 103
pic_sched expects two parameters: an interrupt level and an interval. To assure proper repeat scheduling, pic_sched should be called from within the interrupt handler for the same interrupt. The maximum interval is 15 minutes (0x3fffffff).
Pic maintains two stacks of the current interrupt level. an internal one is maintained by run_irqs, and is valid whenever the emulator code for an interrupt is active. These functions maintain an external stack, which is valid from the time the dos interrupt code is called until the code has issued all necessary EOIs. Because pic will not necessarily get control immediately after an EOI, another EOI (for another interrupt) could occur. This external stack is kept strictly synchronized with the actions of the dos code to avoid any problems. pic_push and pic_pop maintain the external stack.
Next Previous Contents
|The DOSEMU team|