IOAPIC: Difference between revisions

From OSDev Wiki
Jump to navigation Jump to search
(24 intermediate revisions by 17 users not shown)
Line 1: Line 1:
(削除) == Basic Info == (削除ここまで)
(追記) {{DISPLAYTITLE:I/O APIC}} (追記ここまで)
The Intel I/O Advanced Programmable Interrupt Controller is used to distribute external interrupts in a more advanced manner than that of the standard [[8259 PIC]]. With the I/O APIC, interrupts can be distributed to physical or logical (clusters) (削除) of (削除ここまで)processors and can be prioritized. Each I/O APIC (削除) can handle up to (削除ここまで)24 (削除) IRQs (削除ここまで).
The Intel I/O Advanced Programmable Interrupt Controller is used to distribute external interrupts in a more advanced manner than that of the standard [[8259 PIC]]. With the I/O APIC, interrupts can be distributed to physical or logical (clusters (追記) of (追記ここまで)) processors and can be prioritized. Each I/O APIC (追記) typically handles (追記ここまで)24 (追記) external interrupts (追記ここまで).


== Detecting I/O APIC ==
== Detecting I/O APIC ==
In order to detect the existence of an I/O APIC (or multiple ones), the Intel Multi-Processor or ACPI tables must be parsed. In the MP tables, configuration tables with the entry identification of 0x02 are for I/O APICs. Parsing will tell how many (if any) I/O APICs exist, what are their APIC ID, base MMIO address and first IRQ (or GSI - Global System Interrupt). For more information (削除) of (削除ここまで)parsing the MP tables, see the External MP Tables Links section below. So you can have, say, 2 I/O APICs, the first handling IRQs 0 - 23 and the second 24 - 47.
In order to detect the existence of an I/O APIC (or multiple ones), the Intel Multi-Processor or (追記) [[RSDP| (追記ここまで)ACPI(追記) ]] (追記ここまで)tables (追記) (specifically, the [[MADT]]) (追記ここまで)must be parsed. In the MP tables, configuration tables with the entry identification of 0x02 are for I/O APICs. Parsing will tell how many (if any) I/O APICs exist, what are their APIC ID, base MMIO address and first IRQ (or GSI - Global System Interrupt). For more information (追記) on (追記ここまで)parsing the MP tables, see the External MP Tables Links section below. So you can have, say, 2 I/O APICs, the first handling IRQs 0 - 23 and the second 24 - 47.


== Programming the I/O APIC ==
== Programming the I/O APIC ==
Each I/O APIC has a set of 3 32-bit registers and up to (削除) 24 (削除ここまで)64-bit registers (one per IRQ). The 64-bit registers have (削除) actually (削除ここまで)to be accessed as two 32-bit reads/writes(削除) . So here we have 51 registers (削除ここまで). All registers are memory indexed. It means that you actually have only two 32-bit registers in memory, called IOREGSEL and IOREGWIN. You put the register index in IOREGSEL, and then you can read/write in IOREGWIN. The first three registers contain general (削除) informations (削除ここまで)about this I/O APIC, the (削除) remaing (削除ここまで)contain the specific configuration for each (削除) of the 24 IRQs (削除ここまで).
Each I/O APIC has a set of (追記) 2 or (追記ここまで)3 (追記) (depending on version) (追記ここまで)32-bit registers and up to (追記) many (追記ここまで)64-bit registers (one per IRQ). The 64-bit registers have to be accessed as two 32-bit reads/writes. All registers are memory indexed. It means that you actually have only two 32-bit registers in memory, called IOREGSEL and IOREGWIN. You put the register index in IOREGSEL, and then you can read/write in IOREGWIN. The first three registers contain general (追記) information (追記ここまで)about this I/O APIC, (追記) while (追記ここまで)the (追記) remaining registers (追記ここまで)contain the specific configuration for each (追記) IRQ (追記ここまで).


== IOAPICID ==
(追記) = (追記ここまで)== IOAPICID (追記) = (追記ここまで)==
This register has index 0 (you write 0 to IOREGSEL and then read from IOREGWIN). It's a Read Only (削除) registers (削除ここまで)with almost all bits reserved. The only interesting field is in bits 24 - 27: the APIC ID for this device (each peripheral which is interfaced with the APIC Bus needs an APIC ID, not only CPUs). You shall find this ID in ACPI/MP Tables as well.
This register has index 0 (you write 0 to IOREGSEL and then read from IOREGWIN). It's a Read(追記) - (追記ここまで)Only (追記) register (追記ここまで)with almost all bits reserved. The only interesting field is in bits 24 - 27: the APIC ID for this device (each peripheral which is interfaced with the APIC Bus needs an APIC ID, not only CPUs). You shall find this ID in ACPI/MP Tables as well.


== IOAPICVER ==
(追記) = (追記ここまで)== IOAPICVER (追記) = (追記ここまで)==
This register (index 1) contains the I/O APIC Version in bits 0 - (削除) 8 (削除ここまで), and the '''Max Redirection Entry''' which is "how many IRQs can this I/O APIC handle - 1". It (削除) cannot be higher than 23 (allowing 24 IRQs) and it's (削除ここまで)encoded in bits 16 - 23.
This register (index 1) contains the I/O APIC Version in bits 0 - (追記) 7 (追記ここまで), and the '''Max Redirection Entry''' which is "how many IRQs can this I/O APIC handle - 1". It (追記) is (追記ここまで)encoded in bits 16 - 23.


== IOAPICARB ==
(追記) = (追記ここまで)== IOAPICARB (追記) = (追記ここまで)==
This register (index 2) contains in bits 24 - 27 the APIC Arbitration ID. '''TODO'''
This register (index 2) contains in bits 24 - 27 the APIC Arbitration ID. '''TODO'''


== IOREDTBL ==
(追記) = (追記ここまで)== IOREDTBL (追記) = (追記ここまで)==
Following there are two 32-bit register for each IRQ. The first IRQ has indexes 0x10 and 0x11, the second 0x12 and 0x13, the third 0x14 and 0x15, and so on. So the ''Redirection Entry'' register for IRQ ''n'' is 0x10 + n * 2 (+ 1). In the first of the two registers you access to the (削除) high dword (削除ここまで), and the second for the (削除) low dword (削除ここまで). Each redirection entry is made of the following fields:
Following there are two 32-bit register for each IRQ. The first IRQ has indexes 0x10 and 0x11, the second 0x12 and 0x13, the third 0x14 and 0x15, and so on. So the ''Redirection Entry'' register for IRQ ''n'' is 0x10 + n * 2 (+ 1). In the first of the two registers you access to the (追記) LOW uint32_t / bits 31:0 (追記ここまで), and the second for the (追記) high uint32_t / 63:32 (追記ここまで). Each redirection entry is made of the following fields:
{|
{| (追記) class="wikitable" (追記ここまで)
|-
|-
! Field
! Field
Line 25: Line 26:
! Description
! Description
|-
|-
| Vector
(追記) | style="width: 100px;" (追記ここまで)| Vector
| 0 - 7
(追記) | style="width: 50px;" (追記ここまで)| 0 - 7
| The Interrupt vector that will be raised on the specified CPU(s).
| The Interrupt vector that will be raised on the specified CPU(s).
|-
|-
Line 40: Line 41:
| 12
| 12
| If 0, the IRQ is just relaxed and waiting for something to happen (or it has fired and already processed by Local APIC(s)). If 1, it means that the IRQ has been sent to the Local APICs but it's still waiting to be delivered.
| If 0, the IRQ is just relaxed and waiting for something to happen (or it has fired and already processed by Local APIC(s)). If 1, it means that the IRQ has been sent to the Local APICs but it's still waiting to be delivered.
(追記) |- (追記ここまで)
| Pin Polarity
| Pin Polarity
| 13
| 13
| 0: Active high, 1: Active low. For ISA IRQs assume Active High unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
| 0: Active high, 1: Active low. For ISA IRQs assume Active High unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
| -
|-
| Remote IRR
| Remote IRR
| 14
| 14
| '''TODO'''
| '''TODO'''
| -
|-
| Trigger Mode
| Trigger Mode
| 15
| 15
| 0: Edge, 1: Level. For ISA IRQs assume Edge unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
| 0: Edge, 1: Level. For ISA IRQs assume Edge unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
| -
|-
| Mask
| Mask
| 16
| 16
| Just like in the old PIC, you can temporary disable this IRQ by setting this bit, and reenable it by clearing the bit.
| Just like in the old PIC, you can temporary disable this IRQ by setting this bit, and reenable it by clearing the bit.
| -
|-
| Destination
| Destination
| 56 - 63
| 56 - 63
Line 64: Line 66:
The register IOREGSEL is an MMIO register select register that is used to access all the other I/O APIC registers. The IOWIN register is the 'data' register. Once the IOREGSEL register has been set, the IOWIN register can be used to write or read the register in the IOREGSEL. The actual position in memory of the two registers is specified in the ACPI MADT Table and/or in the MP table. The IOREGSEL is at the address specified, and IOREGWIN is at the same address + 0x10.
The register IOREGSEL is an MMIO register select register that is used to access all the other I/O APIC registers. The IOWIN register is the 'data' register. Once the IOREGSEL register has been set, the IOWIN register can be used to write or read the register in the IOREGSEL. The actual position in memory of the two registers is specified in the ACPI MADT Table and/or in the MP table. The IOREGSEL is at the address specified, and IOREGWIN is at the same address + 0x10.


<source lang="(削除) c (削除ここまで)">
<source lang="(追記) cpp (追記ここまで)">
(削除) (削除ここまで)void write_ioapic_register(const uintptr_t apic_base, const uint8_t offset, const uint32_t val)
(削除) (削除ここまで){
(追記) #define IOAPICID 0x00 (追記ここまで)
(削除) (削除ここまで)/* tell IOREGSEL where we want to write to */
(追記) #define IOAPICVER 0x01 (追記ここまで)
(削除) (削除ここまで)*(uint32_t*)(apic_base) = offset;
(追記) #define IOAPICARB 0x02 (追記ここまで)
(削除) (削除ここまで)/* write the value to IOWIN */
(追記) #define IOAPICREDTBL(n) (0x10 + 2 * n) // lower-32bits (add +1 for upper 32-bits) (追記ここまで)
(削除) (削除ここまで)*(uint32_t*)(apic_base + 0x10) = val;
(削除) (削除ここまで)}
void write_ioapic_register(const uintptr_t apic_base, const uint8_t offset, const uint32_t val)
{
(追記) (追記ここまで)/* tell IOREGSEL where we want to write to */
(追記) (追記ここまで)*((追記) volatile (追記ここまで)uint32_t*)(apic_base) = offset;
(追記) (追記ここまで)/* write the value to IOWIN */
(追記) (追記ここまで)*((追記) volatile (追記ここまで)uint32_t*)(apic_base + 0x10) = val;
}
(削除) (削除ここまで)uint32_t read_ioapic_register(const uintptr_t apic_base, const uint8_t offset)
uint32_t read_ioapic_register(const uintptr_t apic_base, const uint8_t offset)
(削除) (削除ここまで){
{
(削除) (削除ここまで)/* tell IOREGSEL where we want to read from */
(追記) (追記ここまで)/* tell IOREGSEL where we want to read from */
(削除) (削除ここまで)*(uint32_t*)(apic_base) = offset;
(追記) (追記ここまで)*((追記) volatile (追記ここまで)uint32_t*)(apic_base) = offset;
(削除) (削除ここまで)/* return the data from IOWIN */
(追記) (追記ここまで)/* return the data from IOWIN */
(削除) (削除ここまで)return *(uint32_t*)(apic_base + 0x10);
(追記) (追記ここまで)return *((追記) volatile (追記ここまで)uint32_t*)(apic_base + 0x10);
}
(追記) } (追記ここまで)
(追記) /* @class IOAPIC (追記ここまで)
(追記) * (追記ここまで)
(追記) * A sample driver code which control an IOAPIC. It handles one IOAPIC and exposes (追記ここまで)
(追記) * some functions. It is totally representational, .i.e you should add locking support (追記ここまで)
(追記) * link all IOAPIC classes in a data structure and much more. Here we are just showing (追記ここまで)
(追記) * what & how your'e gonna handle this in C++. (追記ここまで)
(追記) * (追記ここまで)
(追記) * You could also note that IOAPIC registers "may" cross a page boundary. So maybe you (追記ここまで)
(追記) * may need to map the physical-base to a double-page (means allocate twice the amount (追記ここまで)
(追記) * of memory from vmm). (追記ここまで)
(追記) */ (追記ここまで)
(追記) class IOAPIC (追記ここまで)
(追記) { (追記ここまで)
(追記) public: (追記ここまで)
(追記) enum DeliveryMode (追記ここまで)
(追記) { (追記ここまで)
(追記) EDGE = 0, (追記ここまで)
(追記) LEVEL = 1, (追記ここまで)
(追記) }; (追記ここまで)
(追記) enum DestinationMode (追記ここまで)
(追記) { (追記ここまで)
(追記) PHYSICAL = 0, (追記ここまで)
(追記) LOGICAL = 1 (追記ここまで)
(追記) }; (追記ここまで)
(追記) union RedirectionEntry (追記ここまで)
(追記) { (追記ここまで)
(追記) struct (追記ここまで)
(追記) { (追記ここまで)
(追記) uint64_t vector : 8; (追記ここまで)
(追記) uint64_t delvMode : 3; (追記ここまで)
(追記) uint64_t destMode : 1; (追記ここまで)
(追記) uint64_t delvStatus : 1; (追記ここまで)
(追記) uint64_t pinPolarity : 1; (追記ここまで)
(追記) uint64_t remoteIRR : 1; (追記ここまで)
(追記) uint64_t triggerMode : 1; (追記ここまで)
(追記) uint64_t mask : 1; (追記ここまで)
(追記) uint64_t reserved : 39; (追記ここまで)
(追記) uint64_t destination : 8; (追記ここまで)
(追記) }; (追記ここまで)
(追記) struct (追記ここまで)
(追記) { (追記ここまで)
(追記) uint32_t lowerDword; (追記ここまで)
(追記) uint32_t upperDword; (追記ここまで)
(追記) }; (追記ここまで)
(追記) }; (追記ここまで)
(追記) unsigned char id(){ return (apicId); } (追記ここまで)
(追記) unsigned char ver(){ return (apicVer); } (追記ここまで)
(追記) unsigned char redirectionEntries(){ return (redirEntryCnt); } (追記ここまで)
(追記) unsigned long globalInterruptBase(){ return (globalIntrBase); } (追記ここまで)
(追記) IOAPIC::IOAPIC(unsigned long physRegs, unsigned long apicId, unsigned long gsib) (追記ここまで)
(追記) { (追記ここまで)
(追記) this->virtAddr = KernelMemory::allocatePage(PAGE_SIZE); (追記ここまで)
(追記) /* map virtAddr to physical-regs. Note that your paging code may not support (追記ここまで)
(追記) automatically aligning physRegs to page-boundaries. Be sure to check! */ (追記ここまで)
(追記) EnsureMapping(virtAddr, physRegs, PagePresent | PageReadWrite | PageCacheDisable); (追記ここまで)
(追記) (追記ここまで)
(追記) virtAddr += physRegs % PAGE_SIZE; (追記ここまで)
(追記) apicId = (read(IOAPICID) >> 24) & 0xF0; (追記ここまで)
(追記) apicVer = read(IOAPICVER);// cast to uint8_t (unsigned char) hides upper bits (追記ここまで)
(追記) //< max. redir entry is given IOAPICVER[16:24] (追記ここまで)
(追記) redirEntryCnt = (read(IOAPICVER) >> 16) + 1;// cast to uint8_t occuring ok! (追記ここまで)
(追記) globalIntrBase = gsib; (追記ここまで)
(追記) } (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Bit of assignment here - implement this on your own. Use the lowerDword & upperDword (追記ここまで)
(追記) * fields of RedirectionEntry using (追記ここまで)
(追記) * ent.lowerDword = read(entNo); (追記ここまで)
(追記) * ent.upperDword = read(entNo); (追記ここまで)
(追記) * return (ent); (追記ここまで)
(追記) * (追記ここまで)
(追記) * Be sure to check that entNo < redirectionEntries() (追記ここまで)
(追記) * (追記ここまで)
(追記) * @param entNo - entry no. for which redir-entry is required (追記ここまで)
(追記) * @return entry associated with entry no. (追記ここまで)
(追記) */ (追記ここまで)
(追記) RedirectionEntry getRedirEntry(unsigned char entNo); (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Bit of assignment here - implement this on your own. Use the lowerDword & upperDword (追記ここまで)
(追記) * fields of RedirectionEntry using (追記ここまで)
(追記) * write(entNo, ent->lowerDword); (追記ここまで)
(追記) * write(entNo, ent->upperDword); (追記ここまで)
(追記) * (追記ここまで)
(追記) * Be sure to check that entNo < redirectionEntries() (追記ここまで)
(追記) * (追記ここまで)
(追記) * @param entNo - entry no. for which redir-entry is required (追記ここまで)
(追記) * @param entry - ptr to entry to write (追記ここまで)
(追記) */ (追記ここまで)
(追記) void writeRedirEntry(unsigned char entNo, RedirectionEntry *entry); (追記ここまで)
(追記) private: (追記ここまで)
(追記) /* (追記ここまで)
(追記) * This field contains the physical-base address for the IOAPIC (追記ここまで)
(追記) * can be found using an IOAPIC-entry in the ACPI 2.0 MADT. (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned long physRegs; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Holds the base address of the registers in virtual memory. This (追記ここまで)
(追記) * address is non-cacheable (see paging). (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned long virtAddr; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Software has complete control over the apic-id. Also, hardware (追記ここまで)
(追記) * won't automatically change its apic-id so we could cache it here. (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned char apicId; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Hardware-version of the apic, mainly for display purpose. ToDo: specify (追記ここまで)
(追記) * more purposes. (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned char apicVer; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Although entries for current IOAPIC is 24, it may change. To retain (追記ここまで)
(追記) * compatibility make sure you use this. (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned char redirEntryCnt; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * The first IRQ which this IOAPIC handles. This is only found in the (追記ここまで)
(追記) * IOAPIC entry of the ACPI 2.0 MADT. It isn't found in the IOAPIC (追記ここまで)
(追記) * registers. (追記ここまで)
(追記) */ (追記ここまで)
(追記) unsigned long globalIntrBase; (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Reads the data present in the register at offset regOff. (追記ここまで)
(追記) * (追記ここまで)
(追記) * @param regOff - the register's offset which is being read (追記ここまで)
(追記) * @return the data present in the register associated with that offset (追記ここまで)
(追記) */ (追記ここまで)
(追記) uint32_t read(unsigned char regOff) (追記ここまで)
(追記) { (追記ここまで)
(追記) *(uint32_t volatile*) virtAddr = regOff; (追記ここまで)
(追記) return *(uint32_t volatile*)(virtAddr + 0x10); (追記ここまで)
(追記) } (追記ここまで)
(追記) /* (追記ここまで)
(追記) * Writes the data into the register associated. (追記ここまで)
(追記) * (追記ここまで)
(追記) * @param regOff - the register's offset which is being written (追記ここまで)
(追記) * @param data - dword to write to the register (追記ここまで)
(追記) */ (追記ここまで)
(追記) void write(unsigned char regOff, uint32_t data) (追記ここまで)
(追記) { (追記ここまで)
(追記) *(uint32_t volatile*) virtAddr = regOff; (追記ここまで)
(追記) *(uint32_t volatile*)(virtAddr + 0x10) = data; (追記ここまで)
(追記) } (追記ここまで)
}(追記) ; (追記ここまで)
</source>
</source>
(削除) Note: 'uintptr_t' is the size of the default memory reference. If a 32-bit Protected Mode setup, it's equivalent to uint32_t, and uint64_t in 64-bit Long Mode environments. (削除ここまで)'apic_base' is the memory base address for a selected IOAPIC, these can be found by enumerating them from the MP Tables.
'apic_base' is the memory base address for a selected IOAPIC, these can be found by enumerating them from the MP (追記) or ACPI (追記ここまで)Tables(追記) . (追記ここまで)
(追記) == IO APIC Inputs == (追記ここまで)
(追記) How other hardware (devices, etc) use IO APIC inputs is completely arbitrary - the motherboard/chipset designer can hard-wire anything they like to any IO APIC input. For the motherboard designer's convenience, most but not all legacy IRQs are often (but not always) connected "1:1" to IO APIC inputs (e.g. IO APIC input #1 may be the same as PIC chip input #1) as this makes firmware a little easier (e.g. no need for "interrupt redirection entries" in ACPI's MADT/APIC table), but this is not a requirement of any standard and not something that useful operating system software can rely on. (追記ここまで)
(追記) To correctly determine what how IO APIC inputs are used (and how they must be configured - as active high or active low, and as edge triggered or level triggered) operating system software must either: (追記ここまで)
(追記) 1) Use APIC's MADT/APIC table to determine how legacy IRQs are mapped to IO APIC inputs; then use ACPI's AML (with a suitable interpreter) to determine how PCI devices are connected to IO APIC inputs (追記ここまで)
(追記) 2) Use Intel's "MultiProcessor Specification" tables to determine how both legacy IRQs and PCI IRQs are mapped to IO APIC inputs. Note that Intel's "MultiProcessor Specification" is deprecated (an operating system should use ACPI where possible, and fall back to MultiProcessor Specification tables if ACPI doesn't exist or can't be used) (追記ここまで)
(追記) 3) Provide (many) motherboard specific drivers, where each driver is able to use motherboard specific information to determine how IO inputs are used (追記ここまで)
(追記) 4) Use an excessively "clever" auto-detection scheme (with a high risk of misconfiguration and race conditions). These schemes typically begin with whatever information can be obtained easily (e.g. determining legacy IRQs from ACPI's MADT/APIC table), assuming everything else can be configured as "level triggered active low" (to suit PCI), and then asking device drivers to repeatedly forcing their device to generate an IRQ (while ruling out IO APIC inputs that weren't triggered within a certain period of time after the IRQ was caused) until the specific IO APIC that the device uses can be determined (追記ここまで).


== External Links ==
== External Links ==
=== MP Tables ===
=== MP Tables ===
* [http://download.intel.com/design/(削除) pentium (削除ここまで)/(削除) datashts (削除ここまで)/24201606.pdf Intel MultiProcessor Specification]
* [(追記) https://web.archive.org/web/20121002210153/ (追記ここまで)http://download.intel.com/design/(追記) archives/processors/pro (追記ここまで)/(追記) docs (追記ここまで)/24201606.pdf Intel MultiProcessor Specification]


=== I/O APIC ===
=== I/O APIC ===
* [http://download.intel.com/design/chipsets/datashts/29056601.pdf Intel 82093AA I/O APIC]
* [(追記) http://web.archive.org/web/20161130153145/ (追記ここまで)http://download.intel.com/design/chipsets/datashts/29056601.pdf Intel 82093AA I/O APIC]
* [http://download.intel.com/design/processor/manuals/253668.pdf Intel SDM 3A (see (削除) ch. (削除ここまで)9.9)]
* [(追記) http://web.archive.org/web/20140713224739/ (追記ここまで)http://download.intel.com/design/processor/manuals/253668.pdf Intel SDM 3A (see (追記) chapter (追記ここまで)9.9)]


[[de:I/O Advanced Programmable Interrupt Controller]]
[[de:I/O Advanced Programmable Interrupt Controller]]
[[Category:Interrupts]]
[[Category:Interrupts]]
(追記) [[Category:Multiprocessing]] (追記ここまで)

Latest revision as of 18:46, 24 June 2024


The Intel I/O Advanced Programmable Interrupt Controller is used to distribute external interrupts in a more advanced manner than that of the standard 8259 PIC. With the I/O APIC, interrupts can be distributed to physical or logical (clusters of) processors and can be prioritized. Each I/O APIC typically handles 24 external interrupts.

Detecting I/O APIC

In order to detect the existence of an I/O APIC (or multiple ones), the Intel Multi-Processor or ACPI tables (specifically, the MADT) must be parsed. In the MP tables, configuration tables with the entry identification of 0x02 are for I/O APICs. Parsing will tell how many (if any) I/O APICs exist, what are their APIC ID, base MMIO address and first IRQ (or GSI - Global System Interrupt). For more information on parsing the MP tables, see the External MP Tables Links section below. So you can have, say, 2 I/O APICs, the first handling IRQs 0 - 23 and the second 24 - 47.

Programming the I/O APIC

Each I/O APIC has a set of 2 or 3 (depending on version) 32-bit registers and up to many 64-bit registers (one per IRQ). The 64-bit registers have to be accessed as two 32-bit reads/writes. All registers are memory indexed. It means that you actually have only two 32-bit registers in memory, called IOREGSEL and IOREGWIN. You put the register index in IOREGSEL, and then you can read/write in IOREGWIN. The first three registers contain general information about this I/O APIC, while the remaining registers contain the specific configuration for each IRQ.

IOAPICID

This register has index 0 (you write 0 to IOREGSEL and then read from IOREGWIN). It's a Read-Only register with almost all bits reserved. The only interesting field is in bits 24 - 27: the APIC ID for this device (each peripheral which is interfaced with the APIC Bus needs an APIC ID, not only CPUs). You shall find this ID in ACPI/MP Tables as well.

IOAPICVER

This register (index 1) contains the I/O APIC Version in bits 0 - 7, and the Max Redirection Entry which is "how many IRQs can this I/O APIC handle - 1". It is encoded in bits 16 - 23.

IOAPICARB

This register (index 2) contains in bits 24 - 27 the APIC Arbitration ID. TODO

IOREDTBL

Following there are two 32-bit register for each IRQ. The first IRQ has indexes 0x10 and 0x11, the second 0x12 and 0x13, the third 0x14 and 0x15, and so on. So the Redirection Entry register for IRQ n is 0x10 + n * 2 (+ 1). In the first of the two registers you access to the LOW uint32_t / bits 31:0, and the second for the high uint32_t / 63:32. Each redirection entry is made of the following fields:

Field Bits Description
Vector 0 - 7 The Interrupt vector that will be raised on the specified CPU(s).
Delivery Mode 8 - 10 How the interrupt will be sent to the CPU(s). It can be 000 (Fixed), 001 (Lowest Priority), 010 (SMI), 100 (NMI), 101 (INIT) and 111 (ExtINT). Most of the cases you want Fixed mode, or Lowest Priority if you don't want to suspend a high priority task on some important Processor/Core/Thread.
Destination Mode 11 Specify how the Destination field shall be interpreted. 0: Physical Destination, 1: Logical Destination
Delivery Status 12 If 0, the IRQ is just relaxed and waiting for something to happen (or it has fired and already processed by Local APIC(s)). If 1, it means that the IRQ has been sent to the Local APICs but it's still waiting to be delivered.
Pin Polarity 13 0: Active high, 1: Active low. For ISA IRQs assume Active High unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
Remote IRR 14 TODO
Trigger Mode 15 0: Edge, 1: Level. For ISA IRQs assume Edge unless otherwise specified in Interrupt Source Override descriptors of the MADT or in the MP Tables.
Mask 16 Just like in the old PIC, you can temporary disable this IRQ by setting this bit, and reenable it by clearing the bit.
Destination 56 - 63 This field is interpreted according to the Destination Format bit. If Physical destination is choosen, then this field is limited to bits 56 - 59 (only 16 CPUs addressable). You put here the APIC ID of the CPU that you want to receive the interrupt. TODO: Logical destination format...

IOREGSEL and IOWIN

The register IOREGSEL is an MMIO register select register that is used to access all the other I/O APIC registers. The IOWIN register is the 'data' register. Once the IOREGSEL register has been set, the IOWIN register can be used to write or read the register in the IOREGSEL. The actual position in memory of the two registers is specified in the ACPI MADT Table and/or in the MP table. The IOREGSEL is at the address specified, and IOREGWIN is at the same address + 0x10.

#define IOAPICID 0x00
#define IOAPICVER 0x01
#define IOAPICARB 0x02
#define IOAPICREDTBL(n) (0x10 + 2 * n) // lower-32bits (add +1 for upper 32-bits)
voidwrite_ioapic_register(constuintptr_tapic_base,constuint8_toffset,constuint32_tval)
{
/* tell IOREGSEL where we want to write to */
*(volatileuint32_t*)(apic_base)=offset;
/* write the value to IOWIN */
*(volatileuint32_t*)(apic_base+0x10)=val;
}

uint32_tread_ioapic_register(constuintptr_tapic_base,constuint8_toffset)
{
/* tell IOREGSEL where we want to read from */
*(volatileuint32_t*)(apic_base)=offset;
/* return the data from IOWIN */
return*(volatileuint32_t*)(apic_base+0x10);
}
/* @class IOAPIC
 *
 * A sample driver code which control an IOAPIC. It handles one IOAPIC and exposes
 * some functions. It is totally representational, .i.e you should add locking support
 * link all IOAPIC classes in a data structure and much more. Here we are just showing
 * what & how your'e gonna handle this in C++.
 *
 * You could also note that IOAPIC registers "may" cross a page boundary. So maybe you
 * may need to map the physical-base to a double-page (means allocate twice the amount
 * of memory from vmm).
 */
classIOAPIC
{
public:
enumDeliveryMode
{
EDGE=0,
LEVEL=1,
};
enumDestinationMode
{
PHYSICAL=0,
LOGICAL=1
};
unionRedirectionEntry
{
struct
{
uint64_tvector:8;
uint64_tdelvMode:3;
uint64_tdestMode:1;
uint64_tdelvStatus:1;
uint64_tpinPolarity:1;
uint64_tremoteIRR:1;
uint64_ttriggerMode:1;
uint64_tmask:1;
uint64_treserved:39;
uint64_tdestination:8;
};
struct
{
uint32_tlowerDword;
uint32_tupperDword;
};
};
unsignedcharid(){return(apicId);}
unsignedcharver(){return(apicVer);}
unsignedcharredirectionEntries(){return(redirEntryCnt);}
unsignedlongglobalInterruptBase(){return(globalIntrBase);}
IOAPIC::IOAPIC(unsignedlongphysRegs,unsignedlongapicId,unsignedlonggsib)
{
this->virtAddr=KernelMemory::allocatePage(PAGE_SIZE);
/* map virtAddr to physical-regs. Note that your paging code may not support 
 automatically aligning physRegs to page-boundaries. Be sure to check! */
EnsureMapping(virtAddr,physRegs,PagePresent|PageReadWrite|PageCacheDisable);

virtAddr+=physRegs%PAGE_SIZE;
apicId=(read(IOAPICID)>>24)&0xF0;
apicVer=read(IOAPICVER);// cast to uint8_t (unsigned char) hides upper bits
//< max. redir entry is given IOAPICVER[16:24]
redirEntryCnt=(read(IOAPICVER)>>16)+1;// cast to uint8_t occuring ok!
globalIntrBase=gsib;
}
/*
 * Bit of assignment here - implement this on your own. Use the lowerDword & upperDword
 * fields of RedirectionEntry using
 * ent.lowerDword = read(entNo);
 * ent.upperDword = read(entNo);
 * return (ent);
 *
 * Be sure to check that entNo < redirectionEntries()
 *
 * @param entNo - entry no. for which redir-entry is required
 * @return entry associated with entry no.
 */
RedirectionEntrygetRedirEntry(unsignedcharentNo);
/*
 * Bit of assignment here - implement this on your own. Use the lowerDword & upperDword
 * fields of RedirectionEntry using
 * write(entNo, ent->lowerDword);
 * write(entNo, ent->upperDword);
 *
 * Be sure to check that entNo < redirectionEntries()
 *
 * @param entNo - entry no. for which redir-entry is required
 * @param entry - ptr to entry to write
 */
voidwriteRedirEntry(unsignedcharentNo,RedirectionEntry*entry);
private:
/*
 * This field contains the physical-base address for the IOAPIC
 * can be found using an IOAPIC-entry in the ACPI 2.0 MADT.
 */
unsignedlongphysRegs;
/*
 * Holds the base address of the registers in virtual memory. This
 * address is non-cacheable (see paging).
 */
unsignedlongvirtAddr;
/*
 * Software has complete control over the apic-id. Also, hardware
 * won't automatically change its apic-id so we could cache it here.
 */
unsignedcharapicId;
/*
 * Hardware-version of the apic, mainly for display purpose. ToDo: specify
 * more purposes.
 */
unsignedcharapicVer;
/*
 * Although entries for current IOAPIC is 24, it may change. To retain
 * compatibility make sure you use this.
 */
unsignedcharredirEntryCnt;
/*
 * The first IRQ which this IOAPIC handles. This is only found in the
 * IOAPIC entry of the ACPI 2.0 MADT. It isn't found in the IOAPIC
 * registers.
 */
unsignedlongglobalIntrBase;
/*
 * Reads the data present in the register at offset regOff.
 *
 * @param regOff - the register's offset which is being read
 * @return the data present in the register associated with that offset
 */
uint32_tread(unsignedcharregOff)
{
*(uint32_tvolatile*)virtAddr=regOff;
return*(uint32_tvolatile*)(virtAddr+0x10);
}
/*
 * Writes the data into the register associated. 
 *
 * @param regOff - the register's offset which is being written
 * @param data - dword to write to the register
 */
voidwrite(unsignedcharregOff,uint32_tdata)
{
*(uint32_tvolatile*)virtAddr=regOff;
*(uint32_tvolatile*)(virtAddr+0x10)=data;
}
};

'apic_base' is the memory base address for a selected IOAPIC, these can be found by enumerating them from the MP or ACPI Tables.


IO APIC Inputs

How other hardware (devices, etc) use IO APIC inputs is completely arbitrary - the motherboard/chipset designer can hard-wire anything they like to any IO APIC input. For the motherboard designer's convenience, most but not all legacy IRQs are often (but not always) connected "1:1" to IO APIC inputs (e.g. IO APIC input #1 may be the same as PIC chip input #1) as this makes firmware a little easier (e.g. no need for "interrupt redirection entries" in ACPI's MADT/APIC table), but this is not a requirement of any standard and not something that useful operating system software can rely on.

To correctly determine what how IO APIC inputs are used (and how they must be configured - as active high or active low, and as edge triggered or level triggered) operating system software must either:

1) Use APIC's MADT/APIC table to determine how legacy IRQs are mapped to IO APIC inputs; then use ACPI's AML (with a suitable interpreter) to determine how PCI devices are connected to IO APIC inputs

2) Use Intel's "MultiProcessor Specification" tables to determine how both legacy IRQs and PCI IRQs are mapped to IO APIC inputs. Note that Intel's "MultiProcessor Specification" is deprecated (an operating system should use ACPI where possible, and fall back to MultiProcessor Specification tables if ACPI doesn't exist or can't be used)

3) Provide (many) motherboard specific drivers, where each driver is able to use motherboard specific information to determine how IO inputs are used

4) Use an excessively "clever" auto-detection scheme (with a high risk of misconfiguration and race conditions). These schemes typically begin with whatever information can be obtained easily (e.g. determining legacy IRQs from ACPI's MADT/APIC table), assuming everything else can be configured as "level triggered active low" (to suit PCI), and then asking device drivers to repeatedly forcing their device to generate an IRQ (while ruling out IO APIC inputs that weren't triggered within a certain period of time after the IRQ was caused) until the specific IO APIC that the device uses can be determined.

External Links

MP Tables

I/O APIC

Retrieved from "https://wiki.osdev.org/index.php?title=IOAPIC&oldid=29153"