xref: /reactos/hal/halx86/mp/ioapic.c (revision 5100859e)
1 /*
2  * COPYRIGHT:             See COPYING in the top level directory
3  * PROJECT:               ReactOS kernel
4  * FILE:                  hal/halx86/mp/ioapic.c
5  * PURPOSE:
6  * PROGRAMMER:
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include <hal.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* GLOBALS *****************************************************************/
16 
17 MP_CONFIGURATION_INTSRC IRQMap[MAX_IRQ_SOURCE]; /* Map of all IRQs */
18 ULONG IRQCount = 0;                             /* Number of IRQs  */
19 ULONG IrqApicMap[MAX_IRQ_SOURCE];
20 
21 UCHAR BUSMap[MAX_BUS];				/* Map of all buses in the system */
22 UCHAR PCIBUSMap[MAX_BUS];			/* Map of all PCI buses in the system */
23 
24 IOAPIC_INFO IOAPICMap[MAX_IOAPIC];		/* Map of all I/O APICs in the system */
25 ULONG IOAPICCount;				/* Number of I/O APICs in the system */
26 
27 ULONG IRQVectorMap[MAX_IRQ_SOURCE];             /* IRQ to vector map */
28 
29 /* EISA interrupts are always polarity zero and can be edge or level
30  * trigger depending on the ELCR value.  If an interrupt is listed as
31  * EISA conforming in the MP table, that means its trigger type must
32  * be read in from the ELCR */
33 
34 #define default_EISA_trigger(idx)	(EISA_ELCR_Read(IRQMap[idx].SrcBusIrq))
35 #define default_EISA_polarity(idx)	(0)
36 
37 /* ISA interrupts are always polarity zero edge triggered,
38  * when listed as conforming in the MP table. */
39 
40 #define default_ISA_trigger(idx)	(0)
41 #define default_ISA_polarity(idx)	(0)
42 
43 /* PCI interrupts are always polarity one level triggered,
44  * when listed as conforming in the MP table. */
45 
46 #define default_PCI_trigger(idx)	(1)
47 #define default_PCI_polarity(idx)	(1)
48 
49 /* MCA interrupts are always polarity zero level triggered,
50  * when listed as conforming in the MP table. */
51 
52 #define default_MCA_trigger(idx)	(1)
53 #define default_MCA_polarity(idx)	(0)
54 
55 /***************************************************************************/
56 
57 extern VOID Disable8259AIrq(ULONG irq);
58 ULONG IOAPICRead(ULONG Apic, ULONG Offset);
59 VOID IOAPICWrite(ULONG Apic, ULONG Offset, ULONG Value);
60 
61 /* FUNCTIONS ***************************************************************/
62 
63 /*
64  * EISA Edge/Level control register, ELCR
65  */
66 static ULONG EISA_ELCR_Read(ULONG irq)
67 {
68    if (irq < 16)
69    {
70       PUCHAR port = (PUCHAR)(0x4d0 + (irq >> 3));
71       return (READ_PORT_UCHAR(port) >> (irq & 7)) & 1;
72    }
73    DPRINT("Broken MPtable reports ISA irq %d\n", irq);
74    return 0;
75 }
76 
77 static ULONG
78 IRQPolarity(ULONG idx)
79 {
80    ULONG bus = IRQMap[idx].SrcBusId;
81    ULONG polarity;
82 
83    /*
84     * Determine IRQ line polarity (high active or low active):
85     */
86    switch (IRQMap[idx].IrqFlag & 3)
87    {
88       case 0: /* conforms, ie. bus-type dependent polarity */
89          {
90             switch (BUSMap[bus])
91 	    {
92 	       case MP_BUS_ISA: /* ISA pin */
93 		  polarity = default_ISA_polarity(idx);
94 		  break;
95 
96 	       case MP_BUS_EISA: /* EISA pin */
97 		  polarity = default_EISA_polarity(idx);
98 		  break;
99 
100 	       case MP_BUS_PCI: /* PCI pin */
101 		  polarity = default_PCI_polarity(idx);
102 		  break;
103 
104 	       case MP_BUS_MCA: /* MCA pin */
105                   polarity = default_MCA_polarity(idx);
106 		  break;
107 
108 	       default:
109 		  DPRINT("Broken BIOS!!\n");
110 		  polarity = 1;
111 	    }
112 	 }
113 	 break;
114 
115       case 1: /* high active */
116 	 polarity = 0;
117 	 break;
118 
119       case 2: /* reserved */
120 	 DPRINT("Broken BIOS!!\n");
121 	 polarity = 1;
122 	 break;
123 
124       case 3: /* low active */
125          polarity = 1;
126 	 break;
127 
128       default: /* invalid */
129 	 DPRINT("Broken BIOS!!\n");
130 	 polarity = 1;
131    }
132    return polarity;
133 }
134 
135 static ULONG
136 IRQTrigger(ULONG idx)
137 {
138    ULONG bus = IRQMap[idx].SrcBusId;
139    ULONG trigger;
140 
141    /*
142     * Determine IRQ trigger mode (edge or level sensitive):
143     */
144    switch ((IRQMap[idx].IrqFlag >> 2) & 3)
145    {
146       case 0: /* conforms, ie. bus-type dependent */
147 	 {
148             switch (BUSMap[bus])
149 	    {
150 	       case MP_BUS_ISA: /* ISA pin */
151 	          trigger = default_ISA_trigger(idx);
152 		  break;
153 
154 	       case MP_BUS_EISA: /* EISA pin */
155 		  trigger = default_EISA_trigger(idx);
156 		  break;
157 
158 	       case MP_BUS_PCI: /* PCI pin */
159 		  trigger = default_PCI_trigger(idx);
160 		  break;
161 
162                case MP_BUS_MCA: /* MCA pin */
163 		  trigger = default_MCA_trigger(idx);
164 		  break;
165 
166                default:
167                   DPRINT("Broken BIOS!!\n");
168 		  trigger = 1;
169 	    }
170 	 }
171 	 break;
172 
173       case 1: /* edge */
174 	 trigger = 0;
175 	 break;
176 
177       case 2: /* reserved */
178 	 DPRINT("Broken BIOS!!\n");
179 	 trigger = 1;
180 	 break;
181 
182       case 3: /* level */
183  	 trigger = 1;
184 	 break;
185 
186       default: /* invalid */
187 	 DPRINT("Broken BIOS!!\n");
188 	 trigger = 0;
189    }
190    return trigger;
191 }
192 
193 static ULONG
194 Pin2Irq(ULONG idx,
195 	ULONG apic,
196 	ULONG pin)
197 {
198    ULONG irq, i;
199    ULONG bus = IRQMap[idx].SrcBusId;
200 
201    /*
202     * Debugging check, we are in big trouble if this message pops up!
203     */
204    if (IRQMap[idx].DstApicInt != pin)
205    {
206       DPRINT("broken BIOS or MPTABLE parser, ayiee!!\n");
207    }
208 
209    switch (BUSMap[bus])
210    {
211       case MP_BUS_ISA: /* ISA pin */
212       case MP_BUS_EISA:
213       case MP_BUS_MCA:
214 	irq = IRQMap[idx].SrcBusIrq;
215 	break;
216 
217       case MP_BUS_PCI: /* PCI pin */
218 	 /*
219 	  * PCI IRQs are mapped in order
220 	  */
221 	 i = irq = 0;
222 	 while (i < apic)
223 	 {
224 	    irq += IOAPICMap[i++].EntryCount;
225 	 }
226 	 irq += pin;
227 	 break;
228 
229       default:
230 	 DPRINT("Unknown bus type %d.\n",bus);
231 	 irq = 0;
232    }
233    return irq;
234 }
235 
236 static ULONG
237 AssignIrqVector(ULONG irq)
238 {
239 #if 0
240    static ULONG current_vector = FIRST_DEVICE_VECTOR, vector_offset = 0;
241 #endif
242    ULONG vector;
243    /* There may already have been assigned a vector for this IRQ */
244    vector = IRQVectorMap[irq];
245    if (vector > 0)
246    {
247       return vector;
248    }
249 #if 0
250    if (current_vector > FIRST_SYSTEM_VECTOR)
251    {
252       vector_offset++;
253       current_vector = FIRST_DEVICE_VECTOR + vector_offset;
254    }
255    else if (current_vector == FIRST_SYSTEM_VECTOR)
256    {
257       DPRINT1("Ran out of interrupt sources!");
258       ASSERT(FALSE);
259    }
260 
261    vector = current_vector;
262    IRQVectorMap[irq] = vector;
263    current_vector += 8;
264    return vector;
265 #else
266    vector = IRQ2VECTOR(irq);
267    IRQVectorMap[irq] = vector;
268    return vector;
269 #endif
270 }
271 
272 /*
273  * Find the IRQ entry number of a certain pin.
274  */
275 static ULONG
276 IOAPICGetIrqEntry(ULONG apic,
277 		  ULONG pin,
278 		  ULONG type)
279 {
280    ULONG i;
281 
282    for (i = 0; i < IRQCount; i++)
283    {
284       if (IRQMap[i].IrqType == type &&
285 	  (IRQMap[i].DstApicId == IOAPICMap[apic].ApicId || IRQMap[i].DstApicId == MP_APIC_ALL) &&
286 	  IRQMap[i].DstApicInt == pin)
287       {
288          return i;
289       }
290    }
291    return -1;
292 }
293 
294 
295 VOID
296 IOAPICSetupIrqs(VOID)
297 {
298    IOAPIC_ROUTE_ENTRY entry;
299    ULONG apic, pin, idx, irq, first_notcon = 1, vector, trigger;
300 
301    DPRINT("Init IO_APIC IRQs\n");
302 
303    /* Setup IRQ to vector translation map */
304    memset(&IRQVectorMap, 0, sizeof(IRQVectorMap));
305 
306    for (apic = 0; apic < IOAPICCount; apic++)
307    {
308       for (pin = 0; pin < IOAPICMap[apic].EntryCount; pin++)
309       {
310          /*
311 	  * add it to the IO-APIC irq-routing table
312 	  */
313 	 memset(&entry,0,sizeof(entry));
314 
315 	 entry.delivery_mode = (APIC_DM_LOWEST >> 8);
316 	 entry.dest_mode = 1;  /* logical delivery */
317 	 entry.mask = 1;       /* disable IRQ */
318          entry.dest.logical.logical_dest = 0;
319 
320 	 idx = IOAPICGetIrqEntry(apic,pin,INT_VECTORED);
321 	 if (idx == (ULONG)-1)
322 	 {
323 	    if (first_notcon)
324 	    {
325 	       DPRINT(" IO-APIC (apicid-pin) %d-%d\n", IOAPICMap[apic].ApicId, pin);
326 	       first_notcon = 0;
327 	    }
328 	    else
329 	    {
330 	       DPRINT(", %d-%d\n", IOAPICMap[apic].ApicId, pin);
331             }
332 	    continue;
333 	 }
334 
335          trigger = IRQTrigger(idx);
336 	 entry.polarity = IRQPolarity(idx);
337 
338 	 if (trigger)
339 	 {
340 	    entry.trigger = 1;
341 	 }
342 
343 	 irq = Pin2Irq(idx, apic, pin);
344 
345   	 vector = AssignIrqVector(irq);
346 	 entry.vector = vector;
347 
348 	 DPRINT("vector 0x%.08x assigned to irq 0x%.02x\n", vector, irq);
349 
350          if (irq == 0)
351          {
352             /* Mask timer IRQ */
353             entry.mask = 1;
354          }
355 
356          if ((apic == 0) && (irq < 16))
357 	 {
358 	    Disable8259AIrq(irq);
359 	 }
360          IOAPICWrite(apic, IOAPIC_REDTBL+2*pin+1, *(((PULONG)&entry)+1));
361 	 IOAPICWrite(apic, IOAPIC_REDTBL+2*pin, *(((PULONG)&entry)+0));
362 
363 	 IrqApicMap[irq] = apic;
364 
365 	 DPRINT("Vector %x, Pin %x, Irq %x\n", vector, pin, irq);
366       }
367    }
368 }
369 
370 static VOID
371 IOAPICClearPin(ULONG Apic, ULONG Pin)
372 {
373    IOAPIC_ROUTE_ENTRY Entry;
374 
375    DPRINT("IOAPICClearPin(Apic %d, Pin %d\n", Apic, Pin);
376    /*
377     * Disable it in the IO-APIC irq-routing table
378     */
379    memset(&Entry, 0, sizeof(Entry));
380    Entry.mask = 1;
381 
382    IOAPICWrite(Apic, IOAPIC_REDTBL + 2 * Pin, *(((PULONG)&Entry) + 0));
383    IOAPICWrite(Apic, IOAPIC_REDTBL + 1 + 2 * Pin, *(((PULONG)&Entry) + 1));
384 }
385 
386 static VOID
387 IOAPICClear(ULONG Apic)
388 {
389    ULONG Pin;
390 
391    for (Pin = 0; Pin < /*IOAPICMap[Apic].EntryCount*/24; Pin++)
392    {
393      IOAPICClearPin(Apic, Pin);
394    }
395 }
396 
397 static VOID
398 IOAPICClearAll(VOID)
399 {
400    ULONG Apic;
401 
402    for (Apic = 0; Apic < IOAPICCount; Apic++)
403    {
404       IOAPICClear(Apic);
405    }
406 }
407 
408 VOID
409 IOAPICEnable(VOID)
410 {
411    ULONG i, tmp;
412 
413    /* Setup IRQ to vector translation map */
414    memset(&IRQVectorMap, 0, sizeof(IRQVectorMap));
415 
416    /*
417     * The number of IO-APIC IRQ registers (== #pins):
418     */
419    for (i = 0; i < IOAPICCount; i++)
420    {
421       tmp = IOAPICRead(i, IOAPIC_VER);
422       IOAPICMap[i].EntryCount = GET_IOAPIC_MRE(tmp) + 1;
423    }
424 
425    /*
426     * Do not trust the IO-APIC being empty at bootup
427     */
428    IOAPICClearAll();
429 }
430 
431 VOID
432 IOAPICSetupIds(VOID)
433 {
434   ULONG tmp, apic, i;
435   UCHAR old_id;
436 
437   /*
438    * Set the IOAPIC ID to the value stored in the MPC table.
439    */
440   for (apic = 0; apic < IOAPICCount; apic++)
441   {
442 
443     /* Read the register 0 value */
444     tmp = IOAPICRead(apic, IOAPIC_ID);
445 
446     old_id = IOAPICMap[apic].ApicId;
447 
448     if (IOAPICMap[apic].ApicId >= 0xf)
449     {
450       DPRINT1("BIOS bug, IO-APIC#%d ID is %d in the MPC table!...\n",
451 	      apic, IOAPICMap[apic].ApicId);
452       DPRINT1("... fixing up to %d. (tell your hw vendor)\n",
453 	      GET_IOAPIC_ID(tmp));
454       IOAPICMap[apic].ApicId = GET_IOAPIC_ID(tmp);
455     }
456 
457     /*
458      * We need to adjust the IRQ routing table
459      * if the ID changed.
460      */
461     if (old_id != IOAPICMap[apic].ApicId)
462     {
463       for (i = 0; i < IRQCount; i++)
464       {
465 	if (IRQMap[i].DstApicId == old_id)
466 	{
467 	  IRQMap[i].DstApicId = IOAPICMap[apic].ApicId;
468 	}
469       }
470     }
471 
472     /*
473      * Read the right value from the MPC table and
474      * write it into the ID register.
475      */
476     DPRINT("Changing IO-APIC physical APIC ID to %d\n",
477 	   IOAPICMap[apic].ApicId);
478 
479     tmp &= ~IOAPIC_ID_MASK;
480     tmp |= SET_IOAPIC_ID(IOAPICMap[apic].ApicId);
481 
482     IOAPICWrite(apic, IOAPIC_ID, tmp);
483 
484     /*
485      * Sanity check
486      */
487     tmp = IOAPICRead(apic, 0);
488     if (GET_IOAPIC_ID(tmp) != IOAPICMap[apic].ApicId)
489     {
490       DPRINT1("Could not set I/O APIC ID!\n");
491       ASSERT(FALSE);
492     }
493   }
494 }
495 
496 /* This is performance critical and should probably be done in assembler */
497 VOID IOAPICMaskIrq(ULONG Irq)
498 {
499    IOAPIC_ROUTE_ENTRY Entry;
500    ULONG Apic = IrqApicMap[Irq];
501 
502    *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq);
503    *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1);
504    Entry.dest.logical.logical_dest &= ~(1 << KeGetCurrentProcessorNumber());
505    if (Entry.dest.logical.logical_dest == 0)
506    {
507       Entry.mask = 1;
508    }
509    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1));
510    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0));
511 }
512 
513 /* This is performance critical and should probably be done in assembler */
514 VOID IOAPICUnmaskIrq(ULONG Irq)
515 {
516    IOAPIC_ROUTE_ENTRY Entry;
517    ULONG Apic = IrqApicMap[Irq];
518 
519    *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq);
520    *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1);
521    Entry.dest.logical.logical_dest |= 1 << KeGetCurrentProcessorNumber();
522    Entry.mask = 0;
523    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1));
524    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0));
525 }
526 
527 VOID IOAPICDump(VOID)
528 {
529    ULONG apic, i;
530    ULONG reg0, reg1, reg2=0;
531 
532    DbgPrint("Number of MP IRQ sources: %d.\n", IRQCount);
533    for (i = 0; i < IOAPICCount; i++)
534    {
535       DbgPrint("Number of IO-APIC #%d registers: %d.\n",
536 	       IOAPICMap[i].ApicId,
537                IOAPICMap[i].EntryCount);
538    }
539 
540    /*
541     * We are a bit conservative about what we expect.  We have to
542     * know about every hardware change ASAP.
543     */
544    DbgPrint("Testing the IO APIC.......................\n");
545 
546    for (apic = 0; apic < IOAPICCount; apic++)
547    {
548       reg0 = IOAPICRead(apic, IOAPIC_ID);
549       reg1 = IOAPICRead(apic, IOAPIC_VER);
550       if (GET_IOAPIC_VERSION(reg1) >= 0x10)
551       {
552          reg2 = IOAPICRead(apic, IOAPIC_ARB);
553       }
554 
555       DbgPrint("\n");
556       DbgPrint("IO APIC #%d......\n", IOAPICMap[apic].ApicId);
557       DbgPrint(".... register #00: %08X\n", reg0);
558       DbgPrint(".......    : physical APIC id: %02X\n", GET_IOAPIC_ID(reg0));
559       if (reg0 & 0xF0FFFFFF)
560       {
561          DbgPrint("  WARNING: Unexpected IO-APIC\n");
562       }
563 
564       DbgPrint(".... register #01: %08X\n", reg1);
565       i = GET_IOAPIC_MRE(reg1);
566 
567       DbgPrint(".......     : max redirection entries: %04X\n", i);
568       if ((i != 0x0f) &&    /* older (Neptune) boards */
569 	  (i != 0x17) &&    /* typical ISA+PCI boards */
570 	  (i != 0x1b) &&    /* Compaq Proliant boards */
571 	  (i != 0x1f) &&    /* dual Xeon boards */
572           (i != 0x22) &&   /* bigger Xeon boards */
573 	  (i != 0x2E) &&
574 	  (i != 0x3F))
575       {
576          DbgPrint("  WARNING: Unexpected IO-APIC\n");
577       }
578 
579       i = GET_IOAPIC_VERSION(reg1);
580       DbgPrint(".......     : IO APIC version: %04X\n", i);
581       if ((i != 0x01) &&    /* 82489DX IO-APICs */
582 	  (i != 0x10) &&    /* oldest IO-APICs */
583 	  (i != 0x11) &&    /* Pentium/Pro IO-APICs */
584 	  (i != 0x13))	    /* Xeon IO-APICs */
585       {
586          DbgPrint("  WARNING: Unexpected IO-APIC\n");
587       }
588 
589       if (reg1 & 0xFF00FF00)
590       {
591          DbgPrint("  WARNING: Unexpected IO-APIC\n");
592       }
593 
594       if (GET_IOAPIC_VERSION(reg1) >= 0x10)
595       {
596 	 DbgPrint(".... register #02: %08X\n", reg2);
597 	 DbgPrint(".......     : arbitration: %02X\n",
598 	          GET_IOAPIC_ARB(reg2));
599   	 if (reg2 & 0xF0FFFFFF)
600 	 {
601             DbgPrint("  WARNING: Unexpected IO-APIC\n");
602          }
603       }
604 
605       DbgPrint(".... IRQ redirection table:\n");
606       DbgPrint(" NR Log Phy Mask Trig IRR Pol"
607 	       " Stat Dest Deli Vect:   \n");
608 
609       for (i = 0; i <= GET_IOAPIC_MRE(reg1); i++)
610       {
611          IOAPIC_ROUTE_ENTRY entry;
612 
613 	 *(((PULONG)&entry)+0) = IOAPICRead(apic, 0x10+i*2);
614 	 *(((PULONG)&entry)+1) = IOAPICRead(apic, 0x11+i*2);
615 
616 	 DbgPrint(" %02x %03X %02X  ",
617 		  i,
618 		  entry.dest.logical.logical_dest,
619 		  entry.dest.physical.physical_dest);
620 
621          DbgPrint("%C    %C    %1d  %C    %C    %C     %03X    %02X\n",
622 		  (entry.mask == 0) ? 'U' : 'M',            // Unmasked/masked
623 		  (entry.trigger == 0) ? 'E' : 'L',         // Edge/level sensitive
624 		  entry.irr,
625 		  (entry.polarity == 0) ? 'H' : 'L',        // Active high/active low
626 		  (entry.delivery_status == 0) ? 'I' : 'S', // Idle / send pending
627 		  (entry.dest_mode == 0) ? 'P' : 'L',       // Physical logical
628 		  entry.delivery_mode,
629 		  entry.vector);
630       }
631    }
632 
633    DbgPrint(".................................... done.\n");
634 }
635 
636 VOID
637 HaliReconfigurePciInterrupts(VOID)
638 {
639    ULONG i;
640 
641    for (i = 0; i < IRQCount; i++)
642    {
643       if (BUSMap[IRQMap[i].SrcBusId] == MP_BUS_PCI)
644       {
645          DPRINT("%02x: IrqType %02x, IrqFlag %02x, SrcBusId %02x, SrcBusIrq %02x"
646 	        ", DstApicId %02x, DstApicInt %02x\n",
647 	        i, IRQMap[i].IrqType, IRQMap[i].IrqFlag, IRQMap[i].SrcBusId,
648 	        IRQMap[i].SrcBusIrq, IRQMap[i].DstApicId, IRQMap[i].DstApicInt);
649 
650 	 HalSetBusDataByOffset(PCIConfiguration,
651 	                               IRQMap[i].SrcBusId,
652 				       (IRQMap[i].SrcBusIrq >> 2) & 0x1f,
653 				       &IRQMap[i].DstApicInt,
654 				       0x3c /*PCI_INTERRUPT_LINE*/,
655 				       1);
656 
657       }
658    }
659 }
660 
661 VOID Disable8259AIrq(ULONG irq)
662 {
663     UCHAR tmp;
664 
665     if (irq >= 8)
666     {
667        tmp = READ_PORT_UCHAR((PUCHAR)0xA1);
668        tmp |= (1 << (irq - 8));
669        WRITE_PORT_UCHAR((PUCHAR)0xA1, tmp);
670     }
671     else
672     {
673        tmp = READ_PORT_UCHAR((PUCHAR)0x21);
674        tmp |= (1 << irq);
675        WRITE_PORT_UCHAR((PUCHAR)0x21, tmp);
676     }
677 }
678 
679 ULONG IOAPICRead(ULONG Apic, ULONG Offset)
680 {
681   PULONG Base;
682 
683   Base = (PULONG)IOAPICMap[Apic].ApicAddress;
684   *Base = Offset;
685   return *((PULONG)((ULONG)Base + IOAPIC_IOWIN));
686 }
687 
688 VOID IOAPICWrite(ULONG Apic, ULONG Offset, ULONG Value)
689 {
690   PULONG Base;
691 
692   Base = (PULONG)IOAPICMap[Apic].ApicAddress;
693   *Base = Offset;
694   *((PULONG)((ULONG)Base + IOAPIC_IOWIN)) = Value;
695 }
696 
697 /* EOF */
698