S
S
Semyon Prikhodko2013-02-03 16:29:27
Do it yourself
Semyon Prikhodko, 2013-02-03 16:29:27

Can't relocate local APIC?

For several days I have been trying unsuccessfully to change the local APIC address from the initial 0xFEE00000 to the 0x105000 I need. After writing to the APIC BASE MSR (0x1B), the address is changed, but the APIC registers are not mapped (respectively, the memory is reset to zero instead of containing the initial values ​​described in the specification). Therefore, it is not possible to activate timer interrupts. I thought it was about caching (after all, the specifications indicate that the pager with projected local APIC registers should be marked as strong uncachable), set pwt = pcd = 1 for it, but this did not help. Maybe I'm not considering x86-64 specifics?
problematic code.

#include "apic.h"
#include "display.h"
#include "mem_map.h"
#include "interrupt.h"
#include "util.h"

#define APIC_BASE_MSR 0x1B

#define APIC_SPURIOUS_REG 0x0F0
#define APIC_TIMER_REG 0x320
#define APIC_TIMER_INITIAL_REG 0x380
#define APIC_TIMER_CURRENT_REG 0x390
#define APIC_TIMER_DIVIDE_REG 0x3E0

#define APIC_ENABLE (1 << 11)
#define APIC_LOCAL_ENABLE (1 << 8)
#define APIC_TIMER_MASKED (1 << 16)
#define APIC_TIMER_PERIODIC (1 << 17)

#define APIC_TIMER_DIVIDE_BY_1 0b1011
#define APIC_TIMER_DIVIDE_BY_2 0b0000
#define APIC_TIMER_DIVIDE_BY_4 0b0001
#define APIC_TIMER_DIVIDE_BY_8 0b0010
#define APIC_TIMER_DIVIDE_BY_16 0b0011
#define APIC_TIMER_DIVIDE_BY_32 0b1000
#define APIC_TIMER_DIVIDE_BY_64 0b1001
#define APIC_TIMER_DIVIDE_BY_128 0b1010

ISR_DEFINE(spurious, 0) {
  printf("#SPURIOUS\n");
}

ISR_DEFINE(timer, 0) {
  printf("#TIMER\n");
}

static inline void reg_write(uint64_t reg, uint64_t value) {
  printf("reg %X (prev: %X, curr: %X)\n",
         reg, *(uint64_t*)(APIC_BASE_ADDR + reg), value);
  *(uint64_t*)(APIC_BASE_ADDR + reg) = value;
}

void init_apic(void) {
  uint64_t prev = rdmsr(APIC_BASE_MSR);
  wrmsr(APIC_BASE_MSR, APIC_BASE_ADDR | APIC_ENABLE);
  printf("msr %X (prev: %X, curr: %X)\n",
         APIC_BASE_MSR, prev, rdmsr(APIC_BASE_MSR));

  reg_write(0x30, 0);

  set_isr(INT_VECTOR_SPURIOUS, spurious_isr_getter());
  reg_write(APIC_SPURIOUS_REG, APIC_LOCAL_ENABLE | INT_VECTOR_SPURIOUS);

  set_isr(INT_VECTOR_TIMER, timer_isr_getter());
  reg_write(APIC_TIMER_REG, APIC_TIMER_PERIODIC | INT_VECTOR_TIMER);
  reg_write(APIC_TIMER_INITIAL_REG, 12345);
  reg_write(APIC_TIMER_DIVIDE_REG, APIC_TIMER_DIVIDE_BY_4);
}

The complete code is here . I would be grateful if you point out where I am wrong. Thank you.

Answer the question

In order to leave comments, you need to log in

4 answer(s)
S
Semyon Prikhodko, 2013-02-05
@ababo

The reason was banal - the bootloader did not activate the A20 gate. I found the cause by accident, noticing that the second megabyte of physical memory is actually the same as the first.
Now I don't know what to do with qemu/kvm, because they really don't support local APIC relocation.

@
@ntkt, 2013-02-03
_

The Intel manual says:

Table 9-1 shows how the APIC registers are mapped into the 4-KByte APIC register
space. Registers are 32 bits, 64 bits, or 256 bits in width; all are aligned on 128-bit
boundaries. All 32-bit registers should be accessed using 128-bit aligned 32-bit loads
or stores.


I didn't see it in your code.

J
jcmvbkbc, 2013-02-03
@jcmvbkbc

I see two strange things.
You have one: access to 32-bit APIC registers cannot be done as if it were 64-bit memory. This is what intel developer's manual vol3, p 10.4.1 says:

All 32-bit registers should be accessed using 128-bit aligned 32-bit loads or stores.

The second is in qemu. There (looked at the current git head) there is no code that would move registers when the APIC base address changes.
What are you testing your kernel on?

S
Semyon Prikhodko, 2013-02-03
@ababo

endured

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question