天天看点

android qemu-kvm i8254 pit虚拟设备

8259主片的IRQ0~7对应INT 8~INT F,从片的IRQ8~IRQ15对应INT 70~INT 77。

有份以前上C语言测控时写的代码,使用了8254的,输入采样周期(in ms)和采样次数,每次采样时打印一个'8'。

注意定时器的最大周期比较短,大约55ms,所以需要使用软件方式扩大定时器的周期,注意周期不是10ms的倍数时的特殊处理。

定时器0工作于模式3,方波发生器。用学硬件的话来说,就是自动重装定时器;用学软件的话来说,就是周期定时器,不是oneshot的。

真的看完了,现在开始看模拟的。

8254的初始化是在pc_init1中执行的,设置iobase为0x40,IRQ为0,INT 8:

8254是有三个timer的,只用到了channel 0的timer。

qemu有自己的定时器,输入时钟是1G,对应1ns。8254的输入时钟是1193kHZ,如何模拟的呢?

根据8254的设置,计算出来下一个中断到临的tick次数,在根据8254和qemu timer频率的不同,对tick进行转换,然后设置qemu timer的定时设置,当qemu timer超时时,callback函数就是8254的中断处理函数pit_irq_timer。在中断函数中,再进行一些其它的处理,如重新装载之类的。

qemu_register_reset是用链表保存一些复位函数的:

当然pit_init最后也调用了pit_reset函数对寄存器进行复位,将mode设置为3,设置gate,计数值归零:

这两行设置了寄存器的读写函数,注意这里是PMIO方式,不是MMIO方式的寄存器。0x40~0x43的写函数设置为pit_ioport_write;0x40~0x42的读函数设置为pit_ioport_read:

写函数,看懂寄存器的使用后,这个函数还是比较简单的:

pit_latch_count用于锁存当前的计数值:

pit_load_count用于装载计数值,count_load_time是装载时tick的值(tick++ in every ns);count是8254的周期,8254自己的计数值会按照1193kHZ的频率递减的。注意和count_load_time单位的不同,以及后续单位的转换。最后调用pit_irq_timer_update,对qemu timer进行更新。

pit_irq_timer_update函数干两件事:

1、计算irq_level,就是比较tick的值和设定的值,满足条件时就会qemu_set_irq触发中断请求

2、计算expire_time,并且调用timer_mod更新qemu timer,让qemu timer在8254下一个需要产生中断的时候产生timeout,并调用callback,也就是8254的中断函数

8254的中断函数,也就是qemu timer的callback函数,也调用了pit_irq_timer_update:

寄存器的读函数:

当kvm执行到PMIO的操作时,会退出,然后调用kvm_handle_io:

以8bit读为例子:

PMIO的地址和opaque以及读写函数的绑定,使用register_ioport_read,register_ioport_write函数,在i8254.c的pit_init中调用的:

pit_save,pit_load,register_savevm用于快照和恢复的,可以不看。

现在qemu的8254都是使用了QOM模型了,这个模型太TMD的复杂了。另外hw/i386/kvm/timer/i8254.c中提供了kvm-pit,使用kvm提供的内核态的8254的模拟,中断的处理和IO的读写都在内核态,不需要退出kvm了,速度要更快些。类似的,8259之类的也有kvm内核态的实现,所以说android emulator的性能还是有提升空间的。

继续阅读