OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 5:35 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: QEMU HPET every CCR Register has the same value
PostPosted: Sun Aug 05, 2018 3:16 am 
Offline

Joined: Wed Aug 01, 2018 9:09 pm
Posts: 10
I am using QEMU to test my x86_64 kernel which is loaded via grub2. I am trying to get my HPET timer working. I have 3 timers defined. However, each Timer N Configuration and Capability Register shows the same value of 0x400000030. This means the top 32 bits (the IRQ routing capability for all 3 timers is 0x4 which means they can only route through irq 2.

I have disabled the legacy routing in the GCR so it should not be going through IRQ 2. And even if it was, does it make sense that all 3 timers need to route through the same IRQ channel (2)? Does that even make sense? Is there a bug with QEMU and HPET, or do I likely have a bug in my code? Also, when I run hpet_init and then hpet_start_oneshot, I get a Segment Not Present exception with error code 0xc22 which represents idt index 388 which does not even exist.

For reference, my HPET code is below:

Code:
#define _hpet_MAX_TIMERS 32

#define _hpet_REGISTER_SIZE 0x8
#define _hpet_GCIDR 0
#define _hpet_GCR 0x2
#define _hpet_MCR 0x1e
#define _hpet_CCR 0x20
#define _hpet_CVR 0x21

#define _hpet_get_register(reg)                                                \
    (hpet_info.base_addr + _hpet_REGISTER_SIZE * (reg))
#define _hpet_nth_cvr(n) (_hpet_CVR + 4 * (n))
#define _hpet_nth_ccr(n) (_hpet_CCR + 4 * (n))

typedef struct {
    uint8_t rev_id : 8;     // 0-7
    uint8_t num_timers : 5; // 8 - 12
    // 1 for 64 bit, 0 for 32 bit
    uint8_t main_counter_width : 1;     // 13
    uint8_t reserved : 1;               // 14
    uint8_t legacy_capable : 1;         // 15
    uint16_t vendor_id : 16;            // 16 - 31
    uint32_t counter_clock_period : 32; // 32 - 63
}__attribute__((packed)) gcidr_t;

typedef struct {
    uint8_t reserved1 : 1; // 0
    // edge triggered (0) or level triggered (1)
    uint8_t interrupt_type : 1;   // 1
    uint8_t interrupt_enable : 1; // 2
    uint8_t set_periodic : 1;     // 3
    // Read only 1 if periodic supported
    uint8_t supports_periodic : 1; // 4
    // 1 = 64 bits, 0 = 32 bits
    uint8_t enable_64_bit : 1; // 5
    // If periodic, can set to 1 to write to accumulator
    uint8_t set_accumulator : 1;          // 6
    uint8_t reserved2 : 1;                // 7
    uint8_t force_32_bit : 1;             // 8
    uint8_t irq_idx : 5;                  // 9 - 13
    uint8_t enable_fsb : 1;               // 14
    uint8_t supports_fsb : 1;             // 15
    uint16_t reserved3 : 16;              // 16 - 31
    uint32_t irq_routing_capability : 32; // 32 - 63
} __attribute__((packed)) ccr_t;

typedef struct {
    uint8_t enable_main_counter : 1;   // 0
    uint8_t enable_legacy_routing : 1; // 1
    uint64_t reserved : 62;            // 2 - 63
} __attribute__((packed)) gcr_t;

// My own type for storing timer information
typedef struct {
    uint8_t present : 1;
    uint8_t periodic : 1;
    uint8_t reserved : 6;
} __attribute__((packed)) ntimers_t;

static struct {
    ntimers_t timers[32];
    void *base_addr;
    uint32_t clock_period;
    uint16_t min_tick;
} hpet_info;

#define _hpet_frequency (femtosecond / hpet_info.clock_period)

static ALWAYS_INLINE void _hpet_init_gcr() {
    volatile gcr_t *gcr = _hpet_get_register(_hpet_GCR);
    gcr->enable_legacy_routing = 0;
}

static ALWAYS_INLINE void _hpet_init_timers(const gcidr_t gcidr) {
    int idt_idx = k_HPET_INTERRUPT_IDX_BASE;
    for (int i = 0; i <= gcidr.num_timers; ++i) {
        ntimers_t timer;
        int ccr_reg_id = _hpet_nth_ccr(i);
        volatile ccr_t *ccr = _hpet_get_register(ccr_reg_id);
        ccr->interrupt_type = 0;
        ccr->enable_64_bit = 1;
        ccr->set_accumulator = 0;
        ccr->reserved3 = 0;
        ccr->force_32_bit = 0;
        timer.present = 1;
        timer.periodic = ccr->supports_periodic;
        hpet_info.timers[i] = timer;
        int irq_idx = ctz(ccr->irq_routing_capability);
        ccr->irq_idx = irq_idx;
        ioapic_set_irq(irq_idx, apic_get_id(), idt_idx++);
    }
}

void hpet_start_one_shot(uint64_t time_ns) {
    // TODO choose a timer in a better way
    int timer_id = 0;
    uint64_t time_fs = max(hpet_info.clock_period, ns_to_fs(time_ns));
    ccr_t *ccr = _hpet_get_register(_hpet_nth_ccr(timer_id));
    ccr->set_periodic = 0;
    uint64_t mcr = *(uint64_t *)_hpet_get_register(_hpet_MCR);
    uint64_t *cvr = _hpet_get_register(_hpet_nth_cvr(timer_id));
    *cvr = mcr + time_fs;
    ccr->interrupt_enable = 1;
}

int hpet_init(const hpet_infov1_t *hpet) {
    hpet_info.base_addr = (void *)hpet->address.address;
    hpet_info.min_tick = hpet->minimum_tick;
    memset(&hpet_info.timers, 0, sizeof(hpet_info.timers));
    frame_t frame = {.addr = (size_t)hpet_info.base_addr};
    if (paging_identity_map(frame, paging_ENTRY_SET_WRITABLE(0)) == 1) {
        return 1;
    }
    _hpet_init_gcr();
    const gcidr_t gcidr = *(volatile gcidr_t *)_hpet_get_register(_hpet_GCIDR);
    _hpet_init_timers(gcidr);
    return 0;
}


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Sun Aug 05, 2018 6:01 pm 
Offline
Member
Member
User avatar

Joined: Sat Nov 22, 2014 6:33 pm
Posts: 934
Location: USA
Hi,

I could never get QEMU to work with the HPET either. However, I have an older version since my development platform is WinXP and QEMU support for WinXP is long gone.

I ran a quick test and QEMU version 2.7 (https://qemu.weilnetz.de/) returns all zeros for the high dword of this register, meaning no interrupts supported, and all three returned 0x30 in the bottom byte.

I ran the same test on real hardware and got the following:
Code:
  Timer 0: 0x00F00000_00000030
  Timer 1: 0x00F00000_00000000
  Timer 2: 0x00F00800_00000000

Ben
- http://www.fysnet.net/the_system_core.htm


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Sun Aug 05, 2018 10:28 pm 
Offline

Joined: Wed Aug 01, 2018 9:09 pm
Posts: 10
BenLunt wrote:
Hi,

I could never get QEMU to work with the HPET either. However, I have an older version since my development platform is WinXP and QEMU support for WinXP is long gone.

I ran a quick test and QEMU version 2.7 (https://qemu.weilnetz.de/) returns all zeros for the high dword of this register, meaning no interrupts supported, and all three returned 0x30 in the bottom byte.

I ran the same test on real hardware and got the following:
Code:
  Timer 0: 0x00F00000_00000030
  Timer 1: 0x00F00000_00000000
  Timer 2: 0x00F00800_00000000

Ben
- http://www.fysnet.net/the_system_core.htm


Good to know this is a QEMU issue. Thanks for confirming.

Wondering if anyone has a work around? Is it that HPET timers can't be set up at all on QEMU, or is there some hard coded IRQ's I should route it through? Even with legacy routing enabled on QEMU, I could not get the HPET to fire correctly, so maybe my code is buggy?

Maybe it's time to test on real hardware...


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Mon Aug 06, 2018 12:23 am 
Offline
Member
Member

Joined: Wed Oct 26, 2011 12:00 pm
Posts: 202
It is not an error if the registers return they only support the same IRQ. The interrupts lines can be shared between the different comparators in the HPET. When the interrupt occurs you can easily check which comparator actually fired the interrupt in the interrupt handler and then handle it accordingly.

If the interrupt-map in the upper DWORD returns 0 for no interrupt supported - I suspect there is an issue with your read function, do you read on 32/64 bit boundaries?

_________________
mollenos | gracht (protocol library) | vioarr (window-manager) | bake (package manager)


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Mon Aug 06, 2018 1:23 am 
Offline
Member
Member

Joined: Thu May 17, 2007 1:27 pm
Posts: 999
I am on mobile so I cannot investigate this in detail but I can confirm that HPET works fine on qemu: Both the legacy replacement and I/O APIC routing work as advertised. qemu does not support FSB delivery for the HPET (at least in the configurations that I test on).

BenLunt's values from real HW also look really suspicious; I've never seen such masks (on Intel hardware). At least the "usual" IRQs 2 and 8 f not all should be supported. IIRC on some chipsets all IRQs are supported.

_________________
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Mon Aug 06, 2018 11:18 am 
Offline

Joined: Wed Aug 01, 2018 9:09 pm
Posts: 10
MollenOS wrote:
When the interrupt occurs you can easily check which comparator actually fired the interrupt in the interrupt handler and then handle it accordingly.


How can I check that?

MollenOS wrote:
If the interrupt-map in the upper DWORD returns 0 for no interrupt supported - I suspect there is an issue with your read function, do you read on 32/64 bit boundaries?


I read on 64 bit boundaries (the code above does not, but I updated this so I copy the 64 bit register value into a local and then read it). Maybe I should be using inline assembly mov to read these register values rather than using pointer dereferencing? If so, can someone point me to an example on how to do this?


Top
 Profile  
 
 Post subject: Re: QEMU HPET every CCR Register has the same value
PostPosted: Mon Aug 06, 2018 11:58 am 
Offline
Member
Member

Joined: Wed Oct 26, 2011 12:00 pm
Posts: 202
You need to check the interrupt status register and use the 32 first bits to detect which bits are set. They tell you which counter fires the interrupt.

Make sure you use volatile pointers and try to see if you see any differences by only doing 32 bit reads instead, qemu might not support 64 but accesses which can explain the empty dword

_________________
mollenos | gracht (protocol library) | vioarr (window-manager) | bake (package manager)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot] and 82 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group