害群之马printk

写红外设备的编码学习驱动,学习到代码的高低电平的长度竟然有0,连着的好几个码长还有完全相同的,开始判断可能是GPIO口的学习中断进入不正常,或者是定时器没有设置好。调试花了一个星期,最后却发现是因为内核调试函数printk导致的。


printk作用

printk和printf的作用功能基本相似,只不过一个运行在内核空间,一个运行在用户空间。printk是在内核中运行的向控制台输出显示的函数,Linux内核首先在内核空间分配一个静态缓冲区,作为显示用的空间,然后调用sprintf,格式化显示字符串,最后调用tty_write向终端进行信息的显示。

ARM内核编译的时候默认printk调试信息经过串口打印,而远程登录到开发板的终端是无法显示printk的信息。但可以通过proc文件系统和命令的方式查看。在终端中输入cat \proc\kmsg或者dmesg都可以看到打印信息,前者会实时更新。


中断中的大忌

中断可能随时发生,所以中断处理函数必须随时待命。这样就要求中断处理程序能够快速执行。中断处理程序中不能有会引起睡眠的代码或者阻塞或者耗费大量时间的函数调用。

在linux内部为了中断处理快,同时完成的工作量多,把中断处理程序分成了上半部和下半部两部分。上半部分做必要的硬件复位或者应答,下半部分做数据处理或剩余的工作。这样就保证了中断处理快而多。


细节的错误

static irqreturn_t gpio_study_irq_handler(int irq, void *wbuf)
{
	unsigned int *buf=(unsigned int *)wbuf;
	unsigned int temp;
	if(0==IrDA_cnt){
		timer_setup(&Study_Timer,MAX_CODE_WIDTH);
		timer_on(&Study_Timer);
		IrDA_cnt++;
		return IRQ_RETVAL(IRQ_HANDLED);
	}
	
	temp=ioread32(Study_Timer.tcnto);//读取拷贝的观测值
	timer_off(&Study_Timer); 
	//printk("current observation value: %d\n",temp);
	//这里的内核打印会占据掉中断的很长时间,导致后面出错。
	*(buf+IrDA_cnt-1)=MAX_CODE_WIDTH-read_timer_cnt(&Study_Timer);
	iowrite32(MAX_CODE_WIDTH,Study_Timer.tcntb);//重新填充计数值
	//把填充值更新
	temp=ioread32(Study_Timer.tcon);
	iowrite32(temp|0x02,Study_Timer.tcon);//更新状态寄存器
	temp=ioread32(Study_Timer.tcon);
	iowrite32(temp&(~0x02),Study_Timer.tcon);//更新完寄存器关闭

	timer_on(&Study_Timer);
	IrDA_cnt++;
	if(IrDA_cnt > CODE_MAX_LEN)
	{
		wake_up_interruptible(&IrDA_Study_Queue);
		return IRQ_RETVAL(IRQ_HANDLED);
	}
	return IRQ_RETVAL(IRQ_HANDLED);
}

这是外部中断GPIO口的中断服务函数,每次进入中断记下定时器的计数值。这样就可以知道高低电平的持续时间。开始时,通过printk打印Timer的观察值来跟踪调试程序,发现打印出来的数值是这样的。

<4>current observation value: 49786

<4>current observation value: 49702

<4>current observation value: 50000

<4>current observation value: 49969

<4>current observation value: 50000

<4>current observation value: 49996

<4>current observation value: 50000

<4>current observation value: 49998

<4>current observation value: 47827

<4>current observation value: 49392

<4>current observation value: 49906

<4>current observation value: 50000

很明显的感觉是定时器在某些时刻没有正常工作。但实际上是因为printk占据了中断处理程序中的大部分时间,在最后几行重新装载计数值并timer_on(&Study_Timer)的时候,外部的中断时刻已经到来,导致一出中断瞬间重新又进了中断。因而看到的就是计数值维持在50000左右没有改变。


Reference

[1].Linux Kernel Development.Page 91~93

[2].http://stackoverflow.com/questions/25518427/the-timer-counting-error-of-linux-device-driver-based-on-s3c2440



Previous     Next
/
Published under (CC) BY-NC-SA in categories linux  tagged with