對於我們的android平台,控制台被定義到了串口1上,因此初始化過程就是把控制台的輸出配置到串口1上
對kernel控制台初始化是在掛載檔案系統之前,由於沒有串口的裝置檔案,不能通過開啟裝置檔案來訪問串口,只能直接存取硬體,更類似與裸機的訪問方式。
下面正式來看
板子初始化的過程
android\kernel_imx\arch\arm\mach-mx6\board-mx6q_sabresd.c
[cpp]
MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.boot_params = MX6_PHYS_OFFSET + 0x100,
.fixup = fixup_mxc_board,
.map_io = mx6_map_io,
.init_irq = mx6_init_irq,
.init_machine = mx6_sabresd_board_init,
.timer = &mx6_sabresd_timer,
.reserve = mx6q_sabresd_reserve,
MACHINE_END
MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.boot_params = MX6_PHYS_OFFSET + 0x100,
.fixup = fixup_mxc_board,
.map_io = mx6_map_io,
.init_irq = mx6_init_irq,
.init_machine = mx6_sabresd_board_init,
.timer = &mx6_sabresd_timer,
.reserve = mx6q_sabresd_reserve,
MACHINE_END
這其中有個時鐘初始化mx6_sabresd_timer我們來看它的定義
[cpp]
static struct sys_timer mx6_sabresd_timer = {
.init = mx6_sabresd_timer_init,
};
static void __init mx6_sabresd_timer_init(void)
{
struct clk *uart_clk;
#ifdef CONFIG_LOCAL_TIMERS
twd_base = ioremap(LOCAL_TWD_ADDR, SZ_256);
BUG_ON(!twd_base);
#endif
mx6_clocks_init(32768, 24000000, 0, 0);
uart_clk = clk_get_sys("imx-uart.0", NULL);
early_console_setup(UART1_BASE_ADDR, uart_clk);
}
static struct sys_timer mx6_sabresd_timer = {
.init = mx6_sabresd_timer_init,
};
static void __init mx6_sabresd_timer_init(void)
{
struct clk *uart_clk;
#ifdef CONFIG_LOCAL_TIMERS
twd_base = ioremap(LOCAL_TWD_ADDR, SZ_256);
BUG_ON(!twd_base);
#endif
mx6_clocks_init(32768, 24000000, 0, 0);
uart_clk = clk_get_sys("imx-uart.0", NULL);
early_console_setup(UART1_BASE_ADDR, uart_clk);
}
可以看到這裡調用了early_console_setup(UART1_BASE_ADDR, uart_clk);
這個函數就是檔案系統掛載之前控制台的初始化函數。下面我就開始分析這個函數
android\kernel_imx\arch\arm\plat-mxc\cpu.c
[cpp]
/**
* early_console_setup - setup debugging console
*
* Consoles started here require little enough setup that we can start using
* them very early in the boot process, either right after the machine
* vector initialization, or even before if the drivers can detect their hw.
*
* Returns non-zero if a console couldn't be setup.
* This function is developed based on
* early_console_setup function as defined in arch/ia64/kernel/setup.c
* 這個注釋裡寫的很清楚,在裝置驅動執行之前,為了調試錯誤的需要我們
* 需要在啟動的最初就初始化控制台
*/
void __init early_console_setup(unsigned long base, struct clk *clk)
{
#ifdef CONFIG_SERIAL_IMX_CONSOLE
mxc_early_serial_console_init(base, clk);
#endif
}
這裡調用mxc_early_serial_console_init(base, clk);
android\kernel_imx\drivers\tty\serial、mxc_uart_early.c
int __init mxc_early_serial_console_init(unsigned long base, struct clk *clk)
{
mxc_early_device.clk = clk;
mxc_early_device.port.mapbase = base;
register_console(&mxc_early_uart_console);
return 0;
}
/**
* early_console_setup - setup debugging console
*
* Consoles started here require little enough setup that we can start using
* them very early in the boot process, either right after the machine
* vector initialization, or even before if the drivers can detect their hw.
*
* Returns non-zero if a console couldn't be setup.
* This function is developed based on
* early_console_setup function as defined in arch/ia64/kernel/setup.c
* 這個注釋裡寫的很清楚,在裝置驅動執行之前,為了調試錯誤的需要我們
* 需要在啟動的最初就初始化控制台
*/
void __init early_console_setup(unsigned long base, struct clk *clk)
{
#ifdef CONFIG_SERIAL_IMX_CONSOLE
mxc_early_serial_console_init(base, clk);
#endif
}
這裡調用mxc_early_serial_console_init(base, clk);
android\kernel_imx\drivers\tty\serial、mxc_uart_early.c
int __init mxc_early_serial_console_init(unsigned long base, struct clk *clk)
{
mxc_early_device.clk = clk;
mxc_early_device.port.mapbase = base;
register_console(&mxc_early_uart_console);
return 0;
}
這裡可以看到register_console(&mxc_early_uart_console);就是註冊一個裝置到控制台中,
在最開始註冊的這個裝置肯定是裸機的訪問方式的,因此我們重點來看這個裝置
[cpp]
static struct console mxc_early_uart_console __initdata = {
.name = "ttymxc",
.write = early_mxcuart_console_write,
.setup = mxc_early_uart_setup,
.flags = CON_PRINTBUFFER | CON_BOOT,
.index = -1,
};
static struct console mxc_early_uart_console __initdata = {
.name = "ttymxc",
.write = early_mxcuart_console_write,
.setup = mxc_early_uart_setup,
.flags = CON_PRINTBUFFER | CON_BOOT,
.index = -1,
};
這個裝置提供的裝置提供者
.write = early_mxcuart_console_write,是串口的發送函數
.setup = mxc_early_uart_setup,是串口的初始化函數
.flags = CON_PRINTBUFFER | CON_BOOT,是控制台標誌,CON_BOOT表明這事一個boot的控制台裝置
也就是說是掛載裝置檔案之前的控制台裝置
下面我們來分析初始化函數和 資料發送函數
初始化函數
[cpp]
static int __init mxc_early_uart_setup(struct console *console, char *options)
{
struct mxc_early_uart_device *device = &mxc_early_device;
struct uart_port *port = &device->port;
int length;
if (device->port.membase || device->port.iobase)
return -ENODEV;
/* Enable Early MXC UART Clock */
clk_enable(device->clk);//初始化匯流排時鐘
port->uartclk = 5600000;
port->iotype = UPIO_MEM;
port->membase = ioremap(port->mapbase, SZ_4K);//串口寄存器記憶體映射
if (options) {
device->baud = simple_strtoul(options, NULL, 0);
length = min(strlen(options), sizeof(device->options));
strncpy(device->options, options, length);
} else {
device->baud = probe_baud(port);
snprintf(device->options, sizeof(device->options), "%u",
device->baud);
}
printk(KERN_INFO
"MXC_Early serial console at MMIO 0x%x (options '%s')\n",
port->mapbase, device->options);
return 0;
}
static int __init mxc_early_uart_setup(struct console *console, char *options)
{
struct mxc_early_uart_device *device = &mxc_early_device;
struct uart_port *port = &device->port;
int length;
if (device->port.membase || device->port.iobase)
return -ENODEV;
/* Enable Early MXC UART Clock */
clk_enable(device->clk);//初始化匯流排時鐘
port->uartclk = 5600000;
port->iotype = UPIO_MEM;
port->membase = ioremap(port->mapbase, SZ_4K);//串口寄存器記憶體映射
if (options) {
device->baud = simple_strtoul(options, NULL, 0);
length = min(strlen(options), sizeof(device->options));
strncpy(device->options, options, length);
} else {
device->baud = probe_baud(port);
snprintf(device->options, sizeof(device->options), "%u",
device->baud);
}
printk(KERN_INFO
"MXC_Early serial console at MMIO 0x%x (options '%s')\n",
port->mapbase, device->options);
return 0;
}
其實從這個初始化函數裡看出,它做了很多向mxc_early_device結構體中填入資料的工作,而這些資料
找遍所有代碼也沒有用到,因此這些事沒有意義的,官方代碼給的這點並不太好。但是由於uboot中我們已經初始化了
串口因此這裡就算沒有任何初始化其實串口也可以是使用。
這裡真正有用的就兩句話
clk_enable(device->clk);//初始化匯流排時鐘
port->membase = ioremap(port->mapbase, SZ_4K);//串口寄存器記憶體映射
但是在寄存器映射結束後沒有進行任何串口寄存器初始化,這也很奇怪,我們仔細尋找發現,
寄存器初始化代碼寫在了資料發送函數裡,具體為什麼我們來分析發送函數
early_mxcuart_console_write
[cpp]
/*!
* This function is called to write the console messages through the UART port.
*
* @param co the console structure
* @param s the log message to be written to the UART
* @param count length of the message
*/
void __init early_mxcuart_console_write(struct console *co, const char *s,
u_int count)
{
struct uart_port *port = &mxc_early_device.port;
unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3;
/*
* First save the control registers and then disable the interrupts
*/
oldcr1 = readl(port->membase + MXC_UARTUCR1); //讀取當前三個串口控制寄存器的值
oldcr2 = readl(port->membase + MXC_UARTUCR2);
oldcr3 = readl(port->membase + MXC_UARTUCR3);
cr2 =
oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | //初始化串口寄存器數值
MXC_UARTUCR2_ESCI);
cr3 =
oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI |
MXC_UARTUCR3_DTRDEN);
writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); //使能串口
writel(cr2, port->membase + MXC_UARTUCR2); //配置寄存器
writel(cr3, port->membase + MXC_UARTUCR3);
/* Transmit string */
uart_console_write(port, s, count, mxcuart_console_write_char); //發送資料
/*
* Finally, wait for the transmitter to become empty等待發送完成
*/
do {
status = readl(port->membase + MXC_UARTUSR2);
} while (!(status & MXC_UARTUSR2_TXDC));
/*
* Restore the control registers
*/
writel(oldcr1, port->membase + MXC_UARTUCR1);//恢複串口寄存器數值
writel(oldcr2, port->membase + MXC_UARTUCR2);
writel(oldcr3, port->membase + MXC_UARTUCR3);
}
/*!
* This function is called to write the console messages through the UART port.
*
* @param co the console structure
* @param s the log message to be written to the UART
* @param count length of the message
*/
void __init early_mxcuart_console_write(struct console *co, const char *s,
u_int count)
{
struct uart_port *port = &mxc_early_device.port;
unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3;
/*
* First save the control registers and then disable the interrupts
*/
oldcr1 = readl(port->membase + MXC_UARTUCR1); //讀取當前三個串口控制寄存器的值
oldcr2 = readl(port->membase + MXC_UARTUCR2);
oldcr3 = readl(port->membase + MXC_UARTUCR3);
cr2 =
oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | //初始化串口寄存器數值
MXC_UARTUCR2_ESCI);
cr3 =
oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI |
MXC_UARTUCR3_DTRDEN);
writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); //使能串口
writel(cr2, port->membase + MXC_UARTUCR2); //配置寄存器
writel(cr3, port->membase + MXC_UARTUCR3);
/* Transmit string */
uart_console_write(port, s, count, mxcuart_console_write_char); //發送資料
/*
* Finally, wait for the transmitter to become empty等待發送完成
*/
do {
status = readl(port->membase + MXC_UARTUSR2);
} while (!(status & MXC_UARTUSR2_TXDC));
/*
* Restore the control registers
*/
writel(oldcr1, port->membase + MXC_UARTUCR1);//恢複串口寄存器數值
writel(oldcr2, port->membase + MXC_UARTUCR2);
writel(oldcr3, port->membase + MXC_UARTUCR3);
}
從這個函數看書,它首先儲存了串口控制寄存器的值,然後初始化成符合控制台的,發送完資料後,又恢複了原來的資料
這樣做的目的就是,如果我們載入了串口的驅動,那麼很有可能打亂了控制台的配置,而系統啟動以後
我們還不能動串口驅動的配置,因此最好的辦法就是,每次發送資料都重新設定串口,發送完後再恢複以前的配置。
到了這裡控制台初始化的第一部分已經完成了。我們可以知道沒有檔案系統,控制台是怎麼工作的。