海思外部看门狗驱动
设计概要
本章主要使用海思HI3518C平台芯片,芯片内置看门狗,这里我们没用到内部狗,我们使用的是外部看门口,为的是预防硬件板导致MCU主芯片程序跑偏,所以需要在外部加一个看门狗芯片,定时喂狗。
看门狗芯片是在机器上电就开始工作,属于逻辑芯片,我们需要在机器上电就进行喂狗,所以这里需要在uboot和kernel中实现喂狗程序,等进入文件系统时再移交给文件系统操作。
Uboot部分修改:
Uboot中添加gpio驱动;
1. 实现喂狗函数,在\board\hi3518\board.c
//********************************//
//Watdog //
//Feed_Watdog(0); 正常喂狗
//Feed_Watdog(1); 初始化喂狗程序,第一次喂狗必须执行。
//Feed_Watdog(2); 关闭喂狗程序
//Feed_Watdog(3); 计数喂狗,防止喂狗太快,实现是调用100000次该函数只
//喂一次狗
//********************************//
static unsigned char openwatdog = 0;
void Watdog_init(void)
{
unsigned int u32Temp;
__raw_writel(0x00,0x200f00c0);
u32Temp = __raw_readl(0x20190400);
u32Temp |= (1 << 3);
__raw_writel(u32Temp,0x20190400);
__raw_writel(0,0x20190020);
}
void Feed_Watdog(int key)
{
static unsigned int initwatdog = 0;
static unsigned int freedogstutas = 0;
static unsigned int spiflase_read = 0;
if(key == 1)//第一次初始化喂狗
initwatdog = 1;
else if(key == 2)//关闭喂狗
initwatdog = 0;
if(initwatdog == 2)//正常喂狗
{
if(key == 3)这是在put函数中,因为喂狗太快而设置
{
spiflase_read++;
if(spiflase_read == 100000)
{
spiflase_read = 0;
if(freedogstutas == 0)
{
__raw_writel(0x00,0x20190020);
freedogstutas = 1;
}
else
{
__raw_writel(0xff,0x20190020);
freedogstutas = 0;
}
}
}
else //get函数和main_loop函数喂狗
{
if(freedogstutas == 0)
{
__raw_writel(0x00,0x20190020);
freedogstutas = 1;
}
else
{
__raw_writel(0xff,0x20190020);
freedogstutas = 0;
}
}
}
else if(initwatdog == 1)//第一次初始化喂狗
{
Watdog_init();
initwatdog = 2;
}
}
//该函数是为了配套pl01x实现操作串口是同时进行喂狗。
void hi_watchdog_reset(void) 该函数为了实现全局喂狗
{
static unsigned int freedogstutas = 0;
if(openwatdog == 1);
{
if(freedogstutas == 0)
{
__raw_writel(0x00,0x20190020);
freedogstutas = 1;
}
else
{
__raw_writel(0xff,0x20190020);
freedogstutas = 0;
}
}
}
2. 定义喂狗函数,在\arch\arm\include\asm\u-boot-arm.h中添加
/* watdog */
void Feed_Watdog(int key);
3. 进入主函数循环之前加喂狗,在\arch\arm\lib\board.c中main_loop函数添加
Feed_Watdog(1);
for (;;) {
main_loop ();
}
4. 在\common\main.c中添加
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
Feed_Watdog(0);//add Feed_Watdog
这里是启动延时1秒位置,需要在延时1秒前后都驱动gpio;
5. 进入引导内核函数中加喂狗,在\arch\arm\lib\bootm.c
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
Feed_Watdog(0);
6. 本章是使用SPI Flash,uboot中在引导内核过程中会从Flash读取内核文件,在这读取文件过程中有一两秒是停留在读写过程中,所以我们必须在这里加喂狗程序。具体位于\drivers\mtd\spi\hisfc350\hisfc350.h
#define HISFC350_DMA_WAIT_CPU_FINISH(_host) do {\
unsigned int timeout = 0x10000000; \
while (((hisfc_read((_host), HISFC350_BUS_DMA_CTRL) \
& HISFC350_BUS_DMA_CTRL_START)) && timeout) {\
--timeout; \
Feed_Watdog(3); \
}\
if (!timeout) \
DBG_BUG("dma wait cpu finish timeout\n"); \
} while (0)
//这里添加是为了在spi read数据的时候喂狗;防止操作flash时没喂狗;
5. 本章还运用到了原有系统中喂狗函数,该函数实现是在串口操作中实现,当串口读写调用时,同时会调用喂狗程序,具体实现函数\drivers\serial\serial_pl01x.c
static void pl01x_putc (int portnum, char c)
static int pl01x_getc (int portnum)
这两个函数是串口打印及输入(读写),两个函数都调用WATCHDOG_RESET();
实现喂狗功能。
8. 打开系统喂狗WATCHDOG_RESET宏定义,修改inlcude/configs/hi3518c.h
添加:
/*
watdog
*/
#define CONFIG_HW_WATCHDOG 1
9.定义外部喂狗函数,指向(1)中的喂狗函数,修改inlcude/watchdog.h
#ifdef CONFIG_HW_WATCHDOG
#if defined(__ASSEMBLY__)
#define WATCHDOG_RESET bl hw_watchdog_reset
#else
extern void hi_watchdog_reset(void);//hw_watchdog_reset(void);
#define WATCHDOG_RESET hi_watchdog_reset//hw_watchdog_reset
#endif /* __ASSEMBLY__ */
使uboot所有的WATCHDOG_RESET都调用到board.c中的hi_watchdog_reset函数
至此uboot完成喂狗,在加载完内核后,需要解压内核运行,下一章节说明;
本章主要讲解内核实现喂狗程序,内核实现喂狗本身比较容易,但是因机器是用到SPI Flash,其速度较nand flash慢很多,这导致在解压内核时间需要一两秒左右,这导致喂狗中断,所以需要在解压缩程序中进行喂狗。
1. 在主函数中实现定时喂狗,在\init\main.c
定义及实现代码部分
//------------------------start add watchdog-----------------------------------------
static unsigned int reg_read32(unsigned long addr)
{
return (unsigned int)readl(IO_ADDRESS(addr));
}
//EXPORT_SYMBOL(reg_read32);
static void reg_write32(unsigned long addr, unsigned int data)
{
writel(data, IO_ADDRESS(addr));
}
//EXPORT_SYMBOL(reg_write32);
#define HIDOG_PFX "HiDog: "
#ifndef SLEEP_MILLI_SEC
#define SLEEP_MILLI_SEC(nMilliSec) \
do { \
long timeout = (nMilliSec) * HZ /1000; \
while (timeout > 0) \
{ \
timeout = schedule_timeout(timeout); \
} \
}while (0);
#endif
struct task_struct *p_dog;
void watchdog_thread_exit(void);
static int hidog_deamon(void *data)
{
unsigned char times=0;
while(!kthread_should_stop())
{
reg_write32(0x20190020, 0x00);
SLEEP_MILLI_SEC(500);
reg_write32(0x20190020, 0xff);
SLEEP_MILLI_SEC(500);
//reg_write32(0x20190020, 0x00);
//SLEEP_MILLI_SEC(500);
if(times == 16)
//if(times == 20)
{watchdog_thread_exit();}
times++;
}
return 0;
}
static int watchdog_thread(void)
{
unsigned int u32Temp;
//gpio5_3, output, high, feed
reg_write32(0x200f00c0, 0x00);
u32Temp = reg_read32(0x20190400);
u32Temp |= (1 << 3);
reg_write32(0x20190400, u32Temp);
p_dog = kthread_create(hidog_deamon, NULL, "hidog");
if(IS_ERR(p_dog) <0) {
printk(KERN_ERR HIDOG_PFX "create hidog_deamon failed!\n");
return -1;
}
wake_up_process(p_dog);
printk("<3>""start wdt_thread/n");
return 0;
}
void watchdog_thread_exit(void)
{
if(p_dog)
{
printk("<3>""stop wdt_thread/n");
kthread_stop(p_dog);
p_dog = NULL;
}
}
/***************** end *************************/
初始化函数中调用喂狗实现函数。
static int __init kernel_init(void * unused)中添加
watchdog_thread();
以上函数都是在内核初始化时运行的,这是一种方法,但是需要设置喂狗时间次数,因为什么还需要将喂狗权利移交给文件系统,从内核初始化到文件系统喂狗次数我们经过测算大概需要执行“times == 16”然后调用watchdog_thread_exit退出线程。
本章还设计了一个内核驱动喂狗,这是为了给文件系统实现喂狗设计,文件系统可以直接调用驱动设备直接控制喂狗关闭,其实就是简单的GPIO口操作,以下2-4讲解设计过程。
2. 内核配置文件中添加编译选项\drivers\char\kconfig添加:
config HISI_WATDOG
tristate "HISI_WATDOG module samlpe"
default n
help
HISI_WATDOG module sample
3. 内核编译文件中添加编译选项\drivers\char\Makefile中添加
obj-$(CONFIG_HISI_WATDOG) += watchdog_drv.o
4. 在\drivers\char中添加文件
watchdog_drv.c watchdog_drv.h
该程序为创建线程喂狗,程序中第一次喂狗为低电平,
while(!kthread_should_stop())
{
reg_write32(0x20190020, 0xff);
SLEEP_MILLI_SEC(500);
reg_write32(0x20190020, 0x00);
SLEEP_MILLI_SEC(500);
}
程序可以参照“1”中进行设计,这里我就不多说了。
在此遇到问题:
在uboot过度kernel时,因kernel解压缩时间超过大于1.45秒,我们所选的外部看门狗芯片技术文档显示1.6S-2S喂狗,但实际喂狗时间为1.3S,所以引发此问题;
解决方案:
需要在解压缩中进行喂狗,实现方法如下:
1.在arch/arm/boot/compressed/misc.c 函数的
extern int do_decompress_hi(u8 *input, int len, u8 *output, void (*error)(char *x));
#define Mwritel(v,a) (*(volatile unsigned int *)(a) = (v))
#define Mreadl(a) (*(volatile unsigned int *)(a))
进入解压前后进行喂狗,在decompress_kernel函数中加
void
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
int ret;
output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
//******add by edw*****
u32 u32Temp;
//initfreedog = 1;
Mwritel(0x00, 0x200f00c0);
u32Temp = Mreadl(0x20190400);
u32Temp |= (1 << 3);
Mwritel(u32Temp,0x20190400);
Mwritel(0x00, 0x20190020);
putstr("Uncompressing Linux...");
ret = do_decompress_hi(input_data, input_data_end - input_data,
output_data, error);
Mwritel(0xff, 0x20190020);
//************************//
if (ret)
error("decompressor returned an error");
else
putstr(" done, booting the kernel.\n");
}
2.位于arch/arm/boot/compressed/decompress.c中加
//******************* add by edw*****************//
int do_decompress_hi(u8 *input, int len, u8 *output, void (*error)(char *x))
{ initfreedog = 1;//add by edw
return decompress(input, len, NULL, NULL, output, NULL, error);
}
3.定义喂狗标志,位于lib/decompress_inflate.c中加外部定义
static int INIT nofill(void *buffer, unsigned int len)
{
return -1;
}
extern unsigned char initfreedog;//add by edw
4.喂狗实现函数,位于lib/zlib_inflate/inftrees.c中加定义
//add by edw
#define Mwritel(v,a) (*(volatile unsigned int *)(a) = (v))
#define Mreadl(a) (*(volatile unsigned int *)(a))
unsigned int freedogstutas = 0;
unsigned char initfreedog = 0;//add by edw
void hi_watchdog_free(void)//add by edw
{
u32 u32Temp;
if(initfreedog == 1)
{
if(freedogstutas == 0)
{
Mwritel(0xff,0x20190020);
freedogstutas = 1;
}
else
{ freedogstutas = 0;
Mwritel(0x00,0x20190020);
}
}
}
添加喂狗实现函数在zlib_inflate_table函数中加
/* initialize state for loop */
huff = 0; /* starting code */
sym = 0; /* starting code symbol */
len = min; /* starting code length */
next = *table; /* current table to fill in */
curr = root; /* current table index bits */
drop = 0; /* current bits to drop from code for index */
low = (unsigned)(-1); /* trigger new sub-table when len > root */
used = 1U << root; /* use root table entries */
mask = used - 1; /* mask for comparing low */
/* check available table space */
if (type == LENS && used >= ENOUGH - MAXD)
return 1;
/* process all codes and make table entries */
for (;;) {
hi_watchdog_free();//add by edw
/* create table entry */
this.bits = (unsigned char)(len - drop);
if ((int)(work[sym]) < end) {
this.op = (unsigned char)0;
this.val = work[sym];
}
实现方法说明:在内核解压缩(包括内核本身解压缩和文件系统下使用mount挂载解压文件系统)中会调用到decompress_inflate.c函数中的gunzip接口,该接口调用的接口文件有:
#include "zlib_inflate/inftrees.h"
#include "zlib_inflate/inffast.h"
#include "zlib_inflate/inflate.h"
我们选取inftrees.h中进行喂狗;
在这里出现一个问题,就是该函数(lib下的解压缩函数),每次调用该接口时变量函数都是重新分配空间的,以致变量每次调用都是初始化值;
这就引发一个问题,在kernel解压缩时我们需要对gpio进行操作(虚拟地址映射到物理地址中),这样操作时没问题的;但是在文件系统起来后对该虚拟地址进行操作就会引起指针复用或段错误,所以我们应该采用一种方法--在kernel解压缩时进行操作虚拟地址而在系统起来后不进行操作;
因为解压缩函数都是调用时初始化的,所以不能使用局部变量或者静态变量(编译器在该阶段中不允许采用静态变量)进行操作;
我们只能在外部接口中给其定义一个变量:
从最先的misc.c》decompress_kernel函数开始,该函数中还没有调用到lib接口,decompress.c函数才调用到lib接口,所以我们需要在misc.c中重定义一个函数接口(decompress.c);而在decompress.c中我们需要操作initfreedog变量(开启lib喂狗),并在lib的入口函数(decompress_inflate.c)中定义外部变量以连接上层(decompress.c)和下层(inflate.c)zlib_inflate接口,调用到(inftrees.c)zlib_inflate_table接口;而在inftrees.c中才是真正实现变量initfreedog的;