杂三
时间:2009-06-26 来源:qingfenglala
http://www.mjmwired.net/kernel/Documentation/kernel-doc-nano-HOWTO.txt
比如: drivers/rtc/rtc-pxa.c 中写:
/**
* pxa_rtc_probe() - probe function for pxa micco driver
* @pdev: device structure from bus driver
*
* 1. set gpio0_2 as gpio mode and in direction.
*
* 2. register rtc driver to rtc framework
*
* 3. init work queue which will be used in interrupt handler
*
**/
static int pxa_rtc_probe(struct platform_device *pdev)
{
工具生成:
./scripts/kernel-doc -html drivers/rtc/rtc-pxa.c > /mnt/hgfs/share1/rtc-pxa.htm
最后获得:
pxa_rtc_probe - probe function for pxa micco driver
int pxa_rtc_probe (struct platform_device * pdev)
Arguments
pdev
device structure from bus driver
Description
1. set gpio0_2 as gpio mode and in direction.
2. register rtc driver to rtc framework
3. init work queue which will be used in interrupt handler
天写 rtc 驱动, rtc 寄存器使用了 bcd 编码, 于是写了一个宏进行转换:
#define FROMBCD8(val) (((val >> 4) & 0xf) * 10 + (val & 0xf))
然后为了获取某个寄存器,比如小时, 是这么调用的:
tm->tm_hour = FROMBCD8(micco_read(RTC_HOUR_REG));
但是, micco_read 的打印语句表明该函数被调用了两次!
仔细研究原来这是由于宏的副作用导致的!
宏扩展后变为:
tm->tm_hour = (((micco_read(RTC_HOUR_REG)>> 4) & 0xf) * 10 + (micco_read(RTC_HOUR_REG)& 0xf))
这导致了低效(有时是错误)的代码!micco_read 可是使用低速 i2c 在访问寄存器!
修改为:
u8 val;
val = micco_read(RTC_HOUR_REG);
tm->tm_hour = FROMBCD8(val);
即可!
该实例,表明宏的缺陷!正确的方法是应该使用 inline 函数, 少使用宏。
上面的宏改为:
static u8 inline frombcd8(u8 val)
{
return ((val >> 4) & 0xf) * 10 + (val & 0xf);
}
然后, 调用改为:
tm->tm_hour = frombcd8 (micco_read(RTC_HOUR_REG));
也没有问题, 而且代码更加简洁。
开机过程中的内核打印
作者: zjujoe 转载请注明出处
Email:[email protected]
BLOG:http://blog.csdn.net/zjujoe
前言
嵌入式开发中, 通常使用串口输出调试信息,了解运行状态。 内核启动过程中,在不同阶段会通过不同的方式将调试信息输出到串口。 (注:以下内容针对 arm-linux.)
解压缩阶段
解压缩阶段内核会输出:
Uncompressing Linux................................ done, booting the kernel.
查找内核, 会发现如上输出是在如下语句中打印的:
312 putstr("Uncompressing Linux...");
313 gunzip();
314 putstr(" done, booting the kernel\n");
putstr 会调用 uncompress.h 中的 putc 来完成任务。
通常, 嵌入式开发中,我们假定 bootloader 已经进行了串口初始化, 所以我们只需要对串口进行写操作就可以了。比如:
18static void putc(char c)
19{
20 volatile u32 *uart = (volatile void *) DAVINCI_UART0_BASE;
21
22 while (!(uart[UART_LSR] & UART_LSR_THRE))
23 barrier();
24 uart[UART_TX] = c;
25}
实现函数,就可以看到串口输出了。当然, 如果您是完美主义者, 还应该实现:
27static inline void flush(void)
28{
29 volatile u32 *uart = (volatile void *) DAVINCI_UART0_BASE;
30 while (!(uart[UART_LSR] & UART_LSR_THRE))
31 barrier();
32}
Image 启动早期
这里的“早期”定义为串口驱动初始化之前。 这里又分为两个阶段:汇编阶段与 C 语言阶段。
汇编阶段
在启动正常后一般不需要在此阶段输出信息。但是,如果系统还没有正常启动, 则需要在汇编里向串口输出一些信息。
内核提供了函数:
116ENTRY(printascii)
为了使用它,我们需要实现:
addruart 获得调试串口的地址
senduart 发送一个字节
waituart 等待串口可用
三个汇编函数。
可以参考某一平台来实现, 比如:
http://lxr.linux.no/linux+v2.6.27/arch/arm/mach-pxa/include/mach/debug-macro.S#L16
pxa 的实现表明, 由于 uart 通常是8250 兼容的, 所以很可能我们只需要实现:
addruart, 提供一下串口基地址,即可。
另外, 值得注意的是:汇编代码会经历实地址模式与虚拟地址模式两个阶段。 而上面的打印函数是可以同时用于两种模式的。
C 语言阶段
C 语言代码从 start_kernel 开始, 可以看到,内核很快就会调用 printk:
printk(KERN_NOTICE);
然而在 arm 平台上, 如上的打印要等 console_init()函数执行后,才会输出到串口。
如果有人有兴趣, 可以为arm平台实现一个 patch, 使得在打印内核 notice时就能够向串口输出。 当然, 对于 BSP 开发, 目前的实现基本够用, 因为驱动程序初始化时间较晚,在 console_init 之后。
我们看一下该函数的实现:
3644/*
3645 * Initialize the console device. This is called *early*, so
3646 * we can't necessarily depend on lots of kernel help here.
3647 * Just do some early initializations, and do the complex setup
3648 * later.
3649 */
3650void __init console_init(void)
3651{
3652 initcall_t *call;
3654 /* Setup the default TTY line discipline. */
3655 tty_ldisc_begin();
3657 /*
3658 * set up the console device so that later boot sequences can
3659 * inform about problems etc..
3660 */
3661 call = __con_initcall_start;
3662 while (call < __con_initcall_end) {
3663 (*call)();
3664 call++;
3665 }
3666}
原来该函数会调用 __con_initcall_start 段里的函数。而串口驱动正是在此注册了一个这样的函数, 而提供了 early printk 功能。
比如, http://lxr.linux.no/linux+v2.6.27/drivers/serial/8250.c 中,就实现了改功能:
2645static int __init serial8250_console_init(void)
2646{
2647 if (nr_uarts > UART_NR)
2648 nr_uarts = UART_NR;
2649
2650 serial8250_isa_init_ports();
2651 register_console(&serial8250_console);
2652 return 0;
2653}
2654console_initcall(serial8250_console_init);
本质上,就是提前注册串口终端,而不是在串口驱动初始化时才注册。
Image 启动后期
有了 前面实现的 early print, 内核打印就没有问题了, 如果没有实现early print, 则在串口驱动初始化时,会注册一个console 驱动, 从而实现内核打印。
当然, 前提是我们需要在串口驱动中实现并注册 static struct console 。
具体细节可以参考样例驱动。 这里就不深究了。
内核打印机制浅析
内核函数调用 printk 后, printk 将 打印信息发送到一个 log_buffer. 然后调用如下函数, 试图进行后续处理。
如果已经注册了终端,则会调用终端的输出函数。
另外,终端注册函数也会调用 release_console_sem, 将此前所有的信息一次性打印到 console.
984 /**
985 * release_console_sem - unlock the console system
986 *
987 * Releases the semaphore which the caller holds on the console system
988 * and the console driver list.
989 *
990 * While the semaphore was held, console output may have been buffered
991 * by printk(). If this is the case, release_console_sem() emits
992 * the output prior to releasing the semaphore.
993 *
994 * If there is output waiting for klogd, we wake it up.
995 *
996 * release_console_sem() may be called from any context.
997 */
998 void release_console_sem(void)
999 {
1000 unsigned long flags;
1001 unsigned _con_start, _log_end;
1002 unsigned wake_klogd = 0;
1003
1004 if (console_suspended) {
1005 up(&console_sem);
1006 return;
1007 }
1008
1009 console_may_schedule = 0;
1010
1011 for ( ; ; ) {
1012 spin_lock_irqsave(&logbuf_lock, flags);
1013 wake_klogd |= log_start - log_end;
1014 if (con_start == log_end)
1015 break; /* Nothing to print */
1016 _con_start = con_start;
1017 _log_end = log_end;
1018 con_start = log_end; /* Flush */
1019 spin_unlock(&logbuf_lock);
1020 call_console_drivers(_con_start, _log_end);
1021 local_irq_restore(flags);
1022 }
1023 console_locked = 0;
1024 up(&console_sem);
1025 spin_unlock_irqrestore(&logbuf_lock, flags);
1026 if (wake_klogd)
1027 wake_up_klogd();
1028 }
1029 EXPORT_SYMBOL(release_console_sem);