Linux驱动分析之Uart驱动

简介: 之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。

前言

之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。


Uart驱动分析

内核:4.20

芯片:NXP IMX6

下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。

(1) 装载和卸载函数

//dts匹配表staticconststructof_device_idimx_uart_dt_ids[] = {
  { .compatible="fsl,imx6q-uart", .data=&imx_uart_devdata[IMX6Q_UART], },
  { .compatible="fsl,imx53-uart", .data=&imx_uart_devdata[IMX53_UART], },
  { .compatible="fsl,imx1-uart", .data=&imx_uart_devdata[IMX1_UART], },
  { .compatible="fsl,imx21-uart", .data=&imx_uart_devdata[IMX21_UART], },
  { /* sentinel */ }
};
staticstructuart_driverimx_uart_uart_driver= {
  .owner=THIS_MODULE,
  .driver_name=DRIVER_NAME,
  .dev_name=DEV_NAME, //设备节点名  .major=SERIAL_IMX_MAJOR, //主设备号  .minor=MINOR_START, //次设备号  .nr=ARRAY_SIZE(imx_uart_ports), //串口数  .cons=IMX_CONSOLE,
};
staticstructplatform_driverimx_uart_platform_driver= {
  .probe=imx_uart_probe, //driver和device匹配后回调  .remove=imx_uart_remove,
  .id_table=imx_uart_devtype,
  .driver= {
    .name="imx-uart",
    .of_match_table=imx_uart_dt_ids,
    .pm=&imx_uart_pm_ops,
  },
};
//加载函数staticint__initimx_uart_init(void)
{
//注册uart_driverintret=uart_register_driver(&imx_uart_uart_driver);
//注册platform_driverret=platform_driver_register(&imx_uart_platform_driver);
returnret;
}
//卸载函数staticvoid__exitimx_uart_exit(void)
{
//注销uart_driver和platform_driverplatform_driver_unregister(&imx_uart_platform_driver);
uart_unregister_driver(&imx_uart_uart_driver);
}
module_init(imx_uart_init);
module_exit(imx_uart_exit);

image.gif

上面真正回调probe的是匹配platform_driver, 而不是uart_driver。所以我们会看到调用了uart_register_driverplatform_driver_register

uart_register_driver是为了向uart核心层注册。


(2) probe()函数

staticintimx_uart_probe(structplatform_device*pdev)
{
structimx_port*sport; //nxp对uart_port进行了封装,添加自己的成员void__iomem*base;
intret=0;
u32ucr1;
structresource*res;
inttxirq, rxirq, rtsirq;
//分配内存,并清0sport=devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return-ENOMEM;
//解析设备树,保存到imx_portret=imx_uart_probe_dt(sport, pdev);
if (ret>0)
imx_uart_probe_pdata(sport, pdev);
elseif (ret<0)
returnret;
//省略....//获取IO资源,并映射res=platform_get_resource(pdev, IORESOURCE_MEM, 0);
base=devm_ioremap_resource(&pdev->dev, res);
//省略....//获取RX,TX,RTS中断号rxirq=platform_get_irq(pdev, 0);
txirq=platform_get_irq(pdev, 1);
rtsirq=platform_get_irq(pdev, 2);
//填充imx_port结构体sport->port.dev=&pdev->dev;
sport->port.mapbase=res->start; //映射地址sport->port.membase=base; //物理地址sport->port.type=PORT_IMX,
sport->port.iotype=UPIO_MEM;
sport->port.irq=rxirq; //接收中断sport->port.fifosize=32;
sport->port.ops=&imx_uart_pops; //串口操作函数sport->port.rs485_config=imx_uart_rs485_config; //485配置sport->port.flags=UPF_BOOT_AUTOCONF;
timer_setup(&sport->timer, imx_uart_timeout, 0); //设置定时器sport->gpios=mctrl_gpio_init(&sport->port, 0);
if (IS_ERR(sport->gpios))
returnPTR_ERR(sport->gpios);
//获取IPG时钟sport->clk_ipg=devm_clk_get(&pdev->dev, "ipg");
//省略....//获取PER时钟sport->clk_per=devm_clk_get(&pdev->dev, "per");
//省略....sport->port.uartclk=clk_get_rate(sport->clk_per);
//使能IPG时钟ret=clk_prepare_enable(sport->clk_ipg);
//省略....//读取寄存器值sport->ucr1=readl(sport->port.membase+UCR1);
sport->ucr2=readl(sport->port.membase+UCR2);
sport->ucr3=readl(sport->port.membase+UCR3);
sport->ucr4=readl(sport->port.membase+UCR4);
sport->ufcr=readl(sport->port.membase+UFCR);
uart_get_rs485_mode(&pdev->dev, &sport->port.rs485);
//省略....imx_uart_rs485_config(&sport->port, &sport->port.rs485);
//下面都是对寄存器的配置,可以查看datasheetucr1=imx_uart_readl(sport, UCR1);
ucr1&=~(UCR1_ADEN|UCR1_TRDYEN|UCR1_IDEN|UCR1_RRDYEN|UCR1_TXMPTYEN|UCR1_RTSDEN);
imx_uart_writel(sport, ucr1, UCR1);
if (!imx_uart_is_imx1(sport) &&sport->dte_mode) {
u32ufcr=imx_uart_readl(sport, UFCR);
if (!(ufcr&UFCR_DCEDTE))
imx_uart_writel(sport, ufcr|UFCR_DCEDTE, UFCR);
imx_uart_writel(sport,
IMX21_UCR3_RXDMUXSEL|UCR3_ADNIMP|UCR3_DSR,
UCR3);
  } else {
u32ucr3=UCR3_DSR;
u32ufcr=imx_uart_readl(sport, UFCR);
if (ufcr&UFCR_DCEDTE)
imx_uart_writel(sport, ufcr&~UFCR_DCEDTE, UFCR);
if (!imx_uart_is_imx1(sport))
ucr3|=IMX21_UCR3_RXDMUXSEL|UCR3_ADNIMP;
imx_uart_writel(sport, ucr3, UCR3);
  }
clk_disable_unprepare(sport->clk_ipg);
//申请中断if (txirq>0) { //开启tx中断ret=devm_request_irq(&pdev->dev, rxirq, imx_uart_rxint, 0,
dev_name(&pdev->dev), sport);
//省略.....ret=devm_request_irq(&pdev->dev, txirq, imx_uart_txint, 0,
dev_name(&pdev->dev), sport);
//省略.....ret=devm_request_irq(&pdev->dev, rtsirq, imx_uart_rtsint, 0,
dev_name(&pdev->dev), sport);
//省略.....  } else { //不开tx中断ret=devm_request_irq(&pdev->dev, rxirq, imx_uart_int, 0,
dev_name(&pdev->dev), sport);
//省略.....  }
//保存imx_portimx_uart_ports[sport->port.line] =sport;
platform_set_drvdata(pdev, sport);
//关联uart_driver和uart_portreturnuart_add_one_port(&imx_uart_uart_driver, &sport->port);
}

image.gif

上面其实主要是寄存器配置,中断申请,最后添加port。对裸机程序熟悉的,应该能很轻松的理解,因为我们不是为了针对某款芯片,所以寄存器配置可以忽略,主要还是为了理解Uart的驱动框架。


(3) 串口操作函数(uart_ops)

staticconststructuart_opsimx_uart_pops= {
  .tx_empty=imx_uart_tx_empty,
  .set_mctrl=imx_uart_set_mctrl,
  .get_mctrl=imx_uart_get_mctrl,
  .stop_tx=imx_uart_stop_tx,
  .start_tx=imx_uart_start_tx,
  .stop_rx=imx_uart_stop_rx,
  .enable_ms=imx_uart_enable_ms,
  .break_ctl=imx_uart_break_ctl,
  .startup=imx_uart_startup,
  .shutdown=imx_uart_shutdown,
  .flush_buffer=imx_uart_flush_buffer,
  .set_termios=imx_uart_set_termios, //对串口进行配置  .type=imx_uart_type,
  .config_port=imx_uart_config_port,
  .verify_port=imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)  .poll_init=imx_uart_poll_init,
  .poll_get_char=imx_uart_poll_get_char,
  .poll_put_char=imx_uart_poll_put_char,
#endif};

image.gif

上面的操作函数都是对具体芯片(IMX)的寄存器进行配置。需要根据具体的芯片手册来进行实现。我们简单看几个函数。

    • imx_uart_set_termios --- 配置串口
    staticvoidimx_uart_set_termios(structuart_port*port, structktermios*termios,
    structktermios*old)
    {
    structimx_port*sport= (structimx_port*)port;
    unsignedlongflags;
    u32ucr2, old_ucr1, old_ucr2, ufcr;
    unsignedintbaud, quot;
    unsignedintold_csize=old?old->c_cflag&CSIZE : CS8;
    unsignedlongdiv;
    unsignedlongnum, denom;
    uint64_ttdiv64;
    //设置数据位while ((termios->c_cflag&CSIZE) !=CS7&&         (termios->c_cflag&CSIZE) !=CS8) {
    termios->c_cflag&=~CSIZE;
    termios->c_cflag|=old_csize;
    old_csize=CS8;
      }
    if ((termios->c_cflag&CSIZE) ==CS8)
    ucr2=UCR2_WS|UCR2_SRST|UCR2_IRTS;
    elseucr2=UCR2_SRST|UCR2_IRTS;
    //省略.....//设置停止位if (termios->c_cflag&CSTOPB)
    ucr2|=UCR2_STPB;
    if (termios->c_cflag&PARENB) {
    ucr2|=UCR2_PREN;
    if (termios->c_cflag&PARODD)
    ucr2|=UCR2_PROE;
      }
    del_timer_sync(&sport->timer);
    //设置波特率baud=uart_get_baud_rate(port, termios, old, 50, port->uartclk/16);
    quot=uart_get_divisor(port, baud);
    spin_lock_irqsave(&sport->port.lock, flags);
    //设置奇偶校验sport->port.read_status_mask=0;
    if (termios->c_iflag&INPCK)
    sport->port.read_status_mask|= (URXD_FRMERR|URXD_PRERR);
    if (termios->c_iflag& (BRKINT|PARMRK))
    sport->port.read_status_mask|=URXD_BRK;
    //省略.....//关闭中断old_ucr1=imx_uart_readl(sport, UCR1);
    imx_uart_writel(sport,
    old_ucr1&~(UCR1_TXMPTYEN|UCR1_RRDYEN|UCR1_RTSDEN),
    UCR1);
    old_ucr2=imx_uart_readl(sport, UCR2);
    imx_uart_writel(sport, old_ucr2&~UCR2_ATEN, UCR2);
    while (!(imx_uart_readl(sport, USR2) &USR2_TXDC))
    barrier();
    /* then, disable everything */imx_uart_writel(sport, old_ucr2&~(UCR2_TXEN|UCR2_RXEN|UCR2_ATEN), UCR2);
    old_ucr2&= (UCR2_TXEN|UCR2_RXEN|UCR2_ATEN);
    //计算波特率值div=sport->port.uartclk/ (baud*16);
    if (baud==38400&&quot!=div)
    baud=sport->port.uartclk/ (quot*16);
    div=sport->port.uartclk/ (baud*16);
    if (div>7)
    div=7;
    if (!div)
    div=1;
    rational_best_approximation(16*div*baud, sport->port.uartclk,
    1<<16, 1<<16, &num, &denom);
    tdiv64=sport->port.uartclk;
    tdiv64*=num;
    do_div(tdiv64, denom*16*div);
    tty_termios_encode_baud_rate(termios,
            (speed_t)tdiv64, (speed_t)tdiv64);
    num-=1;
    denom-=1;
    //对上面的设置写入到寄存器中ufcr=imx_uart_readl(sport, UFCR);
    ufcr= (ufcr& (~UFCR_RFDIV)) |UFCR_RFDIV_REG(div);
    imx_uart_writel(sport, ufcr, UFCR);
    imx_uart_writel(sport, num, UBIR);
    imx_uart_writel(sport, denom, UBMR);
    if (!imx_uart_is_imx1(sport))
    imx_uart_writel(sport, sport->port.uartclk/div/1000,
    IMX21_ONEMS);
    imx_uart_writel(sport, old_ucr1, UCR1);
    /* set the parity, stop bits and data size */imx_uart_writel(sport, ucr2|old_ucr2, UCR2);
    if (UART_ENABLE_MS(&sport->port, termios->c_cflag))
    imx_uart_enable_ms(&sport->port);
    spin_unlock_irqrestore(&sport->port.lock, flags);
    }

    image.gif

    应用层是通过struct termios来设置串口,传到底层就是struct ktermios。通过解析设置参数,然后配置对应的寄存器。

      • imx_uart_start_tx --- 串口发送
      staticvoidimx_uart_start_tx(structuart_port*port)
      {
      structimx_port*sport= (structimx_port*)port;
      u32ucr1;
      //判断是否有高优先级数据和环形buffer是否有数据if (!sport->port.x_char&&uart_circ_empty(&port->state->xmit))
      return;
      //省略......//没有开启DMA,则使用Tx中断if (!sport->dma_is_enabled) {
      //触发Tx中断ucr1=imx_uart_readl(sport, UCR1);
      imx_uart_writel(sport, ucr1|UCR1_TXMPTYEN, UCR1);
        }
      if (sport->dma_is_enabled) {
      if (sport->port.x_char) {
      //有高优先级的数据要发送,则使用Tx中断,关闭DMAucr1=imx_uart_readl(sport, UCR1);
      ucr1&=~UCR1_TXDMAEN;
      ucr1|=UCR1_TXMPTYEN;
      imx_uart_writel(sport, ucr1, UCR1);
      return;
          }
      //环形buffer有数据,并且串口没有停止,则使用DMA进行发送if (!uart_circ_empty(&port->state->xmit) &&!uart_tx_stopped(port))
      imx_uart_dma_tx(sport); //DMA发送return;
        }
      }

      image.gif

      使用Tx中断进行发送或DMA进行发送。

        • imx_uart_rxint --- Rx中断处理函数
        staticirqreturn_timx_uart_rxint(intirq, void*dev_id)
        {
        structimx_port*sport=dev_id;
        unsignedintrx, flg, ignored=0;
        structtty_port*port=&sport->port.state->port;
        spin_lock(&sport->port.lock);
        while (imx_uart_readl(sport, USR2) &USR2_RDR) {
        u32usr2;
        flg=TTY_NORMAL;
        sport->port.icount.rx++;
        rx=imx_uart_readl(sport, URXD0);
        usr2=imx_uart_readl(sport, USR2);
        if (usr2&USR2_BRCD) {
        imx_uart_writel(sport, USR2_BRCD, USR2);
        if (uart_handle_break(&sport->port))
        continue;
            }
        //省略......if (sport->port.ignore_status_mask&URXD_DUMMY_READ)
        gotoout;
        //添加到tty核心层if (tty_insert_flip_char(port, rx, flg) ==0)
        sport->port.icount.buf_overrun++;
          }
        out:
        spin_unlock(&sport->port.lock);
        tty_flip_buffer_push(port); //push给tty核心层returnIRQ_HANDLED;
        }

        image.gif

        接收中断就是将收到的数据发送给tty核心层,让它去进行缓存。


        总结

           上面芯片相关的可以跳着看,我们主要是去看Uart驱动的套路。学习驱动就是在学习套路,掌握了套路,它们就会变成模板了。可以和之前的《Linux驱动分析之Uart驱动架构》一起看。

        相关文章
        |
        2月前
        |
        安全 Linux iOS开发
        Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        371 53
        Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        |
        2月前
        |
        Linux API iOS开发
        Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        230 14
        Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
        |
        3月前
        |
        数据管理 Linux iOS开发
        Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
        Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
        142 0
        |
        Unix Linux iOS开发
        Splunk Enterprise 10.0.0 (macOS, Linux, Windows) - 搜索、分析和可视化,数据全面洞察平台
        Splunk Enterprise 10.0.0 (macOS, Linux, Windows) - 搜索、分析和可视化,数据全面洞察平台
        138 0
        |
        5月前
        |
        监控 Linux 开发者
        理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
        综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
        301 0
        |
        9月前
        |
        监控 Linux
        Linux基础:文件和目录类命令分析。
        总的来说,这些基础命令,像是Linux中藏匿的小矮人,每一次我们使用他们,他们就把我们的指令准确的传递给Linux,让我们的指令变为现实。所以,现在就开始你的Linux之旅,挥动你的命令之剑,探索这个充满神秘而又奇妙的世界吧!
        171 19
        |
        10月前
        |
        缓存 网络协议 Linux
        PCIe 以太网芯片 RTL8125B 的 spec 和 Linux driver 分析备忘
        本文详细介绍了 Realtek RTL8125B PCIe 以太网芯片的规格以及在 Linux 中的驱动安装和配置方法。通过深入分析驱动源码,可以更好地理解其工作原理和优化方法。在实际应用中,合理配置和优化驱动程序可以显著提升网络性能和稳定性。希望本文能帮助您更好地使用和管理 RTL8125B,以满足各种网络应用需求。
        1096 33
        |
        10月前
        |
        数据管理 Linux iOS开发
        Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
        Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
        271 0
        Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
        |
        12月前
        |
        存储 运维 监控
        Linux--深入理与解linux文件系统与日志文件分析
        深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
        292 7
        |
        3月前
        |
        Linux 应用服务中间件 Shell
        二、Linux文本处理与文件操作核心命令
        熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
        468 1
        二、Linux文本处理与文件操作核心命令