(本文分析基于linux3.2.0)

关于Uart 和tty的关系,在此不必多谈,总之uart driver 是基于tty实现!下面我们直指主题:


.  tty层的架构

    关于tty层的架构,这个图是最好的描述:

     其中读,写数据必须先通过line discipline,然后通过line discipline将数据分别交付给tty_core层或tty_driver 层;【然而】,不是所有的tty_core 都通过line discipline来和tty_driver来交互。比如一些ioctl命令,就直接传达至tty_drvier层。

         tty_core主要实现在tty_io.c中,line discipline主要实现在N_tty.c中,关于tty driver的实现有很多,我们主要看uart相关的实现,(uart_core 就是一个典型的tty driver。)即serial_core.c.

       uart_core实现了一个tty driver 的注册,通过跟踪代码:uart_register_driver->tty_register_driver->cdev_add 来看,tty的基础是我们异常熟悉的字符设备。那么,App层对其的操作接口便是该字符设备tty_fops提供的接口。其如下所示:

static const struct file_operations tty_fops = {
    .llseek        = no_llseek,
    .read        = tty_read,
    .write        = tty_write,
    .poll        = tty_poll,
    .unlocked_ioctl    = tty_ioctl,
    .compat_ioctl    = tty_compat_ioctl,
    .open        = tty_open,
    .release    = tty_release,
    .fasync        = tty_fasync,
};

明白了这一点,我们可以知道,该层(即tty core 层)便是我们要寻找的“最上层”接口,用户层通过系统调用最终会调用到这里的相应的接口函数。下面的任务便是从最低层的驱动一直分析到该层驱动,如此,我们变会清除uart数据在kernel中的处理流程。

二. Uart和tty之间的数据结构

  同样uart和tty之间的数据结构,也用一个图来描述:

      从上图可以看出,一个uart_driver就对应一个tty_driver,而一个uart_driver可以包括多个uart_state,每个uart_state对应一个uart_device,每个uart_device都有自己的tty_portuart_port。每个tty_port对应一个tty struct,每个tty struct中有自己的收发缓存。

      circ_buf是一个发送缓存,在写数据时,当tty层调用驱动提供的写函数时,数据会首先进入circ_buf的环形缓存区,然后由uart_port从缓存区中取数据,将其写入到串口设备中。

     当uart_port从串口设备接收到数据时,它会直接将数据放入tty的缓存中(tty缓存属于tty_port),进而放入对应的linediscipline的缓存区中。

三.  一些重要的数据结构的讲解

1.关于uart_state:

struct uart_state {
    struct tty_port        port;

    int            pm_state;
    struct circ_buf        xmit;

    struct uart_port    *uart_port;
};

      我们知道uart _drive结构体中的state成员,是一个指针,其最后会用分配N个state结构大小的空间,用来存放各个端口的state。每个state结构体对应一个uart device(既是对uart port的描述)。从以上的知识可知,【一个uart driver 对应多个uart device( port ),对应到数据结构便是uart_state 。每个uart_state对应一个tty_portuart_port结构体,用以描述该device。】circ_buf被称为环形缓冲区,其用于存储每个端口的发送数据。

2. 关于circ_buf:

struct circ_buf {
    char *buf;
    int head;
    int tail;
};

该circ_buf的buf成员主要用于缓存数据,head表示数据的结尾位置,tail表示第一个未读数据的位置。如此本应该,head - tail 便为数据的长度,SIZE-1- head +tail为缓冲区的空闲长度。然而其计算方法如下所示:

#define CIRC_CNT(head,tail,size) (((head) - (tail)) & ((size)-1))
其在上面的基础上还会& ((size)-1),这是为何?

      原来这个Size一般是2^n,在串口中2^12,而(size)-1用二进制表示0xFF..F, 反正最高位变为0,其他为均为1. 这样在运算结果大于0时,表示head >tail,此时&可以不要,然而当head < tail时,这个与就很重要,是它确保得到正确的数据长度。了解了circ_buf的操作对发送数据的流程就较为清楚。

3. tty struct

struct tty_struct {
  .......
    const struct tty_operations *ops;
   .........................
    char *read_buf;
    int read_head;
    int read_tail;
    int read_cnt;
    ....................................
};
    值得注意的是,read_buf是tty层的接受缓冲区,该缓冲区也是一个环形缓冲区。

4.tty结构体在那里被初始化的?

   tty_open->tty_init_dev->initialize_tty_struct, 在initialize_tty_struct中赋值的tty_init_dev中分配了tty结构体initialize_tty_struct如下

void initialize_tty_struct(struct tty_struct *tty,
        struct tty_driver *driver, int idx)
{
    memset(tty, 0, sizeof(struct tty_struct));
    kref_init(&tty->kref);
    tty->magic = TTY_MAGIC;
    tty_ldisc_init(tty);
    tty->session = NULL;
    tty->pgrp = NULL;
    tty->overrun_time = jiffies;
    tty->buf.head = tty->buf.tail = NULL;
    tty_buffer_init(tty);
    mutex_init(&tty->termios_mutex);
    mutex_init(&tty->ldisc_mutex);
    init_waitqueue_head(&tty->write_wait);
    init_waitqueue_head(&tty->read_wait);
    INIT_WORK(&tty->hangup_work, do_tty_hangup);
    mutex_init(&tty->atomic_read_lock);
    mutex_init(&tty->atomic_write_lock);
    mutex_init(&tty->output_lock);
    mutex_init(&tty->echo_lock);
    spin_lock_init(&tty->read_lock);
    spin_lock_init(&tty->ctrl_lock);
    INIT_LIST_HEAD(&tty->tty_files);
    INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;
    tty->ops = driver->ops;        //将driver的ops赋给tty;   
    tty->index = idx;
    tty_line_name(driver, idx, tty->name);
    tty->dev = tty_get_device(tty);
}


四 . 写流程的分析

   鉴于写流程较为简单,我们会先分析写流程,分析从上向下:

 从用户层最接近的tty_io.c中分析tty_write开始:

--------------------------------- /tty/tty_io.c--------------------------------------------------------------------

static ssize_t tty_write(struct file *file, const char __user *buf,
                        size_t count, loff_t *ppos)
{
    ..........................................
    if (!ld->ops->write)
        ret = -EIO;
    else
        ret = do_tty_write(ld->ops->write, tty, file, buf, count);  //调用ldisc中的write函数;
    tty_ldisc_deref(ld);
    return ret;
}
   do_tty_write中调用ldisc中的write函数。

static inline ssize_tdo_tty_write(
    ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
    struct tty_struct *tty,
    struct file *file,
    const char __user *buf,
    size_t count)
{
    ..........................................
        if (copy_from_user(tty->write_buf, buf, size))   //从用户空间读取字符放入tty->write_buf中;
            break;
        ret = write(tty, file, tty->write_buf, size);  //调用ldisc中的write函数;
        if (ret <= 0)
            break;
        written += ret;
        buf += ret;
        count -= ret;
      ...................................
}

--------------------------------- /tty/N_tty.c--------------------------------------------------------------------
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
               const unsigned char *buf, size_t nr)
{
   .....
    DECLARE_WAITQUEUE(wait, current);   //声明等待队列
    ........................

    add_wait_queue(&tty->write_wait, &wait); //添加到等待队列
    while (1) {
        set_current_state(TASK_INTERRUPTIBLE); //设置当前进程为可中断
        if (signal_pending(current)) {
            retval = -ERESTARTSYS;
            break;
        }
        if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
            retval = -EIO;
            break;
        }
        if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
            while (nr > 0) {
                ssize_t num = process_output_block(tty, b, nr);
                if (num < 0) {
                    if (num == -EAGAIN)
                        break;
                    retval = num;
                    goto break_out;
                }
                b += num;
                nr -= num;
                if (nr == 0)
                    break;
                c = *b;
                if (process_output(c, tty) < 0)
                    break;
                b++; nr--;
            }
            if (tty->ops->flush_chars)
                tty->ops->flush_chars(tty);
        } else {
            while (nr > 0) {
                c = tty->ops->write(tty, b, nr);  //调用tty driver中的write函数;
                if (c < 0) {
                    retval = c;
                    goto break_out;
                }
                if (!c)
                    break;
                b += c;
                nr -= c;
            }
        }
        if (!nr)
            break;
        if (file->f_flags & O_NONBLOCK) {
            retval = -EAGAIN;
            break;
        }
        schedule();  //调度处于阻塞状态
    }
break_out:
    __set_current_state(TASK_RUNNING);
    remove_wait_queue(&tty->write_wait, &wait);
    if (b - buf != nr && tty->fasync)
        set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
    return (b - buf) ? b - buf : retval;
}

然后在/tty/serial/serial_core.c中

---------------------------------/tty/serial/serial_core.c--------------------------------------------------------------------

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 = state->uart_port;
    circ = &state->xmit;

    if (!circ->buf)
        return 0;

    spin_lock_irqsave(&port->lock, flags);
    while (1) {
        c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);  //计算到结尾(非tail)的空余长度
        if (count < c)
            c = count;
        if (c <= 0)
            break;
        memcpy(circ->buf + circ->head, buf, c);  //将上层传过来的数据copy到circ->buf
        circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
        buf += c;
        count -= c;
        ret += c;
    }
    spin_unlock_irqrestore(&port->lock, flags);

    uart_start(tty);   //开启发送
    return ret;
}
   通过circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);一旦到达结尾,这是就变成100..00,和(UART_XMIT_SIZE - 1)相与,会使head回到头部0的位置,一旦环形缓冲区满,就退出,如此,会将数据全部填入环形缓冲区。

   通过上面的circ->buf我们可知,上层会把数据放在该缓冲中,那么如何发送呢?使用中断!从uart_start我们就可以窥见一二:

uart_start- >__uart_start :

static void __uart_start(struct tty_struct *tty)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port = state->uart_port;

    if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
        !tty->stopped && !tty->hw_stopped)
        port->ops->start_tx(port);  //使用uart driver层的ops,start_tx
}

---------------------------------/tty/serial/Omap-serial.c-------------------------------------------------------------------



static void serial_omap_start_tx(struct uart_port *port)
{
    struct uart_omap_port *up = (struct uart_omap_port *)port;
    struct circ_buf *xmit;
    unsigned int start;
    int ret = 0;

    if (!up->use_dma) {  //使用非DMA模式;
        pm_runtime_get_sync(&up->pdev->dev);
        serial_omap_enable_ier_thri(up);  //使能发送中断
        pm_runtime_mark_last_busy(&up->pdev->dev);
        pm_runtime_put_autosuspend(&up->pdev->dev);
        return;
    }

..................//使用DMA

}

      很显然,接下来便是中断处理:

static inline irqreturn_t serial_omap_irq(int irq, void *dev_id)
{
    struct uart_omap_port *up = dev_id;
    unsigned int iir, lsr;
    unsigned long flags;

    pm_runtime_get_sync(&up->pdev->dev);
    iir = serial_in(up, UART_IIR);      //读取中断向量寄存器
    if (iir & UART_IIR_NO_INT) {
        pm_runtime_mark_last_busy(&up->pdev->dev);
        pm_runtime_put_autosuspend(&up->pdev->dev);
        return IRQ_NONE;
    }

    spin_lock_irqsave(&up->port.lock, flags);
    lsr = serial_in(up, UART_LSR);
    if (iir & UART_IIR_RLSI) {  //如果发生读中断;
        if (!up->use_dma) {
            if (lsr & UART_LSR_DR)
                receive_chars(up, &lsr);
        } else {
            up->ier &= ~(UART_IER_RDI | UART_IER_RLSI);
            serial_out(up, UART_IER, up->ier);
            if ((serial_omap_start_rxdma(up) != 0) &&
                    (lsr & UART_LSR_DR))
                receive_chars(up, &lsr);
        }
    }

    check_modem_status(up);
    if ((lsr & UART_LSR_THRE) && (iir & UART_IIR_THRI))  //产生发送中断
        transmit_chars(up);

    spin_unlock_irqrestore(&up->port.lock, flags);
    pm_runtime_mark_last_busy(&up->pdev->dev);
    pm_runtime_put_autosuspend(&up->pdev->dev);

    up->port_activity = jiffies;
    return IRQ_HANDLED;
}

static void transmit_chars(struct uart_omap_port *up)
{
    struct circ_buf *xmit = &up->port.state->xmit;
    int count;

    if (up->port.x_char) {  //软流控中有XON/XOFF字符
        serial_out(up, UART_TX, up->port.x_char);
        up->port.icount.tx++;
        up->port.x_char = 0;
        return;
    }
    if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
        serial_omap_stop_tx(&up->port); //发送缓存为空,停止发送,即关闭中断
        return;
    } 
    count = up->port.fifosize / 4;    //有于uart fifo为64Byte,为防止产生溢出,每次最多写64Byte
    do {
        serial_out(up, UART_TX, xmit->buf[xmit->tail]);  //发送数据
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        up->port.icount.tx++;
        if (uart_circ_empty(xmit)) //如果缓存为空,跳出循环
            break;
    } while (--count > 0);
   //如果环形缓冲区中的字节数小于256个字节时,唤醒tty层,向该缓冲区写入更多数据
    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
        uart_write_wakeup(&up->port);
    //发送缓存为空,停止发送,即关闭发中断,打开收中断
    if (uart_circ_empty(xmit))
        serial_omap_stop_tx(&up->port);
}
至此UART的发送数据流程,从上到下就通了!

  从上面可以看出,发送过程是一个主动的过程,stop_tx和start_tx分别用来处理发送前和后的工作,如果想要将232的驱动支持485,那么最核心的思想便是,在start_tx时,发送enable,stop_tx时发送disable便可!
    第一次写blog总结,若有错误,还请各位有道之人指出!
 

Logo

更多推荐