home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1993 May / SIMTEL_0593.ISO / msdos / sysutl / timer.asm < prev    next >
Assembly Source File  |  1989-12-17  |  14KB  |  316 lines

  1. Date: Wednesday, 21 Aug 1985 16:37:04-PDT
  2. From: mitton%beorn.DEC@decwrl.ARPA
  3. Subject: Interrupt Driver Async I/O
  4.  
  5.  
  6. Everything You wanted to know about PC Async Comm, but were afraid to ask....
  7. ----------------------------------------------------------------------------
  8.  
  9. A few months back I sent in a note asking for help about how to do reliable
  10. interrupt driven communications using the IBM PC async comm port.  Now I
  11. know more than you may want to hear.  But I would like to share my knowledge
  12. with the net to help out the adventurous, and expose some of the problems that
  13. make it so difficult, so that these situations might not get designed into
  14. to future products.  Scattered though out this message are some of my own
  15. editorial comments in [square brackets].
  16.  
  17. - The UART used in the IBM PC family is the Western Digital 8250 and friends
  18. (recently the National Semi 8250B, and the 14650 in the AT).  This UART is
  19. single buffered.  There is only one receive character buffer to store a byte
  20. in while the next byte is being assembled in the shift register.  This 
  21. means that you have only ONE CHARACTER TIME (on average) to process that byte 
  22. before the next one wipes it out (an overrun).
  23.  
  24. I bet you thought your AT adapter card or AST Advantage has an 8250B on it?
  25. The IBM PC-AT Tech Reference manual doesn't say what the chip is (unlike the
  26. XT) or that it is different until you look at the schematic.
  27. Surprise!  Look again, its a NS 14650.  National describes the chip on the
  28. same sheet as the 8250B.  It seems to be functionally identical, but with
  29. a faster access time.  Unfortunately, it is still not fast enough for the
  30. '286.  Read IBM's ISV notes on avoiding doing back-to-back I/O references
  31. to the same chip so as to not violate chip access speeds.
  32.  
  33.     [ Modern UARTs should have at least a 4 character FIFO.
  34.     This makes for less interrupt latency problems and more reliability]
  35.     [Also better baud rate generators and dividers are available 
  36.     these days.  19.2k should be easily and accurately supported.]
  37.  
  38. - Next I did the following rough instruction budget.  An IBM PC-XT is an
  39. 8088 running at 4.77Mhz,
  40.              1 clock cycle =  210ns
  41.    1 memory reference = 4 clock cycles =  840ns
  42. 1 average instruction = 4 memory refs  = 3360ns 
  43.  
  44. 1 second / ( 3360ns / avg ins)    ~= 297,619 avg ins/sec
  45.  
  46.  9600 bps = 960 cps
  47.  
  48. (297,619 avg ins/sec) / 960 cps     =  310 avg ins / char
  49.  
  50. Now that's only for half duplex!  Halve that for full duplex load.
  51. And don't forgot to subtract for the time PC memory refresh uses.
  52. It becomes rather clear that the interrupt service code path must be as short
  53. as possible.  At least less than 150 instructions average.
  54.  
  55. Now you may want to quibble with my assumption of 4 clocks/average instruction.
  56. Truth be known, I made it up.  Everything I see indicates that on an 8088
  57. the average is more than that, and knowing that makes this estimate seem
  58. optimistic.  
  59.  
  60. On the AT, things get much better because the 16 bit bus causes more to happen
  61. in fewer cycles.
  62.  
  63. [ Now do understand why a FIFO is needed?]
  64. [ My code on a Rainbow (with a 3 char FIFO UART) code was written in C 
  65. (including interrupt service) and worked fine after first debugged it]
  66.  
  67. - Now if this isn't bad enough, lets toss in a interrupt handling problem:
  68. The IBM PC 8259 Interrupt Controller is programmed in the BIOS to be edge-
  69. sensitive.  The 8250 seems to supply edges properly, except when there may
  70. be multiple interrupts to service (ie: a full duplex receive complete and
  71. transmit complete at the same time).  Now this behavior is not documented
  72. on any spec sheet I've seen, (usually because they don't tell you what happens
  73. in this case)  but rumor has it that National changed the 8250B
  74. (and the 14650) so that it does not toggle the interrupt line when presenting 
  75. such stacked interrupts.  It is unknown whether the WD 8250 does the same.
  76. But the existence of said crock, has been experimentally verified many times.
  77. It is discussed thoroughly but sadly inconclusively in the file EDGES.INT.
  78.  
  79. [ could someone get the UART people to fess up in writing?]
  80. [ also, tell them not to do it again!]
  81.  
  82. - Okay, so what do you do?  Well, you have to write your interrupt service
  83. routines as a loop, servicing the UART until all pending interrupts have
  84. been handled and you won't lose an interrupt edge.  The COMPKG2 and the 
  85. MIT PC/IP service routines do a good job, but they have some flaws.
  86.  
  87.   1) They recheck the UART status by reading the LSR at the bottom of the loop.
  88. Since reading the LSR register will reset any pending receive error conditions,
  89. you could easily lose notification of an overrun, framing, or parity error.
  90. It is much better to re-read the ISR instead, because it serializes the 
  91. the highest priority current status.
  92.  
  93.   2) You should EOI the 8259 interrupt controller at the beginning or in
  94. the middle of the service loop.  Notice that the 8250 clears the interrupt 
  95. condition upon servicing (reading or writing) the appropriate register.
  96. If you EOI afterwards, then there is a window in which an interrupt may
  97. arise from the UART, but get dismissed when you clear the interrupt controller.
  98.  
  99.   Another bug to avoid, which I made once myself, is: do not break the loop 
  100. in the character processing.  The routine will hang with a unserviced interrupt
  101. pending on the UART and no more edges to trigger the 8259.  (unless you 
  102. implement the timer described below)
  103.  
  104. The proper loop as I coded it is as follows:
  105.  
  106.     send EOI to 8259
  107. loop:
  108.     read IIR
  109.     switch(IIR)
  110.     {
  111.         case NO_INTERRUPT:
  112.             iret;
  113.         case XMIT_READY:
  114.             send next char;
  115.             break;
  116.         case RECV_READY:
  117.             receive and buffer char;
  118.             break;
  119.         case LINE_STATUS;
  120.             record error condition;
  121.             break;
  122.         case MODEM_STATUS:
  123.             record state change;
  124.             break;
  125.     }
  126.     goto loop
  127.             
  128. Note:
  129.     - EOI done outside the loop may generate extra nop interrupts,
  130. if no stacked interrupt, but one arrives during a long service path.  
  131. An inside the loop EOI eliminates this but adds more code to the loop.
  132.  
  133.     
  134. - Great, so-far-so-good.  What could screw us up?  Well, I forgot to mention
  135. that what I was writing was a device driver and it runs in the background
  136. on the async ports.  Because we are not dealing with a big system with
  137. device allocation concepts, there are all sorts of DOS programs and 
  138. utilities that can stomp on your comm port.  The MODE command will
  139. do you in, especially if you forget to take the command out of your AUTOEXEC
  140. [took me a month to figure that one out].  BASICA grabs the comm port.   
  141. Even Symphony thinks that it can grab the comm port for it's terminal emulator,
  142. unless you do their not-well documented re-configuration procedure.
  143.  
  144. [software writers: please don't assume that the UART is available!]
  145.  
  146. - Another program that caused us to lose, was PROKEY.  It had hooked on to
  147. Interrupt 1C, the user clock tick handler, which we were using too, and spent 
  148. soooo much time on it, that our timing just totally screwed up.  This was 
  149. finally solved when we fixed another problem below.
  150.  
  151. [Be careful of doing to much on the clock tick.  It could screw up someone
  152. else.]
  153.     
  154. - One thing that I did to add some robustness to all this (and find some bugs)
  155. was to add a timer scheme.  Essentially, whenever I started a transmission
  156. or reception of a message, I initialized a word to a nonzero timeout value.
  157. A clock tick routine decremented the cell, if nonzero, and if it went to
  158. zero, reset it and faked an interrupt to the service routine.  This feature
  159. allowed the code to recover (as opposed to hanging forever) from lost 
  160. interrupts and errant MODE commands.  Hopefully, this should never happen.
  161. But it did at first, and a trace of the current state helped.
  162.  
  163. [real disasters give you first hand experience on how to defensively program.]
  164.  
  165. - Now the device service looks good, but I still am getting overruns at 7200
  166. and 9600.  So I thought some more about where the time goes in the CPU.
  167. Another way that you lose CPU instructions is to other interrupt service
  168. routines and code sections that disable interrupts.  Unfortunately,
  169. unlike a VAX, we don't have the multiple IPL levels to synchronize CPU
  170. threads without shutting out device service.  
  171.  
  172.    In my driver there are 3 levels of synchronization that use interrupt
  173. locking around cross-level queue operations.  Unfortunately, the interrupt
  174. locking queue function was the default even for queue operations in the 
  175. same level.  A better analysis of interrupt locking and necessary 
  176. synchronization lead to fewer and shorter interrupt disabled code sections
  177. and a much better performance level.  I have now even figured out a better
  178. semaphore interlock with the interrupt service routine that will eliminate
  179. even more interrupt disabled code.  
  180.  
  181.     [Interrupt latency on the 8086 architecture is precious!
  182.      You must try to minimize all interrupt disabled code paths.]
  183.     [Another reason to have a FIFO in the UART!]
  184.  
  185. - Finally, I had done almost all I could think of; I had tweaked the
  186. interrupt service loop, bummed the code paths to a minimum, and was still
  187. getting overruns on the XT.  I still had this feeling that they might be
  188. systematic, so I put a little code in the overrun routine that recorded
  189. the segment and offset of the code interrupted just before the overrun
  190. was serviced.  A higher level monitor printed it out.  I was perplexed
  191. because it was always the same: FE00:FEEA   I had sort of expected to
  192. find some code that had just done a STI, but instead I was staring at the
  193. stack cleanup code for the clock tick service in the ROM BIOS. (below)
  194.  
  195. FEA5    TIMER_INT PROC FAR
  196. FEA5    FB    STI        ;Interrupts back on
  197. .... push regs, increment DOS time in RAM, turn off floppies...
  198. FEE3    CD1C    INT 1CH        ;Transfer control to a user routine
  199. FEE5    B020    MOV AL,EOI
  200. FEE7    E620    OUT 020H,AL    ;End of interrupt to 8259
  201. FEE9    5A    POP DX
  202. FEEA    58    POP AX
  203. FEEB    1F    POP DS        ;Restore machine state
  204. FEEC    CF    IRET
  205.  
  206.    Walking back up the code, I don't see anything unusual.  Wait a minute!
  207. Why are we finally EOI'ing the 8259 after the INT 1C, when we STI'ed back
  208. at entry?  Oh, that's to make sure that we don't reenter huh?  Well what
  209. about the 8259 all the time that the INT 1C handlers were running?
  210.  
  211.    Yes folks, The Single Serializing Priority Interrupt Controller has been
  212. blocked the entire time.  This was preventing any other device interrupt
  213. service during the clock tick handling.  Initially, the 1C handler is
  214. an IRET, and it's okay.  But in practice, my driver and other things were 
  215. on there with a substantial total code path, almost guaranteeing lossage.
  216.  
  217.    My fix to this BIOS crock, had to replace the entire interrupt 08 routine,
  218. since it dispatches the interrupt 1C and has that EOI in the end.
  219. I wrote the following code in MWC assembler that handled problem of reentrancy 
  220. as well:
  221.  
  222. / Interrupt 08 Handler
  223. / This routine *REPLACES* the IBM PC BIOS interrupt handler for the
  224. / clock frequency interrupt.  It *MUST* be loaded into the system before
  225. / any other Int 08h user.
  226. / We must replace the BIOS routine because it has a nasty bug in that
  227. / it does not reset the 8259 Interrupt Controller until the Int 1Ch Handler(s)
  228. / are done.  This effectively locks interrupts for the entire period.
  229. / Chaining to the original handler would not work.
  230. /
  231. TIMER_LOW    = 0x006C
  232. TIMER_HIGH    = 0x006E
  233. TIMER_OFL    = 0x0070
  234. MOTOR_STATUS    = 0x003F
  235. MOTOR_COUNT    = 0x0040
  236.  
  237.     .shri
  238. ticks:    .word    0        / ticks flag
  239.  
  240.     .globl    cex_clock_
  241. cex_clock_:            / Timer interrupt entry point
  242.     push    ds
  243.     push    ax
  244.     push    dx
  245.     mov    ax, $0x40    / set BIOS DATA segment value
  246.     mov    ds, ax
  247.  
  248.     inc    TIMER_LOW    / Increment BIOS Time of day
  249.     jnz    T4
  250.     inc    TIMER_HIGH
  251. T4:
  252.     cmp    TIMER_HIGH, $0x18
  253.     jnz    T5
  254.     cmp    TIMER_LOW, $0xB0
  255.     jnz    T5
  256.  
  257.     sub    ax, ax
  258.     mov    TIMER_HIGH, ax
  259.     mov    TIMER_LOW, ax
  260.     movb    TIMER_OFL, $1
  261. T5:                / Test for diskette timeout
  262.     decb    MOTOR_COUNT
  263.     jnz    T6
  264.     andb    MOTOR_STATUS, $0xF0
  265.     movb    al, $0x0C
  266.     mov    dx, $0x03F2
  267.     outb    dx, al
  268. T6:                /New code starts here----
  269.     movb    al, $0x20        / reset 8259 Interrupt Controller
  270.     outb    0x20, al        / this allows other interrupts to queue
  271.     inc    cs:ticks        / increment tick semphore
  272.     cmp    cs:ticks, $1        /is it the first time?
  273.     jne    T8            /No, just leave
  274.  
  275. T7:    pushf                /fake interrupt call to local routine
  276.     cli                /
  277.     push    cs            /
  278.     call    cex_clicker        / giving us first priority
  279.  
  280.     int    0x1c            / call everyone else
  281.  
  282.     cli                / disable interrupts, if reenabled
  283.     dec    cs:ticks        / check if others came in
  284.     jnz    T7            / yes, do it again
  285.  
  286. T8:    pop    dx            / restore state
  287.     pop    ax
  288.     pop    ds
  289.     iret                / exit interrupt service
  290.  
  291.  
  292. This routine also solved the PROKEY problem mentioned above, since
  293. I hooked my driver directly in the clock tick chain before anyone else.
  294. The 4 instructions after T7 can be removed, and this can be used as a
  295. general purpose Int 08 handler replacement.   After installing this,
  296. I noticed that my polygon terminal emulator no longer gets overruns either.
  297.  
  298. There may be some 1C clock tick users that would be upset by the change,
  299. but I haven't found one yet.  (Even the PC Network tolerates it)
  300. Another way to work around this problem, if you don't need priority over
  301. the 1C handlers, is to hook Int 08, call it and wait for it to return.
  302. There are probably yet another way around this problem, but this seems the 
  303. cleanest to me.
  304.  
  305.     [DOS needs better clock tick services!]
  306.     [and don't hog them!]
  307.  
  308. Dave Mitton, DECnet-DOS Development.
  309. Enet: OLORIN::MITTON     
  310. Arpa: mitton%olorin.DEC@decwrl.ARPA
  311. Usenet: decwrl!dec-rhea!dec-olorin!mitton
  312. /opinions expressed here are mine, although DEC might feel likewise/
  313.  
  314. Posted:    Wed 21-Aug-1985 19:33 Eastern Standard Time, Tewksbury, Mass.
  315. To:    RHEA::DECWRL::"info-ibmpc@usc-isib.arpa"
  316.