• Linux UART驱动分析


    1. 介绍

    8250是IBM PC及兼容机使用的一种串口芯片; 16550是一种带先进先出(FIFO)功能的8250系列串口芯片; 16550A则是16550的升级版本, 修复了FIFO相关BUG, 也是目前比较常见的串口芯片.

    本文介绍的是Xilinx UART 驱动分析, 因为没有找到其datasheet, 硬件操作部分分析16550的实现.

    Xilinx UART驱动主要由drivers/tty/serial/xilinx_uartps.c来实现
    其相关配置和基本信息可参考<Zynq UART>

    2. 结构体

    uart_driverconsole结构变量, 以及实现了uart_ops函数操作集定义如下图所示

    static struct uart_driver cdns_uart_uart_driver = {
        .owner          = THIS_MODULE,
        .driver_name    = CDNS_UART_NAME,          /* xuartps */
        .dev_name       = CDNS_UART_TTY_NAME,      /* ttyPS */
        .major          = CDNS_UART_MAJOR,         /* 0, 注册时动态分配 */
        .minor          = CDNS_UART_MINOR,         /* 0, 注册时动态分配*/
        .nr             = CDNS_UART_NR_PORTS,      /* 2 */
    #ifdef CONFIG_SERIAL_XILINX_PS_UART_CONSOLE
        .cons           = &cdns_uart_console,      /* ttyPS */
    #endif
    };
    
    static struct console cdns_uart_console = {
        .name           = CDNS_UART_TTY_NAME,      /* ttyPS */
        .write          = cdns_uart_console_write,
        .device         = uart_console_device,
        .setup          = cdns_uart_console_setup,
        .flags          = CON_PRINTBUFFER,
        .index          = -1,                      /* 由cmdline指定(e.g. console=ttyPS ) */
        .data           = &cdns_uart_uart_driver,
    };
    
    static const struct uart_ops cdns_uart_ops = {
        .set_mctrl     = cdns_uart_set_mctrl,
        .get_mctrl     = cdns_uart_get_mctrl,
        .start_tx      = cdns_uart_start_tx,
        .stop_tx       = cdns_uart_stop_tx,
        .stop_rx       = cdns_uart_stop_rx,
        .tx_empty      = cdns_uart_tx_empty,
        .break_ctl     = cdns_uart_break_ctl,
        .set_termios   = cdns_uart_set_termios,
        .startup       = cdns_uart_startup,
        .shutdown      = cdns_uart_shutdown,
        .pm            = cdns_uart_pm,
        .type          = cdns_uart_type,
        .verify_port   = cdns_uart_verify_port,
        .request_port  = cdns_uart_request_port,
        .release_port  = cdns_uart_release_port,
        .config_port   = cdns_uart_config_port,
    #ifdef CONFIG_CONSOLE_POLL
        .poll_get_char = cdns_uart_poll_get_char,
        .poll_put_char = cdns_uart_poll_put_char,
    #endif
    };

    3. 初始化

    模块入口为cdns_uart_init
    首先注册UART驱动

    uart_register_driver(&cdns_uart_uart_driver);

    随后又注册platform驱动

    platform_driver_register(&cdns_uart_platform_driver);

    其中cdns_uart_platform_driver定义如下

    static const struct of_device_id cdns_uart_of_match[] = {
        { .compatible = "xlnx,xuartps", },
        { .compatible = "cdns,uart-r1p8", },
        { .compatible = "cdns,uart-r1p12", .data = &zynqmp_uart_def },
        { .compatible = "xlnx,zynqmp-uart", .data = &zynqmp_uart_def },
        {}
    };
    
    static struct platform_driver cdns_uart_platform_driver = {
        .probe   = cdns_uart_probe,
        .remove  = cdns_uart_remove,
        .driver  = {
            .name = CDNS_UART_NAME,
            .of_match_table = cdns_uart_of_match,
            .pm = &cdns_uart_dev_pm_ops,
            },
    };

    而在arch/arm/boot/dts/zynq-7000.dtsi中, 定义了uart设备树相关信息

    uart0: serial@e0000000 {
        compatible = "xlnx,xuartps", "cdns,uart-r1p8";
        status = "disabled";
        clocks = <&clkc 23>, <&clkc 40>;
        clock-names = "uart_clk", "pclk";
        reg = <0xE0000000 0x1000>;
        interrupts = <0 27 4>;
    };
    
    uart1: serial@e0001000 {
        compatible = "xlnx,xuartps", "cdns,uart-r1p8";
        status = "disabled";
        clocks = <&clkc 24>, <&clkc 41>;
        clock-names = "uart_clk", "pclk";
        reg = <0xE0001000 0x1000>;
        interrupts = <0 50 4>;
    };

    关于设备树, 可参考<Linux设备树解析>
    从文章中我们知道内核会将设备树解析为platform_device, 匹配后则会调用cdns_uart_probe
    下面以uart0驱动probe分析一下该函数

    int cdns_uart_probe(struct platform_device *pdev)
    {
        int id, irq;
        struct uart_port *port;
        struct resource *res;
        struct cdns_uart *cdns_uart_data;
    
        /* 分配驱动私有数据结构体 */
        cdns_uart_data = devm_kzalloc(&pdev->dev, sizeof(*cdns_uart_data), GFP_KERNEL);
    
        /* 从dts获取时钟(clocks), pclk=40, uart_clk=23 */
        cdns_uart_data->pclk = devm_clk_get(&pdev->dev, "pclk");
        cdns_uart_data->uartclk = devm_clk_get(&pdev->dev, "uart_clk");
        /* 准备时钟源 */
        clk_prepare(cdns_uart_data->pclk);
        clk_prepare(cdns_uart_data->uartclk);
    
        /* 从dts获取编址(reg), start=0xE0000000,end=0xE0001000 */
        platform_get_resource(pdev, IORESOURCE_MEM, 0);
    
        /* 从dts获取中断(interrupts), 中断号为27 !!! */
        platform_get_irq(pdev, 0);
    
        /* 获取设备编号, 此处为0 */
        id = of_alias_get_id(pdev->dev.of_node, "serial");
    
        /* 初始化uart端口 */
        port = cdns_uart_get_port(id);
    
        /* 设置uart端口硬件相关参数 */
        port->mapbase = res->start;
        port->irq = irq;
        port->dev = &pdev->dev;
        port->uartclk = clk_get_rate(cdns_uart_data->uartclk);
        port->private_data = cdns_uart_data;
        cdns_uart_data->port = port;
        platform_set_drvdata(pdev, port);
    
        /* 添加uart端口 */
        uart_add_one_port(&cdns_uart_uart_driver, port);
    }
    
    static struct uart_port cdns_uart_port[CDNS_UART_NR_PORTS]; /* 2 */
    struct uart_port *cdns_uart_get_port(int id)
    {
        struct uart_port *port;
    
        /* 获取本地定义的uart_port结构体变量 */
        port = &cdns_uart_port[id];
    
        spin_lock_init(&port->lock);
        port->membase  = NULL;
        port->irq      = 0;
        port->type     = PORT_UNKNOWN;        /* 会在config_port中设置为PORT_XUARTPS */
        port->iotype   = UPIO_MEM32;          /* 串口接口寄存器的地址类型 */ 
        port->flags    = UPF_BOOT_AUTOCONF;   /* 该标志会使uart_add_one_port调用config_port */ 
        port->ops      = &cdns_uart_ops;      /* 即前面定义的uart_ops函数操作集 */ 
        port->fifosize = CDNS_UART_FIFO_SIZE; /* 64 */
        port->line     = id;                  /* 0 */ 
        port->dev      = NULL;
        return port;
    }

    4. 16550介绍

    16550寄存器信息如下

    RegisterAddressMap

    RBF定义如下

    RBF

    THR定义如下

    THR

    IER定义如下

    IER

    IIR定义如下

    IIR

    FCR定义如下

    FCR

    LCR定义如下

    LCR

    MCR定义如下

    MCR

    LSR定义如下

    LSR

    MSR定义如下

    MSR

    SCR定义如下

    SCR

    5. 硬件操作实现

    这里分析8250/16550对uart_ops的实现serial8250_pops
    主要代码位于drivers/tty/serial/8250/8250_port.c

    tx_empty: serial8250_tx_empty
    读取并判断LSR的第THRE、TEMT位是否为1

    set_mctrl: serial8250_set_mctrl
    将位设置(RTS、DTR、OUT1、OUT2、LOOP)写入MCR

    get_mctrl: serial8250_get_mctrl
    读取MSR, 即Modem Interface的当前状态

    stop_tx: serial8250_stop_tx
    禁用IER的THRI/ETBEI位

    start_tx: serial8250_start_tx
    启用IER的THRI/ETBEI位; 当LSR的THRE位为1, 通过操作THR将circ_buf的数据搬运至UART

    stop_rx: serial8250_stop_rx
    禁用IER的RLSI/ELSI和RDI/ERBFI位

    enable_ms: serial8250_enable_ms
    启用IER的MSI/EDSSI

    break_ctl: serial8250_break_ctl
    启动或者禁用LCR的SBC/SetBreak位

    startup: serial8250_startup
    1. 设置FCR清空FIFO缓冲区, 清空中断寄存器(LSR、RX、IIR、MSR), 初始化相关寄存器
    2. 调用uart_8250_ops::setup_irq(univ8250_setup_irq)
    3. 设置MCR寄存器
    4. 为TX/RX请求DMA通道

    univ8250_setup_irq
      serial_link_irq_chain
        request_irq
          serial8250_interrupt
            dw8250_handle_irq
            /* 即uart_port::handle_irq */
              serial8250_handle_irq
                handle_rx_dma(Running here???)
                  serial8250_rx_dma
                  /* uart_8250_port::uart_8250_dma::rx_dma */
                    __dma_rx_complete
                      tty_insert_flip_string
                      /* 将数据插入接收数据缓冲区 */
                      tty_flip_buffer_push
                      /* 将数据搬至线路规程层 */
                        tty_schedule_flip
                          flush_to_ldisc
                serial8250_rx_chars
                  serial8250_read_char
                    uart_insert_char
                      tty_insert_flip_char
                      /* 将数据插入接收数据缓冲区 */
                  tty_flip_buffer_push
                  /* 将数据搬至线路规程层 */
                    tty_schedule_flip
                      flush_to_ldisc

    shutdown: serial8250_shutdown
    初始化寄存器(...), 注销中断处理程序(???)

    set_termios: serial8250_set_termios
    设置相关寄存器(...)

    set_ldisc: serial8250_set_ldisc
    如果没有设置了Modem状态, 则禁用IER的MSI位

    pm: serial8250_pm
    休眠(???)

    type: serial8250_type
    获取硬件名称

    release_port: serial8250_release_port
    释放端口占用物理资源, 如Memory, I/O

    request_port: serial8250_request_port
    请求物理资源

    config_port: serial8250_config_port
    按照传入参数配置端口

    verify_port: serial8250_verify_port
    校验端口配置是否有效

    参考:
    <Xinu uart-ns16550>
    <AXI UART 16550 v2.0>
    <XPS 16550 UART v3.00>
    <dw_apb_uart Databook>
    <Serial UART information>

  • 相关阅读:
    化DataSet对象并压缩
    数据库连接
    触发器
    事务
    关于C语言的宏
    GCC中的一些参数
    《tkinter实用教程六》tkinter ttk.Label 控件
    《tkinter实用教程三》ttk.Button 控件
    5. 替换空格
    《tkinter实用教程二》ttk 子模块
  • 原文地址:https://www.cnblogs.com/hzl6255/p/9560138.html
Copyright © 2020-2023  润新知