在Linux內(nèi)核調(diào)試的時(shí)候,最開始因?yàn)樵O(shè)備驅(qū)動(dòng)沒有初始化,串口也不能正常的訪問,而內(nèi)核好像也不能通過一般的Jlink調(diào)試,這個(gè)具體原因還不清楚,只是現(xiàn)象上看斷點(diǎn)停掉之后就不會(huì)繼續(xù)往下運(yùn)行(好像和之前的一個(gè)bug有點(diǎn)類似呀),先不管這些,總之我們需要有一個(gè)可以觀察內(nèi)核運(yùn)行情況的東西,嵌入式中最為常用的當(dāng)然就是printf了,雖然耗時(shí),但是簡單易用,下面就給出一個(gè)即使設(shè)備驅(qū)動(dòng)沒有初始化也能開始打印的方法。
內(nèi)核剛啟動(dòng)時(shí)候運(yùn)行匯編代碼的時(shí)候
在內(nèi)核啟動(dòng)的初期,代碼都是匯編代碼,這時(shí)候用printf好像有點(diǎn)麻煩,但是內(nèi)核代碼其實(shí)也集成了一些調(diào)試代碼,稍加修改就可以實(shí)現(xiàn)串口的打印,比如在head-common.S中有這么一個(gè)匯編函數(shù)的定義:
arch/arm/kernel/head-common.S
192 __error_p:
193 #ifdef CONFIG_DEBUG_LL
194 adr r0, str_p1
195 bl printascii
196 mov r0, r9
197 bl printhex8
198 adr r0, str_p2
199 bl printascii
200 b __error
201 str_p1: .asciz "\nError: unrecognized/unsupported processor variant (0x"
202 str_p2: .asciz ").\n"
203 .align
204 #endif
205 ENDPROC(__error_p)
這個(gè)實(shí)際就調(diào)用了一個(gè)打印的代碼,這個(gè)里面打印了一個(gè)hex8字符,還有一個(gè)字符串,我們來看看printascii是怎么實(shí)現(xiàn)的,這個(gè)函數(shù)是在debug.S中定義的
arch/arm/kernel/debug.S
80 ENTRY(printascii)
81 addruart_current r3, r1, r2
82 b 2f
83 1: waituart r2, r3
84 senduart r1, r3
85 busyuart r2, r3
86 teq r1, #'\n'
87 moveq r1, #'\r'
88 beq 1b
89 2: teq r0, #0
90 ldrneb r1, [r0], #1
91 teqne r1, #0
92 bne 1b
93 ret lr
94 ENDPROC(printascii)
這個(gè)函數(shù)里面調(diào)用了waituart, senduart, busyuart幾個(gè)函數(shù),這幾個(gè)函數(shù)是平臺(tái)相關(guān)的,再繼續(xù)找一下我們看到,他們的定義是在arch/arm/include/debug/stm32.S中實(shí)現(xiàn)的,
arch/arm/include/debug/stm32.S
12 #if defined(CONFIG_ARCH_STM32F7)
13 # define STM32_USART_SR (0x1C) /* Interrupt&Status Register */
14 # define STM32_USART_DR (0x28) /* Transmit Data Register */
15 #else
16 # define STM32_USART_SR (0x00) /* Status Register */
17 # define STM32_USART_DR (0x04) /* Data Register */
18 #endif
19
20 #define STM32_USART_SR_TXE (1 << 7) /* Transmitter Empty */
21
22 .macro addruart, rp, rv, tmp
23 ldr \rp, =CONFIG_DEBUG_UART_PHYS @ phys address
24 ldr \rv, =CONFIG_DEBUG_UART_VIRT @ virt address
25 .endm
26
27 .macro senduart,rd,rx
28 strb \rd, [\rx, #(STM32_USART_DR)] @ Write to Data Register
29 .endm
30
31 .macro waituart,rd,rx
32 1001: ldr \rd, [\rx, #(STM32_USART_SR)] @ Read Status Register
33 tst \rd, #STM32_USART_SR_TXE @ TXE = 1 when TDR shifted
34 beq 1001b @ branch if TXE = 0
35 .endm
36
37 .macro busyuart,rd,rx
38 1001: ldr \rd, [\rx, #(STM32_USART_SR)] @ Read Status Register
39 tst \rd, #STM32_USART_SR_TXE @ TXE = 0 when TDR has data
40 beq 1001b @ branch if TXE = 0
41 .endm
這里面定義了和uart相關(guān)的幾個(gè)宏,這個(gè)其實(shí)也比較簡單,一個(gè)是uart的狀態(tài)寄存器,用來輪詢uart數(shù)據(jù)是否發(fā)送完成,一個(gè)是數(shù)據(jù)寄存器,用來向uart發(fā)送數(shù)據(jù),比如移植到RT1050之后,我們就可以通過修改寄存器的地址和狀態(tài)位的位移實(shí)現(xiàn)一個(gè)簡單的串口。
注意,如果需要使用匯編階段的串口需要定義CONFIG_DEBUG_LL_INCLUDE這個(gè)宏,比如:
#define CONFIG_DEBUG_LL_INCLUDE "debug/stm32.S"
這樣相應(yīng)的匯編文件會(huì)包含到debug.S中,從而實(shí)現(xiàn)調(diào)試信息的打印。
當(dāng)然還有以下兩個(gè)宏需要定義
2542 CONFIG_DEBUG_UART_PHYS=0x40184000
2543 CONFIG_DEBUG_UART_VIRT=0x40184000
2544 CONFIG_DEBUG_UNCOMPRESS=y
2545 CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
2546 CONFIG_EARLY_PRINTK=y
完成以上修改之后就可以實(shí)現(xiàn)匯編階段的打印了。
C語言階段的打印
匯編階段的打印是比較簡單的,當(dāng)程序可以運(yùn)行c語言代碼的時(shí)候,就應(yīng)該祭出我們常用的調(diào)試?yán)鱬rintf了,在源代碼中我們發(fā)現(xiàn)內(nèi)核也貼心的實(shí)現(xiàn)了相應(yīng)的函數(shù),不過里面的Printf并不是把信息打印到串口上,而是保存在一個(gè)char buffer里面,當(dāng)串口初始化完成之后再一股腦打印出來。這個(gè)對我們的調(diào)試毫無用處呀,畢竟很多錯(cuò)誤都是在串口初始化之前出現(xiàn)的,難道要盲調(diào)?當(dāng)然不是,我們可以將這個(gè)printf稍微修改一下,加點(diǎn)代碼就可以實(shí)現(xiàn)數(shù)據(jù)到串口的打印,我們首先看看printf的具體實(shí)現(xiàn)。
kernel/printk/printk.c
1974 asmlinkage __visible int printk(const char *fmt, ...)
1975 {
1976 va_list args;
1977 int r;
1978
1979 va_start(args, fmt);
1980 r = vprintk_func(fmt, args);
1981 va_end(args);
1982
1983 return r;
1984 }
1985 EXPORT_SYMBOL(printk);
以上是printk的定義,原諒我沒法繼續(xù)往下追,因?yàn)関printk_func = this_cpu_read(printk_func);這個(gè)一大堆的宏定義,看起來還和線程相關(guān)的,我們的目的是在初始化的時(shí)候調(diào)用這個(gè)函數(shù),和線程沒有一毛錢關(guān)系呀,看來此路不通。
偶然間想到還有一個(gè)early_printf,我們來看看這個(gè)東西是怎么一回事
1980 #ifdef CONFIG_EARLY_PRINTK
1981 struct console *early_console;
1982
1983 asmlinkage __visible void early_printk(const char *fmt, ...)
1984 {
1985 va_list ap;
1986 char buf[512];
1987 int n;
1988
1989 if (!early_console)
1990 return;
1991
1992 va_start(ap, fmt);
1993 n = vscnprintf(buf, sizeof(buf), fmt, ap);
1994 va_end(ap);
1995
2010 early_console->write(early_console, buf, n);
2011 }
2012 #endif
這個(gè)就是early_printk的實(shí)現(xiàn),要使用這個(gè)函數(shù)需要定義CONFIG_EARLY_PRINTK這個(gè)宏,這個(gè)的可改造性比較強(qiáng),因?yàn)槔锩娑x了char buf,并且對各個(gè)變量進(jìn)行了處理,就是這個(gè)函數(shù)在哪里調(diào)用的我還不是特別清楚。另外early_console在哪里定義的也要研究一下。
不過n = vscnprintf(buf, sizeof(buf), fmt, ap);這個(gè)我們可以利用一下,把他用來解析printk中的格式字符串,然后將結(jié)果依次通過串口發(fā)送出去,為了簡單起見,我直接用固定地址訪問串口的寄存器,畢竟調(diào)試完了就可以刪掉了呀。
1895 asmlinkage __visible int printk(const char *fmt, ...)
1896 {
1897 printk_func_t vprintk_func;
1898 va_list args;
1899 int r;
1900 int n;
1901 char buf[512];
1902
1903 va_start(args, fmt);
1904
1905 /*
1906 * If a caller overrides the per_cpu printk_func, then it needs
1907 * to disable preemption when calling printk(). Otherwise
1908 * the printk_func should be set to the default. No need to
1909 * disable preemption here.
1910 */
1911 vprintk_func = this_cpu_read(printk_func);
1912 r = vprintk_func(fmt, args);
1913 n = vscnprintf(buf, sizeof(buf), fmt, args);
1914 va_end(args);
1915
1916 ////////// DEBUG by Major ////////////////
1917 int *uart_data;
1918 int *uart_status;
1919 int temp;
1920 int i;
1921 uart_data = (int*)0x4018401c;
1922 uart_status = (int*)0x40184014;
1923 for(i = 0; i< n; i++){
1924 while(!((*uart_status) & (1<<23)));
1925 temp = (*uart_data)&0xffffff00;
1926 *uart_data = (int)(temp|(buf[i]));
1927 }
1928 while(!((*uart_status) & (1<<23)));
1929 temp = (*uart_data)&0xffffff00;
1930 *uart_data = (int)(temp|'\r');
1931 ////////// DEBUG by Major ////////////////
1932
1933
1934 return r;
1935 }
1936 EXPORT_SYMBOL(printk);
以上就是改造之后的函數(shù),經(jīng)過改造之后就可以在串口上未初始化的時(shí)候打印一些必要的調(diào)試信息,并且支持格式化字符串的打印。