本文对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设备注册

iTCO_wdt设备的注册实现过程在文件drivers/mfd/lpc_ich.c中。在LPC探测函数lpc_ich_probe对ACPI基地址进行赋值,代码如下:
    priv->abase = ACPIBASE; // ACPI基地址priv->actrl_pbase = ACPICTRL_PMCBASE;

其定义是:

#define ACPIBASE        0x40
#define ACPICTRL_PMCBASE    0x44

所有地址都可以在手册对应章节中找到。

初始化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 寄存器介绍

iTCO_wdt.c文件对于TCO基地址和寄存器说明如下:
/* 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有效。返回当前的计数,写入任何数值即可重新计数。
TCO_STS:状态寄存器。bit3表示计数达到0后产生SMI(System Management Interrupt)。
TCO1_CNT:控制寄存器。bit11表示是否开启计数(即开启或停止看门狗)。
TCO_TMR:计数寄存器。bit16~25有效。手册说每次计数为0.6秒,最大超时时间为1023*0.6=613.8秒。不过笔者从代码和实际测试结果来看,至少在e3800上是不正确的。e3800的这个寄存器数值即超时秒数。

3.2 入口代码

iTCO_wdt是一个platform设备驱动,其入口代码片段如下:
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_RLDregister */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驱动相关推荐

  1. STM32 学习笔记 -- 基于stm32f4的看门狗配置和实验代码

    基于stm32f4的看门狗配置和实验代码 以下本人对stm32f4xx的独立看门狗和窗口看门狗学习.理解和总结,程序的说明和解释均在注释中,仔细阅读不难理解.我已经过验证,有问题或错误请指出. 版权声 ...

  2. linux块设备驱动编写,Linux内核学习笔记 -49 工程实践-编写块设备驱动的基础

    块设备可以随机存储.字符设备,比如键盘,只能按照输入顺序存取,不可随机,打乱输入的字节流. 文件系统层,包括常见的文件系统,以及虚拟文件系统层VFS,字符设备可以直接用应用程序打开.块设备不会在应用程 ...

  3. 我的内核学习笔记7:Intel LPC驱动lpc_ich分析

    接触这么久的内核代码,还没有真正分析一个完整的驱动源码,都是零零散散写只言片字.本文就作一个尝试,写一写Linux内核源码分析层面的文章. 本文介绍基于Intel baytrail系列的e3800系列 ...

  4. Windows x64内核学习笔记(四)—— 9-9-9-9-12分页

    Windows x64内核学习笔记(四)-- 9-9-9-9-12分页 前言 9-9-9-9-12分页 实验一:线性地址转物理地址 页表基址 定位基址 PTE to PXE 实验二:通过页表基址定位各 ...

  5. Windows x64内核学习笔记(三)—— SMEP SMAP

    Windows x64内核学习笔记(三)-- SMEP & SMAP SMEP & SMAP 实验:构造IDT后门 第一步:编译以下代码 第二步:构造IDT后门 第三步:运行程序 第四 ...

  6. 《寒江独钓》内核学习笔记

    <寒江独钓>内核学习笔记(1)-- IRP - .Little Hann 时间 2013-11-30 15:40:00  博客园_.Little Hann原文  http://www.cn ...

  7. Windows x64内核学习笔记(二)—— IA-32e模式

    Windows x64内核学习笔记(二)-- IA-32e模式 IA-32e模式 模式检测 强制平坦段 任务切换 中断门描述符 FS / GS 模式切换 32位程序进内核 64位程序进内核 实验:模式 ...

  8. Windows x64内核学习笔记(五)—— KPTI(未完待续)

    Windows x64内核学习笔记(五)-- KPTI(未完待续) KPTI 实验一:构造IDT后门并读取Cr3 参考资料 KPTI 描述:KPTI(Kernel page-table isolati ...

  9. Windows x64内核学习笔记(一)—— 环境与配置

    Windows x64内核学习笔记(一)-- 环境与配置 前言 新特性 基础要求 实验环境 Guest Win10配置 问题解决 参考资料 前言 之前,跟着海哥学习了windows内核的一些机制,包括 ...

最新文章

  1. C#用 SendKyes 结合 Process 或 API FindWindow、SendMessage(PostMessage) 等控制外部程序
  2. 使用FFT来计算IFFT
  3. AU3学习案例----------考勤机手工补卡
  4. 第十五届智能车竞赛不公平竞争情况反映以及审议结果
  5. SparkSql常用命令操作
  6. MyBatis-14MyBatis动态SQL之【foreach】
  7. ajax向服务端发送请求验证用户名是否可用小示例
  8. 求你了,别再用 print 调试代码了
  9. CentOS bug修复指令集(阿里云漏洞修复方法)
  10. idea下git log乱码问题
  11. linux内核设计与实现 怎么读,《Linux内核设计与实现》读书笔记(一)
  12. D3 scaleThreshold
  13. 海量图片去重算法-局部分块Hash算法
  14. ToStringBuilder.reflectionToString
  15. Unity做MMD(一)资源处理
  16. IntelliJ Idea 解决 Could not autowire. No beans of 'xxxx' type found 的错误提示
  17. 第一章 C语言编程(Ivor Horton)
  18. Swift学习笔记 (四十二) 不透明类型
  19. MIT推出3D全息图生成新方法,可在智能手机上实时运行
  20. C语言 强符号与弱符号

热门文章

  1. C11全系产品涨价后 零跑汽车宣布T03全系车型调价
  2. IDC:2021年全年中国市场折叠屏产品规模约150万台
  3. 4999元起!iQOO 9 Pro今日首销:骁龙8旗舰处理器+独立显示芯片Pro
  4. 京东集团副总裁王楠:数智化是打造国际消费中心城市的必由之路
  5. 国家邮政局:9月份全国快递服务企业业务收入完成921.4亿元,同比增长11.8%
  6. SIA:全球半导体行业销售额7月达到454亿美元 创下月度纪录
  7. 小米12系列或首发骁龙898旗舰芯:三星4nm工艺
  8. 一加9R国行版即将到来:搭载骁龙870 售价预计3K内
  9. 苹果正开发更轻薄MacBook Air 且配备MagSafe
  10. 三星Galaxy Note 20相机细节曝光:这个镜头略显鸡肋或被舍弃