[tty与uart]3.tty驱动分析

时间:2022-07-05 11:35:05

转自:http://www.wowotech.net/linux_kenrel/183.html

目录:

1 首先分析设备驱动的注册

1.1 uart_register_driver分析

1.2 tty_register_driver分析

1.3 serial8250_register_ports()函数分析

1.4 serial8250_probe()函数分析

2 然后,我们来看设备的打开过程

3 TTY设备的读

3.1 read_chan()

4 TTY设备的写

5 总结

首先分析设备驱动的注册

对于8250.c来说,主要涉及:

  • serial8250_init()--->uart_register_driver(&serial8250_reg)
  • serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
  • serial8250_probe(struct platform_device *dev)

struct uart_driver serial8250_reg的定义如下:

 static static struct uart_driver serial8250_reg = {
.owner = THIS_MODULE,
.driver_name = "serial",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = ,
.nr = UART_NR,
.cons = SERIAL8250_CONSOLE,
};

struct uart_driver serial8250_reg

1.1 uart_register_driver分析

主要完成了一下功能:

  • 分配数个uart_state结构体内存:      (在uart_add_one_port()里会用到它来关联uart_port)
  • 分配tty_driver。normal  = alloc_tty_driver(drv->nr)
  • 关联struct uart_driver和tty_driver:
    uart_driver-> tty_driver= tty_driver;     tty_driver ->driver_state = uart_driver;
  • 设置tty_driver的操作函数为uart_ops(tty_operations类型)中的操作函数:
  • 调用tty_register_driver():根据tty_driver里的数据来注册字符设备(来自于uart_driver);并添加到tty_drivers链表;调用tty_register_device()产生设备文件。 
 int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval; BUG_ON(drv->state); /*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out; normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out; drv->tty_driver = normal; normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = ;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops); /*
* Initialise the UART state(s).
*/
for (i = ; i < drv->nr; i++) {
struct uart_state *state = drv->state + i; state->close_delay = ; /* .5 seconds */
state->closing_wait = ; /* 30 seconds */ mutex_init(&state->mutex);
} retval = tty_register_driver(normal);
out:
if (retval < ) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}

uart_register_driver

1.2 tty_register_driver分析

与传统的字符设备驱动程序完全一致,主要做了一下工作:

  • 创建字符设备
  • 注册字符设备
  • 设置udev,创建/dev节点,名称为"%s%d", driver->name, index + driver->name_base,
                       normal->name = uart_driver->dev_name; //来自于uart_driver= "ttyS", //见struct uart_driver serial8250_reg的定义。
                       driver->name_base =0;
                       driver->num=(0--- driver->num);  // driver->num = uart_driver->nr = UART_NR = 8 
                                   因此创建的节点名为:/dev/ttySx  x=(0…7)
  • Proc文件系统操作; 
 int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL; if (driver->flags & TTY_DRIVER_INSTALLED)
return ; if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * * sizeof(void *), GFP_KERNEL);
if (!p)
return -ENOMEM;
} if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start, driver->num,
driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < ) {
kfree(p);
return error;
} if (p) {
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
driver->termios_locked = (struct ktermios **)(p + driver->num * );
} else {
driver->ttys = NULL;
driver->termios = NULL;
driver->termios_locked = NULL;
} cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num); if (error) {
unregister_chrdev_region(dev, driver->num);
driver->ttys = NULL;
driver->termios = driver->termios_locked = NULL;
kfree(p);
return error;
} if (!driver->put_char)
driver->put_char = tty_default_put_char; mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex); if ( !(driver->flags & TTY_DRIVER_DYNAMIC_DEV) ) {
for(i = ; i < driver->num; i++)
tty_register_device(driver, i, NULL);
}
proc_tty_register_driver(driver);
return ;
}

tty_register_driver

此时,内核已经注册了tty_drivers到全局链表tty_drivers。

1.3 serial8250_register_ports()函数分析

主要完成以下任务:

  • 为端口号line赋值 
  • 初始化定时器
  • 为uart_8250_port->uart_port.ops赋值= &serial8250_pops
  • 为uart_8250_port[].uart_port->device赋值
  • 将uart_8250_port[].uart_port挂入uart_driver->state[]->port 
 static void __init serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
int i; serial8250_isa_init_ports(); for (i = ; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i]; up->port.dev = dev;
uart_add_one_port(drv, &up->port);
}
}

serial8250_register_ports

1.4 serial8250_probe()函数分析

通过struct plat_serial8250_port *p = dev->dev.platform_data获取platform_device的设备私有数据(里面一般包括mapbase、irq、iotype等),将这些数据赋给uart_port,然后调用:

serial8250_register_port()--->uart_add_one_port(&serial8250_reg, &uart->port)

将uart_port注册到uart_driver->state[]->port里面。

 static int __devinit serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev->dev.platform_data;
struct uart_port port;
int ret, i; memset(&port, , sizeof(struct uart_port)); for (i = ; p && p->flags != ; p++, i++) {
port.iobase = p->iobase;
port.membase = p->membase;
port.irq = p->irq;
port.uartclk = p->uartclk;
port.regshift = p->regshift;
port.iotype = p->iotype;
port.flags = p->flags;
port.mapbase = p->mapbase;
port.hub6 = p->hub6;
port.dev = &dev->dev;
if (share_irqs)
port.flags |= UPF_SHARE_IRQ;
ret = serial8250_register_port(&port);
if (ret < ) {
dev_err(&dev->dev, "unable to register port at index %d "
"(IO%lx MEM%llx IRQ%d): %d\n", i,
p->iobase, (unsigned long long)p->mapbase,
p->irq, ret);
}
}
return ;
}

serial8250_probe

然后,我们来看设备的打开过程

以/dev/ttyS0为例。

根据系统在前面在此字符设备注册的fops,在open()后,系统应该是进入tty_fops的tty_open()函数。

可以明确:

tty_struct结构是在tty_open()时构建;

tty_struct保存在file->private_data; 
        以后的操作通过filp就可以找到tty_struct

然后通过tty_struct->tty_driver->open(tty_struct*, filp)调用的是tty_operations uart_ops.open =uart_open(serile_core.c);通过 uart_register_driver()->tty_set_operations(normal, &uart_ops)注册。
        tty_operations里的函数都是以(tty_struct, file* filp) 为参数。

而在uart_open(tty_struct*, filp)里,进行一些初始化后,调用了uart_startup(state, 0),此函数主要做了两件事:
        1)分配并初始化transmit 和 temporary缓冲区circ_buf
        2)调用port->ops->startup(port
                port=state.port |state = uart_driver->state[] |uart_driver=tty_struct->tty_driver->driver_state

 static int tty_open(int input, int output, int primary, void *d,
char **dev_out)
{
struct tty_chan *data = d;
int fd, err; fd = os_open_file(data->dev, of_set_rw(OPENFLAGS(), input, output), );
if(fd < )
return fd; if(data->raw){
CATCH_EINTR(err = tcgetattr(fd, &data->tt));
if(err)
return err; err = raw(fd);
if(err)
return err;
} *dev_out = data->dev;
return fd;
}

tty_open

总结

tty_open()后,创建了tty_struct,并保存在filp中;再调用uart层的tty_operations->uart_ops.open(),在里面创建了发送的circ_buf;然后调用了uart_port->uart_ops->open(tty, filp)。

tty_struct对应一个已经打开的具体tty设备。

3 TTY设备的读

TTY设备的读分为两部分:首先是进程读取tty_struct对应的缓冲区并阻塞当前进程;然后设备中断里,接收数据,唤醒进程的读操作。

程序首先进入tty_read():

  • 首先通过file->private_data获取tty_struct,然后再获取tty_ldisc;
  • 最后调用tty_ldisc->read。对于N_TTY即tty_ldisc_N_TTY.read()=read_chan()  
 static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct * tty;
struct inode *inode;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
inode = file->f_path.dentry->d_inode;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* We want to wait for the line discipline to sort out in this
situation */
ld = tty_ldisc_ref_wait(tty);
lock_kernel();
if (ld->read)
i = (ld->read)(tty,file,buf,count);
else
i = -EIO;
tty_ldisc_deref(ld);
unlock_kernel();
if (i > )
inode->i_atime = current_fs_time(inode->i_sb);
return i;

tty_read 

3.1 read_chan()

  • 初始化延迟工作队列:init_dev()==>initialize_tty_struct()==>INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc)
  • tty->read_wait只被n_tty_receive_buf()函数(或里面的分支)调用;
  • n_tty_receive_buf()只被flush_to_ldisc()调用
  • 而tty_flip_buffer_push()有两种方式来调用flush_to_ldisc(): 
         1)tty->low_latency===> flush_to_ldisc(&tty->buf.work.work); 
         2)schedule_delayed_work(&tty->buf.work, 1); 
    两者都是调用flush_to_ldisc(),不同点在于后者是延迟执行flush_to_ldisc()。延迟工作队列是在initialize_tty_struct()===>INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);中进行初始化的。

对于驱动层,调用轨迹如下:

在open()操作里申请中断;在中断里唤醒进程。

tty_open()==>………==>serial8250_startup()==>serial_link_irq_chain()==>request_irq()--------申请中断

serial8250_interrupt()--------------------------------------------------------------------------------------------处理中断

->serial8250_handle_port()

->receive_chars()

-> uart_insert_char()               //接收字符,存入tty_buffer,tty_struct包含tty_bufhead

->tty_insert_flip_char()  //而tty_bufhead包含三个tty_buffer成员:head、tail、free
    ->tty_flip_buffer_push()

-> flush_to_ldisc()

-> n_tty_receive_buf()

-> memcpy(tty->read_buf + tty->read_head, cp, i);  //拷贝数据至tty->read_buf

->tty->read_cnt += i                   //指示接收buff的字符数。

//与read_chan()-->input_available_p()
                                                                 里对tty->read_cnt的判断对应

->wake_up(&tty->read_wait)          //唤醒进程

大致是下图的流程:

调用tty_insert_flip_char或者tty_insert_flip_string将数据放入tty的缓存tty->tty_buffer;然后调用tty_flip_buffer_push(),将数据从tty缓存拷贝至ldisc缓存。

  • tty_insert_flip_string:将hardware driver中的数据缓冲到tty_buffer中,而这个tty_buffer指针则是在tty_port->buf->tail.
  • tty_flip_buffer_push:将tty_buffer也即tty驱动层缓冲区数据推到tty线路规程层缓冲区,否则tty核心层无法读取到数据,这样用户也就无法从tty核心层取到数据,可以理解为userspace->tty核心->line discipline->tty驱动.
    • 源码中tty_flip_buffer_push会启动flush_to_ldisc的work, 在work进程中会把tty_buffer中的数据推到ldisc的缓冲区。
    • userspace->read->tty_read->n_tty_read(tty_ldisc_ops)读取ldisc缓冲区数据

[tty与uart]3.tty驱动分析

[tty与uart]3.tty驱动分析

 static ssize_t read_chan(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
int minimum, time;
ssize_t retval = ;
ssize_t size;
long timeout;
unsigned long flags; do_it_again: if (!tty->read_buf) {
printk("n_tty_read_chan: called with read_buf == NULL?!?\n");
return -EIO;
} c = job_control(tty, file);
if(c < )
return c; minimum = time = ;
timeout = MAX_SCHEDULE_TIMEOUT;
if (!tty->icanon) {
time = (HZ / ) * TIME_CHAR(tty);
minimum = MIN_CHAR(tty);
if (minimum) {
if (time)
tty->minimum_to_wake = ;
else if (!waitqueue_active(&tty->read_wait) ||
(tty->minimum_to_wake > minimum))
tty->minimum_to_wake = minimum;
} else {
timeout = ;
if (time) {
timeout = time;
time = ;
}
tty->minimum_to_wake = minimum = ;
}
} /*
* Internal serialization of reads.
*/
if (file->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&tty->atomic_read_lock))
return -EAGAIN;
}
else {
if (mutex_lock_interruptible(&tty->atomic_read_lock))
return -ERESTARTSYS;
} add_wait_queue(&tty->read_wait, &wait);
while (nr) {
/* First test for status change. */
if (tty->packet && tty->link->ctrl_status) {
unsigned char cs;
if (b != buf)
break;
cs = tty->link->ctrl_status;
tty->link->ctrl_status = ;
if (tty_put_user(tty, cs, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
break;
}
/* This statement must be first before checking for input
so that any interrupt will set the state back to
TASK_RUNNING. */
set_current_state(TASK_INTERRUPTIBLE); if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= ))
tty->minimum_to_wake = (minimum - (b - buf)); if (!input_available_p(tty, )) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
continue;
}
__set_current_state(TASK_RUNNING); /* Deal with packet mode. */
if (tty->packet && b == buf) {
if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
} if (tty->icanon) {
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol; eol = test_and_clear_bit(tty->read_tail,
tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+) &
(N_TTY_BUF_SIZE-));
tty->read_cnt--;
if (eol) {
/* this test should be redundant:
* we shouldn't be reading data if
* canon_data is 0
*/
if (--tty->canon_data < )
tty->canon_data = ;
}
spin_unlock_irqrestore(&tty->read_lock, flags); if (!eol || (c != __DISABLED_CHAR)) {
if (tty_put_user(tty, c, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
int uncopied;
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
if (uncopied) {
retval = -EFAULT;
break;
}
} /* If there is enough space in the read buffer now, let the
* low-level driver know. We use n_tty_chars_in_buffer() to
* check the buffer, as it now knows about canonical mode.
* Otherwise, if the driver is throttled and the line is
* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
* we won't get any more characters.
*/
if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
n_tty_set_room(tty);
check_unthrottle(tty);
} if (b - buf >= minimum)
break;
if (time)
timeout = time;
}
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum; __set_current_state(TASK_RUNNING);
size = b - buf;
if (size) {
retval = size;
if (nr)
clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
goto do_it_again; n_tty_set_room(tty); return retval;
}

read_chan

4 TTY设备的写

首先调用tty_write.

 static ssize_t tty_write(struct file * file, const char __user * buf, size_t count,
loff_t *ppos)
{
struct tty_struct * tty;
struct inode *inode = file->f_path.dentry->d_inode;
ssize_t ret;
struct tty_ldisc *ld; tty = (struct tty_struct *)file->private_data;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->driver->write || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO; ld = tty_ldisc_ref_wait(tty);
if (!ld->write)
ret = -EIO;
else
ret = do_tty_write(ld->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}

tty_write

TTY设备写涉及写进程、中断ISR、即tasklet_action三部分的配合:

[tty与uart]3.tty驱动分析

应用APP函数调用

tty_write()---------------------------------------------------------------------------------------------------tty_io.c

do_tty_write()

copy_from_user()             //将用户空间数据copy至tty->write_buf

write ()                            //对于N_TTY,即tty_ldisc_N_TTY.write()=write_chan()

add_wait_queue()-------------------------------------------------------------------------n_tty.c

//添加等待队列

tty->driver->flush_chars(tty);      //struct tty_operations uart_ops. flush_chars=uart_flush_chars()

uart_start()---------------------------------------------------------------------------serial_core.c

__uart_start()

uart_port->ops->start_tx()   //uart_ops serial8250_pops.start_tx = serial8250_start_tx

transmit_chars()               //启动真正发送------------------------------8250.c

serial_out(up, UART_TX, xmit->buf[xmit->tail])      //copy至芯片发送buffer

uart_write_wakeup(struct uart_port *port)           //调度tasklet_schedule()

tasklet_schedule(&info->tlet);

schedule();                 //进程睡眠

中断函数调用:

serial8250_interrupt()-----------------------------------------------------------------------------------处理中断

serial8250_handle_port()------------------------------------------------------------------------8250.c

if(Transmit-hold-register empty)

transmit_chars()

serial_out(up, UART_TX, xmit->buf[xmit->tail])      //copy至芯片发送缓冲

uart_write_wakeup()---------------------------------------------------serial_core.c

tasklet_schedule()                                    //调度tasklet_schedule()

tasklet在tty_open()-->……-->uart_open()-->uart_get()
        -->tasklet_init(&state->info->tlet, uart_tasklet_action,state)中进行初始化。
       【tty_open()-->tty_struct.tty_driver.open()=uart_open()-->uart_get()-->tasklet_init()】

tasklet_action()调用:

经过tasklet_schedule ()后执行uart_tasklet_action。

uart_tasklet_action ()

tty_wakeup()

ld->write_wakeup(tty)//即n_tty_write_wakeup():发送信号SIGIO给fasync_struct所描述的PID

wake_up_interruptible(&tty->write_wait);    //唤醒写进程

总结

在理清了数据走向和函数调用关系后,我们可以清晰的知道开发TTY驱动,需要我们做什么:

  • 定义uart_driver数据结构;
  • 定义uart_port数据结构;
  • 完成uart_ops操作函数集合。

最后放一张从进程、vfs、tty_core、serial_core到uart驱动各个数据结构之间的相互关系图:

[tty与uart]3.tty驱动分析

博客推荐

tty驱动分析:http://blog.csdn.net/lizuobin2/article/details/51773305,对数据流和读写说的很好

问题

1. 怎样理解tty核心层、tty驱动层、线路规程层、串口核心层、串口驱动层?

核心层其实就是operation的实现方法部分,所以

  • tty核心层为tty_read/tty_write等,包括n_tty.c(line discipline)和tty_io.c中
  • serial核心层为uart_read/uart_write等,包括serial_core.c和imx.c(freescale chip).

2. TTY这层存在的作用是什么?

TTY用来抽象串行接口的设备,抽象了串行接口设备所需要的特性、功能,抽象后,一个tty设备即可表示一个串行输入、输出接口(比如控制台、keypad、串口、pty设备)等