Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1244
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2021-09-25 14:40
文章分类
文章存档

2021年(5)

我的朋友
最近访客

分类: LINUX

2021-09-28 14:38:52

原文地址:Linux 串口终端初始化 作者:spengdong

1. 串口初始化过程

    start_kernel()
          |----- ...
          |----- setup_arch()
          |----- ...
          |----- build_all_zonelists()
          |----- page_alloc_init()
          |----- ...
          |----- trap_init()
          |----- ...
          |----- console_init()
          |----- ...
          |----- mem_init()
          |----- ...
          `----- rest_init()   ---> kernel_thread() --> init() -->do_basic_setup()


1.1 console_init()

[drivers/char/tty_io.c]

/* 只作基本的初始化,详细的初始化在后面做 */
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 = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }
}

然后执行依次执行 .con_initcall.init 节中的函数,该节的每项为一个函数指针,使用宏 console_initcall(FUNC_NAME) 将函数指针填入,该宏定义于 [include/linux/init.h]:

#define console_initcall(fn) \
    static initcall_t __initcall_##fn \
    __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

initcall_t 为一函数指针: typedef int (*initcall_t)(void);

如: console_initcall(serial8250_console_init) 则展开为:

static initcall_t __initcall_serial8250_console_init = __attribute_used__ \
                              __attribute__((__section__(".con_initcall.init"))) = serial8250_console_init;

即定义一个函数指针,使其指向 serial8250_console_init,并使用gcc的 __attribute__ 扩展,将其链接入.con_initcall.init 节,方便管理。

一个典型的 .con_initcall.init 节的内容为:
...
Disassembly of section .con_initcall.init:

80234f90 <__initcall_serial8250_console_init>:
80234f90:   802328e4    lb v1,10468(at)            # 这是一个函数指针,指向serial8250_console_init
80234f94 <__initcall_early_uart_console_init>:   
80234f94:   80232ce4    lb v1,11492(at)
...

因此 console_init() 所做的,就是:

    console_init()
          |----- tty_register_ldisc()               /* Install a line discipline, [drivers/char/tty_io.c] */
          |----- serial8250_console_init()   
          `----- 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 = 1;
    int i;

    if (!first)
        return;
    first = 0;

    for (i = 0; i < nr_uarts; i++) {
        struct uart_8250_port *up = &serial8250_ports[i];

        up->port.line = i;
        spin_lock_init(&up->port.lock);

        init_timer(&up->timer);
        up->timer.function = serial8250_timeout;

        /*
         * ALPHA_KLUDGE_MCR needs to be killed.
         */
        up->mcr_mask = ~ALPHA_KLUDGE_MCR;
        up->mcr_force = ALPHA_KLUDGE_MCR;

        up->port.ops = &serial8250_pops;
    }

    for (i = 0, up = serial8250_ports;
         i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
         i++, up++) {
        up->port.iobase   = old_serial_port[i].port;
        up->port.irq      = irq_canonicalize(old_serial_port[i].irq);
        up->port.uartclk = old_serial_port[i].baud_base * 16;
        up->port.flags    = old_serial_port[i].flags;
        up->port.hub6     = old_serial_port[i].hub6;
        up->port.membase = old_serial_port[i].iomem_base;
        up->port.iotype   = old_serial_port[i].io_type;
        up->port.regshift = old_serial_port[i].iomem_reg_shift;
        if (share_irqs)
            up->port.flags |= 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[] = {
    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 = {
    .name       = "ttyS",
    .write      = serial8250_console_write,
    .device     = uart_console_device,
    .setup      = serial8250_console_setup,
    .flags      = CON_PRINTBUFFER,
    .index      = -1,
    .data       = &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 = {
    .name   = "uart",
    .write = early_uart_write,
    .setup = early_uart_setup,
    .flags = CON_PRINTBUFFER,
    .index = -1,
};

static int __init early_uart_console_init(void)
{
    if (!early_uart_registered) {
        register_console(&early_uart_console);
        early_uart_registered = 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()
          |----- ...
          `----- 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 = preempt_count();

    for (call = __initcall_start; call < __initcall_end; call++) {
        char *msg = 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 = (*call)();

        if (result && result != -ENODEV && initcall_debug) {
            sprintf(msgbuf, "error code %d", result);
            msg = msgbuf;
        }
        if (preempt_count() != count) {
            msg = "preemption imbalance";
            preempt_count() = count;
        }
        if (irqs_disabled()) {
            msg = "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 = .;
.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 = .;
......

链接时,会被替换为实际的地址矣.

写入 .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()
                          `----- 
early_uart_console_switch()
          |----- ...
          `----- ...

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 = 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 = 0; i < NR_IRQS; i++)
        spin_lock_init(&irq_lists[i].lock);

    ret = uart_register_driver(&serial8250_reg);
    if (ret)
        goto out;

    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);   ---> 注册时调用 serial8250_probe()
    if (ret == 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 = &early_device;
    struct uart_port *port = &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 = (port->iotype == UPIO_MEM);
    line = 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
阅读(68) | 评论(0) | 转发(0) |
0

上一篇:zImage内核镜像解压过程详解

下一篇:没有了

给主人留下些什么吧!~~