Why apic timer ticks is taking too long ?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
gamingjam60
Member
Member
Posts: 70
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

Why apic timer ticks is taking too long ?

Post by gamingjam60 »

Here my full code to implement APIC timer but unfortunately APIC ticks is taking more time than expected.

Code: Select all


#define APIC_TIMER_VECTOR  48                   // APIC Timer Interrupt Vector 0x30
#define APIC_IRQ           16                   // APIC Timer Interrupt Request 48 - 32 = 16 (0x10)

#define MAX_APIC_TICKS 0xFFFFFFFFFFFFFFFF       // Maximum ticks for APIC timer

#define LAPIC_TPR_REGISTER           0x80       // Task Priority Register Offset
#define APIC_TIMER_DIV_REGISTER      0x3E0      // Offset for Timer Divide Register
#define APIC_TIMER_INITCNT_REGISTER  0x380      // Offset for Initial Count Register
#define APIC_TIMER_CURRCNT_REGISTER  0x390      // Offset for Current Count Register
#define APIC_LVT_TIMER_REGISTER      0x320      // Offset for LVT Timer Register

#define APIC_LVT_TIMER_MODE_PERIODIC (1 << 17)  // Periodic mode bit
#define APIC_LVT_TIMER_MODE_ONESHOT  (0 << 0 )  // One shot mode bit
#define APIC_LVT_INT_MASKED          (1 << 16)  // Mask interrupt

// Divider values
#define DIV_BY_2    0b000   // 0x0
#define DIV_BY_4    0b001   // 0x1
#define DIV_BY_8    0b010   // 0x2
#define DIV_BY_16   0b011   // 0x3
#define DIV_BY_32   0b100   // 0x4
#define DIV_BY_64   0b101   // 0x5
#define DIV_BY_128  0b110   // 0x6
#define DIV_BY_1    0b111   // 0x7
 
#define MAX_CPUS 256
volatile uint64_t apic_ticks[MAX_CPUS] = {0};

volatile bool apic_calibrated = false;
volatile uint64_t apic_timer_ticks_per_ms = 0;

uint64_t get_core_id() { return get_lapic_id(); }


void calibrate_apic_timer_tsc() {

    // Ensure the Local APIC is enabled
    apic_write(LAPIC_TPR_REGISTER, 0x0); // Accept all interrupts

    // 
    apic_write(APIC_TIMER_DIV_REGISTER, DIV_BY_16);

    // Reset APIC timer to max count
    apic_write(APIC_TIMER_INITCNT_REGISTER, 0xFFFFFFFF);

    tsc_sleep(100000);    // Wait for 100 ms

    // Read remaining APIC timer count
    uint32_t end_count = apic_read(APIC_TIMER_CURRCNT_REGISTER); 

    // Calculate elapsed APIC ticks
    uint32_t elapsed_apic_ticks = 0xFFFFFFFF - end_count;

    // APIC Timer Frequency (ticks per second)
    uint64_t apic_timer_frequency = (uint64_t) (elapsed_apic_ticks * 10);

    // Calculate APIC Timer ticks per millisecond
    apic_timer_ticks_per_ms = apic_timer_frequency / 1000;

    apic_calibrated = true;

    printf(" [-] APIC Timer Frequency: %d ticks/ms\n", apic_timer_ticks_per_ms);
}


void calibrate_apic_timer_pit() {

    // Ensure the Local APIC is enabled
    apic_write(LAPIC_TPR_REGISTER, 0x0); // Accept all interrupts

    // Set Divisor 16
    apic_write(APIC_TIMER_DIV_REGISTER, DIV_BY_16);

    // Reset APIC timer to max count
    apic_write(APIC_TIMER_INITCNT_REGISTER, 0xFFFFFFFF);

    pit_sleep(1000);    // Wait for 1000 ms i.e 1 s

    // Read remaining APIC timer count
    uint32_t end_count = apic_read(APIC_TIMER_CURRCNT_REGISTER); 

    // Calculate elapsed APIC ticks
    uint32_t elapsed_apic_ticks = 0xFFFFFFFF - end_count;

    // APIC Timer Frequency (ticks per second)
    uint64_t apic_timer_frequency = (uint64_t)elapsed_apic_ticks;

    // Calculate APIC Timer ticks per millisecond
    apic_timer_ticks_per_ms = apic_timer_frequency / 1000;

    apic_calibrated = true;

    printf(" [-] APIC Timer Frequency: %d ticks/ms\n", apic_timer_ticks_per_ms);
}


void apic_start_oneshot_timer(uint32_t initial_count) {
    // Ensure the Local APIC is enabled
    apic_write(LAPIC_TPR_REGISTER, 0x0); // Accept all interrupts

    // Set APIC timer to use divider 16
    apic_write(APIC_TIMER_DIV_REGISTER, DIV_BY_16);
    
    // Set APIC initial count to max
    apic_write(APIC_TIMER_INITCNT_REGISTER, initial_count);

    // Set APIC Timer Mode (Periodic Mode = 0x20000, One-shot = 0x0)
    apic_write(APIC_LVT_TIMER_REGISTER, APIC_TIMER_VECTOR | APIC_LVT_TIMER_MODE_ONESHOT);  // Vector 48, one-shot mode
}



void apic_timer_handler(registers_t *regs) {

    uint32_t cpu_id = get_core_id(); // Implement this using APIC ID or CPU-local ID

    if(apic_ticks[cpu_id] >= MAX_APIC_TICKS) {
        apic_ticks[cpu_id] = 0;     // Starts Recount
    }

    apic_ticks[cpu_id] += apic_timer_ticks_per_ms;

    // printf("apic_ticks[%d]: %d\n", cpu_id, apic_ticks[cpu_id]);

    apic_send_eoi();
}



void init_apic_timer(uint32_t interval_ms) {// Start APIC timer with a large count

    if(is_apic_enabled() == false) {
        printf(" [-] APIC is not enabled. Cannot initialize APIC timer.\n");
        return;
    }

    uint64_t cpu_id = get_core_id(); // Get the current CPU core ID

    apic_ticks[cpu_id] = 0; // Initialize APIC ticks for this CPU core

    asm volatile("cli");

    irq_install(APIC_IRQ, (void *)&apic_timer_handler);

    apic_start_oneshot_timer(0xFFFFFFFF);   // Set APIC timer to max count
    
    if(apic_timer_ticks_per_ms == 0) {
        // calibrate_apic_timer_tsc();         // Calibrate APIC timer using TSC
        calibrate_apic_timer_pit();         // Calibrate APIC timer using PIT

        while (!apic_calibrated);           // Wait for calibration to complete
        printf(" [-] APIC Timer calibrated with %d ticks/ms\n", apic_timer_ticks_per_ms);
    }

    uint32_t apic_count = apic_timer_ticks_per_ms * interval_ms;
    
    // Set APIC Timer for periodic interrupts
    apic_write(APIC_LVT_TIMER_REGISTER, APIC_TIMER_VECTOR | APIC_LVT_TIMER_MODE_PERIODIC);  // Vector 48, Periodic Mode
    apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_1);                                        // Set divisor by 16
    apic_write(APIC_TIMER_INITCNT_REGISTER, apic_timer_ticks_per_ms);                       // Set initial count for timer

    asm volatile("sti");

    printf(" [-] APIC Timer initialized with %d ms interval in CPU: %d.\n", interval_ms, get_lapic_id());
}



void apic_delay(uint32_t milliseconds) {  
    uint32_t cpu_id = get_core_id(); 

    uint64_t start_ticks = apic_ticks[cpu_id];
    uint64_t target_ticks = start_ticks +  milliseconds * apic_timer_ticks_per_ms; 

    while (apic_ticks[cpu_id] < target_ticks) {
        asm volatile("hlt");
        // printf("Inside of apic_delay: ticks = %d, start_ticks: %d, target_ticks: %d\n",
        // apic_ticks[cpu_id], start_ticks, target_ticks);
    }
}

Output:

Code: Select all

UTC TIME(start): 17261783
UP Time: 2
UP Time: 3
UTC TIME(END): 17261816
The above output is showing apic_delay(1000) + apic_delay(1000) is taking 33 s (By using RTC) where I have found apic_ticks_per_ms = 27935. Please Help.
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: Why apic timer ticks is taking too long ?

Post by Octocontrabass »

gamingjam60 wrote: Sat Jul 19, 2025 1:52 pmAPIC ticks is taking more time than expected.
Bare metal or virtual machine? Timers in virtual machines don't always tick at the same rate.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

#define APIC_TIMER_VECTOR  48                   // APIC Timer Interrupt Vector 0x30
#define APIC_IRQ           16                   // APIC Timer Interrupt Request 48 - 32 = 16 (0x10)
How are these two different numbers? The APIC timer delivers its interrupts directly to the CPU, so there should only be one number for the interrupt vector it uses.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

// Divider values
#define DIV_BY_2    0b000   // 0x0
#define DIV_BY_4    0b001   // 0x1
#define DIV_BY_8    0b010   // 0x2
#define DIV_BY_16   0b011   // 0x3
#define DIV_BY_32   0b100   // 0x4
#define DIV_BY_64   0b101   // 0x5
#define DIV_BY_128  0b110   // 0x6
#define DIV_BY_1    0b111   // 0x7
The divide configuration register uses bits 0, 1, and 3. These constants use bits 0, 1, and 2.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_1);
Since you're writing those constants directly into the divide configuration register, the divider is not configured the way you expect.
User avatar
gamingjam60
Member
Member
Posts: 70
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

Re: Why apic timer ticks is taking too long ?

Post by gamingjam60 »

Octocontrabass wrote: Sat Jul 19, 2025 8:53 pm
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pmAPIC ticks is taking more time than expected.
Bare metal or virtual machine? Timers in virtual machines don't always tick at the same rate.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

#define APIC_TIMER_VECTOR  48                   // APIC Timer Interrupt Vector 0x30
#define APIC_IRQ           16                   // APIC Timer Interrupt Request 48 - 32 = 16 (0x10)
How are these two different numbers? The APIC timer delivers its interrupts directly to the CPU, so there should only be one number for the interrupt vector it uses.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

// Divider values
#define DIV_BY_2    0b000   // 0x0
#define DIV_BY_4    0b001   // 0x1
#define DIV_BY_8    0b010   // 0x2
#define DIV_BY_16   0b011   // 0x3
#define DIV_BY_32   0b100   // 0x4
#define DIV_BY_64   0b101   // 0x5
#define DIV_BY_128  0b110   // 0x6
#define DIV_BY_1    0b111   // 0x7
The divide configuration register uses bits 0, 1, and 3. These constants use bits 0, 1, and 2.
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_1);
Since you're writing those constants directly into the divide configuration register, the divider is not configured the way you expect.
Thankyou for reply.
The two Number APIC_TIMER_VECTOR and APIC_IRQ are different functionalities. APIC_TIMER_VECTOR is for Register Interrupt and APIC_IRQ is for to install apic_timer_handler from an array into the Interrupt.
Currently I am using QEmu . I am change into apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_1); into apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_16);

I do not understand about your comments
gamingjam60 wrote: Sat Jul 19, 2025 1:52 pm

Code: Select all

apic_write(APIC_TIMER_DIV_REGISTER , DIV_BY_1);
Since you're writing those constants directly into the divide configuration register, the divider is not configured the way you expect.
[/quote]
Can you explain a littile bit more.

Here My Output:

Code: Select all

UTC TIME(start): 17294811
UP Time: 3
UP Time: 4
UTC TIME(END): 17294851
Here You can see 2 sec measures by LAPIC Timer is taking 17294851 - 17294811 = 40 sec where later is measured by RTC timer.
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: Why apic timer ticks is taking too long ?

Post by Octocontrabass »

gamingjam60 wrote: Sat Jul 19, 2025 10:11 pmCurrently I am using QEmu .
Is QEMU using hardware virtualization? If not, timers may not tick at exactly the speed you expect.
gamingjam60 wrote: Sat Jul 19, 2025 10:11 pmCan you explain a littile bit more.
Look at figure 12-10 on page 12-17 of volume 3A of the current version (088US) of the Intel SDM.
Post Reply