For our android platform, the console is defined on Serial 1, so the initialization process is to configure the console output to serial 1.
The initialization of the kernel console is prior to mounting the file system. Because there is no serial port device file, you cannot open the device file to access the serial port, but you can only directly access the hardware, more similar to bare metal access.
Next let's take a look
Board initialization process
Android \ kernel_imx \ arch \ arm \ mach-mx6 \ board-mx6q_sabresd.c
[Cpp]
MACHINE_START (MX6Q_SABRESD, "Freescale I. MX 6 Quad/DualLite/Solo Sabre-SD Board ")
/* Maintainer: Freescale semicondu, 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 6 Quad/DualLite/Solo Sabre-SD Board ")
/* Maintainer: Freescale semicondu, 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
Here is a clock that initializes mx6_sabresd_timer. Let's look at its definition.
[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,240 00000, 0, 0 );
Uart_clk = clk_get_sys ("imx-uart.0", NULL );
Early_console_setup (uart+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,240 00000, 0, 0 );
Uart_clk = clk_get_sys ("imx-uart.0", NULL );
Early_console_setup (uart+base_addr, uart_clk );
}
Early_console_setup (uart+base_addr, uart_clk) is called here );
This function is the initialization function of the console before the file system is mounted. Next I will start to analyze this function.
Android \ kernel_imx \ arch \ arm \ plat-mxc \ cpu. c
[Cpp]
/**
* Early_console_setup-setup debugging console
*
* Using LES 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
* This comment is clearly written. Before the device driver is executed, we need to debug errors.
* You need to initialize the console at the beginning of startup.
*/
Void _ init early_console_setup (unsigned long base, struct clk * clk)
{
# Ifdef CONFIG_SERIAL_IMX_CONSOLE
Mxc_early_serial_lele_init (base, clk );
# Endif
}
Call mxc_early_serial_lele_init (base, clk) here );
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
*
* Using LES 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
* This comment is clearly written. Before the device driver is executed, we need to debug errors.
* You need to initialize the console at the beginning of startup.
*/
Void _ init early_console_setup (unsigned long base, struct clk * clk)
{
# Ifdef CONFIG_SERIAL_IMX_CONSOLE
Mxc_early_serial_lele_init (base, clk );
# Endif
}
Call mxc_early_serial_lele_init (base, clk) here );
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) is used to register a device to the console,
The device registered at the beginning must be accessed by a bare metal device. Therefore, we should focus on this device.
[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,
};
Device access interface provided by this device
. Write = early_mxcuart_console_write, which is the sending function of the serial port.
. Setup = mxc_early_uart_setup, which is the initialization function of the serial port.
. Flags = CON_PRINTBUFFER | CON_BOOT is the console identifier. CON_BOOT indicates a boot console device.
That is, the console device before mounting the Device File
Next we will analyze the initialization function and data sending Function
Initialization Function
[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); // initialize the bus clock
Port-& gt; uartclk = 5600000;
Port-> iotype = UPIO_MEM;
Port-> membase = ioremap (port-> mapbase, SZ_4K); // serial register memory ing
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); // initialize the bus clock
Port-& gt; uartclk = 5600000;
Port-> iotype = UPIO_MEM;
Port-> membase = ioremap (port-> mapbase, SZ_4K); // serial register memory ing
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;
}
In fact, we can see from this initialization function that it has done a lot of work to fill in data into the mxc_early_device struct, and the data
Finding all the code is useless, so it makes no sense to do these things. The official code does not give this very well. However, we have initialized the uboot
The serial port can be used even if there is no initialization.
There are only two useful sentences here.
Clk_enable (device-> clk); // initialize the bus clock
Port-> membase = ioremap (port-> mapbase, SZ_4K); // serial register memory ing
However, it is strange that no serial register Initialization is performed after the register ing is completed,
The register initialization code is written in the data sending function. Why do we analyze the sending function?
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, oldconvention 3, cr2, and so on;
/*
* First save the control registers and then disable the interrupts
*/
Oldcr1 = readl (port-> membase + MXC_UARTUCR1); // read the values of the current three serial control registers
Oldcr2 = readl (port-> membase + MXC_UARTUCR2 );
Old32a = readl (port-> membase + mxc_uartu3303 );
Cr2 =
Oldcr2 &~ (MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | // initialize the serial register value
MXC_UARTUCR2_ESCI );
32a =
Oldconvention 3 &~ (MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI |
MXC_UARTUCR3_DTRDEN );
Writel (MXC_UARTUCR1_UARTEN, port-> membase + MXC_UARTUCR1); // enable serial port
Writel (cr2, port-> membase + MXC_UARTUCR2); // configure the Register
Writel (32a, port-> membase + mxc_uartu3303 );
/* Transmit string */
Uart_lele_write (port, s, count, mxcuart_console_write_char); // send data
/*
* 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); // restores the serial register value.
Writel (oldcr2, port-> membase + MXC_UARTUCR2 );
Writel (old3303, port-> membase + mxc_uartu3303 );
}
/*!
* 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, oldconvention 3, cr2, and so on;
/*
* First save the control registers and then disable the interrupts
*/
Oldcr1 = readl (port-> membase + MXC_UARTUCR1); // read the values of the current three serial control registers
Oldcr2 = readl (port-> membase + MXC_UARTUCR2 );
Old32a = readl (port-> membase + mxc_uartu3303 );
Cr2 =
Oldcr2 &~ (MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | // initialize the serial register value
MXC_UARTUCR2_ESCI );
32a =
Oldconvention 3 &~ (MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI |
MXC_UARTUCR3_DTRDEN );
Writel (MXC_UARTUCR1_UARTEN, port-> membase + MXC_UARTUCR1); // enable serial port
Writel (cr2, port-> membase + MXC_UARTUCR2); // configure the Register
Writel (32a, port-> membase + mxc_uartu3303 );
/* Transmit string */
Uart_lele_write (port, s, count, mxcuart_console_write_char); // send data
/*
* 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); // restores the serial register value.
Writel (oldcr2, port-> membase + MXC_UARTUCR2 );
Writel (old3303, port-> membase + mxc_uartu3303 );
}
Reading from this function, it first saves the value of the serial control register, and then initializes it to conform to the console. After the data is sent, the original data is restored.
The purpose of this operation is that if we load the serial port driver, the console configuration may be disrupted.
We cannot configure the serial driver, so the best way is to re-configure the serial port for each data transmission and restore the previous configuration after sending.
The first part of console initialization has been completed. We can know how the console works without a file system.