我的内核学习笔记9:Intel内部看门狗iTCO_wdt驱动
来源:互联网 发布:看美漫的软件 编辑:程序博客网 时间:2024/06/06 18:53
本文对Intel e3800内部看门狗驱动源码进行分析。
一、概述
Intel e3800内部看门狗在手册的PCU - Power Management Controller (PMC)章节中介绍——在这里,WDT属于电源管理范畴了。有些ARM芯片,将watchdog单独成一个章节,这个东西,笔者是在无意中发现的。涉及的寄存器有TCO_RLD、TCO1_CNT、TCO_TMR。这些寄存器在后文将再次介绍。
在Linux内核中使用的驱动名为iTCO_wdt(为了行文方便,将对应的设备称为“iTCO_wdt设备”)。驱动位于drivers/watchdog/iTCO_wdt.c文件中。
本文基于linux 3.17.1版本内核进行分析。
内核配置(make menuconfig)信息如下:
Device Drivers ---> [*] Watchdog Timer Support ---> <M> Intel TCO Timer/Watchdog
使用模块形式编译,在加载该驱动后,就可以使用lsmod查看。另外,也会生成对应的驱动设备目录:/sys/bus/platform/devices/iTCO_wdt/。
二、iTCO_wdt设备注册
priv->abase = ACPIBASE; // ACPI基地址 priv->actrl_pbase = ACPICTRL_PMCBASE;其定义是:
#define ACPIBASE0x40#define ACPICTRL_PMCBASE0x44所有地址都可以在手册对应章节中找到。
初始化WDT在函数lpc_ich_init_wdt中。这个函数获取ACPI基地址。并初始化wdt_ich_res资源结构体数组,数组包含了ICH_RES_IO_TCO和ICH_RES_IO_SMI。TCO就是WDT使用到的部分。主要代码功能描述如下。
1、读取ACPI基地址值,即通过LPC这个PCI设备的配置空间偏移值ACPIBASE。
pci_read_config_dword(dev, priv->abase, &base_addr_cfg);base_addr = base_addr_cfg & 0x0000ff80;
2、设置resource,即把前面获取到的base_addr地址加上TCO偏移值赋给resource的start成员变量。
res = wdt_io_res(ICH_RES_IO_TCO);res->start = base_addr + ACPIBASE_TCO_OFF;res->end = base_addr + ACPIBASE_TCO_END;
3、添加mfd设备。
lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_WDT]);ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_WDT], 1, NULL, 0, NULL);到这样就完成了mfd的添加。
三、iTCO_wdt驱动
3.1 寄存器介绍
/* Address definitions for the TCO *//* TCO base address */#define TCOBASE(iTCO_wdt_private.tco_res->start)/* SMI Control and Enable Register */#define SMI_EN(iTCO_wdt_private.smi_res->start)#define TCO_RLD(TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */#define TCOv1_TMR(TCOBASE + 0x01) /* TCOv1 Timer Initial Value*/#define TCO_DAT_IN(TCOBASE + 0x02) /* TCO Data In Register*/#define TCO_DAT_OUT(TCOBASE + 0x03) /* TCO Data Out Register*/#define TCO1_STS(TCOBASE + 0x04) /* TCO1 Status Register*/#define TCO2_STS(TCOBASE + 0x06) /* TCO2 Status Register*/#define TCO1_CNT(TCOBASE + 0x08) /* TCO1 Control Register*/#define TCO2_CNT(TCOBASE + 0x0a) /* TCO2 Control Register*/#define TCOv2_TMR(TCOBASE + 0x12) /* TCOv2 Timer Initial Value*/注:在e3800手册上找不到TCO2_CNT和TCOv2_TMR的说明。但在ICH6手册上则可以找到。有兴趣的可以参考文后资源以了解更多。
TCO_RLD:重新计数寄存器。只有bit0~9有效。返回当前的计数,写入任何数值即可重新计数。
3.2 入口代码
static struct platform_driver iTCO_wdt_driver = { .probe = iTCO_wdt_probe, .remove = iTCO_wdt_remove, .shutdown = iTCO_wdt_shutdown, .driver = { .owner = THIS_MODULE, .name = DRV_NAME, },};static int __init iTCO_wdt_init_module(void){ int err; pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION); err = platform_driver_register(&iTCO_wdt_driver); if (err) return err; return 0;}static void __exit iTCO_wdt_cleanup_module(void){ platform_driver_unregister(&iTCO_wdt_driver); pr_info("Watchdog Module Unloaded\n");}module_init(iTCO_wdt_init_module);module_exit(iTCO_wdt_cleanup_module);
3.3、探测函数
iTCO_wdt看门狗的探测函数为iTCO_wdt_probe。
1、获取IO资源:
iTCO_wdt_private.tco_res = platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO); if (!iTCO_wdt_private.tco_res) goto out;
IO资源已经在lpc_ich中设置了,根据ICH_RES_IO_TCO就能获取到TCO的IO资源。
2、设置NO_REBOOT标志
/* Check chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ goto unmap_gcs_pmc; } /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ iTCO_wdt_set_NO_REBOOT_bit();
3、检测IO资源是否有冲突
/* The TCO logic uses the TCO_EN bit in the SMI_EN register */ if (!request_region(iTCO_wdt_private.smi_res->start, resource_size(iTCO_wdt_private.smi_res), dev->name)) { pr_err("I/O address 0x%04llx already in use, device disabled\n", (u64)SMI_EN); ret = -EBUSY; goto unmap_gcs_pmc; } if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) { /* * Bit 13: TCO_EN -> 0 * Disables TCO logic generating an SMI# */ val32 = inl(SMI_EN); val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ outl(val32, SMI_EN); } if (!request_region(iTCO_wdt_private.tco_res->start, resource_size(iTCO_wdt_private.tco_res), dev->name)) { pr_err("I/O address 0x%04llx already in use, device disabled\n", (u64)TCOBASE); ret = -EBUSY; goto unreg_smi; }
4、设置超时时间以及nowayout。
iTCO_wdt_watchdog_dev.timeout = WATCHDOG_TIMEOUT; watchdog_set_nowayout(&iTCO_wdt_watchdog_dev, nowayout);
默认超时时间为30秒。nowayout由内核配置CONFIG_WATCHDOG_NOWAYOUT来确定。内核配置的解释为:Disable watchdog shutdown on close。如果为true,表示关闭watchdog设备也不会停止看门狗。亦即一旦使能看门狗,是无关法关闭的,是没有退路的,所以叫“no way out”。
5、向系统注册看门狗设备。并打印超时时间。
ret = watchdog_register_device(&iTCO_wdt_watchdog_dev); if (ret != 0) { pr_err("cannot register watchdog device (err=%d)\n", ret); goto unreg_tco; } pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout);
3.4 watchdog子系统实现
在探测函数的最后,调用了watchdog_register_device向系统注册watchdong设备。这样就能使用Linux已有的watchdog子系统的框架了。
注册的iTCO_wdt_watchdog_dev及相关操作函数结构体定义如下:
static const struct watchdog_ops iTCO_wdt_ops = {.owner =THIS_MODULE,.start =iTCO_wdt_start,.stop =iTCO_wdt_stop,.ping =iTCO_wdt_ping,.set_timeout =iTCO_wdt_set_timeout,.get_timeleft =iTCO_wdt_get_timeleft,};static struct watchdog_device iTCO_wdt_watchdog_dev = {.info =&ident,.ops =&iTCO_wdt_ops,};其中包括了使能、停止、喂狗、设置超时时间等。“喂狗”是一种通称的叫法,——也有其它称呼,如笔者公司叫此操作为“打狗”。喂狗对应的实现函数是ping。
注册后,watchdog子系统会生成/dev/watchdog设备文件。并提供标准的ioctl控制命令,如WDIOC_SETTIMEOUT、WDIOC_KEEPALIVE。详情可参考drivers/watchdog目录下的watchdog_dev.c和watchdog_core.c文件。本文只关注iTCO如何实现start、stop、ping、set_timeout这几个函数。
3.5 iTCO相关操作
1、iTCO_wdt_start
static int iTCO_wdt_start(struct watchdog_device *wd_dev){unsigned int val;spin_lock(&iTCO_wdt_private.io_lock);iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, wd_dev->timeout);/* disable chipset's NO_REBOOT bit */if (iTCO_wdt_unset_NO_REBOOT_bit()) {spin_unlock(&iTCO_wdt_private.io_lock);pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n");return -EIO;}/* Force the timer to its reload value by writing to the TCO_RLD register */if (iTCO_wdt_private.iTCO_version >= 2)outw(0x01, TCO_RLD);else if (iTCO_wdt_private.iTCO_version == 1)outb(0x01, TCO_RLD);// 0表示TCO计时,1表示禁止/* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */val = inw(TCO1_CNT);val &= 0xf7ff;outw(val, TCO1_CNT);val = inw(TCO1_CNT);spin_unlock(&iTCO_wdt_private.io_lock);// 如果bit11为1,则说明无法启动WDT,失败if (val & 0x0800)return -1;return 0;}
2、iTCO_wdt_stop
static int iTCO_wdt_stop(struct watchdog_device *wd_dev){unsigned int val;spin_lock(&iTCO_wdt_private.io_lock);iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res);/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */val = inw(TCO1_CNT);val |= 0x0800; // 使能计时outw(val, TCO1_CNT);val = inw(TCO1_CNT);/* Set the NO_REBOOT bit to prevent later reboots, just for sure */iTCO_wdt_set_NO_REBOOT_bit();spin_unlock(&iTCO_wdt_private.io_lock); // 如果bit11为0,说明没有成功停止看门狗,返回失败if ((val & 0x0800) == 0)return -1;return 0;}
3、iTCO_wdt_ping
static int iTCO_wdt_ping(struct watchdog_device *wd_dev){spin_lock(&iTCO_wdt_private.io_lock);iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout); // 写1到TCO_RLD寄存器,重新计时/* Reload the timer by writing to the TCO Timer Counter register */if (iTCO_wdt_private.iTCO_version >= 2) {outw(0x01, TCO_RLD);} else if (iTCO_wdt_private.iTCO_version == 1) {/* Reset the timeout status bit so that the timer * needs to count down twice again before rebooting */outw(0x0008, TCO1_STS);/* write 1 to clear bit */outb(0x01, TCO_RLD);}spin_unlock(&iTCO_wdt_private.io_lock);return 0;}
4、iTCO_wdt_set_timeout
static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t){unsigned int val16;unsigned char val8;unsigned int tmrval;tmrval = seconds_to_ticks(t); // 时间(单位为秒)转换为tick/* For TCO v1 the timer counts down twice before rebooting */if (iTCO_wdt_private.iTCO_version == 1)tmrval /= 2;/* from the specs: *//* "Values of 0h-3h are ignored and should not be attempted" */if (tmrval < 0x04)return -EINVAL;if (((iTCO_wdt_private.iTCO_version >= 2) && (tmrval > 0x3ff)) || ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f)))return -EINVAL;iTCO_vendor_pre_set_heartbeat(tmrval); // 将新的超时时间写入TMR寄存器/* Write new heartbeat to watchdog */if (iTCO_wdt_private.iTCO_version >= 2) {spin_lock(&iTCO_wdt_private.io_lock);val16 = inw(TCOv2_TMR);val16 &= 0xfc00;val16 |= tmrval;outw(val16, TCOv2_TMR);val16 = inw(TCOv2_TMR);spin_unlock(&iTCO_wdt_private.io_lock);if ((val16 & 0x3ff) != tmrval)return -EINVAL;} else if (iTCO_wdt_private.iTCO_version == 1) {spin_lock(&iTCO_wdt_private.io_lock);val8 = inb(TCOv1_TMR);val8 &= 0xc0;val8 |= (tmrval & 0xff);outb(val8, TCOv1_TMR);val8 = inb(TCOv1_TMR);spin_unlock(&iTCO_wdt_private.io_lock);if ((val8 & 0x3f) != tmrval)return -EINVAL;}wd_dev->timeout = t;return 0;}
3.6 其它杂项
1、设置NO_REBOOT标志。
static void iTCO_wdt_set_NO_REBOOT_bit(void){ u32 val32; /* Set the NO_REBOOT bit: this disables reboots */ if (iTCO_wdt_private.iTCO_version == 3) { val32 = readl(iTCO_wdt_private.gcs_pmc); val32 |= 0x00000010; // bit4为NO_REBOOT标志 writel(val32, iTCO_wdt_private.gcs_pmc); } //...}
关于NO_REBOOT的解释如下:
No Reboot (NO_REBOOT) (no_reboot): This bit is set when the No Reboot strap is
sampled high on COREPWROK. This bit may be set or cleared by software if the strap is
sampled low but may not override the strap wh en it indicates No Reboot. When set, the
TCO timer will count down and generate th e SMI# on the first timeout, but will not
reboot on the second timeout.
参考资源:
1、baytrail手册:http://www.intel.com/content/www/us/en/embedded/products/bay-trail/atom-e3800-family-datasheet.html
2、ICH6手册:http://www.intel.com/content/www/us/en/io/io-controller-hub-6-datasheet.html
3、内核源码官网:https://www.kernel.org
4、内核源码查询:http://lxr.free-electrons.com/source/?v=3.17
李迟 2016.12.06 周二 夜
- 我的内核学习笔记9:Intel内部看门狗iTCO_wdt驱动
- 我的内核学习笔记7:Intel LPC驱动lpc_ich分析
- 我的内核学习笔记10:Intel GPIO驱动源码分析
- 我的内核学习笔记8:多功能设备mfd驱动
- 我的内核学习笔记3:我的platform驱动模板文件
- 我的 Intel MPI 笔记
- 我的内核学习笔记6:PCI驱动probe的一点认知
- 我的内核学习笔记11:linux leds-gpio驱动应用实例
- 我的内核学习笔记12:linux i2c-gpio驱动应用实例
- 我的内核学习笔记:环境
- STM32 看门狗学习笔记
- 看门狗定时器学习笔记
- 【学习笔记】看门狗子系统
- MSP430学习笔记-看门狗
- linux 3.1内核的驱动路(10)--移植看门狗驱动
- Linux内核学习笔记之网卡驱动的详细分析
- 驱动学习日记1--linux内核模块的简单笔记
- 我的内核学习笔记4:sysfs学习
- 浅谈JavaScript引用类型——RegExp
- i++和++i 的区别
- 有关appcan打包失败
- 用numpy.rollaxis改变图片轴次序
- 中科爱讯双WiFi探针TZ007操作及配置
- 我的内核学习笔记9:Intel内部看门狗iTCO_wdt驱动
- avalon循环分支判断ms-if-loop
- python -正则表达式--单字符匹配
- UVa 1585 Score(习题3-1)
- 我的内核学习笔记10:Intel GPIO驱动源码分析
- 使用 containsString 遇到的问题
- xamarin.iOS 导航栏添加按钮
- 854计算机基础——备考建议+近年考点汇总
- 缓存算法