高精度定时器HPET和I/O APIC一样,用的是内存映射,映射的地址保存在BIOS提供的ACPI表格中

我们首先来获取这个地址

获取HPET的I/O内存地址

先来看一下文档的30-31页:

关键就是那个表格,我们先把他写成C语言的形式

(注意:部分内容在上一篇中已经提过,不再重复了,参见http://blog.csdn.net/goodqt/article/details/15337067)

typedef struct ACPIHeaderHpet{

ACPIHeader header;

u32 eventTimerBlockID;

ACPIAddressFormat baseAddress;

u8 hpetNumber;

u16 minTickInPeriodicMode;

u8 attribute;

} __attribute__ ((packed)) ACPIHeaderHpet;

typedef struct ACPIAddressFormat{

u8 addressSpaceID;

u8 registerBitWidth;

u8 registerBitOffset;

u8 reserved;

u64 address;

} __attribute__ ((packed)) ACPIAddressFormat;对照上面的表格看吧

有了这个,下一步就很好写了

static int parseDT(ACPIHeader *dt)

{

u32 signature = dt->signature;

char signatureString[5];

memcpy((void *)signatureString,(const void *)&signature,4);

signatureString[4] = '\0';

printk("Found device %s from ACPI.\n",signatureString);

if(signature == *(u32 *)"APIC")

parseApic((ACPIHeaderApic *)dt);

else if(signature = *(u32 *)"HPET")

parseHpet((ACPIHeaderHpet *)dt);

return 0;

}找到HPET的表格

static int parseHpet(ACPIHeaderHpet *hpet)

{

char temp[20];

temp[0] = '0';

temp[1] = 'x';

pointer address = (pointer)hpet->baseAddress.address;

hpetAddress = (u8 *)pa2va(address);

itoa(address,temp + 2,0x10,16,'0',1);

printk("\nFound HPET:Address => %s.\n",temp);

return 0;

}这样我们就获得了HPET的地址,并将其储存在了hpetAddress中

运行一下看看,如图:

HPET寄存器

我们先来看一下寄存器的总体概况,先不详细讲解,等到以后用哪个的时候再说哪个

获取HPET的周期

在设置之前我们当然要获取一下HPET计数器的周期,他在第一个寄存器上,名曰General Capability and ID Register,注意是Read-Only只读的

高32位就是HPET计数器的周期.它以呼秒(femptosecond,1呼秒=10^(-15)秒)为单位指示多长时间HPET计数器自增一次(最大是10^8呼秒,约合100纳秒)

int initHpet(void)

{

hpetAddress = getHpetAddress();

if(!hpetAddress)

return -1;

u32 period = hpetIn(HPET_PERIOD_REG);

/*#define HPET_PERIOD_REG 0x4*/

printk("HPET Period:%d\n",period);

return 0;

}

QEMU上打印的是10000000呼秒,约合10纳秒,也就是每隔10纳秒HPET计数器就会自增一次

再次强调一下,这个是Read-Only只读的,不能写数据来改变HPET计数器的周期

现在我们获得了HPET计数器的周期,只不过这是用的呼秒,如果能够换算成每秒计数器走多少可能会比较易懂(毕竟呼秒这个单位我们太不常用了,顶多也就说到毫秒)

现在就来试试看:

/*......*/

u32 period = hpetIn(HPET_PERIOD_REG);

unsigned long long hpetFreq = FSEC_PER_SEC;

hpetFreq = hpetFreq / period;

printk("HPET Period:%d,",period);

{

char temp[20];

itoa(hpetFreq,temp,10,0,0,1);

printk("Freq: %s\n",temp);

}

/*.......*/打印出来的是每秒增加10^8,果然是高精度.....

HPET计数器

唔.....说了这么久计数器的周期,但连计数器长什么样还不知道呢,赶紧来认识一下吧

一眼看上去,这是所以寄存器中最简单的了吧,不但作用简单,结构也简单.....

我们先来试着读取一下低32位,返回0,为什么呢?

对了,我们还没有启动HPET呢,计数器当然是0了,先来启动HPET吧

启动HPET

启动HPET就要用到配置寄存器了,如下

(PS:目测Intel粗心了,Offset居然写着0,查一下我们的第一个表格知道这个寄存器的偏移明明是0x10.....)

我们来解释一下这个寄存器.第一位上是控制计数器的开关,第二位是控制是否产生中断的开关,我们现在主要用第一位

写个enableHpet函数,顺便把disbaleHpet和restartHpet的函数也写了(这样我们就调用restartHpet好了,防止某些"好心"的BIOS自动帮你打开了HPET)

static inline int enableHpet(void)

{

u32 conf = hpetIn(HPET_CONF_REG);

conf |= HPET_CONF_ENABLE;

hpetOut(HPET_CONF_REG,conf);

return 0;

}

static inline int disableHpet(void)

{

u32 conf = hpetIn(HPET_CONF_REG);

conf &= ~HPET_CONF_ENABLE;

hpetOut(HPET_CONF_REG,conf);

return 0;

}

static inline int restartHpet(void)

{

disableHpet();

hpetOut(HPET_COUNTER_REG_LOW,0x0);

hpetOut(HPET_COUNTER_REG_HIGH,0x0);

enableHpet();

return 0;

}终于可以改写我们的函数来打印出走动的counter的数值了,不过还有一个问题,由于计数器有64位,我们必须分两次来读,万一在第一次读完后更新了怎么办,我们举个例子

假如现在第三十二位是0xffffffff,高32位是0x00000000,我们将0xffffffff读了出来,正在这时,计数器更新了,产生进位,下面我就不用再说了吧.....

所以我们还是写一个hpetReadCounter(事实上我们应该再写一个hpetWriteCounter,偷懒先不写了)

static inline u64 hpetReadCounter(void)

{

u64 counter;

disableHpet();

counter = hpetIn(HPET_COUNTER_REG_LOW);

counter |= ((u64)hpetIn(HPET_COUNTER_REG_HIGH)) << 32;

enableHpet();

return counter;

}

打印出新的Counter

printk("Restart HPET and clear counter....\n");

restartHpet();

for(volatile int i = 0;i < 100000;++i)

;

u64 counter = hpetReadCounter();

itoa(counter,temp,0x10,16,'0',1);

printk("We waited a minute,now counter is 0x%s\n",temp);再来看一下输出

对了,最上面的两个数0不是一样多的,可以数数....

这样的话我们就可以做安全检查了,如下

if(!counter)

{

printk("HPET didn't count,discard.\n");

return -1;

}

如果HPET不计数的话我们就放弃HPET,采用最原始的PIT.....

开启HPET中断

HPET的中断参见下表

Timer0触发的IRQ号分8259A和(I/O)APIC.....

我使用的是APIC,为了使效果更明显,首先关闭其他所有中断,只打开IRQ2,并将这个IRQ的处理函数打印一行字"Interrupt!"就返回

现在运行一下,不出意外的话HPET的中断还没启用,你应该看到PIT触发的中断一行行打印......(PIT在APIC下也触发IRQ2)

然后我们让HPET的中断启动

static inline int enableHpetInterrupt(void)

{

u32 conf = hpetIn(HPET_CONF_REG);

conf |= HPET_CONF_ENABLE_INT;

hpetOut(HPET_CONF_REG,conf);

return 0;

}然后主函数中调用一下,再次运行,不出意外你应该发现什么也不输出了,因为我们启动了HPET的中断但没有配置,HPET不会发出任何中断,在这种情况下PIT会被自动禁用,也不会发出任何中断

配置HPET的Timer

这部分比较复杂,有许多许多的标记,如果我们先把每一个标记都弄懂的话大家肯定都没有耐心了,我们不妨参照Linux的源代码,只说几个用到的标记

先看Timer的配置寄存器

这个偏移地址不是固定的,比如我们要用第一个定时器偏移就是0x100 + (1 - 1)*0x20=0x100,第二个则是0x100 + (2 - 1)*0x20=0x120,以此类推

这么多标记我们只说几个就能使我们的代码运行的很好

1.TN_INT_EN_INT 顾名思义,设置这个定时器是否启用

2.TN_TYPE_CNF   决定这个计时器是周期的还是一次性的,另外下一个位TN_PER_INT_CAP(是Read-Only)表示此为是否可用

其他位有兴趣的可以自己查文档解决

但是只有这些信息还不够,HPET还要知道什么时间触发中断

这个寄存器就简单多了,只有一个信息,对于周期性的定时器,这个寄存器决定多长时间触发一次中断,对于一次性的定时器,这个寄存器决定什么时候触发中断

好了,现在我们来编写hpetStartTimer

static inline int hpetStartTimer(int index,int periodic,u32 time)

{

u32 conf = hpetIn(HPET_TIMER_CONF_REG(index));

if(periodic)

{

if(conf & HPET_TIMER_CONF_PER_CAP) /*判断这个Timer是否支持周期性.*/

conf |= HPET_TIMER_CONF_PERIODIC;

else

return -1;

}

conf |= HPET_TIMER_CONF_ENABLE;

hpetOut(HPET_TIMER_CONF_REG(index),conf);

hpetOut(HPET_TIMER_CMP_REG(index),time);

return 0;

}然后我们调用这个函数

if(hpetStartTimer(0,1,0x2ffffff))

{

disableHpet();

return -1;

}

这里我们选择了如果不支持周期性就直接退出,其实没有必要这样做,我们只要在每次中断执行时修改寄存器,使其延迟指定的时间再次发生中断,就可以模拟周期性

另外实际使用时还要通过我们一开始得到的freq计算这里的参数u32 time,这部分内容就不再涉及了....

现在再次运行,会发现以不大的速度不断打印"Interrupt!",通过调节参数time,就可以轻松的改变其打印速度

试了下,QEMU环境下每秒最多产生大约100个中断,VirtualBox或者其他比较好的模拟器、真实的机器可以产生更多,1000个也没有问题的....

参考资料

HPET规范:http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf

IA-PC HPET (High Precision Event Timers) Specification

hpet 定时器中断 8259 linux,[OSDEV]编程高精度定时器(HPET)相关推荐

  1. Linux用户进程高精度定时器去抖动

    Linux用户进程高精度定时器去抖动[新手自学APUE]. 通过实践 itimer定时器去抖动 总结的几点注意事项: 定时器精度要支持较高精度,定时器设置值要大于 [上确界](最大值)(每 两次 抖动 ...

  2. Linux环境编程多线程定时器、延时队列以及分布式定时器的现实与原理分析

    Linux环境编程多线程定时器.延时队列以及分布式定时器的现实与原理分析丨线程池丨中间件丨后端开发丨C/C++linux服务器开发 视频讲解如下,点击观看: Linux环境编程多线程定时器.延时队列以 ...

  3. Linux网络编程 | 高性能定时器 :时间轮、时间堆

    文章目录 时间轮 时间堆 在上一篇博客中我实现了一个基于排序链表的定时器容器,但是其存在一个缺点--随着定时器越来越多,添加定时器的效率也会越来越低. 而下面的两个高效定时器--时间轮.时间堆,会完美 ...

  4. linux内核高分辨率定时器,64位Linux上的高分辨率定时器支持

    我试图在2.6.39.1 64位Linux上启用高分辨率定时器支持.为此,我首先在.config中设置CONFIG_HIGH_RES_TIMERS=y.64位Linux上的高分辨率定时器支持 而且,g ...

  5. 单片机定时器中断倒计时c语言,我用定时器中断控制倒计时30秒。程序不报错,但是显示一直停在30..请问好友们,帮我看看问题出在哪里?...

    意味着我的定时器中断,的变量 i 没有发生变化,为什么呢? #include #define uchar unsigned char uchar code table[]={0xc0,0xf9,0xa ...

  6. 定时休眠 linux,linux系统编程手册 定时器和休眠

    一.间隔定时器 1.setitimer settitimer创建一个间隔式定时器,这种定时器会在未来某个时间到期,并于此后(可选择地)每隔一段时间到期一次 int setitimer(int whic ...

  7. Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

    转自:http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已 ...

  8. Linux环境编程 用户层定时器使用一 timerfd的使用

    timerfd是linux提供的定时器机制,基于文件描述符,定时器精度最高可达纳秒级别,接口包括定时器创建.启动定时器.关闭定时器和删除定时器.下面介绍一下timerfd  API接口和一个结合epo ...

  9. linux 定时器 jiffies,linux下jiffies定时器和hrtimer高精度定时器(示例代码)

    一.jiffies定时器,HZ=100,精度只能达到10ms. 注:采用jiffies+msecs_to_jiffies(xx ms);可做到ms级,不过精度不够 #include //DO--> ...

最新文章

  1. 多线程下ArrayList类线程不安全的解决方法及原理
  2. shell编程开发应用指南
  3. Fragment要点复习
  4. Scala _11SparkWordCountscala语言java语言
  5. jQuery源码分析--Event模块(1)
  6. 深度学习及AR在移动端打车场景下的应用
  7. [收藏]上班族的真实写照
  8. php背景时间渐变,CSS3怎么实现背景颜色渐变?(图文+视频)
  9. 腾讯被迫下架《怪物猎人世界》;传谷歌将支持 Win10 ;苹果或将复活 MagSafe | 极客头条...
  10. CTP: NET封装后接口中序列化数据的问题
  11. 软件测试-兼容性测试
  12. 【热点】印度年轻人跟中国年轻人有什么不同
  13. 获取任意一个微信小程序码的两种方式
  14. 计算机ppt音乐,ppt背景音乐_适合ppt播放的轻音乐
  15. IDEA怎么查看现在的项目使用的JDK版本? 2016年4月19日22:51
  16. 《带着神兽自学少儿编程》之01:爸爸下载Scratch【关注可畅读】
  17. Ubuntu16(ROS_Kinetic)海康威视网络摄像机(单目)内参标定
  18. GROUP Function
  19. 前端入门: 用css设置文字样式。
  20. 谈谈Django REST Framework(DRF)中的序列化器

热门文章

  1. 在线制作banner的网站
  2. 微信小程序的生命周期函数
  3. 微信小程序推荐大全100个
  4. 浅谈CMPP3协议架构实现
  5. python能做界面吗_如何使用pyQT做pythonGUI界面|
  6. hadoop-ykt(自定义key)
  7. 【随机过程】第二版龚光鲁译课后习题4.5参考答案
  8. 计算机使用计数制是,进位计数制
  9. Apriori算法的介绍
  10. td可编辑(html标签可编辑)