I'm following a book to write a linux like kernel, however, met problems with the APIC chapter.
Before everything, I'll list my platform. I'm on Windows 10, using Virtual Box to run Ubuntu 18.04, and run test codes on bochs within it.
Currently my understanding about APIC are as follow:
1, There are built on Local APIC on each core and I/O APIC on motherboard
2, Local APIC can be accessed using memory mapping or MSR referencing
3, I/O APIC are accessed by 3 registers IOREGSEL, IOWIN, EOI. The basic idea is to set the value for IOREGSEL and access the corresponding register with IOWIN.
4, There are 3 mode, the interested one is Symmetric I/O mode
5, I/O APIC have 24 pins, pin 1 is linked to keyboard
6, To enable APIC and I/O APIC, there are serials of works to do:
a) Mask 8529A interrupt
b) Enable xAPIC and 2xAPIC, so that MSR access are possible
c) Mask all LVT (if Local interrupt are not needed)
d) Setting RTE entries for I/O APIC
e) Setting IMCR register to 0x01h, force 8529A interrupt pass signal to I/O APIC
f) Find Other Interrupt Control Register(OIC) through Root Complex Base Address Register(RCBA), and set OIC[8]=1b to enable I/O APIC
Now I'll present my questions:
1, On both bochs and Virtual Box, the Max LVT Entry number is detected as 6 (according to Manual, there are 6+1=7 LVT entries), and the LVT_CMCI entry can not be accessed(gp fault).
2, It is said different chips on motherboard will map RCBA to different port, and I would have to look it up through manuals. But would there be a way to detect it by software itself, otherwise how did the commercial OS fit different platform.
3, Since I'm on virtual machine, how could I detect the accessibility of RCBA
Thanks to anyone who can provide a clue to my questions or helping me understand more about this chapter.
I'll present some of my code on setting up APIC for a simple keyboard interrupt.
First would be interrupt handling function
void IRQ0x21_interrupt(Int_Info_No_Err STK)
{
Ent_Int;
color_printk(RED,BLACK,"do_IRQ: 0x21\t");
unsigned char x;
x = io_in8(0x60);
color_printk(RED,BLACK,"key code:%#08x\n",x);
wrmsr(0x80b, 0UL);
//io_out8(0x20,0x20);
Ret_Int;
}
Ret_Int & Ent_Int are macros defined to handle the interrupt stack, wrmsr() function write 0 to MSR address 0x80b(EOI)
Next would be the setup function for LAPIC and I/O APIC, assuming that physical address 0xFEC00000 is already mapped in page table
void APIC_init(void)
{
int i;
int virtual_index_address;
int virtual_data_address;
int virtual_EOI_address;
unsigned long tmp;
//Set interrupt, note No.33 link to IRQ0x21_interrupt() function
for(i = 32;i < 56;i++)
{
_Set_INT(IDT_PTR.Offset + i, ATTR_INTR_GATE, 2, interrupt[i - 32]);
}
//Mask 8529A
io_out8(0x21,0xff);
io_out8(0xa1,0xff);
//enable IMCR
io_out8(0x22,0x70);
io_out8(0x23,0x01);
#pragma region Init_LAPIC
//Enabling xAPIC(IA32_APIC_BASE[10]) and 2xAPIC(IA32_APIC_BASE[11])
tmp = rdmsr(0x1b);
tmp |= ((1UL << 10) | (1UL << 11));
wrmsr(0x1b,tmp);
//Enabling LAPIC(SVR[8])
tmp = rdmsr(0x80f);
tmp |= (1UL << 8); //No support for EOI broadcast, no need to set bit SVR[12]
wrmsr(0x80f,tmp);
//Mask all LVT
tmp = 0x10000;
//wrmsr(0x82F, tmp); Virtual machine do not support
wrmsr(0x832, tmp);
wrmsr(0x833, tmp);
wrmsr(0x834, tmp);
wrmsr(0x835, tmp);
wrmsr(0x836, tmp);
wrmsr(0x837, tmp);
#pragma endregion
#pragma region Init_IOAPIC
virtual_index_address = (unsigned char*)(0xFEC00000 + PAGE_OFFSET);
virtual_data_address = (unsigned int*)(0xFEC00000 + PAGE_OFFSET + 0x10);
virtual_EOI_address = (unsigned int*)(0xFEC00000 + PAGE_OFFSET + 0x40);
//Setting RTEs, mask all but 0x01 RTE table for keyboard
for(i = 0x10;i < 0x40;i += 2){
*virtual_index_address = i;
io_mfence;
*IOAPIC_MAP.virtual_data_address = 0x10020 + ((i - 0x10) >> 1) & 0xffffffff;
io_mfence;
*IOAPIC_MAP.virtual_index_address = i + 1;
io_mfence;
*IOAPIC_MAP.virtual_data_address = ((0x10020 + ((i - 0x10) >> 1)) >> 32) & 0xffffffff;
io_mfence;
}
*virtual_index_address = 0x12;
io_mfence;
*IOAPIC_MAP.virtual_data_address = 0x10020 + (2 >> 1) & 0xffffffff;
io_mfence;
*IOAPIC_MAP.virtual_index_address = i + 1;
io_mfence;
*IOAPIC_MAP.virtual_data_address = ((0x10020 + (2 >> 1)) >> 32) & 0xffffffff;
io_mfence;
#pragma endregion
}
So according to the answers, the I/O APIC is set to open once I complete initialization for RTEs. If any one can be so kind to tell me if the above code would work or not(for a simple keyboard interrupt). Thank you so much.