(由于之前的blog已经关闭了,所以将此文章迁移至这里,并非转载)
HPET
This page is not meant as a full description of HPET, only as a lightweight introduction. If you need any information not covered by this article, consult the HPET specification.
这篇文章并没有完全的去说明HPET,只是简单地概述了一下。如果你需要的内容这里没有讲述到,请参考HPET的说明文档。
Preface
HPET, or High Precision Event Timer, is a piece of hardware designed by Intel and Microsoft to replace older PIT and RTC. It consists of (usually 64-bit) main counter (which counts up), as well as from 3 to 32 32 or 64 bit wide comparators. HPET is programmed using memory mapped IO, and the base address of HPET can be found using ACPI.
HPET(High Precision Event Timer,高精度时钟),是Intel和微软为了替换之前PIT或RTC(都可以当定时器用的一个东西)所设计的一个硬件。它由一个主的计数器和比较器组成,计数器一般是64位的,比较器至少有3个最多32个(这个我没考察过,只是翻译),通常比较器是32位或64位的。HPET通过内存映射IO来操作,内存的基地址可以从ACPI中找到。
Detecting HPET using ACPI
The HPET specification defines an ACPI 2.0 table that is to be used to detect presence, address and capabilities of HPET present in the system. If this table doesn’t exist, you should assume there is no HPET and you should fall back to PIT.
HPET规范中规定了会定义一个ACPI 2.0表(在ACPI中),可以通过这个表来检测硬件中是否存在HPET,并且可以获取到基地址以及HPET功能上的一些信息。如果这个表没有,那只能用PIT了。
struct address_structure
{
uint8_t address_space_id; // 0 - system memory, 1 - system I/O
uint8_t register_bit_width;
uint8_t register_bit_offset;
uint8_t reserved;
uint64_t address;
} __attribute__((packed));
struct description_table_header
{
char signature[4]; // 'HPET' in case of HPET table
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oemid[6];
uint64_t oem_tableid;
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
} __attribute__((packed));
struct hpet : public description_table_header
{
uint8_t hardware_rev_id;
uint8_t comparator_count:5;
uint8_t counter_size:1;
uint8_t reserved:1;
uint8_t legacy_replacement:1;
pci_vendor_t pci_vendor_id;
address_structure address;
uint8_t hpet_number;
uint16_t minimum_tick;
uint8_t page_protection;
} __attribute__((packed));
HPET – timer vs comparators
There is just one up counting main counter in the timer, but interrupt generation is handled by comparators. There’re from 3 to 32 comparators, and the exact amount is indicated by comparator_count
field in the above hpet
structure. Keep in mind you have to initialize both the main counter and all of the comparators. Also, the routing as well as allowed routing of comparator interrupts is independent, so you have to detect and set it up for each of them individually. More information on this procedure is provided further in the text.
HPET中只有一个通过定时器自加的计数器,通过不同的比较器来产生中断。(可以理解为只有一个基准Timer,并且自加计数器的值,然后通过比较器来比较是否触发中断。)HPET中大约有3-32个比较器,具体的值时候hpet
结构体中的comparator_count
字段来设置。必须要同时初始化主计数器和所有的比较器。每个比较器所触发的中断都是可以独立配置的,所以一般情况下都需要为每个比较器单独的设置中断。在下文中将会继续说明这部分内容。
HPET operating modes
HPET offers two operating modes: one-shot (also called “non-periodic” by the specification) and periodic mode.
HPET提供两种操作模式:单次触发(也叫非周期触发)和周期触发。
One-shot mode
In non-periodic mode, the OS programs one of timer’s comparator registers with value of main counter that is to trigger an interrupt. If the timer is set to 32 bit mode, it will also generate an interrupt when the counter wraps around. The comparator register’s value is never written by the hardware, you are free to write to it and read from it at any time, therefore you can change at what value in the main counter the interrupt will be generated.
在单次触发模式中,操作系统写一个值到比较器里面,当主计数器达到该值的时候,就会产生一个中断。如果定时器设置为32位模式的情况下,当计数器溢出的时候也会产生一个中断。比较器的寄存器可以随时的进行读写,因为硬件不会操作它。这样你就可以随意的设置定时器了。
Every comparator in HPET must support non-periodic mode.
每个HPET的比较器,必须支持单次触发模式,估计是文档规定的(反正我是没考证过)。
Periodic mode
Periodic mode is more tricky than non-periodic mode. For periodic mode, similarly to one-shot mode, you write a value at which an interrupt shall be generated to the comparator register. When the interrupt is generated, however, the hardware will increase the value in comparator register by the last value written to it! This is a consequence of HPET’s main counter being up-counting.
周期触发模式相比单次触发模式更加方便(tricky这个单次真不好翻译,意会吧)。周期触发模式和单次触发模式的操作很像,都是写比较器的寄存器进行操作。但是,硬件会在最后一次写如寄存器的值后自加这个值,根据HPET计数器。
So, if the main counter’s value is 12345 when we set the timer up, and we write 12456 to comparator (i.e. the interrupt should trigger 111 time units from now), when the interrupt triggers, 12456 will be added to the comparator register, so it will become 24912, which is 12456 time units from the first interrupt. There are two techniques to deal with this problem; they will both be described in later part of the article.
例如,如果主计数器当前是12345,我们写12456到比较器的寄存器中(中断将会在111个周期后触发),当中断触发后,比较器的寄存器会变成24912(这个数来自12456+12456,也就是比较器的寄存器中的值又增加了一次上次写入的值)。有两种方法可以解决这个问题,文本将在后面进行说明。
Comparators are NOT required to support this mode; you must detect this capability when initializing a comparator. More information on this is provided further in the article.
比较器并非必须支持这种模式,所以在初始化比较器时,必须检测是否支持这个功能。具体信息应该咨询硬件架构提供商(自己查文档吧)。
Interrupt routing
HPET supports three interrupt mapping options: “legacy replacement” option, standard option, and FSB option.
HPET支持三种中断映射模式,原始模式,标准模式,FSB模式。
“Legacy replacement” mapping
In this mapping, HPET’s timer (comparator) #0 replaces PIT interrupts, whereas timer #1 replaces RTC’s interrupts (in other words, PIC and RTC will no longer cause interrupts). While the HPET specification provides the following table describing the routing of timers #0 and #1 in this mapping, it is recommended to at least check the routing of IRQ0 and IRQ8 to I/O APIC in ACPI tables.
在原始模式中,定时器(比较器)#0替换PIT的中断,定时器#1替换RTC的中断(意思就是,HPET替换PIC和RTC,并且PIC和RTC不会产生中断)。下表来自HPET的说明,其中规定了中断映射的方式。建议具体确认下IRQ0和IRQ8在IOAPIC和ACPI表的信息。
Timer | PIC mapping | IOAPIC mapping |
---|---|---|
0 | IRQ0 | IRQ2 |
1 | IRQ8 | IRQ8 |
N | As per IRQ Routing Field | As per IRQ Routing Field |
Standard mapping
In standard mapping, each timer has its own interrupt routing control. Allowed I/O APIC inputs are found by reading timer’s capabilities register.
标准模式中,每个定时器(比较器)有自己的中断控制,通过定时器(比较器)的寄存器及IOAPIC进行控制(就是这么个意思,别纠结翻译了)。
FSB mapping
This mapping is almost identical to PCI’s Message Signaled Interrupts. The “Timer N FSB Interrupt Route Register” which defines how FSB interrupts are configured can be found in the specification. FSB interrupts are enabled using “Tn_FSB_EN_CNF” field in timer’s configuration register. This mapping mode will not be further discussed in this
先解释下FSB,就是前端总线的意思。
这种模式就是大多数使用的PCI消息的方式触发中断(其实本质是PCI内存写事件触发的中断,大体参考下而已)。根据文档来具体操作每个定时器的中断配置寄存器的配置。使用“Tn_FSB_EN_CNF”寄存器来控制是否使用FSB中断模式。本文中将不对这种方式进行讲解(外文就这么写的,我也不补充了,如果有心情,会以后更新)。
HPET registers
The following table and field descriptions can also be found in the specification. “Offset” means offset from the address defined in address field of hpet struct. The following table skips reserved registers defined in the specification.
下表所描述的寄存器信息可以在HPET文档中找到。“Offset”指的事hpet结构体在内存中的偏移(根据我的经验 ,一般是指物理内存)。下表中已经将没用的寄存器给去掉了。
Offset | Register | Type |
---|---|---|
0x000 – 000×7 | General Capabilities and ID Register | Read only |
0x010 – 0x017 | General Configuration Register | Read/write |
0x020 – 0x027 | General Interrupt Status Register | Read/write clear |
0x0F0 – 0x0F7 | Main Counter Value Register | Read/write |
(0x100 + 0x20 * N) – (0x107 + 0x20 * N) | Timer N Configuration and Capability Register | Read/write |
(0x108 + 0x20 * N) – (0x10F + 0x20 * N) | Timer N Comparator Value Register | Read/write |
(0x110 + 0x20 * N) – (0x117 + 0x20 * N) | Timer N FSB Interrupt Route Register | Read/write |
General Capabilities and ID Register
Bits | Name | Description |
---|---|---|
63:32 | COUNTER_CLK_PERIOD | Main counter tick period in femptoseconds (10^-15 seconds). Must not be zero, must be less or equal to 0x05F5E100, or 100 nanoseconds. |
31-16 | VENDOR_ID | This field should be interpreted similarly to PCI’s vendor ID. |
15 LEG_RT_CAP If this bit is 1, HPET is capable of using “legacy replacement” mapping.|
|14 |Reserved| –|
|13 |COUNT_SIZE_CAP |If this bit is 1, HPET main counter is capable of operating in 64 bit mode.
|12:8| NUM_TIM_CAP |The amount of timers – 1.|
|7:0 |REV_ID |Indicates which revision of the function is implemented; must not be 0.|
General Configuration Register
Bits | Name | Description |
---|---|---|
63:2 | Reserved | – |
1 | LEG_RT_CNF | 0 – “legacy replacement” mapping is disabled1 – “legacy replacement” mapping is enabled |
0 | ENABLE_CNF | Overall enable.0 – main counter is halted, timer interrupts are disabled1 – main counter is running, timer interrupts are allowed if enabled |
General Interrupt Status Register
Bits | Name | Description |
---|---|---|
63:32 | Reserved | – |
n | Tn_INT_STS | The functionality is dependent of whether edge or level-triggered mode is used for timer #n.For level-triggered: the default value is 0. When a corresponding timer interrupt is active, this bit is set. If it is set, software can clear it by writing 1 to this bit. Writes of 0 have no effect.For edge-triggered: this bit should be ignored. It is always set to 0. |
Main Counter Value Register
Bits 63:0 of this register are called MAIN_COUNTER_VAL. Writes to this register should only be done when the counter is halted (ENABLE_CNF = 0). Reads will return current value of the main counter. 32-bit counters will always return 0 for the upper 32 bits. If 32 bit reads are performed on 64 bit counter, consult 2.4.7 in the specification for instructions how to do it safely. It is recommended to use 32 bit counter when on 32-bit only software.
只有在计数器不使能(停止)的情况下才可以操作 MAIN_COUNTER_VAL的0-63位。可以直接从这些寄存器中读取出当前的值。在32位计数器模式下,前32位将都是0。如果一定要使用32位的方式去操作一个64位的计数器,在HPET文档的2.4.7节中将会说明如何才能安全的进行操作。再次建议在如果要使用32位的方式去操作,尽量将计数器配置为32位模式。
Timer N Configuration and Capability Register
Bits | Name | Description |
---|---|---|
63:32 | Tn_INT_ROUTE_CAP | Timer n Interrupt Routing Capability. If bit X is set in this field, it means that this timer can be mapped to IRQX line of I/O APIC. |
31:16 | Reserved | – |
15 | Tn_FSB_INT_DEL_CAP | If this read-only bit is 1, this timer supports FSB interrupt mapping. |
14 | Tn_FSB_EN_CNF | If this bit is set to 1, this timer will use FSB interrupt mapping. |
13:9 | Tn_INT_ROUTE_CNF | This field indicates I/O APIC routing. Allowed values can be determined using Tn_INT_ROUTE_CAP. If an illegal value is written, then value read back from this field will not match the written value. |
8 | Tn_32MODE_CNF | For 64-bit timer, if this field is set, the timer will be forced to work in 32-bit mode. Otherwise it has no effect. |
7 | Reserved | – |
6 | Tn_VAL_SET_CNF | This field is used to allow software to directly set periodic timer’s accumulator. Detailed explanation is provided further in the article. |
5 | Tn_SIZE_CAP | If this read-only bit is set to 1, the size of the timer is 64-bit. Otherwise, it’s 32-bit. |
4 | Tn_PER_INT_CAP | If this read-only bit is set to 1, this timer supports periodic mode. |
3 | Tn_TYPE_CNF | If Tn_PER_INT_CAP is 1, then writing 1 to this field enables periodic timer and writing 0 enables non-periodic mode. Otherwise, this bit will be ignored and reading it will always return 0. |
2 | Tn_INT_ENB_CNF | Setting this bit to 1 enables triggering of interrupts. Even if this bit is 0, this timer will still set Tn_INT_STS. |
1 | Tn_INT_TYPE_CNF | 0 – this timer generates edge-triggered interrupts.1 – this timer generates level-triggered interrupts. When the interrupt is generated, Tn_INT_STS is set. If another interrupt occurs before that bit is cleared, the interrupt will remain active. |
0 | Reserved | – |
Timer N Comparator Value Register
Bits 63:0 (or 31:0, if the timer operates in 32 bit mode) are used to compare with main counter to check if an interrupt should be generated.
比较器使用寄存器中的值同主计数器进行比较(64位模式比较64位,32位模式比较32位),来判断是否产生中断。
Initialization
The following is the procedure you need to perform to initialize main counter and comparators in order to receive interrupts.
下面这段文章将会告诉你如何去初始化主计数器和比较器,并且使其能产生所需要的中断。
General initialization: 1. Find HPET base address in ‘HPET’ ACPI table. 2. Calculate HPET frequency (f = 10^15 / period). 3. Save minimal tick (either from ACPI table or configuration register). 4. Initialize comparators. 5. Set ENABLE_CNF bit.
全局初始化:1.在ACPI的HPET表中,找到HPET的基地址。2.计算HPET的频率(f = 10^15 / period)。3.保存最小的tick(无论是从ACPI表或配置寄存器)。4.初始化比较器。5.设置时能(ENABLE_CNF)位。
Timer N initialization: 1. Determine if timer N is periodic capable, save that information to avoid re-reading it every time. 2. Determine allowed interrupt routing for current timer and allocate an interrupt for it.
定时器N初始化:1.检测定时器N所支持的触发模式,保存这部分信息,避免每次都需要从新读。2.检测所支持的中断模式,并且分配一个中断。
I am enabling the timers only when I actually use them, so there’s no “real” initialization of comparators here.
外文原文的坐着习惯每次都在实际需要使用的时候采取时能具体的定时器,所以下面并没有真正的初始化具体的比较器,应该是具体需要使用的时候才真正的时能。
Keep in mind that allowed interrupt routing may be insane. Namely, you probably want to use some of ISA interrupts – or, at very least, be able to use them at one point unambiguously. Last time I checked VirtualBox allowed mappings for HPET, it allowed every timer to be routed to any of 32 I/O APIC inputs present on the system. Knowing how buggy hardware can be, I wouldn’t be too surprised if there exists a PC with HPET claiming that input #31 is allowed, when there are only 24 I/O APIC inputs. Be aware of this when choosing interrupt routing for timers.
上面大意,我理解为瞎乱使用中断路由基本会祸害死人。IOAPIC中断输入是公用的,所以别用重复了,并且一定要规划好怎么映射以及怎么使用,或者说如何复用。
Using timers
One-shot mode
To enable one-shot mode:
下面代码指出如何配置单次触发模式的定时器。
// "time" is time in femptoseconds from now to interrupt
if (time < COUNTER_CLK_PERIOD)
{
time = adjust_time(time);
}
write_register_64(timer_configuration(n), (ioapic_input << 9) | (1 << 2));
write_register_64(timer_comparator(n), read_register(main_counter) + time);
I hope the above code is obvious. If it’s not, please analyze the meaning of specific fields in registers used above.
外文原文作者懒了,懒得说明每行的具体意思。大体能猜出来就行了。
Periodic mode
To enable periodic mode:
下面代码指出如何配置周期触发模式的定时器。
// "time" is time in femptoseconds from now to interrupt
if (time < COUNTER_CLK_PERIOD)
{
time = adjust_time(time);
}
write_register_64(timer_configuration(n), (ioapic_input << 9) | (1 << 2) | (1 << 3) | (1 << 6));
write_register_64(timer_comparator(n), read_register(main_counter) + time);
write_register_64(timer_comparator(n), time);
This snippet requires some more comments.
外文原文作者良心发现了,需要给一些注释翻译。
Bit 2 is the same as above, Interrupt Enable. Bit 3 is also quite straightforward – 1 means periodic timer. But we’ve also set bit 6. Why?
bit 2和之前配置的相同,都是为了使能中断。bit3也是需要配置的,配置为1代表的是使用周期模式。但是为何需要配置Bit 6,外文作者引用如下内容去解释。
Let’s take a look at quote from the HPET specification:
外文作者又懒了,直接抄书了。
Timer n Value Set: […] Software uses this read/write bit only for timers that have been set to periodic mode. By writing this bit to a 1, the software is then allowed to directly set a periodic timer’s accumulator.
Software does NOT have to write this bit back to 0 (it automatically clears). Software should not write a 1 to this bit position if the timer is set to non-periodic mode.
This means that next write to timer N comparator register will have the usual meaning, while second next write will write directly to the accumulator. I believe that the wording could’ve been much better.
Bit 6的意思是说第一次写比较器的寄存器是通常的意义,就是写个需要比较的数,到达的时候就触发中断,但是连续的第二次写比较器的寄存器意义就改变了,之前不是说过他触发中断后,会把值增加么?设置完Bit 6后,连续的第二次写比较器的寄存器的值就是增加的值,这样就将之前留下的问题给解决了。
译者注,连续两次写寄存器的代码中间一定要做一些保护,因为有些编译器会进行优化,直接将上面那句在编译阶段就给扔了,所以一定要做一些保护,避免自动优化造成的错误。
结尾,我是Boris,留下邮箱为方便大家交流xuezhe.liu@hotmail.com。有关HPET的问题可以进行咨询及讨论。
本文翻译自HPET – OSDev Wiki。如需原文文档,请参考链接http://wiki.osdev.org/HPET。