查看“HPET”的源代码
←
HPET
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
:''本页不是对HPET的完整描述,只是一个轻量级的介绍。 如果你需要本文未涵盖的任何信息,请参考[http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf HPET规范]。'' '''HPET'''或高精度事件定时器(High Precision Event Timer),是英特尔和微软设计的一款硬件,用于取代较旧的 [[PIT]] 和 [[RTC]]。 它由(通常为64位)主计数器(递增计数)以及3到32位或64位宽的比较器组成。 HPET使用内存映射IO进行编程,使用[[ACPI]]可以找到HPET的基地址。 == 使用ACPI检测HPET == 那个[http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf HPET规范]定义了一个ACPI 2.0表,用于检测系统中存在的HPET的存在、地址和功能。 如果该表不存在,你应该假设没有HPET,并且应该回退到[[PIT]]或[[APIC Timer]]。 <source lang="cpp"> 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)); </source> == HPET-定时器(timer)vs比较器(comparators) == 定时器中只有一个递增计数主计数器,但中断生成由*比较器*处理。 有3到32个比较器,确切的数量由上面<code>hpet</code>结构中的<code>compasator_count</code>字段表示。 请记住,你必须初始化主计数器和所有比较器。 此外,比较器中断的路由以及'''允许的'''路由是独立的,因此你必须为每个中断单独检测和设置它。 正文中进一步提供了有关此过程的更多信息。 == 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)。 {| {{wikitable}} ! 定时器 ! 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寄存器== 下表和字段描述也可以在规范中找到。 “偏移量”指与<code>hpet</code>struct的<code>address</code>字段中定义的地址的偏移量。 下表跳过规范中定义的保留寄存器。 {| {{wikitable}} !偏移 !寄存器 ! 类型 |- |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=== {| {{wikitable}} ! 位 !名称 !描述 |- |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=== {| {{wikitable}} !位 !名称 !描述 |- |63:2 | 保留 | - |- |1 |LEG_RT_CNF |0-“旧版替换”映射已禁用 1-启用“传统替换”映射 |- |0 |ENABLE_CNF | 整体(Overall)启用。 0-主计数器停止,定时器中断被禁用 1-主计数器正在运行,如果启用,则允许定时器中断 |} ===General Interrupt Status Register=== {| {{wikitable}} !位 !名称 !描述 |- |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=== {| {{wikitable}} !位 !名称 !描述 |- |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)模式=== 要启用一次性模式: <source lang="cpp"> // "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); </source> 我希望上面的代码是显而易见的。 如果不是,请分析上面使用的寄存器中特定字段的含义。 === 周期模式 === 要启用周期模式,请执行以下操作: <source lang="cpp"> // "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); </source> 这段代码需要更多注释。 位2同上,中断使能。 第3位也很简单——1表示周期定时器。 但我们也设置了第6位。 为什么? 让我们看看HPET规范的引用内容: <blockquote>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.</blockquote> <blockquote>软件不必将此位写回0(自动清0)。 如果定时器设置为非周期性模式,则软件不应在此位位置写入1。</blockquote> 这意味着下一次写入定时器N比较器寄存器将具有通常的含义,而“第二次”下一次写入将直接写入累加器。 我举得这里的措辞本可以更好。 == 另见 == * [[IOAPIC]] * [[APIC]] * [[APIC timer]] * [[PIT]] * [[RTC]] ==外部链接== * [http://en.wikipedia.org/wiki/High_Precision_Event_Timer HPET article on Wikipedia] * [http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf Intel's HPET Specification v1.0a] [[Category:Common Devices]] [[Category:Interrupts]] [[Category:Time]]
本页使用的模板:
模板:Wikitable
(
查看源代码
)
返回至“
HPET
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息