xref: /reactos/hal/halx86/mp/ioapic.c (revision 4514e91d)
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    DPRINT1("Broken MPtable reports ISA irq %lu\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                   DPRINT1("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          DPRINT1("Broken BIOS\n");
121 	 polarity = 1;
122 	 break;
123 
124       case 3: /* low active */
125          polarity = 1;
126 	 break;
127 
128       default: /* invalid */
129          DPRINT1("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                   DPRINT1("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          DPRINT1("Broken BIOS\n");
179 	 trigger = 1;
180 	 break;
181 
182       case 3: /* level */
183  	 trigger = 1;
184 	 break;
185 
186       default: /* invalid */
187          DPRINT1("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       DPRINT1("Broken BIOS or MPTABLE parser\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 	 DPRINT1("Unknown bus type %lu\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\n");
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) %u-%lu\n", IOAPICMap[apic].ApicId, pin);
326 	       first_notcon = 0;
327 	    }
328 	    else
329 	    {
330                DPRINT(", %u-%lu\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%.08lx assigned to irq 0x%.02lx\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 %lx, Pin %lx, Irq %lx\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 %lu, Pin %lu\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#%lu ID is %u in the MPC table\n", apic, IOAPICMap[apic].ApicId);
451       IOAPICMap[apic].ApicId = GET_IOAPIC_ID(tmp);
452       DPRINT1(" Fixed up to %u. (Tell your hardware vendor)\n", IOAPICMap[apic].ApicId);
453     }
454 
455     /*
456      * We need to adjust the IRQ routing table
457      * if the ID changed.
458      */
459     if (old_id != IOAPICMap[apic].ApicId)
460     {
461       for (i = 0; i < IRQCount; i++)
462       {
463 	if (IRQMap[i].DstApicId == old_id)
464 	{
465 	  IRQMap[i].DstApicId = IOAPICMap[apic].ApicId;
466 	}
467       }
468     }
469 
470     /*
471      * Read the right value from the MPC table and
472      * write it into the ID register.
473      */
474     DPRINT("Changing IO-APIC physical APIC ID to %u\n", IOAPICMap[apic].ApicId);
475 
476     tmp &= ~IOAPIC_ID_MASK;
477     tmp |= SET_IOAPIC_ID(IOAPICMap[apic].ApicId);
478 
479     IOAPICWrite(apic, IOAPIC_ID, tmp);
480 
481     /*
482      * Sanity check
483      */
484     tmp = IOAPICRead(apic, 0);
485     if (GET_IOAPIC_ID(tmp) != IOAPICMap[apic].ApicId)
486     {
487       DPRINT1("Could not set I/O APIC ID!\n");
488       ASSERT(FALSE);
489     }
490   }
491 }
492 
493 /* This is performance critical and should probably be done in assembler */
494 VOID IOAPICMaskIrq(ULONG Irq)
495 {
496    IOAPIC_ROUTE_ENTRY Entry;
497    ULONG Apic = IrqApicMap[Irq];
498 
499    *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq);
500    *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1);
501    Entry.dest.logical.logical_dest &= ~(1 << KeGetCurrentProcessorNumber());
502    if (Entry.dest.logical.logical_dest == 0)
503    {
504       Entry.mask = 1;
505    }
506    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1));
507    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0));
508 }
509 
510 /* This is performance critical and should probably be done in assembler */
511 VOID IOAPICUnmaskIrq(ULONG Irq)
512 {
513    IOAPIC_ROUTE_ENTRY Entry;
514    ULONG Apic = IrqApicMap[Irq];
515 
516    *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq);
517    *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1);
518    Entry.dest.logical.logical_dest |= 1 << KeGetCurrentProcessorNumber();
519    Entry.mask = 0;
520    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1));
521    IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0));
522 }
523 
524 VOID IOAPICDump(VOID)
525 {
526    ULONG apic, i;
527    ULONG reg0, reg1, reg2=0;
528 
529    DbgPrint("Number of MP IRQ sources: %d.\n", IRQCount);
530    for (i = 0; i < IOAPICCount; i++)
531    {
532       DbgPrint("Number of IO-APIC #%d registers: %d.\n",
533 	       IOAPICMap[i].ApicId,
534                IOAPICMap[i].EntryCount);
535    }
536 
537    /*
538     * We are a bit conservative about what we expect.  We have to
539     * know about every hardware change ASAP.
540     */
541    DbgPrint("Testing the IO APIC.......................\n");
542 
543    for (apic = 0; apic < IOAPICCount; apic++)
544    {
545       reg0 = IOAPICRead(apic, IOAPIC_ID);
546       reg1 = IOAPICRead(apic, IOAPIC_VER);
547       if (GET_IOAPIC_VERSION(reg1) >= 0x10)
548       {
549          reg2 = IOAPICRead(apic, IOAPIC_ARB);
550       }
551 
552       DbgPrint("\n");
553       DbgPrint("IO APIC #%d......\n", IOAPICMap[apic].ApicId);
554       DbgPrint(".... register #00: %08X\n", reg0);
555       DbgPrint(".......    : physical APIC id: %02X\n", GET_IOAPIC_ID(reg0));
556       if (reg0 & 0xF0FFFFFF)
557       {
558          DbgPrint("  WARNING: Unexpected IO-APIC\n");
559       }
560 
561       DbgPrint(".... register #01: %08X\n", reg1);
562       i = GET_IOAPIC_MRE(reg1);
563 
564       DbgPrint(".......     : max redirection entries: %04X\n", i);
565       if ((i != 0x0f) &&    /* older (Neptune) boards */
566 	  (i != 0x17) &&    /* typical ISA+PCI boards */
567 	  (i != 0x1b) &&    /* Compaq Proliant boards */
568 	  (i != 0x1f) &&    /* dual Xeon boards */
569           (i != 0x22) &&   /* bigger Xeon boards */
570 	  (i != 0x2E) &&
571 	  (i != 0x3F))
572       {
573          DbgPrint("  WARNING: Unexpected IO-APIC\n");
574       }
575 
576       i = GET_IOAPIC_VERSION(reg1);
577       DbgPrint(".......     : IO APIC version: %04X\n", i);
578       if ((i != 0x01) &&    /* 82489DX IO-APICs */
579 	  (i != 0x10) &&    /* oldest IO-APICs */
580 	  (i != 0x11) &&    /* Pentium/Pro IO-APICs */
581 	  (i != 0x13))	    /* Xeon IO-APICs */
582       {
583          DbgPrint("  WARNING: Unexpected IO-APIC\n");
584       }
585 
586       if (reg1 & 0xFF00FF00)
587       {
588          DbgPrint("  WARNING: Unexpected IO-APIC\n");
589       }
590 
591       if (GET_IOAPIC_VERSION(reg1) >= 0x10)
592       {
593 	 DbgPrint(".... register #02: %08X\n", reg2);
594 	 DbgPrint(".......     : arbitration: %02X\n",
595 	          GET_IOAPIC_ARB(reg2));
596   	 if (reg2 & 0xF0FFFFFF)
597 	 {
598             DbgPrint("  WARNING: Unexpected IO-APIC\n");
599          }
600       }
601 
602       DbgPrint(".... IRQ redirection table:\n");
603       DbgPrint(" NR Log Phy Mask Trig IRR Pol"
604 	       " Stat Dest Deli Vect:   \n");
605 
606       for (i = 0; i <= GET_IOAPIC_MRE(reg1); i++)
607       {
608          IOAPIC_ROUTE_ENTRY entry;
609 
610 	 *(((PULONG)&entry)+0) = IOAPICRead(apic, 0x10+i*2);
611 	 *(((PULONG)&entry)+1) = IOAPICRead(apic, 0x11+i*2);
612 
613 	 DbgPrint(" %02x %03X %02X  ",
614 		  i,
615 		  entry.dest.logical.logical_dest,
616 		  entry.dest.physical.physical_dest);
617 
618          DbgPrint("%C    %C    %1d  %C    %C    %C     %03X    %02X\n",
619 		  (entry.mask == 0) ? 'U' : 'M',            // Unmasked/masked
620 		  (entry.trigger == 0) ? 'E' : 'L',         // Edge/level sensitive
621 		  entry.irr,
622 		  (entry.polarity == 0) ? 'H' : 'L',        // Active high/active low
623 		  (entry.delivery_status == 0) ? 'I' : 'S', // Idle / send pending
624 		  (entry.dest_mode == 0) ? 'P' : 'L',       // Physical logical
625 		  entry.delivery_mode,
626 		  entry.vector);
627       }
628    }
629 
630    DbgPrint(".................................... done.\n");
631 }
632 
633 VOID
634 HaliReconfigurePciInterrupts(VOID)
635 {
636    ULONG i;
637 
638    for (i = 0; i < IRQCount; i++)
639    {
640       if (BUSMap[IRQMap[i].SrcBusId] == MP_BUS_PCI)
641       {
642          DPRINT("%02lx: IrqType %02x, IrqFlag %04x, SrcBusId %02x"
643                 ", SrcBusIrq %02x, DstApicId %02x, DstApicInt %02x\n",
644                 i, IRQMap[i].IrqType, IRQMap[i].IrqFlag, IRQMap[i].SrcBusId,
645                 IRQMap[i].SrcBusIrq, IRQMap[i].DstApicId, IRQMap[i].DstApicInt);
646 
647 	 HalSetBusDataByOffset(PCIConfiguration,
648 	                               IRQMap[i].SrcBusId,
649 				       (IRQMap[i].SrcBusIrq >> 2) & 0x1f,
650 				       &IRQMap[i].DstApicInt,
651 				       0x3c /*PCI_INTERRUPT_LINE*/,
652 				       1);
653 
654       }
655    }
656 }
657 
658 VOID Disable8259AIrq(ULONG irq)
659 {
660     UCHAR tmp;
661 
662     if (irq >= 8)
663     {
664        tmp = READ_PORT_UCHAR((PUCHAR)0xA1);
665        tmp |= (1 << (irq - 8));
666        WRITE_PORT_UCHAR((PUCHAR)0xA1, tmp);
667     }
668     else
669     {
670        tmp = READ_PORT_UCHAR((PUCHAR)0x21);
671        tmp |= (1 << irq);
672        WRITE_PORT_UCHAR((PUCHAR)0x21, tmp);
673     }
674 }
675 
676 ULONG IOAPICRead(ULONG Apic, ULONG Offset)
677 {
678   PULONG Base;
679 
680   Base = (PULONG)IOAPICMap[Apic].ApicAddress;
681   *Base = Offset;
682   return *((PULONG)((ULONG)Base + IOAPIC_IOWIN));
683 }
684 
685 VOID IOAPICWrite(ULONG Apic, ULONG Offset, ULONG Value)
686 {
687   PULONG Base;
688 
689   Base = (PULONG)IOAPICMap[Apic].ApicAddress;
690   *Base = Offset;
691   *((PULONG)((ULONG)Base + IOAPIC_IOWIN)) = Value;
692 }
693 
694 /* EOF */
695