qemu intel i6300esb watchdog虚拟外设分析

文章目录

  • 1.简介
  • 2.添加外设
  • 3.编写喂狗程序测试效果
  • 4.分析qemu代码
    • 4.1.开启i6300esb调试开关
    • 4.2.设备实现i6300esb_realize
    • 4.3.设备重启函数i6300esb_reset
    • 4.4.读写IO端口寄存器
    • 4.5.读写IO内存寄存器
    • 4.6.超时机制
      • 4.6.1.超时处理函数
      • 4.6.2.如何判断超时
  • reference

1.简介

本文介绍qemu是如何模拟和使用intel 6300esb芯片组的watchdog功能的,watchdog的基本概念,可以参考1,本文不涉及详细介绍如何使用centos或者ubuntu等发行版自带的喂狗程序,如有相关操作,只是为了演示如何触发qemu的一些相关函数的调用。本文用的代码版本为开源的qemu2.8.0。

2.添加外设

libvirt中添加

...

  

...

或者直接在qemu命令行中添加

-device i6300esb,id=watchdog0,bus=pci.0,addr=0x6 -watchdog-action reset 

其中-watchdog-action是设置watchdog timeout时间过后的行为的,此处拿reset做实验,其他选项可以自行查阅qemu命令行或者libvirt xml2

添加成功之后可以在guest OS中看到外设

-device i6300esb,id=watchdog0,bus=pci.0,addr=0x6 -watchdog-action reset 

3.编写喂狗程序测试效果

原始代码引用自1,稍微做了点修改

#include 
#include 
#include 
#include 

#define WDT_DEVICE_FILE "/dev/watchdog"

int main(void)
{
  int g_watchdog_fd = -1;
  int timeout = 0;
  int timeout_reset = 120;
	int ret = 1;
	int sleep_time = 10;
  //开启watchdog
  g_watchdog_fd = open(WDT_DEVICE_FILE, O_RDWR);
  if (g_watchdog_fd == -1)
    {
        printf("Error in file open WDT device file(%s)...\n", WDT_DEVICE_FILE);

        return 0;
    }
  //获取watchdog的超时时间(heartbeat)
  ioctl(g_watchdog_fd, WDIOC_GETTIMEOUT, &timeout);
  printf("default timeout %d sec.\n", timeout);
  //设置watchdog的超时时间(heartbeat)
  ioctl(g_watchdog_fd, WDIOC_SETTIMEOUT, &timeout_reset);
  printf("We reset timeout as %d sec.\n", timeout_reset);
  //喂狗
  while(1){
      //喂狗
      ret = ioctl(g_watchdog_fd, WDIOC_KEEPALIVE, 0);
      //喂狗也通过写文件的方式,向/dev/watchdog写入字符或者数字等
      // static unsigned char food = 0;
      //write(g_watchdog_fd, &food, 1);
      if (ret != 0) {
          printf("Feed watchdog failed. \n");
          close(g_watchdog_fd);
          return -1;
      } else {
          printf("Feed watchdog every %d seconds.\n", sleep_time);
      }
      //feed_watchdog_time是喂狗的时间间隔,要小于watchdog的超时时间
      sleep(10);
    }
  //关闭watchdog
  write(g_watchdog_fd, "V", 1);
  //以下方式实测并不能关闭watchdog
  //ioctl(g_watchdog_fd, WDIOC_SETOPTIONS, WDIOS_DISABLECARD)
  close(g_watchdog_fd);
}

4.分析qemu代码

4.1.开启i6300esb调试开关

\* hw/watchdog/wdt_i6300esb.c *\
#define I6300ESB_DEBUG 1

然后重新编译安装qemu,打印的内容分三个阶段,刚启动qemu,就可以看到如下打印

i6300esb: i6300esb_realize: I6300State = 0x55e80b28d670
i6300esb: i6300esb_reset: I6300State = 0x55e80b28d670
i6300esb: i6300esb_disable_timer: timer disabled

以上打印,是由于qemu初始化虚拟外设i6300esb的时候调用的函数产生的,然后输入c启动guest os,会进一步的产生打印

(qemu) c
/home/qemu-2.8.0/cpus.c resume_all_vcpus 1372
/home/qemu-2.8.0/cpus.c resume_all_vcpus 1372
/home/qemu-2.8.0/cpus.c resume_all_vcpus 1372
/home/qemu-2.8.0/cpus.c resume_all_vcpus 1372
(qemu) 
i6300esb: i6300esb_config_read: addr = 0, len = 2//Vendor ID 8086h Read Only
i6300esb: i6300esb_config_read: addr = a, len = 2//Sub Class Code Register (SCC) 80h Read Only
i6300esb: i6300esb_config_read: addr = e, len = 1//Header Type Register (HEDT) 00h Read Only
i6300esb: i6300esb_config_read: addr = 0, len = 2//Vendor ID 8086h Read Only
i6300esb: i6300esb_config_read: addr = a, len = 2
i6300esb: i6300esb_config_read: addr = e, len = 1
i6300esb: i6300esb_config_read: addr = 0, len = 2
i6300esb: i6300esb_config_read: addr = 0, len = 4
i6300esb: i6300esb_config_read: addr = 8, len = 4//Revision ID Register (RID) See NOTE: Read Only
......
i6300esb: i6300esb_config_write: addr = 60, data = 3, len = 2//WDT Configuration 00h Read/Write
i6300esb: i6300esb_config_read: addr = 68, len = 1//WDT Lock Register 00h Read/Write
i6300esb: i6300esb_config_write: addr = 68, data = 0, len = 1
i6300esb: i6300esb_disable_timer: timer disabled
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_readw: addr = c
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writew: addr = c, val = 300//0000 0011 0000 0000//第8位是调用重置定时器函数,第九位是重置reboot flag
//这里其实调用了i6300esb_restart_timer,但是d->enabled为0,因此直接返回了,没有打印
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writel: addr = 0, val = 3c00//0011 1100 0000 0000//第12位也是重置reboot flag
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writel: addr = 4, val = 3c00//0011 1100 0000 0000
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writew: addr = c, val = 100//0000 0001 0000 0000,以后正常工作是写这个数字,上面的应该都是测试用的,出现100之后,这个函数就不打印了
i6300esb: i6300esb_config_read: addr = 0, len = 4
i6300esb: i6300esb_config_read: addr = 4, len = 4
....

这些打印是由于guest OS的内核在初始化的时候,产生了IO exit或者MMIO exit,由kvm返回到qemu,进行io读写的模拟产生的。在guest os中执行喂狗程序,qemu产生的打印如下

6300esb: i6300esb_config_write: addr = 68, data = 2, len = 1
i6300esb: i6300esb_restart_timer: stage 1, timeout 96000
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writel: addr = 0, val = 7800, stage = 2
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writel: addr = 4, val = 7800, stage = 2
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writew: addr = c, val = 100
i6300esb: i6300esb_restart_timer: stage 1, timeout 96000
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writew: addr = c, val = 100
i6300esb: i6300esb_restart_timer: stage 1, timeout 96000
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_mem_writew: addr = c, val = 86
i6300esb: i6300esb_mem_writew: addr = c, val = 100
i6300esb: i6300esb_restart_timer: stage 1, timeout 96000
i6300esb: i6300esb_mem_writew: addr = c, val = 80
i6300esb: i6300esb_timer_expired: stage 2
2019-07-23T08:58:45.111484Z qemu-system-x86_64: network script /etc/qemu-ifdown failed with status 256

最后强制使用ctrl+c退出喂狗程序,由于第3节中的喂狗程序没有处理强制退出的信号,因此程序没有将watchdog硬件关闭,导致i6300esb watchdog硬件等待超时,从而触发reset效果,这就是宏观上的工作方式,后面会分析几个主要函数。

4.2.设备实现i6300esb_realize

/* vl.c:4574*/
if (qemu_opts_foreach(qemu_find_opts("device"),
                          device_init_func, NULL, NULL)) {
        exit(1);
    }

以上函数是在qemu主线程中,通过一个循环,遍历所有qemu命令行中的device配置,进行设备的初始化,这里的调用关系比较复杂,还涉及到不同的总线,此处只非精确的列出大致调用层级,可以看到i6300esb是PCI设备:

|----->device_init_func
	|----->qdev_device_add
		|----->device_set_realized
			|----->pci_qdev_realize
				|----->i6300esb_realize

因为这是通用架构,因此其他代码本文不分析,有兴趣的可以查阅其他文档,关于QDEV的,这里主要看i6300esb_realize函数:

/* hw/watchdog/wdt_i6300esb.c:418 */
/* 该函数负责实现虚拟外设,本质上和内存条pc.ram没有区别,只是io设备增加了ops,会执行一些动作,原理可以自行学习io虚拟化的流程*/
static void i6300esb_realize(PCIDevice *dev, Error **errp)
{
    I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);//通用设备类型的参数,类型转换成I6300State类型

    i6300esb_debug("I6300State = %p\n", d);

    d->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, i6300esb_timer_expired, d);//添加一个定时器,第二个参数就是注册回调函数
    //以后该定时器到期或者超时时候可以触发的行为,就由该函数决定
    d->previous_reboot_flag = 0;
    //这里就是初始化一个标记,代表之前没有重启过(应该是由于定时器超时造成的重启,人工重启不确定算不算)

    memory_region_init_io(&d->io_mem, OBJECT(d), &i6300esb_ops, d,
                          "i6300esb", 0x10);
    //为该外设分配存储空间,注意这里是io空间,不是内存空间,因此调用的是memory_region_init_io,因为这个函数的第三个参数会附带各种行为,
    //这些行为就是为了模拟外设的读写的,而内存不需要这样模拟,所以没有这些ops
    pci_register_bar(&d->dev, 0, 0, &d->io_mem);
    //将该设备注册到PCI总线上,就代表设备插到PCI插槽上面了
    /* qemu_register_coalesced_mmio (addr, 0x10); ? */
}

4.3.设备重启函数i6300esb_reset

该函数是一个配置重置函数,设备最开始初始化完毕之后会调用一次,作为模拟真实硬件各种寄存器的初始值,注意,这里说的初始化,
是指的qemu程序模拟外设的初始化,而不是虚拟机的guest OS开机的过程中设备驱动的初始化,各主要字段详细的作用,需要对照手册看3,这里的初始化值没有那么重要,因为在guest OS启动过程中,驱动程序会调用i6300esb_mem_writel函数将配置都修改掉,当超时
的时候,guest OS重启或者关机之前又要调用本函数,之后guest OS再开机,再次加载驱动,又重新改写配置,反复循环。初始化的时候调用关系如下:

|----->qemu_system_reset//初始化完成后会调用一次重置函数,这样各种参数全部都刷新成初始值了,为启动做准备
	|----->qemu_devices_reset
		|----->qbus_reset_all_fn
			|----->i6300esb_reset				

在分析i6300esb_reset函数之前,得先了解i6300esb WDT功能流程3,该芯片组功能很强大,qemu2.8中仅仅模拟了watchdog的功能,因此,只需要看相应的章节就可以了,抽象出来的工作流程如下:

Created with Raphaël 2.2.0 1.上电 2.硬件初始化 3.操作系统启动 是否有i6300esb设备? 4.加载驱动 是否启动开启设备 5.进入stage1 执行喂狗指令 6.刷新定时器 是否超时 7.进入stage2 是否超时 不使用watchdog yes no yes no yes no yes no yes no

按照上图的逻辑,4.2节中的i6300esb_realize函数就相当于工厂里面生产出来了一个i6300esb设备,pci_register_bar函数就相当于把该设备插入到pci插槽上,随后上图中的‘1.上电’和‘2.硬件初始化’,就是开机自检,初始化各种硬件,设定各种寄存器的初始值,在qemu里,因为不是完全按照物理电路的时序来走的,稍微有点偏差,但是思想上是一样的,这里就可以大致对应i6300esb_reset函数,虽然并没有真正的reset,但是函数体的内容就是起的这个效果,对每个状态和寄存器变量进行初始化,来模拟真正硬件设备的初始值。i6300esb WDT开始工作后分两个阶段,一开始是进入stage1,开始计时,如果超时到设定的时间的一半的时候,会触发一个内部中断3,然后进入stage2,这个内部中断目前意义不大,qemu并没有做额外的行为,只是打印了一条告警信息,真实硬件具体有什么内涵,还不得而知。到了stage2,如果再次超时,则会触发watchdog注册的行为,在真实硬件中,芯片会通过WDT_TOUT引脚发送一个外部中断,会直接让系统关机或者重启,在qemu中,则会调用注册好的回调函数i6300esb_timer_expired。

/* hw/watchdog/wdt_i6300esb.c:149 */
/* 
*/
static void i6300esb_reset(DeviceState *dev)
{
    PCIDevice *pdev = PCI_DEVICE(dev);
    I6300State *d = WATCHDOG_I6300ESB_DEVICE(pdev);

    i6300esb_debug("I6300State = %p\n", d);

    i6300esb_disable_timer(d);//去使能该设备的定时器

    /* NB: Don't change d->previous_reboot_flag in this function. */

    d->reboot_enabled = 1;
    d->clock_scale = CLOCK_SCALE_1KHZ;
    d->int_type = INT_TYPE_IRQ;//根据芯片手册,这个代表WDT配置寄存器的0-1bit位,初始化为0
    d->free_run = 0;
    d->locked = 0;
    d->enabled = 0;//设备初始化是没有使能的
    d->timer1_preload = 0xfffff;//stage1状态的超时时间,寄存器mmio地址为Base + 00h
    d->timer2_preload = 0xfffff;//stage2状态的超时时间,寄存器mmio地址为Base + 04h
    d->stage = 1;//从stage1开始
    d->unlock_state = 0;
}

其他个别变量暂时不用管,只看最主要的几个,最核心的就是三个,timer1_preload,timer2_preload和stage,分别代表第一阶段超时时间,第二阶段超时时间,以及当前阶段是多少。

/* qemu-timer.c:404 */
static void i6300esb_disable_timer(I6300State *d)
{
    i6300esb_debug("timer disabled\n");

    timer_del(d->timer);//从该定时器所属的QEMUTimerList中,找到自己,并且删除
}

4.4.读写IO端口寄存器

上一节的代码执行完成后,就是硬件已经做好准备了,直到qemu初始化完成,也不会再调用和i6300esb相关的函数了,在guest os开始启动后,qemu会反复调用i6300esb_config_write,i6300esb_config_read这两个函数(也会调用其他函数,下一节再分析),这两个函数的打印和产生原因在4.4节中已经进行了说明,本文不会深入对guest os的驱动进行分析,仅仅会简要提到部分关键内容。

/* hw/watchdog/wdt_i6300esb.c:215 */
static void i6300esb_config_write(PCIDevice *dev, uint32_t addr,
                                  uint32_t data, int len)
{
    I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
    int old;

    i6300esb_debug("addr = %x, data = %x, len = %d\n", addr, data, len);
	/*当qemu有以下打印的时候,就是配置WDT Configuration Register,对WDT_INT_TYPE等信息进行配置*/
	/*i6300esb: i6300esb_config_write: addr = 60, data = 3, len = 2*/
    if (addr == ESB_CONFIG_REG && len == 2) {//0x60,
        d->reboot_enabled = (data & ESB_WDT_REBOOT) == 0;
        d->clock_scale =
            (data & ESB_WDT_FREQ) != 0 ? CLOCK_SCALE_1MHZ : CLOCK_SCALE_1KHZ;
        d->int_type = (data & ESB_WDT_INTTYPE);//0x3 & 0x11
    /*当qemu有以下打印的时候,就是配置WDT Lock Register,设置定时器的使能*/
	/*i6300esb: i6300esb_config_write: addr = 68, data = 0, len = 1*/
    } else if (addr == ESB_LOCK_REG && len == 1) {//0x68
        if (!d->locked) {
            d->locked = (data & ESB_WDT_LOCK) != 0;
            d->free_run = (data & ESB_WDT_FUNC) != 0;
            old = d->enabled;
            d->enabled = (data & ESB_WDT_ENABLE) != 0;
            /*如果之前定时器是关闭的,现在收到打开命令,则调用定时器重置功能,然后进入stage 1*/
            if (!old && d->enabled) /* Enabled transitioned from 0 -> 1 */
                i6300esb_restart_timer(d, 1);
            else if (!d->enabled)
                i6300esb_disable_timer(d);
        }
    } else {
        pci_default_write_config(dev, addr, data, len);//绝大多数走的这里,这里都是一些PCI通用配置,没有涉及到
        //定时器,不影响WDT定时器功能的理解。
    }
}

i6300esb_config_read函数结构和以上函数相似,,可以看到,只有addr为ESB_CONFIG_REG或者ESB_LOCK_REG的时候,才会由本函数来模拟效果,其他addr,都会直接调用通用的pci函数进行处理。可以看出,这种虚拟化外设的架构层级的设计思想,将所有通用的pci操作,抽象出来,单独实现,然后把特定的功能截获,进行模拟,跟真正的硬件的层级还是有一定区别的,例如,该i6300esb代码是放在hw/watchdog目录下,说明该代码只模拟watchdog功能,而无视此芯片组完整的其他功能,而完整的i6300esb全称是Intel 6300ESB I/O Controller Hub,也就是我们通常所说的南桥芯片,是有很多功能的,因此在qemu的虚拟化主板上,是不需要模拟南桥芯片的物理形态的,只需要针对在相应的IO访问,或者mmio访问的时候,做相应的处理,使其能够达到硬件的效果,就可以了。

/* hw/watchdog/wdt_i6300esb.c:244 */
static uint32_t i6300esb_config_read(PCIDevice *dev, uint32_t addr, int len)
{
    I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
    uint32_t data;

    i6300esb_debug ("addr = %x, len = %d\n", addr, len);

    if (addr == ESB_CONFIG_REG && len == 2) {
        data =
            (d->reboot_enabled ? 0 : ESB_WDT_REBOOT) |
            (d->clock_scale == CLOCK_SCALE_1MHZ ? ESB_WDT_FREQ : 0) |
            d->int_type;
        return data;
    } else if (addr == ESB_LOCK_REG && len == 1) {
        data =
            (d->free_run ? ESB_WDT_FUNC : 0) |
            (d->locked ? ESB_WDT_LOCK : 0) |
            (d->enabled ? ESB_WDT_ENABLE : 0);
        return data;
    } else {
        return pci_default_read_config(dev, addr, len);
    }
}

4.5.读写IO内存寄存器

其中i6300esb_mem_writew和i6300esb_mem_writel函数是最重要的,read函数基本作用不大。i6300esb_mem_writew函数就是处理喂狗的函数。

 /* hw/watchdog/wdt_i6300esb.c:312 */
 static void i6300esb_mem_writew(void *vp, hwaddr addr, uint32_t val)
{
    I6300State *d = vp;

    i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
    /*对0xc地址所代表的寄存器先写入0x80,然后写入0x86是一个固定操作,代表可以获取一次对0xc寄存器的写权限*/
    /*所以guest os内核驱动程序需要执行一条喂狗指令的时候,必须执行这个,见下一个代码段*/
    if (addr == 0xc && val == 0x80)
        d->unlock_state = 1;//解锁第一阶段
    else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
        d->unlock_state = 2;//解锁第二阶段,代表解锁成功
    else {
        if (d->unlock_state == 2) {//如果是已经解锁成功
            if (addr == 0xc) {//判断寄存器mmio地址
                if ((val & 0x100) != 0)//如果满足这里,代表是用户程序在执行喂狗命令
                    /* This is the "ping" from the userspace watchdog in
                     * the guest ...
                     */
                    i6300esb_restart_timer(d, 1);//喂狗成功则重启定时器

                /* Setting bit 9 resets the previous reboot flag.
                 * There's a bug in the Linux driver where it sets
                 * bit 12 instead.
                 */
                if ((val & 0x200) != 0 || (val & 0x1000) != 0) {
                    d->previous_reboot_flag = 0;//这里可以不管
                }
            }

            d->unlock_state = 0;
        }
    }
}

驱动执行写寄存器操作之前,例如writel(val, ESB_TIMER2_REG);,需要先执行一下代码打开该寄存器的写权限,而且有效次数只有一次,想写入下一个数据,比如再次重复此操作。

/*driver/watchdog/i6300esb.c:*/
static inline void esb_unlock_registers(void)
{
	writew(ESB_UNLOCK1, ESB_RELOAD_REG);
	writew(ESB_UNLOCK2, ESB_RELOAD_REG);
}

i6300esb_mem_writel函数是用来配置timeout时间

/*driver/watchdog/i6300esb.c:345*/
static void i6300esb_mem_writel(void *vp, hwaddr addr, uint32_t val)
{
    I6300State *d = vp;

    i6300esb_debug ("addr = %x, val = %x, stage = %d \n", (int) addr, val, d->unlock_state);
    /*这里的0x80和0x86并没有使用到,应该只是为了以防万一*/
    if (addr == 0xc && val == 0x80)
        d->unlock_state = 1;
    else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
        d->unlock_state = 2;
    else {
        if (d->unlock_state == 2) {
            if (addr == 0)
                d->timer1_preload = val & 0xfffff;//这里是给stage 1阶段的定时器写时间
            else if (addr == 4)
                d->timer2_preload = val & 0xfffff;//这里是给stage 2阶段的定时器写时间
     
            d->unlock_state = 0;
        }
    }
}

4.6.超时机制

4.6.1.超时处理函数

第一次超时会调用这个函数,此处stage=1,这里没有对i6300esb的内部中断进行模拟,因为没有意义,然后调用i6300esb_restart_timer(d, 2)进入stage2,并且重启计时器,记录下半阶段的时间,如果也超时了,则调用watchdog_perform_action()进行处理,这个处理函数比较简单,就不分析了。

/*driver/watchdog/i6300esb.c:182*/
static void i6300esb_timer_expired(void *vp)
{
    I6300State *d = vp;

    i6300esb_debug("stage %d\n", d->stage);

    if (d->stage == 1) {
        /* What to do at the end of stage 1? */
        switch (d->int_type) {
        case INT_TYPE_IRQ:
            fprintf(stderr, "i6300esb_timer_expired: I would send APIC 1 INT 10 here if I knew how (XXX)\n");
            break;
        case INT_TYPE_SMI:
            fprintf(stderr, "i6300esb_timer_expired: I would send SMI here if I knew how (XXX)\n");
            break;
        }

        /* Start the second stage. */
        i6300esb_restart_timer(d, 2);
    } else {
        /* Second stage expired, reboot for real. */
        if (d->reboot_enabled) {
            d->previous_reboot_flag = 1;
            watchdog_perform_action(); /* This reboots, exits, etc */
            i6300esb_reset(&d->dev.qdev);
        }

        /* In "free running mode" we start stage 1 again. */
        if (d->free_run)
            i6300esb_restart_timer(d, 1);
    }
}

4.6.2.如何判断超时

判断超时是在主循环里面做的,大致流程如下

|----->main_loop()//主循环,基于glib mainloop的
	|----->main_loop_wait(nonblocking)
		|----->qemu_clock_run_timers(type)
			|----->timerlist_run_timers(main_loop_tlg.tl[type]);
				|----->i6300esb_timer_expired(opaque)

主循环线程,会反复调用timerlist_run_timers来遍历每个定时器链表,判断超时时间,下面只粘贴关键代码

bool timerlist_run_timers(QEMUTimerList *timer_list)
{
    QEMUTimer *ts;
    int64_t current_time;
    bool progress = false;
    QEMUTimerCB *cb;
    void *opaque;

    /*omit*/
    /*获取当前时间*/
    current_time = qemu_clock_get_ns(timer_list->clock->type);
    for(;;) {//这个循环,结束条件为本轮次函数调用没有任何定时器超时了为止
        qemu_mutex_lock(&timer_list->active_timers_lock);
        ts = timer_list->active_timers;//获取激活的定时器链表的head用来遍历
        if (!timer_expired_ns(ts, current_time)) {//判断是否超时
            qemu_mutex_unlock(&timer_list->active_timers_lock);
            break;
        }
        //如果超时,则从当前链表中剔除,并且调用超时函数,此处涉及到i6300esb的定时器,则调用i6300esb_timer_expired
        /* remove timer from the list before calling the callback */
        timer_list->active_timers = ts->next;
        ts->next = NULL;
        ts->expire_time = -1;
        cb = ts->cb;
        opaque = ts->opaque;
        qemu_mutex_unlock(&timer_list->active_timers_lock);

        /* run the callback (the timer list can be modified) */
        cb(opaque);//i6300esb_timer_expired
        progress = true;
    }

out:
    qemu_event_set(&timer_list->timers_done_ev);
    return progress;
}

整体设计就是这样,这个看门狗模块是学习qemu定时器的最佳案例。

reference


  1. linux下的watchdog ↩︎ ↩︎

  2. Watchdog device ↩︎

  3. Intel® 6300ESB I/O Controller Hub Datasheet ↩︎ ↩︎ ↩︎

你可能感兴趣的:(虚拟化,i6300esb,6300esb,qemu,虚拟化,virtualization)