基于IMX6Q的uboot启动流程分析(1):uboot入口函数
基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f
基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r
基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备
第4章: uboot中的串口设备
本节主要是围绕uboot中的串口设备的初始化流程内容进行介绍。
4.1 uboot中串口相关结构体
4.1.1 serial_device
serial_device结构体用于描述串口设备,其定义在include/serial.h
文件中,其内容为:
struct serial_device {
/* enough bytes to match alignment of following func pointer */
char name[16];
int (*start)(void); //串口初始化
int (*stop)(void);
void(*setbrg)(void); //硬件设置
int (*getc)(void); //获取字符
int (*tstc)(void);
void(*putc)(const char c); //发送字符
void(*puts)(const char *s); //发送字符串
#if CONFIG_POST & CONFIG_SYS_POST_UART
void(*loop)(int);
#endif
struct serial_device *next;
};
4.2 设置IOMUX
在init_sequence_f
初始化序列中,board_early_init_f
函数中有关于串口IOMUX设置,其实现函数为setup_iomux_uart
,函数内容为:
static void setup_iomux_uart(void)
{
SETUP_IOMUX_PADS(uart1_pads);
}
4.3 serial_init硬件初始化
在调用serial_init
函数前,先调用init_sequence_f
初始化序列中的init_baud_rate
函数,从环境变量中获取波特率,其函数内容为:
static int init_baud_rate(void)
{
gd->baudrate = env_get_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}
在init_sequence_f
初始化序列中,serial_init
函数用于初始化串口,函数定义在drivers/serial/serial.c
文件中,其内容为:
int serial_init(void)
{
gd->flags |= GD_FLG_SERIAL_READY;
return get_current()->start();
}
在serial_init
函数中,分为两步:
- 调用
get_current
函数获取当前的使用的串口; - 调用串口的
start
函数进行初始化。
4.3.1 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) {
#ifdef CONFIG_SPL_BUILD
puts("Cannot find console\n");
hang();
#else
panic("Cannot find console\n");
#endif
}
return dev;
}
上述内容中,若uboot未完成重定位工作,将使用默认提供串口设备;若已经完成重定位,则使用已有的串口。
通过default_serial_console
函数来获取默认的串口设备,在IMX6Q中使用的是drivers/serial/serial_mxc.c
文件中的default_serial_console
函数,其内容为:
__weak struct serial_device *default_serial_console(void)
{
return &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
函数,将mxc_serial_drv
作为默认的串口设备,后续对其进行初始化。
4.3.2 start函数
uboot串口通过设备结构体成员函数start
进行初始化操作,mxc_serial_drv
串口设备的成员函数start
由上得知,调用mxc_serial_init
函数,其内容为:
static int mxc_serial_init(void)
{
_mxc_serial_init(mxc_base, false);
serial_setbrg();
return 0;
}
上述代码中,调用函数为serial_setbrg
进行硬件初始化,定义在drivers/serial/serial.c
文件,其内容为:
void serial_setbrg(void)
{
get_current()->setbrg();
}
函数最终调用的是串口设备结构体函数成员setbrg
,IMX6Q串口调用mxc_serial_setbrg
函数,其内容为:
static void mxc_serial_setbrg(void)
{
u32 clk = imx_get_uartclk();
if (!gd->baudrate)
gd->baudrate = CONFIG_BAUDRATE;
_mxc_serial_setbrg(mxc_base, clk, gd->baudrate, false);
}
在初始化过程中,主要有以下三个工作:
-
获取串口时钟
通过
imx_get_uartclk
函数来获取串口时钟,实际调用get_uart_clk
函数,定义在arch/arm/mach-imx/mx6/clock.c
文件中,内容为:static u32 get_uart_clk(void) { u32 reg, uart_podf; u32 freq = decode_pll(PLL_USBOTG, MXC_HCLK) / 6; /* static divider */ reg = __raw_readl(&imx_ccm->cscdr1); if (is_mx6sl() || is_mx6sx() || is_mx6dqp() || is_mx6ul() || is_mx6sll() || is_mx6ull()) { if (reg & MXC_CCM_CSCDR1_UART_CLK_SEL) freq = MXC_HCLK; } reg &= MXC_CCM_CSCDR1_UART_CLK_PODF_MASK; uart_podf = reg >> MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET; return freq / (uart_podf + 1); }
-
再次获取波特率
获取波特率代码为:
if (!gd->baudrate) gd->baudrate = CONFIG_BAUDRATE;
若环境变量没有设置波特率,即
init_baud_rate
阶段获取的值为0,则通过宏CONFIG_BAUDRATE
设置波特率。 -
串口初始化
在串口初始化前,已经进行获取到了时钟与波特率,调用
_mxc_serial_setbrg
函数实现硬件初始化,其内容为:static void _mxc_serial_setbrg(struct mxc_uart *base, unsigned long clk, unsigned long baudrate, bool use_dte) { u32 tmp; tmp = RFDIV << UFCR_RFDIV_SHF; if (use_dte) tmp |= UFCR_DCEDTE; else tmp |= (TXTL << UFCR_TXTL_SHF) | (RXTL << UFCR_RXTL_SHF); writel(tmp, &base->fcr); writel(0xf, &base->bir); writel(clk / (2 * baudrate), &base->bmr); writel(UCR2_WS | UCR2_IRTS | UCR2_RXEN | UCR2_TXEN | UCR2_SRST, &base->cr2); writel(UCR1_UARTEN, &base->cr1); }
4.4 console第一阶段初始化
在init_sequence_f
初始化序列中,调用console_init_f
函数进行console第一阶段初始化,定义在common/console.c
文件中,其内容为:
int console_init_f(void)
{
gd->have_console = 1;
console_update_silent();
print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);
return 0;
}
console在第一阶段初始化时,将gd->have_console
变量置为1,代表已经有console;
在IMX6Q中,没有设置slient,则console_update_silent
为空;
在IMX6Q中,PRE_CONSOLE_BUFFER
没有使用,则print_pre_console_buffer
函数同样为空。
**经过IOMUX设置、serila_init硬件初始化以及console第一阶段初始化后,uboot串口在重定位前所有的初始化已经完成。**其中最重要的是,通过get_current
获取到串口设备mxc_serial_drv
,为重定位后串口初始化提供函数接口,但此时的串口不能使用putc\get函数。
4.5 serial_initializ函数
uboot重定位后,调用关于串口第一个函数是serial_initializ
,调用在init_sequence_r
初始化列表中,定义在drivers/serial/serial.c
文件中,函数的内容为:
int serial_initialize(void)
{
atmel_serial_initialize();
mcf_serial_initialize();
mpc85xx_serial_initialize();
mxc_serial_initialize();
ns16550_serial_initialize();
pl01x_serial_initialize();
pxa_serial_initialize();
sh_serial_initialize();
mtk_serial_initialize();
xen_debug_serial_initialize();
serial_assign(default_serial_console()->name);
return 0;
}
函数主要有两个工作,第一个工作是进一步初始化串口,在IMX6Q中调用mxc_serial_initialize
函数;第二个工作是,调用serial_assign(default_serial_console()->name)
根据串口name属性,将其设置为当前使用的串口。
下面详细介绍第一个工作内容,mxc_serial_initialize
函数为:
void mxc_serial_initialize(void)
{
serial_register(&mxc_serial_drv);
}
函数中调用serial_register
,注册串口mxc_serial_drv
设备,其部分内容为:
void serial_register(struct serial_device *dev)
{
//...
dev->next = serial_devices;
serial_devices = dev;
}
上述内容中,首先将mxc_serial_drv
设置插入串口设备列表,然后将其赋值给全局变量serial_devices
。
至此,uboot重定位后,完成mxc_serial_drv
串口设备注册,下面需要串口基本输入输出等功能。
4.6 stdio_add_devices函数
在init_sequence_r
初始化序列中,调用stdio_add_devices
函数执行stdio设备的注册,函数在common/stdio.c
文件中定义。stdio设备层工作在serail层之上,但stdio设备并不限于serial设备,比如标准输出设备可能包括键盘、显示器,但我们这里只讨论stdio中的serail设备。
stdio_add_devices
函数通过调用serial_stdio_init
函数实现串口相关初始化,其定义在drivers/serial/serial.c
文件中,内容为:
void serial_stdio_init(void)
{
struct stdio_dev dev;
struct serial_device *s = serial_devices;
while (s) {
memset(&dev, 0, sizeof(dev));
strcpy(dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = serial_stub_start;
dev.stop = serial_stub_stop;
dev.putc = serial_stub_putc;
dev.puts = serial_stub_puts;
dev.getc = serial_stub_getc;
dev.tstc = serial_stub_tstc;
dev.priv = s;
stdio_register(&dev);
s = s->next;
}
}
上述内容得知,主要有以下几点工作:
- 通过
serial_devices
全局变量获取mxc_serial_drv
串口设备; - 开辟stdio设备;
- 通过串口设备为stdio设备赋值变量;
- 调用
stdio_register
函数注册stdio设备。
下面主要介绍第三点工作stdio设备的如何通过串口进行输入输出。
-
字符输出putc
stdio设备调用函数
serial_stub_putc
实现字符输出,函数内容为:static void serial_stub_putc(struct stdio_dev *sdev, const char ch) { struct serial_device *dev = sdev->priv; dev->putc(ch); }
从上述内容得知,实际调用
mxc_serial_drv
设备的putc
函数,即mxc_serial_putc
,其内容为:static void mxc_serial_putc(const char c) { /* If \n, also do \r */ if (c == '\n') serial_putc('\r'); writel(c, &mxc_base->txd); /* wait for transmitter to be ready */ while (!(readl(&mxc_base->ts) & UTS_TXEMPTY)) WATCHDOG_RESET(); }
调用写函数,将需要发送的字符写道
mxc_base->txd
寄存器中,并检查是否发送完成。 -
字符串输出puts
stdio设备调用函数
serial_stub_puts
实现字符串输出,函数内容为:static void serial_stub_puts(struct stdio_dev *sdev, const char *str) { struct serial_device *dev = sdev->priv; dev->puts(str); }
从上述内容得知,实际调用
mxc_serial_drv
设备的puts
函数,即default_serial_puts
,其内容为:void default_serial_puts(const char *s) { struct serial_device *dev = get_current(); while (*s) dev->putc(*s++); }
从函数内容得知,发送字符串,就是调用
putc
循环发送字符。 -
字符输入getc
stdio设备调用函数
serial_stub_getc
实现字符输入,函数内容为:static int stdio_serial_getc(struct stdio_dev *dev) { return serial_getc(); }
调用
serial_getc
函数,其内容为:int serial_getc(void) { return get_current()->getc(); }
实际调用
mxc_serial_drv
设备的getc
函数,即mxc_serial_getc
,其内容为:static int mxc_serial_getc(void) { while (readl(&mxc_base->ts) & UTS_RXEMPTY) WATCHDOG_RESET(); return (readl(&mxc_base->rxd) & URXD_RX_DATA); /* mask out status from upper word */ }
调用读函数,将
mxc_base->rxd
寄存器数据读取出来,并检测是否接受完毕。 -
输入检测tstc
stdio设备调用函数
serial_stub_tstc
实现检测串口是否有输入,函数内容为:static int stdio_serial_tstc(struct stdio_dev *dev) { return serial_tstc(); }
实际调用
mxc_serial_drv
设备的tstc函数,即mxc_serial_tstc
,其内容为:static int one_time_rx_line_always_low_workaround_needed = 1; static int mxc_serial_tstc(void) { /* If receive fifo is empty, return false */ if (readl(&mxc_base->ts) & UTS_RXEMPTY) return 0; /* Empty RX FIFO if receiver is stuck because of RXD line being low */ if (one_time_rx_line_always_low_workaround_needed) { one_time_rx_line_always_low_workaround_needed = 0; if (!(readl(&mxc_base->sr2) & USR2_RDR)) { while (!(readl(&mxc_base->ts) & UTS_RXEMPTY)) { (void) readl(&mxc_base->rxd); } return 0; } } return 1; }
调用读函数,读取mxc_base->ts寄存器中的FIFO状态,判断是否有输入。
one_time_rx_line_always_low_workaround_needed
是mxc_serial_drv
设备特有的检测,pl011串口驱动则是直接读取FIFO的状态寄存器来判断。
至此,uboot的串口stdio设备支持输入、输出以及输入检测功能。
4.7 console第二阶段初始化
在console第一阶段初始化过程中,只是设置console相关标志位,并未进行console实际的初始化。
console第二阶段初始化,通过调用console_init_r
函数实现,定义在common/console.c
文件中,其内容为:
943 int console_init_r(void)
944 {
945 char *stdinname, *stdoutname, *stderrname;
946 struct stdio_dev *inputdev = NULL, *outputdev = NULL, *errdev = NULL;
947 int i;
948 int iomux_err = 0;
949 int flushpoint;
950
951 /* update silent for env loaded from flash (initr_env) */
952 if (console_update_silent())
953 flushpoint = PRE_CONSOLE_FLUSHPOINT1_SERIAL;
954 else
955 flushpoint = PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL;
956
957 /* set default handlers at first */
958 gd->jt->getc = serial_getc;
959 gd->jt->tstc = serial_tstc;
960 gd->jt->putc = serial_putc;
961 gd->jt->puts = serial_puts;
962 gd->jt->printf = serial_printf;
963
964 /* stdin stdout and stderr are in environment */
965 /* scan for it */
966 stdinname = env_get("stdin");
967 stdoutname = env_get("stdout");
968 stderrname = env_get("stderr")SYS_CONSOLE_IS_IN_ENV;
969
970 if (OVERWRITE_CONSOLE == 0) { /* if not overwritten by config switch */
971 inputdev = console_search_dev(DEV_FLAGS_INPUT, stdinname);
972 outputdev = console_search_dev(DEV_FLAGS_OUTPUT, stdoutname);
973 errdev = console_search_dev(DEV_FLAGS_OUTPUT, stderrname);
974 if (CONFIG_IS_ENABLED(CONSOLE_MUX)) {
975 iomux_err = iomux_doenv(stdin, stdinname);
976 iomux_err += iomux_doenv(stdout, stdoutname);
977 iomux_err += iomux_doenv(stderr, stderrname);
978 if (!iomux_err)
979 /* Successful, so skip all the code below. */
980 goto done;
981 }
982 }
983 /* if the devices are overwritten or not found, use default device */
984 if (inputdev == NULL) {
985 inputdev = console_search_dev(DEV_FLAGS_INPUT, "serial");
986 }
987 if (outputdev == NULL) {
988 outputdev = console_search_dev(DEV_FLAGS_OUTPUT, "serial");
989 }
990 if (errdev == NULL) {
991 errdev = console_search_dev(DEV_FLAGS_OUTPUT, "serial");
992 }
993 /* Initializes output console first */
994 if (outputdev != NULL) {
995 /* need to set a console if not done above. */
996 console_doenv(stdout, outputdev);
997 }
998 if (errdev != NULL) {
999 /* need to set a console if not done above. */
1000 console_doenv(stderr, errdev);
1001 }
1002 if (inputdev != NULL) {
1003 /* need to set a console if not done above. */
1004 console_doenv(stdin, inputdev);
1005 }
1006
1007 done:
1008
1009 if (!IS_ENABLED(CONFIG_SYS_CONSOLE_INFO_QUIET))
1010 stdio_print_current_devices();
1011
...
1017
1018 if (IS_ENABLED(CONFIG_SYS_CONSOLE_ENV_OVERWRITE)) {
1019 /* set the environment variables (will overwrite previous env settings) */
1020 for (i = 0; i < MAX_FILES; i++)
1021 env_set(stdio_names[i], stdio_devices[i]->name);
1022 }
1023
1024 gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
1025
1026 print_pre_console_buffer(flushpoint);
1027 return 0;
1028 }
-
第958-962行,为console提供输入输出基本功能,其中
serial_printf
(其他函数同上)实现printf功能,其内容为:int serial_printf(const char *fmt, ...) { va_list args; uint i; char printbuffer[CONFIG_SYS_PBSIZE]; va_start(args, fmt); /* For this to work, printbuffer must be larger than * anything we ever want to print. */ i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args); va_end(args); serial_puts(printbuffer); return i; }
函数首先调用
vscnprintf
函数处理需要打印的数据,然后调用串口的serial_puts
输出函数,将处理完的数据进行打印。 -
未分析完