qemu intel i6300esb watchdog虚拟外设分析

news/2025/2/13 17:37:02/

文章目录

  • 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中添加

...
<devices><watchdog model='i6300esb'/>
</devices>
...

或者直接在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 <linux/watchdog.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>#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;//开启watchdogg_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);}//关闭watchdogwrite(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,因为这个函数的第三个参数会附带各种行为,//这些行为就是为了模拟外设的读写的,而内存不需要这样模拟,所以没有这些opspci_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位,初始化为0d->free_run = 0;d->locked = 0;d->enabled = 0;//设备初始化是没有使能的d->timer1_preload = 0xfffff;//stage1状态的超时时间,寄存器mmio地址为Base + 00hd->timer2_preload = 0xfffff;//stage2状态的超时时间,寄存器mmio地址为Base + 04hd->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) {//0x68if (!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_expiredprogress = 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 ↩︎ ↩︎ ↩︎


http://www.ppmy.cn/news/553783.html

相关文章

linux驱动程序ioctl函数用法

一、 什么是ioctl ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理&#xff0c;就是对设备的一些特性进行控制&#xff0c;例如串口的传输波特率、马达的转速等等。它的调用个数如下&#xff1a; int ioctl(int fd, ind cmd, …)&#xff1b;…

S3C2440 字符设备驱动程序之中断方式的按键驱动_编写代码(七)

参考&#xff1a;https://blog.csdn.net/fengyuwuzu0519/article/details/71046343 字符设备驱动程序之中断方式的按键驱动_编写代码 使用中断方式&#xff0c;那么肯定有一个中断的初始化注册&#xff0c;就是告诉内核&#xff0c;我按下按键的时候会触发一个中断&#xff0c;…

创建自定义的Spring Boot Starter

1. 概述 Spring boot的开发人员给那些流行的开源项目提供了很多Starter&#xff0c;但是我们并不局限于这些。 我们可以创建自己的Starter&#xff0c;如果我们有一个公司内部使用的代码库&#xff0c;如果我们实在Spring boot 项目中使用&#xff0c;那给这个代码库创建一个…

vue_t_v14

VUE vue框架的两大核心&#xff1a; 数据驱动和组件化。 一、前端开发历史 1994年可以看做前端历史的起点&#xff08;但是没有前端的叫法&#xff09; 1995年网景推出了JavaScript 1996年微软推出了iframe标签&#xff0c; 实现了异步的局部加载 1999年W3C发布第四代HTML标…

Linux设备驱动模型与 sysfs实现分析以及设计模式应用

RTOS和Linux系统上开发驱动的方式非常的不同,在RTOS系统下,驱动和驱动之间并没有实质性的联系,不同的驱动和BSP之间仅仅通过一层很薄很薄的设备管理框架聚合在一起构成RTOS的设备管理子系统。图形化表示如下: 设备驱动&BSP之间互相独立,互不影响,互不依赖,独立实现,…

usb驱动开发13——设备生命线

上一节勉勉强强把struct urb这个中心给说完&#xff0c;接着看那三个基本点。 第一个基本点&#xff0c;usb_alloc_urb函数&#xff0c;创建urb的专用函数&#xff0c;为一个urb申请内存并做初始化&#xff0c;在drviers/usb/core/urb.c里定义&#xff1a; /** usb_alloc_urb …

驱动中ioctl参数分析

一、ioctl的简介&#xff1a; 虽然在文件操作结构体"structfile_operations"中有很多对应的设备操作函数&#xff0c;但是有些命令是实在找不到对应的操作函数。如CD-ROM的驱动&#xff0c;想要一个弹出光驱的操作&#xff0c;这种操作并不是所有的字符设备都需要的…

【NLP】用python实现文本转语音处理

一、说明 介绍一款python调用库&#xff0c;离线软件包pyttsx3 API&#xff0c;它能够将文字转化成语音文件。Python 中有多种 API 可用于将文本转换为语音。pyttsx3 是一个非常易于使用的工具&#xff0c;可将输入的文本转换为音频。与其它类似的库不同&#xff0c;它可以离线…