热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux串口终端初始化

origin:http:blog.chinaunix.netuid-25984886-id-3029881.html1.串口初始化过程start_kernel()|-----
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()
          |----- ...
          `----- 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&#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



推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 本文介绍了使用readlink命令获取文件的完整路径的简单方法,并提供了一个示例命令来打印文件的完整路径。共有28种解决方案可供选择。 ... [详细]
  • 【重识云原生】第四章云网络4.8.3.2节——Open vSwitch工作原理详解
    2OpenvSwitch架构2.1OVS整体架构ovs-vswitchd:守护程序,实现交换功能,和Linux内核兼容模块一起,实现基于流的交换flow-basedswitchin ... [详细]
  • 第七课主要内容:多进程多线程FIFO,LIFO,优先队列线程局部变量进程与线程的选择线程池异步IO概念及twisted案例股票数据抓取 ... [详细]
author-avatar
手机用户2502860713
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有