从tty到uart层,分析uart数据流程(一)
(本文分析基于linux3.2.0)关于Uart 和tty的关系,在此不必多谈,总之uart driver 是基于tty实现!下面我们直指主题:一. tty层的架构 关于tty层的架构,这个图是最好的描述: 其中读,写数据必须先通过line discipline,然后通过line discipline将数据分别交付给tty_core层或tty_d
(本文分析基于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_port、uart_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_port和uart_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总结,若有错误,还请各位有道之人指出!
更多推荐
所有评论(0)