u-boot串口和stdio、console初始化及相关操作详解

本文详细解析了U-Boot在Nitrogen6Q平台上,通过board_init_f和board_init_r函数进行串口初始化的过程。从波特率设置、串口设备配置到控制台输出,深入探讨了各阶段的关键函数与操作。

转自:https://blog.youkuaiyun.com/metersun/article/details/52527553

当u-boot的启动执行到_main函数处,将在_main函数中执行板级的前初始化和后初始化操作,即函数board_init_f和board_init_r。
串口的初始化以及相关stdio、console操作穿插在这两个函数的执行过程中。下面将分别详细讨论这两阶段中涉及的串口及stdio、
console设备操作。
这里所使用的u-boot版本为2015.7,硬件为I.MX6 boundary nitrogen6q开发平台。

一.board_init_f阶段串口操作相关函数
下面以执行顺序来逐个分析board_init_f阶段所涉及的函数:
1.init_baud_rate
文件common/board_f.c中的函数init_baud_rate首先尝试从环境变量中获取波特率的设定值,如果找不到波特率相关的环境变量,
则使用默认的波特率配置115200。
最后将环境变量中波特率设定值或波特率默认值赋值给全局变量gd->baudrate。
2.serial_init

int serial_init(void)
{
gd->flags |= GD_FLG_SERIAL_READY;
return get_current()->start();
}
该函数在文件drivers/serial/serial.c中定义,先执行
gd->flags |= GD_FLG_SERIAL_READY
GD_FLG_SERIAL_READY为串口可用标志。然后调用get_current()->start()。
get_current是静态函数,定义如下:
static struct serial_device *get_current(void)
{
struct serial_device *dev;
if(!(gd->flags & GD_FLG_RELOC))
dev = default_serial_console();
else if(!serial_current)
dev = default_serial_console();
else
dev = serial_current;
/* We must have a console device */
if(!dev){
panic("Cannot find console\n");
}

returndev;

}
标志GD_FLG_RELOC在后续的board_r阶段初始化函数initr_reloc中设置,gd->flags相应的标志位此时还未置标,则将执行
dev=default_serial_console();
default_serial_console函数在drivers/serial/serial_mxc.c中的定义为:
__weak struct serial_device *default_serial_console(void)
{
return &mxc_serial_drv;
}
而其前变量mxc_serial_drv定义时被初始化为:
static struct serial_device mxc_serial_drv={
.name = "mxc_serial",
.start = mxc_serial_init,
.stop = NULL,
.setbrg = mxc_serial_setbrg,
.putc = mxc_serial_putc,
.puts = default_serial_puts,
.getc = mxc_serial_getc,
.tstc = mxc_serial_tstc,
};
那么,get_current()->start()中的get_current()调用将返回上述结构体的地址,即默认的串口设备指针。
接下来就执行mxc_serial_drv.start函数,即mxc_serial_init。
mxc_serial_init主要执行SOC层 i.mx芯片串口硬件配置的初始化操作:使能串口模块时钟,设置波特率和数据位长度等。
注意,UART的IOMUX设置已在前面的函数board_early_init_f中完成,详见该函数说明。
3.console_init_f
该函数在console层的文件common/console.c中实现:

int console_init_f(void)
{
gd->have_console = 1;
print_pre_console_buffer(0);/*此处为空函数 */
return 0;
}
一旦设置了gd->have_console,标志着printf函数即可使用。
我们跟踪common/console.c中的printf可以看到:
int printf(constchar*fmt,...)
{
va_list args;
uint i;
char printbuffer[CONFIG_SYS_PBSIZE];
if (!gd->have_console)
return 0;

__builtin_va_start(args,fmt);
i = vsprintf(printbuffer,fmt,args);
__builtin_va_end(args);

puts(printbuffer);
return i;
}
gd->have_console在上面的函数console_init_f中被置为1,那么接下来就可以使用puts函数进行信息的输出,
我们继续跟踪puts,其在common/console.c中实现为:
void puts(constchar*s)
{
if(!gd->have_console)
return pre_console_puts(s); //空函数
if(gd->flags & GD_FLG_DEVINIT){
fputs(1,s);
}else{
pre_console_puts(s);
serial_puts(s);
}
}
此时gd->flags中的GD_FLG_DEVINIT还未置标,那么puts函数将使用drivers/serial/serial.c中的serial_puts(s)
进行数据信息输出:
void serial_puts(const char *s)
{
get_current()->puts(s);
}
这样又回到了和上面2小节相仿的get_current()函数,那么,最终将使用mxc_serial_drv结构体中的成员函数puts进行信息输出。

综上所述,结构体struct serial_device是在一般serial层中对串口设备的描述,可以看做串口设备的抽象,struct serial_device对下层,即SOC层的串口属性(如名称)和设备操作函数进行了封装。
定义一个struct serial_device变量,并填充结构体中的操作函数和属性,则代表一个具体的串口设备在serial层中的实现。
在serial层,提供了诸如serial_puts,serial_getc的外部访问接口。而这些函数都将调用其私有get_current函数获取一个serial的实例。并通过调用该serial设备的操作函数puts,getc,最终定位到最底层的SOC serial操作实现。
SOC serial操作函数对struct serial_device结构体相应成员变量的填充,可看作是SOC serial到serial的注册。 
我们知道标准输出的printf函数是包括在控制台(console)的stdio设备中, 在board_init_f阶段中,stdio设备并未准备好,也未完成控制台注册,那么,作为替代,这里提供了一种非标准输入输出函数printf(其实是调用了上述的serial_puts)的实现。此后的很长一段代码中将使用这里的printf执行流程完成信息输出,直至stdio设备注册到console中。

转载于:https://www.cnblogs.com/I-L-o-v-e-z-h-o-u/p/11289412.html

### u-boot、kernel、APP 与普通 C 语言程序的区别联系 #### 区别 1. **u-boot** u-boot 是一个引导加载程序,负责初始化硬件环境并加载操作系统内核。它的运行环境是裸机环境,没有操作系统的支持[^2]。u-boot 的代码需要直接与底层硬件交互,例如配置寄存器、初始化内存控制器等。因此,u-boot 的开发通常需要深入理解目标硬件的架构外设。 2. **Kernel** Linux 内核是一个复杂的软件系统,负责管理硬件资源并提供系统服务。它运行在由 u-boot 初始化的环境中,并依赖设备树或板级支持包(BSP)来识别管理硬件资源[^2]。内核的代码结构复杂,包含进程管理、内存管理、文件系统等多个子系统。与普通 C 程序不同,内核代码运行在特权模式下,可以直接访问硬件资源。 3. **APP 普通 C 程序** APP 普通 C 程序运行在操作系统提供的用户空间中,依赖内核提供的系统调用接口与硬件交互。它们不需要直接管理硬件资源,而是通过标准库或框架间接操作。这些程序通常以 ELF 格式编译,并依赖动态链接库(如 glibc)来实现功能[^1]。 #### 联系 1. **编程语言** u-boot、kernel 普通 C 程序都使用 C 语言编写,因此在语法基本数据结构上具有相似性。然而,由于运行环境的不同,它们的实现细节设计目标存在显著差异。 2. **交叉编译工具链** 这些组件都需要使用交叉编译工具链进行编译。例如,针对 ARMv8 架构,可以使用 `aarch64-linux-gnu-gcc` 工具链[^1]。编译过程中,不同的组件可能需要特定的编译选项配置文件。 3. **启动流程** 系统启动时,u-boot 首先运行,初始化硬件并加载内核。内核启动后,挂载根文件系统并执行初始化脚本。最终,应用程序从根文件系统中加载并运行。这一过程展示了各个组件之间的紧密联系[^2]。 4. **共享代码技术** 某些技术在多个组件中共享。例如,设备树(Device Tree)用于描述硬件配置,既被 u-boot 使用,也被内核解析[^3]。此外,某些驱动程序可能同时存在于 u-boot 内核中,以便在早期启动阶段提供硬件支持。 #### 示例代码 以下是一个简单的 C 程序示例,展示其与内核交互的方式: ```c // hello.c - 简单的应用程序 #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } ``` 编译命令如下: ```bash aarch64-linux-gnu-gcc -o hello hello.c ``` 上述程序通过标准库函数 `printf` 输出信息,而 `printf` 最终通过系统调用与内核交互,将数据写入控制台设备[^1]。 --- ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值