美文网首页
嵌入式Linux开发-串口驱动

嵌入式Linux开发-串口驱动

作者: 大丽水手吃卤蛋 | 来源:发表于2020-02-28 20:55 被阅读0次

参考资料:

  • 《LINUX设备驱动程序第三版》
  • linux-5.4.9

0.前言

在今天终于离职了,办完了所有的手续,感觉一身轻松,在上一家公司,作为一名程序员,已经在偏离写代码开发的歪门邪道上越走越远了,现在疫情比较严重,非常时间,大家还是要少出门,注意安全,所以就趁这段时间总结一下之前工作中的问题和经验吧。

1.tty与串口驱动

tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或者虚拟终端。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。
有三种类型的tty'驱动程序:控制台、串口和pty。控制台和pty驱动程序已经被编写好饿了,而且可能也不必为这两类tty驱动程序编写其他的驱动程序。这使得任何使用tty核心与用户和系统交互的新驱动程序都可被看成是串口驱动程序。

2.关于tty

Linux tty驱动程序的核心紧挨着标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端类型设备使用。内核负责控制通过tty设备的数据流,并且格式化这些数据。这使得tty驱动程序把重点放在处理流向或者流出硬件的数据上,而不必重点考虑使用常规方法与用户空间的交互。为了控制数据流,有许多不同的线路规程(line discipline)可虚拟地“插入”任何的tty设备上,这由不同的tty线路规程驱动程序实现。

在这里插入图片描述

tty核心从用户那里得到将被发往tty设备的数据,然后把数据发送给tty线路规程驱动程序,该驱动程序负责把数据传递给tty驱动程序。tty驱动程序对数据进行格式化,然后才能发送给硬件。从tty硬件那里接收到的数据将回溯到tty驱动程序,然后流入tty线路规程驱动程序,接着是tty核心,最后用户从tty核心哪里得到数据。有时tty驱动程序直接与tty核心通信,tty核心将数据直接发送给tty驱动程序,但通常是tty线路规程驱动程序修改在二者之间流动的数据。

3.添加Device

在arch/arm/boot/dts下的.dtsi文件下添加串口相关的device信息

在这里插入图片描述

在imx.c下会通过MODULE_DEVICE_TABLE()去匹配一个device

在这里插入图片描述

4.添加Driver

串口设备驱动核心结构体为uart_driver,在文件linux/drivers/serial/imx.c

static struct uart_driver imx_uart_uart_driver = {
    .owner          = THIS_MODULE,
    .driver_name    = DRIVER_NAME,                 //driver name
    .dev_name       = DEV_NAME,                    //device name
    .major          = SERIAL_IMX_MAJOR,            //主设备号
    .minor          = MINOR_START,                 //次设备号
    .nr             = ARRAY_SIZE(imx_uart_ports),
    .cons           = IMX_CONSOLE,
};

串口驱动注册

static int __init imx_uart_init(void)
{
    int ret = uart_register_driver(&imx_uart_uart_driver);         //用uart核心层注册一个驱动程序

    if (ret)
        return ret;

    ret = platform_driver_register(&imx_uart_platform_driver);    //调用platform_driver_register向内核注册
    if (ret != 0)
        uart_unregister_driver(&imx_uart_uart_driver);

    return ret;
}

uart_register_driver()函数,在drivers/tty/serial/serial_core.c中。
任何tty驱动程序的主要数据结构是结构tty_driver,被用来向tty核心注册和注销驱动程序

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
    int i, retval = -ENOMEM;

    ...
    //每个端口对应一个state
    drv->state = kzalloc(sizeof(struct uart_state)* drv->nr, GFP_KERNEL);
    ...
    //分配该串口驱动对应的tty_drvier,tty_driver结构将根据tty驱动程序的需求,用正确的信息初始化
    normal = alloc_tty_driver(drv->nr);
    ...
    //让drv->tty_driver字段指向这个tty_driver
    drv->tty_driver = normal;
    ...
    //使用tty_set_operation函数拷贝在驱动程序定义的一系列操作函数
    tty_set_operation(normal, &uart_ops);
    ...
    //注册tty驱动程序
    retval = tty_register_driver(normal);

}

为了向tty核心注册这个驱动程序,必须将tty_driver结构传递给tty_register_driver函数。
在注册自身后,驱动程序使用tty_register_device函数注册它所控制的设备,该函数有三个参数:

  • 属于该设备的tty_driver结构指针
  • 设备的次设备号
  • 指向该tty设备所绑定的device结构指针,如果tty设备没有绑定任何device结构,该参数为NULL
 int tty_register_driver(struct tty_driver *driver)
 {
    ...
    for (i = 0; i < driver->num; i++) {
            d = tty_register_device(driver, i, NULL);
            ...
    }
    ...
 }

5.platform机制

在driver和device匹配成功之后,会调用probe函数

在这里插入图片描述
static int imx_uart_probe(struct platform_device *pdev)
{
    struct imx_port *sport;
    void __iomem *base;
    int ret = 0;
    u32 ucr1;
    struct resource *res;
    int txirq, rxirq, rtsirq;

    ...
    
    sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
    
    ...
    
    //添加一个端口结构
    return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    struct uart_state *state;
    struct tty_port *port;
    int ret = 0;
    struct device *tty_dev;
    int num_groups;

    ...
    
    //注册tty设备
    tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
            uport->line, uport->dev, port, uport->tty_groups);
    if (!IS_ERR(tty_dev)) {
        device_set_wakeup_capable(tty_dev, 1);
    } else {
        dev_err(uport->dev, "Cannot register tty device on line %d\n",
               uport->line);
    }

    ...

    return ret;
}

6.串口数据流

在这里插入图片描述
  • open函数
static int tty_open(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty;
    int noctty, retval;
    dev_t device = inode->i_rdev;              //表示设备文件的结点,这个域实际上包含了设备号
    unsigned saved_flags = filp->f_flags;

    ...

    if (tty->ops->open)
        retval = tty->ops->open(tty, filp);    //调用uart_open函数
    else
        retval = -ENODEV;
    filp->f_flags = saved_flags;

    ...
    
    return 0;
}
static int uart_open(struct tty_struct *tty, struct file *filp)
{
    struct uart_state *state = tty->driver_data;
    int retval;

    retval = tty_port_open(&state->port, tty, filp);
    if (retval > 0)
        retval = 0;

    return retval;
}

当用户使用open打开由驱动程序分配的设备节点时,tty核心将调用open函数。tty核心使用分配给该设备tty_struct结构的指针,以及一个文件描述符作为参数调用该函数。

  • release函数
int tty_release(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty = file_tty(filp);
    struct tty_struct *o_tty = NULL;
    int do_sleep, final;
    int idx;
    long    timeout = 0;
    int once = 1;

    ...
    
    tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);

    if (tty->ops->close)
        tty->ops->close(tty, filp);   //调用uart_close函数

    /* If tty is pty master, lock the slave pty (stable lock order) */
    tty_lock_slave(o_tty);
    
    ...

    tty_release_struct(tty, idx);
    return 0;
}
static void uart_close(struct tty_struct *tty, struct file *filp)
{
    struct uart_state *state = tty->driver_data;

    if (!state) {
        struct uart_driver *drv = tty->driver->driver_state;
        struct tty_port *port;

        state = drv->state + tty->index;
        port = &state->port;
        spin_lock_irq(&port->lock);
        --port->count;
        spin_unlock_irq(&port->lock);
        return;
    }

    pr_debug("uart_close(%d) called\n", tty->index);

    tty_port_close(tty->port, tty, filp);
}

当用户使用先前由open函数创建的文件句柄作为参数调用close时,tty核心调用close函数指针,此时该设备将被关闭。

  • write函数
static ssize_t tty_write(struct file *file, const char __user *buf,
                        size_t count, loff_t *ppos)
{
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;
    ssize_t ret;

    if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
        return -EIO;
    if (!tty || !tty->ops->write || tty_io_error(tty))
            return -EIO;
    /* Short term debug to catch buggy drivers */
    if (tty->ops->write_room == NULL)
        tty_err(tty, "missing write_room method\n");
    ld = tty_ldisc_ref_wait(tty); 
    if (!ld)
        return hung_up_tty_write(file, buf, count, ppos);
    /*调用线路规程操作函数集中的n_tty_write()函数*/
    if (!ld->ops->write)
        ret = -EIO;
    else
        ret = do_tty_write(ld->ops->write, tty, file, buf, count);
    tty_ldisc_deref(ld);
    return ret;
}
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
               const unsigned char *buf, size_t nr)
{
    const unsigned char *b = buf;
    DEFINE_WAIT_FUNC(wait, woken_wake_function);
    int c;
    ssize_t retval = 0;

    /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
    if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
        retval = tty_check_change(tty);
        if (retval)
            return retval;
    }

    down_read(&tty->termios_rwsem);

    /* Write out any echoed characters that are still pending */
    process_echoes(tty);

    add_wait_queue(&tty->write_wait, &wait);
    while (1) {
    
            ...
            /*调用uart_flush_chars向硬件发送数据*/
            if (tty->ops->flush_chars)
                tty->ops->flush_chars(tty);
        } else {
            struct n_tty_data *ldata = tty->disc_data;

            while (nr > 0) {
                mutex_lock(&ldata->output_lock);
                c = tty->ops->write(tty, b, nr);   //或者调用uart_write函数
                mutex_unlock(&ldata->output_lock);
                if (c < 0) {
                    retval = c;
                    goto break_out;
                }
                if (!c)
                    break;
                b += c;
                nr -= c;
            }
        }
        
        ...
        
    }
break_out:
    remove_wait_queue(&tty->write_wait, &wait);
    if (nr && tty->fasync)
        set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
    up_read(&tty->termios_rwsem);
    return (b - buf) ? b - buf : retval;
}
static void uart_flush_chars(struct tty_struct *tty)
{
    uart_start(tty);
}
static int uart_write(struct tty_struct *tty,
                    const unsigned char *buf, int count)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port;
    struct circ_buf *circ;
    unsigned long flags;
    int c, ret = 0;

    /*
     * This means you called this function _after_ the port was
     * closed.  No cookie for you.
     */
    if (!state) {
        WARN_ON(1);
        return -EL3HLT;
    }

    port = uart_port_lock(state, flags);
    circ = &state->xmit;
    if (!circ->buf) {
        uart_port_unlock(port, flags);
        return 0;
    }

    while (port) {
        c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
        if (count < c)
            c = count;
        if (c <= 0)
            break;
        memcpy(circ->buf + circ->head, buf, c);
        circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
        buf += c;
        count -= c;
        ret += c;
    }

    __uart_start(tty);
    uart_port_unlock(port, flags);
    return ret;
}
static void uart_start(struct tty_struct *tty)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port;
    unsigned long flags;

    port = uart_port_lock(state, flags);
    __uart_start(tty);
    uart_port_unlock(port, flags);
}
static void __uart_start(struct tty_struct *tty)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port = state->uart_port;

    if (port && !uart_tx_stopped(port))
        port->ops->start_tx(port);   //调用imx.c中的imx_uart_start_tx函数发送数据
}

当数据要被发送给硬件时,用户调用write函数,首先tty核心接收到了该调用,然后内核将数据发送给tty驱动程序的write函数。tty核心同时也告诉tty驱动程序发功数据的大小。

  • read函数
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
            loff_t *ppos)
{
    int i;
    struct inode *inode = file_inode(file);
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;

    if (tty_paranoia_check(tty, inode, "tty_read"))
        return -EIO;
    if (!tty || tty_io_error(tty))
        return -EIO;

    /* We want to wait for the line discipline to sort out in this
       situation */
    ld = tty_ldisc_ref_wait(tty);
    if (!ld)
        return hung_up_tty_read(file, buf, count, ppos);
    /*调用线路规程操作函数集中的n_tty_read()函数*/
    if (ld->ops->read)
        i = ld->ops->read(tty, file, buf, count);
    else
        i = -EIO;
    tty_ldisc_deref(ld);

    if (i > 0)
        tty_update_time(&inode->i_atime);

    return i;
}
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
             unsigned char __user *buf, size_t nr)
{
    struct n_tty_data *ldata = tty->disc_data;
    unsigned char __user *b = buf;
    DEFINE_WAIT_FUNC(wait, woken_wake_function);
    int c;
    int minimum, time;
    ssize_t retval = 0;
    long timeout;
    int packet;
    size_t tail;

    ...
    
    while (nr) {
        
        ...
        /*如果配置了icanon,那么就按canonical模式拷贝读取的数据*/
        if (ldata->icanon && !L_EXTPROC(tty)) {
            retval = canon_copy_from_read_buf(tty, &b, &nr);
            if (retval)
                break;
        } else {
            int uncopied;

            /* Deal with packet mode. */
            if (packet && b == buf) {
                if (put_user(TIOCPKT_DATA, b)) {
                    retval = -EFAULT;
                    break;
                }
                b++;
                nr--;
            }

            uncopied = copy_from_read_buf(tty, &b, &nr);
            uncopied += copy_from_read_buf(tty, &b, &nr);
            if (uncopied) {
                retval = -EFAULT;
                break;
            }
        }

        n_tty_check_unthrottle(tty);

        if (b - buf >= minimum)
            break;
        if (time)
            timeout = time;
    }
    
    ...

    return retval;
}
  1. 如果配置了串口中断,串口在接收到数据后,触发中断
static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
{
    struct imx_port *sport = dev_id;
    unsigned int rx, flg, ignored = 0;
    struct tty_port *port = &sport->port.state->port;

    spin_lock(&sport->port.lock);

    while (imx_uart_readl(sport, USR2) & USR2_RDR) {
        
        ...

        if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))
            continue;

        if (unlikely(rx & URXD_ERR)) {
            
            ...
            /*读取寄存器的数据*/
            rx &= (sport->port.read_status_mask | 0xFF);

            if (rx & URXD_BRK)
                flg = TTY_BREAK;
            else if (rx & URXD_PRERR)
                flg = TTY_PARITY;
            else if (rx & URXD_FRMERR)
                flg = TTY_FRAME;
            if (rx & URXD_OVRRUN)
                flg = TTY_OVERRUN;

#ifdef SUPPORT_SYSRQ
            sport->port.sysrq = 0;
#endif
        }

        if (sport->port.ignore_status_mask & URXD_DUMMY_READ)
            goto out;

        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);
    return IRQ_HANDLED;
}

调用tty_insert_flip_char将把tty驱动程序获得的、准备发给用户的字符添加到交替缓冲区中。第一个参数是保存数据的tty_struct结构,第二个参数是需要保存的数据,第三个参数是为此字符设置的标志位。如果接收到的字符是常规字符,标志位应该被设置为TTY_NORMAL。

  1. 如果配置了DMA模式,从DMA拷贝数据
static int imx_uart_start_rx_dma(struct imx_port *sport)
{
    struct scatterlist *sgl = &sport->rx_sgl;
    struct dma_chan *chan = sport->dma_chan_rx;
    struct device *dev = sport->port.dev;
    struct dma_async_tx_descriptor *desc;
    int ret;

    sport->rx_ring.head = 0;
    sport->rx_ring.tail = 0;
    sport->rx_periods = RX_DMA_PERIODS;

    sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
    
    ...
    
    desc->callback = imx_uart_dma_rx_callback;
    desc->callback_param = sport;

    dev_dbg(dev, "RX: prepare for the DMA.\n");
    sport->dma_is_rxing = 1;
    sport->rx_cookie = dmaengine_submit(desc);
    dma_async_issue_pending(chan);
    return 0;
}

tty核心和tty_driver结构并未提供read函数,当tty驱动程序接收到数据后,它将负责把从硬件获取到的任何数据传递给tty核心,而不是使用传统的read函数。tty核心将缓冲数据直到接到来自用户的请求。由于tty核心已提供了缓冲逻辑,因此没有必要为每个tty驱动程序实现它们自己的缓冲区逻辑。当用户要求驱动程序开始或者停止传输数据时,tty核心将通知tty驱动程序。

7.DMA和中断的配置

static int imx_uart_startup(struct uart_port *port)
{
    struct imx_port *sport = (struct imx_port *)port;
    int retval, i;
    unsigned long flags;
    int dma_is_inited = 0;
    u32 ucr1, ucr2, ucr4;

    ...
    /*如果dma_is_inited为真,则配置DMA模式,否则使能串口中断*/
    if (dma_is_inited) {
        imx_uart_enable_dma(sport);
        imx_uart_start_rx_dma(sport);
    } else {
        ucr1 = imx_uart_readl(sport, UCR1);
        ucr1 |= UCR1_RRDYEN;
        imx_uart_writel(sport, ucr1, UCR1);

        ucr2 = imx_uart_readl(sport, UCR2);
        ucr2 |= UCR2_ATEN;
        imx_uart_writel(sport, ucr2, UCR2);
    }

    spin_unlock_irqrestore(&sport->port.lock, flags);

    return 0;
}

相关文章

网友评论

      本文标题:嵌入式Linux开发-串口驱动

      本文链接:https://www.haomeiwen.com/subject/mxbrhhtx.html