linux驱动_uart

本文深入解析了Linux下UART驱动的设计与实现,详细介绍了设备树配置、数据结构定义、驱动注册流程以及数据收发机制等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux uart驱动基础知识下面链接这篇文章写得很完备,我没必要再介绍了,就写目前项目的代码,方便以后重温。
Linux的tty架构及UART驱动详解

本项目驱动文件包括:

/kernel/drivers/sstar/serial/ms_uart.c  # 主要实现文件
/kernel/drivers/sstar/serial/infinity2m/ms_uart.h # 头文件,定义串口号
/kernel/drivers/sstar/serial/infinity2m/uart_pads.c # 硬件相关

uart dts(设备树)

本项目 uart dts文件路径:
/kernel/arch/arm/boot/dts/infinity2m-doublenet.dtsi
和uart 相关的内容如下,注意注释的内容:

    aliases {
        console = &uart0;
        serial0 = &uart0; // 别名,驱动会用到serial, 用户层文件系统对应:/dev/ttySx
        serial1 = &uart1; // 对应 of_alias_get_id(pdev->dev.of_node, "serial"), /dev/ttyS1
        serial2 = &fuart;
        serial3 = &uart2;
    };

    ...

    soc { // 注意uart是在soc下的,是平台设备,所以要注册平台驱动
        ...
        uart0: uart0@1F221000 { // 节点头部格式:<name>[@<unit-address>]
            compatible = "sstar,uart"; // 与驱动程序的 ".of_match_table" 对应
            reg = <0x1F221000 0x100>;  // platform_get_resource(pdev, IORESOURCE_MEM, 0)
            interrupts = <GIC_SPI INT_IRQ_UART_0 IRQ_TYPE_LEVEL_HIGH>; // platform_get_resource(pdev, IORESOURCE_IRQ, 0)
            clocks = <&CLK_uart0>; // of_clk_get(pdev->dev.of_node, 0)
            status = "ok"; 
        };
        uart1: uart1@1F221200 {
            compatible = "sstar,uart";
            reg = <0x1F221200 0x100>;
            interrupts = <GIC_SPI INT_IRQ_UART_1 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&CLK_uart1>;
            status = "ok";
        };
        fuart: uart2@1F220400 {
            compatible = "sstar,uart";
            reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
            interrupts = <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_URDMA IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&CLK_fuart>;
            dma = <0>;
            sctp_enable = <0>;//rts cts enable is 1
            status = "ok";
        };
        uart2: uart2@1F221400 {
            compatible = "sstar,uart";
            reg = <0x1F221400 0x100>;
            interrupts = <GIC_SPI INT_IRQ_UART_2 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&CLK_uart2>;
            status = "disabled"; // 没有使能,所以没有这个设备
        };
        ...
    }

数据结构

参考文章里面提到几个重要数据结构:struct uart_driver, struct console, struct uart_state, struct uart_port, struct uart_ops.在项目代码里面uart_port被封装进struct ms_uart_port结构体了,如下:

struct ms_uart_port {
    struct uart_port    port;   // 这个是 uart_add_one_port 的参数
    struct ms_urdma    *urdma;
    struct device      *dev;
    struct clk         *clk;
    int use_dma;
#if UART_TX_TASK
    struct tasklet_struct xmit_tasklet;
#endif
    int rx_guard;
    u8 backupIER;
    u8 backupLCR;
    u8 backupMCR;
    u16 backupDivisor;
    u8 padmux;
    u8 pad_mode;
    u8 rs485_gpio_flag; // 这个标志位对RS485应用比较重要
    struct timer_list	timer;		/* "no irq" timer */
    u16 bugs;		/* port bugs */
    CamOsThread urdma_task;
};

static struct ms_uart_port  console_port;
static struct uart_driver ms_uart_driver; // 全局uart驱动

/* Serial Console Structure Definition */
static struct console ms_uart_console =   // 全局console
{
    .name   = MS_CONSOLE_DEV,
    .write  = ms_uart_console_write,
    .setup  = ms_uart_console_setup,
    .flags  = CON_PRINTBUFFER,
	.device = uart_console_device,
	.data   = &ms_uart_driver,  // uart 驱动
    .index  = -1,
#if CONSOLE_DMA
    .match = ms_uart_console_match,
#endif
};

static struct uart_driver ms_uart_driver = {
    .owner        = THIS_MODULE,
    .driver_name    = "ms_uart",
    .dev_name    = "ttyS",
    .nr        = 8,
    .cons        = &ms_uart_console, // console
};

以上面代码可以看到,uart驱动包括了console,console里面的data也指向了uart 驱动,uart_port 与driver的关系要看uart 驱动的注册过程。

驱动注册

static const struct of_device_id ms_uart_of_match_table[] = {
    { .compatible = "sstar,uart" },
    {}
};
MODULE_DEVICE_TABLE(of, ms_uart_of_match_table);

static struct platform_driver ms_uart_platform_driver = {
    .remove = ms_uart_remove,
    .probe = ms_uart_probe,
#ifdef CONFIG_PM
    .suspend = ms_uart_suspend,
    .resume = ms_uart_resume,
#endif
    .driver = {
        .name = "ms_uart",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(ms_uart_of_match_table),
    },
};

static s32 __init ms_uart_module_init(void)
{
    int ret;

    ret = uart_register_driver(&ms_uart_driver);
    if (ret != 0)
        return ret;
    ret = platform_driver_register(&ms_uart_platform_driver);
    if (ret != 0)
    {
        // UART_ERR("[ms_uart]platform_driver_register failed!!\n");
        uart_unregister_driver(&ms_uart_driver);
    }
    return ret;
}


static void __exit ms_uart_module_exit(void)
{
    platform_driver_unregister(&ms_uart_platform_driver);
    uart_unregister_driver(&ms_uart_driver);
}

module_init(ms_uart_module_init);
module_exit(ms_uart_module_exit);

从上面代码来看,module_init 时注册了 ms_uart_driverms_uart_platform_driver 驱动,ms_uart_platform_driver 指定了设备匹配的table(如下),当内核启动到解析设备树文件时,会调用驱动的probe函数(ms_uart_probe),在probe函数里面,设备和驱动会绑定和初始化。

static const struct of_device_id ms_uart_of_match_table[] = {
    { .compatible = "sstar,uart" },
    {}
};

probe 过程

下面是经过简化处理的探测函数,读一下注释基本了解执行过程。

/* UART Operations */
static struct uart_ops ms_uart_ops =
{
    .tx_empty     = ms_uart_tx_empty,      // 串口的Tx FIFO缓存是否为空
    .set_mctrl    = ms_uart_set_mctrl,     // 设置串口modem控制
    .get_mctrl    = ms_uart_get_mctrl,     // 获取串口modem控制
    .stop_tx      = ms_uart_stop_tx,       // 禁止串口发送数据
    .start_tx     = ms_uart_start_tx,      // 使能串口发送数据
    .stop_rx      = ms_uart_stop_rx,       // 禁止串口接收数据
    .enable_ms    = ms_uart_enable_ms,     // 使能modem的状态信号
    .break_ctl    = ms_uart_break_ctl,     // 设置break信号
    .startup      = ms_uart_startup,       // 启动串口,应用程序打开串口设备文件时,该函数会被调用
    .shutdown     = ms_uart_shutdown,      // 关闭串口,应用程序关闭串口设备文件时,该函数会被调用
    .set_termios  = ms_uart_set_termios,   // 设置串口参数
    .type         = ms_uart_type,          // 返回一描述串口类型的字符串
    .release_port = ms_uart_release_port,  // 释放串口已申请的IO端口/IO内存资源,必要时还需iounmap
    .request_port = ms_uart_request_port,  // 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口
    .config_port  = ms_uart_config_port,   // 执行串口所需的自动配置
    .verify_port  = ms_uart_verify_port,   // 核实新串口的信息
    //.ioctl // IO控制
};

static s32 ms_uart_probe(struct platform_device *pdev)
{
    struct ms_uart_port *mp;
    struct resource *res;

    // new 了一个 ms_uart_port
    mp = devm_kzalloc(&pdev->dev, sizeof(*mp), GFP_KERNEL);

    // 初始化 uart_port 的spin_lock
    spin_lock_init(&mp->port.lock);

    // 读取 dts 里面的 "serialx = &uartx", mp->port.line = x
    mp->port.line = of_alias_get_id(pdev->dev.of_node, "serial");
    pdev->id=mp->port.line;

    // 读取dts的 "clocks = <&CLK_uart0>;"
    mp->clk = of_clk_get(pdev->dev.of_node, 0);

    //enable clk in probe, because if UART no clk, it can not be accessed.
    clk_prepare_enable(mp->clk);
    mp->port.uartclk = clk_get_rate(mp->clk);

    // 读取dts的 "reg = <0x1F221000 0x100>;"
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    mp->port.membase = (void *)res->start;

    // 读取dts的 "interrupts = <GIC_SPI INT_IRQ_UART_0 IRQ_TYPE_LEVEL_HIGH>;"
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    mp->port.irq = res->start;

    // 读取dts的"dma = <0>;"
    of_property_read_u32(pdev->dev.of_node, "dma", &mp->use_dma);
    if (mp->use_dma)
    { // dts里面值为0,所有dma相关的代码都忽略了
    }

    // 继续初始化 struct uart_port 结构体
    mp->port.type = PORT_8250;
    mp->port.dev = &pdev->dev;
    mp->port.ops=&ms_uart_ops;  // 这里指定了上面定义的所有操作函数
    mp->port.regshift = 0;
    mp->port.fifosize = 16;     // 16 字节,可能跟硬件相关,没datasheet不清楚
    mp->port.timeout  =HZ;
    mp->port.iotype=UPIO_MEM;
    mp->port.rs485_config = ms_uart_rs485_config; // rs485 配置函数,用户层可能会调用到

    // 绑定硬件,因为dts没指定"pad",所以of_property_read_u32返回非0值
    if (of_property_read_u32(pdev->dev.of_node, "pad", &tx_pad))  //read property failed
    {
        //set default pad
        if(mp->port.line==0)
            mp->padmux=MUX_PM_UART;
        else if(mp->port.line==1)
            mp->padmux=MUX_UART1;
        else if(mp->port.line==2)
            mp->padmux=MUX_FUART;
        else if(mp->port.line==3)
            mp->padmux=MUX_UART2;
        else
            ret = -EINVAL;
    }
    else  //read property successfully
    {
        if (ms_uart_get_padmux(tx_pad, &(mp->padmux), &(mp->pad_mode)) != 0)
            ret = -EINVAL;
    }

    // 把new出来的 struct ms_uart_port 保存到串口设备去
    platform_set_drvdata(pdev, mp);

    // 关键在这里,把struct uart_port 加到 uart_driver去了
    ret = uart_add_one_port(&ms_uart_driver, &mp->port);

    return 0;
}

数据发送接收流程

tty设备发送、接收数据流程图如下(别人的图):
tty设备发送、接收数据流程图

发送
从上面 ms_uart_ops 可知,用户层发送数据会调用 “.start_tx = ms_uart_start_tx”。基本流程是先把RS485使能(如果是RS485口),然后使能uart发送缓存为空中断,在中断处理函数 ms_uart_interrupt 里面调用 ms_putchar 函数,然后从“ **struct circ_buf xmit = struct uart_port p->state->xmit ”环回缓存里面取数据并塞到uart 数据发送缓存寄存器,发送完就调用 ms_uart_stop_tx 停止发送。发送数据流程如下图(别人的图):
发送数据流程

接收
在中断处理函数ms_uart_interrupt 接收分支里面调用 ms_getchar 函数,从寄存器读取数据,调用 uart_insert_char 函数往上层送。接收数据流程图如下(别人的图):
接收数据流程图
其它的开发内容基本是 ms_uart_ops 回调函数的实现,不一一解读了。

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值