origin: http://blog.chinaunix.net/uid-25984886-id-3029881.html
1. 串口初始化过程 start_kernel() |----- ... |----- setup_arch() |----- ... |----- build_all_zonelists() |----- page_alloc_init() |----- ... |----- trap_init() |----- ... |----- console_init() |----- ... |----- mem_init() |----- ... &#96;----- rest_init() ---> kernel_thread() --> init() -->do_basic_setup() 1.1 console_init() [drivers/char/tty_io.c] /* 只作基本的初始化&#xff0c;详细的初始化在后面做 */ void __init console_init(void) { initcall_t *call; /* Setup the default TTY line discipline. */ (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ #ifdef CONFIG_EARLY_PRINTK disable_early_printk(); #endif call &#61; __con_initcall_start; while (call <__con_initcall_end) { (*call)(); call&#43;&#43;; } } 然后执行依次执行 .con_initcall.init 节中的函数&#xff0c;该节的每项为一个函数指针&#xff0c;使用宏 console_initcall(FUNC_NAME) 将函数指针填入&#xff0c;该宏定义于 [include/linux/init.h]: #define console_initcall(fn) \ static initcall_t __initcall_##fn \ __attribute_used__ __attribute__((__section__(".con_initcall.init")))&#61;fn initcall_t 为一函数指针&#xff1a; typedef int (*initcall_t)(void); 如&#xff1a; console_initcall(serial8250_console_init) 则展开为&#xff1a; static initcall_t __initcall_serial8250_console_init &#61; __attribute_used__ \ __attribute__((__section__(".con_initcall.init"))) &#61; serial8250_console_init; 即定义一个函数指针&#xff0c;使其指向 serial8250_console_init&#xff0c;并使用gcc的 __attribute__ 扩展&#xff0c;将其链接入.con_initcall.init 节&#xff0c;方便管理。 一个典型的 .con_initcall.init 节的内容为&#xff1a; ... Disassembly of section .con_initcall.init: 80234f90 <__initcall_serial8250_console_init>: 80234f90: 802328e4 lb v1,10468(at) # 这是一个函数指针&#xff0c;指向serial8250_console_init 80234f94 <__initcall_early_uart_console_init>: 80234f94: 80232ce4 lb v1,11492(at) ... 因此 console_init() 所做的&#xff0c;就是&#xff1a; console_init() |----- tty_register_ldisc() /* Install a line discipline, [drivers/char/tty_io.c] */ |----- serial8250_console_init() &#96;----- early_uart_console_init() 1.1.1 serial8250_console_init serial8250_console_init() 定义于 [drivers/serial/8250.c] static int __init serial8250_console_init(void) { serial8250_isa_init_ports(); register_console(&serial8250_console); return 0; } console_initcall(serial8250_console_init); static struct uart_8250_port serial8250_ports[UART_NR]; static void __init serial8250_isa_init_ports(void) { struct uart_8250_port *up; static int first &#61; 1; int i; if (!first) return; first &#61; 0; for (i &#61; 0; i
struct uart_8250_port *up &#61; &serial8250_ports[i];
up->port.line &#61; i;
spin_lock_init(&up->port.lock);
init_timer(&up->timer);
up->timer.function &#61; serial8250_timeout;
/*
* ALPHA_KLUDGE_MCR needs to be killed.
*/
up->mcr_mask &#61; ~ALPHA_KLUDGE_MCR;
up->mcr_force &#61; ALPHA_KLUDGE_MCR;
up->port.ops &#61; &serial8250_pops;
}
for (i &#61; 0, up &#61; serial8250_ports;
i
i&#43;&#43;, up&#43;&#43;) {
up->port.iobase &#61; old_serial_port[i].port;
up->port.irq &#61; irq_canonicalize(old_serial_port[i].irq);
up->port.uartclk &#61; old_serial_port[i].baud_base * 16;
up->port.flags &#61; old_serial_port[i].flags;
up->port.hub6 &#61; old_serial_port[i].hub6;
up->port.membase &#61; old_serial_port[i].iomem_base;
up->port.iotype &#61; old_serial_port[i].io_type;
up->port.regshift &#61; old_serial_port[i].iomem_reg_shift;
if (share_irqs)
up->port.flags |&#61; UPF_SHARE_IRQ;
}
}
serial8250_isa_init_ports() 所做的事即使用 old_serial_port 来初始化 struct uart_8250_port 结构数组 serial8250_ports. 这个 old_serial_port 定义为:
static const struct old_serial_port old_serial_port[] &#61; {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};
[include/asm-mips/serial.h]
#define SERIAL_PORT_DFNS \
DDB5477_SERIAL_PORT_DEFNS \
EV64120_SERIAL_PORT_DEFNS \
IP32_SERIAL_PORT_DEFNS \
JAZZ_SERIAL_PORT_DEFNS \
STD_SERIAL_PORT_DEFNS \
MOMENCO_OCELOT_G_SERIAL_PORT_DEFNS \
MOMENCO_OCELOT_C_SERIAL_PORT_DEFNS \
MOMENCO_OCELOT_SERIAL_PORT_DEFNS \
MOMENCO_OCELOT_3_SERIAL_PORT_DEFNS \
BCM947XX_SERIAL_PORT_DEFNS \
BCM56218_SERIAL_PORT_DEFNS
这个根据具体的平台配置,使用相应的宏定义. 如当 CONFIG_HAVE_STD_PC_SERIAL_PORT 时:
#ifdef CONFIG_HAVE_STD_PC_SERIAL_PORT
#define STD_SERIAL_PORT_DEFNS \
/* UART CLK PORT IRQ FLAGS */ \
{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ \
{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ \
{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ \
{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */
#else /* CONFIG_HAVE_STD_PC_SERIAL_PORTS */
#define STD_SERIAL_PORT_DEFNS
#endif /* CONFIG_HAVE_STD_PC_SERIAL_PORTS */
否则为空宏
serial8250_isa_init_ports() 后, serial8250_console_init() 调用 register_console(&serial8250_console) 注册一个struct console 结构:
static struct uart_driver serial8250_reg;
static struct console serial8250_console &#61; {
.name &#61; "ttyS",
.write &#61; serial8250_console_write,
.device &#61; uart_console_device,
.setup &#61; serial8250_console_setup,
.flags &#61; CON_PRINTBUFFER,
.index &#61; -1,
.data &#61; &serial8250_reg,
};
其用来描述一个 serial8250 的 console.
这个register_console() 定义于 [kernel/printk.c]
1.1.2 early_uart_console_init()
[drivers/serial/8250_early.c]
static struct console early_uart_console __initdata &#61; {
.name &#61; "uart",
.write &#61; early_uart_write,
.setup &#61; early_uart_setup,
.flags &#61; CON_PRINTBUFFER,
.index &#61; -1,
};
static int __init early_uart_console_init(void)
{
if (!early_uart_registered) {
register_console(&early_uart_console);
early_uart_registered &#61; 1;
}
return 0;
}
console_initcall(early_uart_console_init);
和 serial8250_console_init() 类似,也是注册一个 console 结构,表示一个 uart console
1.2 rest_init()
rest_init()
|----- ...
|----- smp_prepare_cpus(max_cpus)
|----- do_pre_smp_initcalls()
|----- smp_init()
|----- sched_init_smp()
|----- cpuset_init_smp()
|----- do_basic_setup()
|----- ...
&#96;----- init_post()
1.2.1 do_basic_setup()
到 do_basic_setup() 时,与体系结构相关的部分已经初始化完了,现在开始初始化设备了:
[init/main.c]
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init(); /* initialize driver model */
init_irq_proc();
do_initcalls(); /* 顺序执行 .initcall.init 节中的所有函数 */
}
1.2.1 driver_init()
driver_init() 定义于 [drivers/base/init.c] 主要完成 driver subsystem 的初始化:
void __init driver_init(void)
{
/* These are the core pieces */
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
attribute_container_init();
}
这些函数主要调用 subsystem_register() 注册一个struct subsystem 结构,进入kobjects.
1.2.2 do_initcall()
这个于上面 console_init() 类似,其是顺序执行 .initcall.init 节中的所有函数:
[init/main.c]
extern initcall_t __initcall_start[], __initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *call;
int count &#61; preempt_count();
for (call &#61; __initcall_start; call <__initcall_end; call&#43;&#43;) {
char *msg &#61; NULL;
char msgbuf[40];
int result;
if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
}
result &#61; (*call)();
if (result && result !&#61; -ENODEV && initcall_debug) {
sprintf(msgbuf, "error code %d", result);
msg &#61; msgbuf;
}
if (preempt_count() !&#61; count) {
msg &#61; "preemption imbalance";
preempt_count() &#61; count;
}
if (irqs_disabled()) {
msg &#61; "disabled interrupts";
local_irq_enable();
}
if (msg) {
printk(KERN_WARNING "initcall at 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(": returned with %s\n", msg);
}
}
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
关于符号地址 __initcall_start, __initcall_end 的来源,则是由编译系统写在 [arch/mips/kernel/vmlinux.lds]中:
......
__initcall_start &#61; .;
.initcall.init : {
*(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
}
__initcall_end &#61; .;
......
链接时,会被替换为实际的地址矣.
写入 .initcall.init 节的函数指针,有一组辅助的宏定义于[include/linux/init.h]:
#define pure_initcall(fn) __define_initcall("0",fn,1)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
其优先级依次降低,优先级越高的,越靠前,则先被执行.
在这个节的最后,可以看到调用串口相关的初始化函数:
Disassembly of section .initcall.init:
......
......
80234f80 <__initcall_serial8250_init6>:
80234f80: 80232910 lb v1,10512(at)
80234f84 <__initcall_random32_reseed7>:
80234f84: 802319c4 lb v1,6596(at)
80234f88 <__initcall_seqgen_init7>:
80234f88: 80231ae8 lb v1,6888(at)
80234f8c <__initcall_early_uart_console_switch7>:
80234f8c: 80233140 lb v1,12608(at)
因此:
do_basic_setup()
|----- ...
|----- driver_init()
|----- init_irq_proc()
|----- do_initcalls()
|----- ...
|----- ...
|----- serial8250_init()
|----- seqgen_init()
&#96;----- early_uart_console_switch()
|----- ...
&#96;----- ...
1.2.2.1 serial8250_init()
[drivers/serial/8250.c]
static int __init serial8250_init(void)
{
int ret, i;
if (nr_uarts > UART_NR)
nr_uarts &#61; UART_NR;
printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
"%d ports, IRQ sharing %sabled\n", nr_uarts,
share_irqs ? "en" : "dis");
for (i &#61; 0; i
spin_lock_init(&irq_lists[i].lock);
ret &#61; uart_register_driver(&serial8250_reg);
if (ret)
goto out;
serial8250_isa_devs &#61; platform_device_alloc("serial8250",
PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret &#61; -ENOMEM;
goto unreg_uart_drv;
}
ret &#61; platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
ret &#61; platform_driver_register(&serial8250_isa_driver); ---> 注册时调用 serial8250_probe()
if (ret &#61;&#61; 0)
goto out;
platform_device_del(serial8250_isa_devs);
put_dev:
platform_device_put(serial8250_isa_devs);
unreg_uart_drv:
uart_unregister_driver(&serial8250_reg);
out:
return ret;
}
注意 platform_driver_register() 中,注册时调用 serial8250_probe(), 从[arch/mips/emma3p/et10068/platform.c] 中设置的 struct platform_device 结构数组中获得板极相关的串口设备.
1.2.2.2 early_uart_console_switch()
[drivers/serial/8250_early.c]
static int __init early_uart_console_switch(void)
{
struct early_uart_device *device &#61; &early_device;
struct uart_port *port &#61; &device->port;
int mmio, line;
if (!(early_uart_console.flags & CON_ENABLED))
return 0;
/* Try to start the normal driver on a matching line. */
mmio &#61; (port->iotype &#61;&#61; UPIO_MEM);
line &#61; serial8250_start_console(port, device->options); /* start console */
if (line <0)
printk("No ttyS device at %s 0x%lx for console\n",
mmio ? "MMIO" : "I/O port",
mmio ? port->mapbase :
(unsigned long) port->iobase);
unregister_console(&early_uart_console);
if (mmio)
iounmap(port->membase);
return 0;
}
late_initcall(early_uart_console_switch);
到此串口终端正式可用矣~~~
2. 兼容 8250 的串口控制器驱动位于:
drivers/serial/8250_early.c
drivers/serial/8250.c
drivers/serial/serial_core.c