printk与 uart console关系分析(草稿)

2024-03-01 14:32

本文主要是介绍printk与 uart console关系分析(草稿),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1,了解kernel启动过程

asmlinkage void __init start_kernel(void)
{...parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,-1, -1, &unknown_bootoption);...console_init();...rest_init();
}

了解下console_setup, console_init, uart_add_one_port.

console_setup如何调用而来:

在kernel/printk.c中console_setup代码:

static int __init console_setup(char *str)
{char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */char *s, *options, *brl_options = NULL;int idx;#ifdef CONFIG_A11Y_BRAILLE_CONSOLEif (!memcmp(str, "brl,", 4)) {brl_options = "";str += 4;} else if (!memcmp(str, "brl=", 4)) {brl_options = str + 4;str = strchr(brl_options, ',');if (!str) {printk(KERN_ERR "need port name after brl=\n");return 1;}*(str++) = 0;}
#endif/** Decode str into name, index, options.*/if (str[0] >= '0' && str[0] <= '9') {strcpy(buf, "ttyS");strncpy(buf + 4, str, sizeof(buf) - 5);} else {strncpy(buf, str, sizeof(buf) - 1);}buf[sizeof(buf) - 1] = 0;if ((options = strchr(str, ',')) != NULL)*(options++) = 0;//记录option,例如1152008n
#ifdef __sparc__if (!strcmp(str, "ttya"))strcpy(buf, "ttyS0");if (!strcmp(str, "ttyb"))strcpy(buf, "ttyS1");
#endiffor (s = buf; *s; s++)if ((*s >= '0' && *s <= '9') || *s == ',')break;idx = simple_strtoul(s, NULL, 10);//得到console=ttySxxx中串口号, 例如0,1,2...*s = 0;//使得buf内容不再包含串口号。__add_preferred_console(buf, idx, options, brl_options);//完成对console_cmdline数组的填充console_set_on_cmdline = 1;return 1;
}
__setup("console=", console_setup);

__add_preferred_console函数:

static int __add_preferred_console(char *name, int idx, char *options,char *brl_options)
{struct console_cmdline *c;int i;/**      See if this tty is not yet registered, and*      if we have a slot free.*/for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)if (strcmp(console_cmdline[i].name, name) == 0 &&console_cmdline[i].index == idx) {if (!brl_options)selected_console = i;return 0;}if (i == MAX_CMDLINECONSOLES)return -E2BIG;if (!brl_options)selected_console = i;//记录selected_console的值>=0c = &console_cmdline[i];strlcpy(c->name, name, sizeof(c->name));//记录串口名字c->options = options;//记录串口设置参数
#ifdef CONFIG_A11Y_BRAILLE_CONSOLEc->brl_options = brl_options;
#endif c->index = idx;//记录作为串口的index号,在console_setup中从console=ttySxxx中得到。return 0;
}


__setup跟踪, 在include/linux/init.h

#define __setup_param(str, unique_id, fn, early)                        \static const char __setup_str_##unique_id[] __initconst \__aligned(1) = str; \static struct obs_kernel_param __setup_##unique_id      \__used __section(.init.setup)                   \__attribute__((aligned((sizeof(long)))))        \= { __setup_str_##unique_id, fn, early }#define __setup(str, fn)                                        \__setup_param(str, fn, fn, 0)
知道该类函数定义在*.init.setup区。并由start_kernel一直调用到console_setup.

console_init()函数定义在drivers/tty/tty_io.c,主要完成对console_initcall 声明函数的调用:

void __init console_init(void)
{initcall_t *call;/* Setup the default TTY line discipline. */tty_ldisc_begin();/** set up the console device so that later boot sequences can* inform about problems etc..*/call = __con_initcall_start;while (call < __con_initcall_end) {(*call)();call++;//依次调用console_initcall声明的函数,like:console_initcall(sirfsoc_uart_console_init);}
}
在sirfsoc_uart_console_init中:

static int __init sirfsoc_uart_console_init(void)
{register_console(&sirfsoc_uart_console);return 0;
}
console_initcall(sirfsoc_uart_console_init);
下面对register_console函数进行分析:

void register_console(struct console *newcon)
{int i;unsigned long flags;struct console *bcon = NULL;/** before we register a new CON_BOOT console, make sure we don't* already have a valid console*/if (console_drivers && newcon->flags & CON_BOOT) {//boot console 如果开启了early_printk hacking,可以跟踪分析arch/arm/kernel/early_printk.c/* find the last or real console */for_each_console(bcon) {if (!(bcon->flags & CON_BOOT)) {printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);return;}}}if (console_drivers && console_drivers->flags & CON_BOOT)//boot consolebcon = console_drivers;if (preferred_console < 0 || bcon || !console_drivers)preferred_console = selected_console;//对于real console preferred_console >=0if (newcon->early_setup)newcon->early_setup();/**      See if we want to use this console driver. If we*      didn't select a console we take the first one*      that registers here.*/if (preferred_console < 0) {if (newcon->index < 0)newcon->index = 0;if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {newcon->flags |= CON_ENABLED;if (newcon->device) {newcon->flags |= CON_CONSDEV;preferred_console = 0;}}}/**      See if this console matches one we selected on*      the command line.*/for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {if (strcmp(console_cmdline[i].name, newcon->name) != 0)continue;if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)continue;if (newcon->index < 0)newcon->index = console_cmdline[i].index;//对于console_initcall类型函数调用的register_console, 对console的index
//附上console=ttySxxx传过来的串口号。
#ifdef CONFIG_A11Y_BRAILLE_CONSOLEif (console_cmdline[i].brl_options) {newcon->flags |= CON_BRL;braille_register_console(newcon,console_cmdline[i].index,console_cmdline[i].options,console_cmdline[i].brl_options);return;}
#endifif (newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0)break;//第一次register_cosole不能成功从这里退出,因为port->mapbase == 0, mapbase 没有在probe中被设置。newcon->flags |= CON_ENABLED;//如果setup成功设置CON_ENABLED标记。newcon->index = console_cmdline[i].index;if (i == selected_console) {newcon->flags |= CON_CONSDEV;preferred_console = selected_console;}break;}if (!(newcon->flags & CON_ENABLED))return;/** If we have a bootconsole, and are switching to a real console,* don't print everything out again, since when the boot console, and* the real console are the same physical device, it's annoying to* see the beginning boot messages twice*/if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))newcon->flags &= ~CON_PRINTBUFFER;//如注释所示,避免了real-console再次打印log信息/**      Put this console in the list - keep the*      preferred driver at the head of the list.*/console_lock();if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {newcon->next = console_drivers;console_drivers = newcon;if (newcon->next)newcon->next->flags &= ~CON_CONSDEV;} else {newcon->next = console_drivers->next;console_drivers->next = newcon;}if (newcon->flags & CON_PRINTBUFFER) {/** console_unlock(); will print out the buffered messages* for us.*/raw_spin_lock_irqsave(&logbuf_lock, flags);console_seq = syslog_seq;console_idx = syslog_idx;console_prev = syslog_prev;raw_spin_unlock_irqrestore(&logbuf_lock, flags);/** We're about to replay the log buffer.  Only do this to the* just-registered console to avoid excessive message spam to* the already-registered consoles.*/exclusive_console = newcon;}console_unlock();console_sysfs_notify();/** By unregistering the bootconsoles after we enable the real console* we get the "console xxx enabled" message on all the consoles -* boot consoles, real consoles, etc - this is to ensure that end* users know there might be something in the kernel's log buffer that* went to the bootconsole (that they do not see on the real console)*/if (bcon &&((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&!keep_bootcon) {/* we need to iterate through twice, to make sure we print* everything out, before we unregister the console(s)*/printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);//开启了early_printkfor_each_console(bcon)if (bcon->flags & CON_BOOT)unregister_console(bcon);} else {printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);//未开启early_printk}
}
EXPORT_SYMBOL(register_console);

对probe函数中uart_add_one_port的分析:



1, 名词解析:

console_sem:

/** console_sem protects the console_drivers list, and also* provides serialisation for access to the entire console* driver system.*/

console_drivers:

static DEFINE_SEMAPHORE(console_sem);
struct console *console_drivers;
EXPORT_SYMBOL_GPL(console_drivers);

在这里将console_drivers设置成export_symbol的原因是在include/linux/console.h中定义了一个宏:

#define for_each_console(con) \for (con = console_drivers; con != NULL; con = con->next)

用来便利console_drivers的列表。

2,函数解析

register_console:

arch/arm/kernel/early_printk.c:

static int __init setup_early_printk(char *buf)
{early_console = &early_console_dev;register_console(&early_console_dev);return 0;
}
early_param("earlyprintk", setup_early_printk); 

static struct console early_console_dev = {.name =         "earlycon",.write =        early_console_write,.flags =        CON_PRINTBUFFER | CON_BOOT,.index =        -1,
};

在boot/uboot/include/configs/atlas6cb.h中设置bootargs:

"devargs=" \"setenv bootargs no_console_suspend retain_initrd " \"earlyprintk resumewait " \"mem=${meminfo} " \"console=ttySiRF3 " \"lpj=7995392 " \"real_root=/dev/${bootinf}blk${bootdev}p${rootpart} " \"resume=/dev/${bootinf}blk${bootdev}p${resumepart} "\"scert_start=0x3800 " \"scert_size=0x1000\0" \

与pr_notice("Kernel command line: %s\n", boot_command_line);

3, 解压过程中log 打印“Uncompressing Linux... done, booting the kernel.”

make kmenuconfig->Kernel Hacking->Kernel low-level debugging functions->Platform ownself debug console.

由于选择了自己平台的console口,所以在选中CONFIG_DEBUG_LL的同时就没有选中Kernel low-level debug output via semihosting I/O,这就是CONFIG_DEBUG_SEMIHOSTING选项,开启DEBUG_LL只能选择一个debug口,因而CONFIG_DEBUG_SEMIHOSTING没有打开。代开arch/arm/Kconfig.debug文件:

choiceprompt "Kernel low-level debugging port"depends on DEBUG_LLconfig DEBUG_SEMIHOSTINGbool "Kernel low-level debug output via semihosting I/O"helpSemihosting enables code running on an ARM target to usethe I/O facilities on a host debugger/emulator through asimple SVC call. The host debugger or emulator must havesemihosting enabled for the special svc call to be trappedotherwise the kernel will crash.This is known to work with OpenOCD, as well asARM's Fast Models, or any other controlling environmentthat implements semihosting.For more details about semihosting, please seechapter 8 of DUI0203I_rvct_developer_guide.pdf from ARM Ltd.endchoice

config中的choice选项只能选择一种。

接着打开arch/arm/kernel/debug.S

#if !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif

第一句就是要包含DEBUG_LL_INCLUDE config选项:即arch/arm/Kconfig.debug文件中:

config DEBUG_LL_INCLUDEstringdefault "debug/bcm2835.S" if DEBUG_BCM2835default "debug/cns3xxx.S" if DEBUG_CNS3XXXdefault "debug/exynos.S" if DEBUG_EXYNOS_UARTdefault "debug/highbank.S" if DEBUG_HIGHBANK_UARTdefault "debug/icedcc.S" if DEBUG_ICEDCCdefault "debug/imx.S" if DEBUG_IMX1_UART || \DEBUG_IMX25_UART || \DEBUG_IMX21_IMX27_UART || \DEBUG_IMX31_UART || \DEBUG_IMX35_UART || \DEBUG_IMX51_UART || \DEBUG_IMX53_UART ||\DEBUG_IMX6Q_UARTdefault "debug/mvebu.S" if DEBUG_MVEBU_UARTdefault "debug/mxs.S" if DEBUG_IMX23_UART || DEBUG_IMX28_UARTdefault "debug/nomadik.S" if DEBUG_NOMADIK_UARTdefault "debug/omap2plus.S" if DEBUG_OMAP2PLUS_UARTdefault "debug/picoxcell.S" if DEBUG_PICOXCELL_UARTdefault "debug/pxa.S" if DEBUG_PXA_UART1 || DEBUG_MMP_UART2 || \DEBUG_MMP_UART3default "debug/sirf.S" if DEBUG_SIRFPRIMA2_UART1 || DEBUG_SIRFMARCO_UART1default "debug/socfpga.S" if DEBUG_SOCFPGA_UARTdefault "debug/sunxi.S" if DEBUG_SUNXI_UART0 || DEBUG_SUNXI_UART1default "debug/tegra.S" if DEBUG_TEGRA_UARTdefault "debug/ux500.S" if DEBUG_UX500_UARTdefault "debug/vexpress.S" if DEBUG_VEXPRESS_UART0_DETECT || \DEBUG_VEXPRESS_UART0_CA9 || DEBUG_VEXPRESS_UART0_RS1default "debug/vt8500.S" if DEBUG_VT8500_UART0default "debug/zynq.S" if DEBUG_ZYNQ_UART0 || DEBUG_ZYNQ_UART1default "mach/debug-macro.S"

包含板子内部所带的debug文件。

同时在arch/arm/kernel/debug.S中:

#ifndef CONFIG_DEBUG_SEMIHOSTINGENTRY(printascii)addruart_current r3, r1, r2b       2f
1:              waituart r2, r3senduart r1, r3busyuart r2, r3teq     r1, #'\n'moveq   r1, #'\r'beq     1b
2:              teq     r0, #0ldrneb  r1, [r0], #1teqne   r1, #0bne     1bmov     pc, lr
ENDPROC(printascii)ENTRY(printch)addruart_current r3, r1, r2mov     r1, r0mov     r0, #0b       1b
ENDPROC(printch)

定义了printascii 和 printch函数,并调用了waituart,senduart,busyuart等函数。这些函数就是DEBUG_LL_INCLUDE中要包含的板子调试信息中所定义,如:

在arch/arm/include/debug/sirf.S

#if defined(CONFIG_DEBUG_SIRFPRIMA2_UART1)
#define SIRFSOC_UART1_PA_BASE          0xb0060000
#elif defined(CONFIG_DEBUG_SIRFMARCO_UART1)
#define SIRFSOC_UART1_PA_BASE          0xcc060000
#else
#define SIRFSOC_UART1_PA_BASE          0
#endif#define SIRFSOC_UART1_VA_BASE           0xFEC60000#define SIRFSOC_UART_TXFIFO_STATUS      0x0114
#define SIRFSOC_UART_TXFIFO_DATA        0x0118#define SIRFSOC_UART1_TXFIFO_FULL                       (1 << 5)
#define SIRFSOC_UART1_TXFIFO_EMPTY                      (1 << 6).macro  addruart, rp, rv, tmpldr     \rp, =SIRFSOC_UART1_PA_BASE             @ physicalldr     \rv, =SIRFSOC_UART1_VA_BASE             @ virtual.endm.macro  senduart,rd,rxstr     \rd, [\rx, #SIRFSOC_UART_TXFIFO_DATA].endm.macro  busyuart,rd,rx.endm.macro  waituart,rd,rx
1001:   ldr     \rd, [\rx, #SIRFSOC_UART_TXFIFO_STATUS]tst     \rd, #SIRFSOC_UART1_TXFIFO_EMPTYbeq     1001b.endm

定义了寄存器的地址,以及上述*uart的含义,直接想硬件寄存器放字符去打印出来。这就是lowlevel打印的函数形成的过程。

putc形成过程:

arch/arm/Kconfig.debug文件:

config DEBUG_UNCOMPRESSbooldefault y if ARCH_MULTIPLATFORM && DEBUG_LL && \!DEBUG_OMAP2PLUS_UART && \!DEBUG_TEGRA_UART

在打开DEBUG_LL后,默认应该打开了DEBUG_UNCOMPRESS config 选项。在arch/arm/boot/compressd/Makefile文件中:

ifeq ($(CONFIG_DEBUG_UNCOMPRESS),y)
OBJS    += debug.o

即编译arch/arm/boot/compressed/debug.S文件。该文件对putc函数进行了定义同样是使用arch/arm/include/debug/sirf.S中定义的*uart 函数。

在arch/arm/include/debug/uncompress.h文件中包含putc()函数的定义

#ifdef CONFIG_DEBUG_UNCOMPRESS
extern void putc(int c);
#else
static inline void putc(int c) {}
#endif

因而在真正的uart console启动起来之前,putc和printascii均调用的是直接想串口丢数据到txfifo,数据打印出来。

在sirf平台默认的就是uart1口。


4, console_setup:

__setup("console=", console_setup);
/** Decode str into name, index, options.
*/
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1; 




这篇关于printk与 uart console关系分析(草稿)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/762774

相关文章

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Tomcat版本与Java版本的关系及说明

《Tomcat版本与Java版本的关系及说明》:本文主要介绍Tomcat版本与Java版本的关系及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Tomcat版本与Java版本的关系Tomcat历史版本对应的Java版本Tomcat支持哪些版本的pythonJ

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An