HPET

来自osdev
跳到导航 跳到搜索
本页不是对HPET的完整描述,只是一个轻量级的介绍。 如果你需要本文未涵盖的任何信息,请参考HPET规范

HPET或高精度事件定时器(High Precision Event Timer),是英特尔和微软设计的一款硬件,用于取代较旧的 PITRTC。 它由(通常为64位)主计数器(递增计数)以及3到32位或64位宽的比较器组成。 HPET使用内存映射IO进行编程,使用ACPI可以找到HPET的基地址。

使用ACPI检测HPET

那个HPET规范定义了一个ACPI 2.0表,用于检测系统中存在的HPET的存在、地址和功能。 如果该表不存在,你应该假设没有HPET,并且应该回退到PITAPIC Timer

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;
    uint16_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)

定时器中只有一个递增计数主计数器,但中断生成由*比较器*处理。 有3到32个比较器,确切的数量由上面hpet结构中的compasator_count字段表示。 请记住,你必须初始化主计数器和所有比较器。 此外,比较器中断的路由以及允许的路由是独立的,因此你必须为每个中断单独检测和设置它。 正文中进一步提供了有关此过程的更多信息。

HPET操作模式

HPET提供两种操作模式: 单次(one-shot,规范中也称为“非周期”)和周期模式。

One-shot模式

在非周期模式下,操作系统用主计数器的值对定时器的一个比较器寄存器进行编程,以触发中断。 如果定时器设置为32位模式,则计数器回绕(wraps around)时也会产生中断。 比较器寄存器的值永远不会被硬件写入,你可以随时对其进行写入和读取,因此你可以更改主计数器中将生成中断的值。

HPET中的每个比较器都必须支持非周期模式。

周期模式

周期模式比非周期模式更棘手。 对于周期模式,类似于一次触发模式,写入一个值,在该值处,将向比较器寄存器生成中断。 但是,当产生中断时,硬件将将比较器寄存器中的值增加最后一个写入它的值! 这是HPET的主要计数器向上计数的结果。

因此,如果我们设置定时器时,主计数器的值是12345,并且我们将12456写入比较器(即,从现在起,中断将触发111个时间单位),当中断触发时,12456将“添加”到比较器寄存器,因此它将变为24912,即从第一个中断开始的12456个时间单位。 有两种技术可以处理此问题;这两种技术都将在本文的后面部分介绍。

不需要 比较器来支持此模式; 初始化比较器时,你必须检测到此功能。 本文将进一步提供有关这方面的更多信息。

中断例程(Interrupt routing)

HPET支持三种中断映射选项: “遗留替换(legacy replacement)” 选项、标准选项和FSB选项。

“旧版替换”映射

在此映射中,HPET的定时器(比较器)#0替换PIT中断,而定时器#1替换RTC的中断(换句话说,PIC和RTC将不再导致中断)。 虽然HPET规范提供了下表描述了此映射中定时器 #0和 #1的路由,但建议至少检查ACPI表中IRQ0和IRQ8到I/O APIC的路由例程(routing)。

定时器 PIC映射 IOAPIC映射
0 IRQ0 IRQ2
1 IRQ8 IRQ8
N 根据IRQ路由字段 根据IRQ路由字段

标准映射

在标准映射中,每个定时器都有自己的中断路由控制。 通过读取定时器的能力寄存器可以找到允许的I/O APIC输入。

FSB映射

这种映射几乎与PCI的消息信号中断相同。 定义如何配置FSB中断的“定时器N FSB中断路由寄存器”可在规范中找到。 使用定时器配置寄存器中的 “Tn_FSB_EN_CNF” 字段启用FSB中断。 本文将不再进一步讨论这种映射模式。

HPET寄存器

下表和字段描述也可以在规范中找到。 “偏移量”指与hpetstruct的address字段中定义的地址的偏移量。 下表跳过规范中定义的保留寄存器。

偏移 寄存器 类型
0x000 - 0x007 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

名称 描述
63:32 COUNTER_CLK_PERIOD 主计数器计时周期,单位为飞秒(10^-15s)。 不得为零,必须小于或等于0x05F5E100或100纳秒。
31-16 供应商ID 此字段的解释应与PCI的供应商ID类似。
15 LEG_RT_CAP 如果此位为1,则HPET能够使用 “遗留替换” 映射。
14 保留 -
13 COUNT_SIZE_CAP 该位为1时,HPET主计数器可以在64位模式下运行。
12:8 NUM_TIM_CAP 定时器的数量为-1。
7:0 REV_ID

表示实现的是哪个版本的功能,不能为0

General Configuration Register

名称 描述
63:2 保留 -
1 LEG_RT_CNF 0-“旧版替换”映射已禁用

1-启用“传统替换”映射

0 ENABLE_CNF 整体(Overall)启用。

0-主计数器停止,定时器中断被禁用

1-主计数器正在运行,如果启用,则允许定时器中断

General Interrupt Status Register

名称 描述
63:32 保留 -
n Tn_INT_STS 该功能取决于定时器#n使用的是边缘触发模式还是电平触发模式。

对于电平触发:,默认值为0。 当相应的定时器中断处于活动状态时,将设置此位为1。 如果设置了1,软件可以通过向该位写入1来清除它。 写入0不起作用。

对于边缘触发: 应忽略此位。 它始终设置为0。

Main Counter Value Register

该寄存器的位63:0称为MAIN_COUNTER_VAL。 仅当计数器暂停 (ENABLE_CNF = 0) 时,才应执行对该寄存器的写入操作。 Reads将返回主计数器的当前值。32位计数器对于较高的32位总是返回0。 如果在64位计数器上执行32位读取,请参考规范中的2.4.7,了解如何安全执行该操作的说明。 建议在仅32位软件上使用32位计数器。

Timer N Configuration and Capability Register

名称 描述
63:32 Tn_INT_ROUTE_CAP 定时器n中断路由功能。 如果在该字段中设置了位X,则意味着该定时器可以映射到I/O APIC的IRQX线。
31:16 Reserved -
15 Tn_FSB_INT_DEL_CAP 如果此只读位为1,则此定时器支持FSB中断映射。
14 Tn_FSB_EN_CNF 如果该位设置为1,该定时器将使用FSB中断映射。
13:9 Tn_INT_ROUTE_CNF 表示I/O APIC路由。可以使用TN_INT_ROUTE_CAP确定允许值。如果写入非法值,则从此字段读回的值将与写入的值不匹配。
8 Tn_32MODE_CNF 对于64位定时器,如果设置了此字段,则定时器将被强制以32位模式工作。否则没有效果。
7 Reserved -
6 Tn_VAL_SET_CNF 此字段用于允许软件直接设置周期定时器的累加器。详细解释将在本文中进一步提供。
5 Tn_SIZE_CAP 如果该只读位设置为1,则定时器的大小为64位。否则,它是32位的。
4 Tn_PER_INT_CAP 如果此只读位设置为1,则此定时器支持周期性模式。
3 Tn_TYPE_CNF 如果Tn_PER_INT_CAP为1,则向该字段写入1将启用周期定时器,写入0将启用非周期模式。否则,该位将被忽略,读取它将始终返回0。
2 Tn_INT_ENB_CNF 将此位设置为1可启用中断触发。即使此位为0,此定时器仍将设置TN_INT_STS。
1 Tn_INT_TYPE_CNF 0 - this timer generates edge-triggered interrupts.

1-此定时器生成电平触发的中断。当产生中断时,设置Tn_INT_STS。如果在清除该位之前发生另一个中断,则该中断将保持活动状态。

0 Reserved -

Timer N Comparator Value Register

位63:0(或31:0,如果定时器在32位模式下工作)用于与主计数器进行比较,以检查是否应生成中断。

初始化

以下是初始化主计数器和比较器以接收中断所需执行的过程。

一般初始化:

1. 在‘HPET’ACPI表中查找HPET基地址。

2. 计算HPET频率 (f = 10 ^ 15/周期)。

3.保存最小刻度(来自ACPI表或配置寄存器)。

4. 初始化比较器。

5. 设置ENABLE_CNF位

定时器N初始化:

1.确定定时器N是否具有周期性功能,保存该信息以避免每次重新读取。

2.确定当前定时器的允许中断路由,并为其分配一个中断。

我只在实际使用定时器时才启用它们,所以这里没有比较器的“真正”初始化。

请记住,允许的中断例程 可能跑飞。 也就是说,你可能还需要使用一些ISA中断-或者至少能够在某一点上明确使用它们。 上次我检查VirtualBox允许的HPET映射时,它允许将每个定时器路由到系统上存在的32个I/O APIC输入中的“任意”。 知道硬件有多有漏洞,我不会太惊讶,如果有一台带有HPET的PC声称允许输入#31,而实际上只有24个I/O APIC输入。 为定时器选择中断路由时请注意这一点。

使用定时器

单次(One-shot)模式

要启用一次性模式:

// "time" is time in femtoseconds 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);

我希望上面的代码是显而易见的。 如果不是,请分析上面使用的寄存器中特定字段的含义。

周期模式

要启用周期模式,请执行以下操作:

// "time" is time in femtoseconds 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);

这段代码需要更多注释。

位2同上,中断使能。 第3位也很简单——1表示周期定时器。 但我们也设置了第6位。 为什么?

让我们看看HPET规范的引用内容:

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.

软件不必将此位写回0(自动清0)。 如果定时器设置为非周期性模式,则软件不应在此位位置写入1。

这意味着下一次写入定时器N比较器寄存器将具有通常的含义,而“第二次”下一次写入将直接写入累加器。 我举得这里的措辞本可以更好。

另见

外部链接