Linux设备模型之tty&&uart驱动架构分析

来源:互联网 发布:云计算培训学费 编辑:程序博客网 时间:2024/05/08 19:35

一:前言

Tty(Teletype)这个名称源于电传打字节的简称。在linux表示各种终端,主要有串行端口终端、伪终端、控制终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题.

二:tty驱动概貌
Tty架构如下所示:

如上图所示,用户空间主要是通过设备文件同tty_core交互。tty_core根据用空间操作的类型再选择跟line discipline和tty_driver交互。例如设置硬件的ioctl指令就直接交给tty_driver处理。Read和write操作就会交给line discipline处理。

Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置。tty线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如PPP(端对端协议Peer-Peer Protocol)和Bluetooth(蓝牙技术)。处理之后。就会将数据交给tty_driver。

Tty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串。将其传给终端设备。(The driver’s job
is to format data that is sent to it in a manner that the hardware can understand, andreceive data from the hardware.)

值得注意的是,这个架构没有为tty_driver提供read操作。也就是说tty_core 和line discipline都没有办法从tty_driver里直接读终端信息。这是因为tty_driver对应的hardware并不一定是输入数据和输出数据的共同负载者。例如控制终端,输出设备是显示器,输入设备是键盘。基于这样的原理。在line discipline中有一个输入缓存区。并提供了一个名叫receive_buf()的接口函数。对应的终端设备只要调用line discipine的receive_buf()函数,将数据写入到输入缓存区就可以了。
如果一个设备同时是输入设备又是输出设备。那在设备的中断处理中调用receive_buf()将数据写入即可.

三:tty驱动接口分析
具体的tty驱动设计可以参考LDD3。这里只对它的接口实现做一个分析。tty driver的所有操作都包含在tty_driver中。内核即供了一个名叫alloc_tty_driver()来分配这个tty_driver。当然我们也可以在自己的驱动中将它定义成一个静态的结构。对tty_driver进行一些必要的初始化之后,调用tty_register_driver()将其注册.
alloc_tty_driver()接口代码如下所示:
struct tty_driver *alloc_tty_driver(int lines)
{
         struct tty_driver *driver;

         driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
         if (driver) {
                   driver->magic = TTY_DRIVER_MAGIC;
                   driver->num = lines;
                   /* later we'll move allocation of tables here */
         }
         return driver;
}
这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.
在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.

tty_register_driver()用来注册一个tty_driver。代码如下:
int tty_register_driver(struct tty_driver *driver)
{
         int error;
         int i;
         dev_t dev;
         void **p = NULL;

         //TTY_DRIVER_INSTALLED:已安装的
         if (driver->flags & TTY_DRIVER_INSTALLED)
                   return 0;

         //TTY_DRIVER_DEVPTS_MEM:使用devpts进行动态内存映射
         if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
                   p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
                   if (!p)
                            return -ENOMEM;
         }

         //注册字符设备号
         //如果没有指定driver->major
         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 * 2);
         } 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;
         }

         //指定默认的put_char
         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);

         //如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理
         if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
                   for (i = 0; i num; i++)
                       tty_register_device(driver, i, NULL);
         }
         proc_tty_register_driver(driver);
         return 0;
}
这个函数操作比较简单。就是为tty_driver创建字符设备。然后将字符设备的操作集指定为tty_fops.并且将tty_driver挂载到tty_drivers链表中.其实这个链表的作用跟我们之前分析的input子系统中的input_dev[ ]数组类似。都是以设备号为关键字找到对应的driver.
特别的。如果没有定义TTY_DRIVER_DYNAMIC_DEV.还会在sysfs中创建一个类设备.这样主要是为了udev管理设备.

四:设备文件的操作
设备文件的操作是本节分析的重点。它的主要操作是将各项操作对应到ldsic或者是tty_driver.

4.1:打开tty设备的操作
从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open()。代码如下:
static int tty_open(struct inode *inode, struct file *filp)
{
         struct tty_struct *tty;
         int noctty, retval;
         struct tty_driver *driver;
         int index;
         dev_t device = inode->i_rdev;
         unsigned short saved_flags = filp->f_flags;

         nonseekable_open(inode, filp);

retry_open:
         //O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端

         //noctty:需不需要更改当前进程的控制终端
         noctty = filp->f_flags & O_NOCTTY;
         index  = -1;
         retval = 0;

         mutex_lock(&tty_mutex);

         //设备号(5,0) 即/dev/tty.表示当前进程的控制终端
         if (device == MKDEV(TTYAUX_MAJOR, 0)) {
                   tty = get_current_tty();
                   //如果当前进程的控制终端不存在,退出
                   if (!tty) {
                            mutex_unlock(&tty_mutex);
                            return -ENXIO;
                   }

                   //取得当前进程的tty_driver
                   driver = tty->driver;
                   index = tty->index;
                   filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
                   /* noctty = 1; */
                   goto got_driver;
         }
#ifdef CONFIG_VT
         //设备号(4,0).即/dev/tty0:表示当前的控制台
         if (device == MKDEV(TTY_MAJOR, 0)) {
                   extern struct tty_driver *console_driver;
                   driver = console_driver;
                   //fg_console: 表示当前的控制台
                   index = fg_console;
                   noctty = 1;
                   goto got_driver;
         }
#endif
         //设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()
         if (device == MKDEV(TTYAUX_MAJOR, 1)) {
                   driver = console_device(&index);
                   if (driver) {
                            /* Don't let /dev/console block */
                            filp->f_flags |= O_NONBLOCK;
                            noctty = 1;
                            goto got_driver;
                   }
                   mutex_unlock(&tty_mutex);
                   return -ENODEV;
         }

         //以文件的设备号为关键字,到tty_drivers中搜索所注册的driver
         driver = get_tty_driver(device, &index);
         if (!driver) {
                   mutex_unlock(&tty_mutex);
                   return -ENODEV;
         }
got_driver:
         //index表示它的次设备号
         retval = init_dev(driver, index, &tty);
         mutex_unlock(&tty_mutex);
         if (retval)
                   return retval;

         filp->private_data = tty;
         file_move(filp, &tty->tty_files);
         check_tty_count(tty, "tty_open");
         if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
             tty->driver->subtype == PTY_TYPE_MASTER)
                   noctty = 1;
#ifdef TTY_DEBUG_HANGUP
         printk(KERN_DEBUG "opening %s...", tty->name);
#endif
         if (!retval) {
                   if (tty->driver->open)
                            retval = tty->driver->open(tty, filp);
                   else
                            retval = -ENODEV;
         }
         filp->f_flags = saved_flags;

         if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
                                                        !capable(CAP_SYS_ADMIN))
                   retval = -EBUSY;

         if (retval) {
#ifdef TTY_DEBUG_HANGUP
                   printk(KERN_DEBUG "error %d in opening %s...", retval,
                          tty->name);
#endif
                   release_dev(filp);
                   if (retval != -ERESTARTSYS)
                            return retval;
                   if (signal_pending(current))
                            return retval;
                   schedule();
                   /*
                    * Need to reset f_op in case a hangup happened.
                    */
                   if (filp->f_op == &hung_up_tty_fops)
                            filp->f_op = &tty_fops;
                   goto retry_open;
         }

         mutex_lock(&tty_mutex);
         spin_lock_irq(&current->sighand->siglock);

         //设置当前进程的终端
         if (!noctty &&
             current->signal->leader &&
             !current->signal->tty &&
             tty->session == NULL)
                   __proc_set_tty(current, tty);
         spin_unlock_irq(&current->sighand->siglock);
         mutex_unlock(&tty_mutex);
         tty_audit_opening();
         return 0;
}
注意在这里有个容易忽略的操作:init_dev()。
Init_dev() -à initialize_tty_struct() à tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
看一下tty_ldisc_assign(tty, tty_ldisc_get(N_TTY))的操作:
Tty_ldisc_get():
struct tty_ldisc *tty_ldisc_get(int disc)
{
         unsigned long flags;
         struct tty_ldisc *ld;

         if (disc = NR_LDISCS)
                   return NULL;

         spin_lock_irqsave(&tty_ldisc_lock, flags);

         ld = &tty_ldiscs[disc];
         /* Check the entry is defined */
         if (ld->flags & LDISC_FLAG_DEFINED) {
                   /* If the module is being unloaded we can't use it */
                   if (!try_module_get(ld->owner))
                            ld = NULL;
                   else /* lock it */
                            ld->refcount++;
         } else
                   ld = NULL;
         spin_unlock_irqrestore(&tty_ldisc_lock, flags);
         return ld;
}
这个函数的操作为到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.

tty_ldisc_assign操作如下:
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
{
         tty->ldisc = *ld;
         tty->ldisc.refcount = 0;
}
即将取出来的idisc作为tty->ldisc字段.
在这段代码中涉及到了tty_driver,tty_struct, struct tty_ldisc.这三者之间的关系用下图表示如下:


在这里,为tty_struct的ldisc是默认指定为tty_ldiscs[N_TTY].该ldisc对应的是控制终端的线路规范。可以在用空间用带TIOCSETD的ioctl调用进行更改.
将上述open用流程图的方式表示如下:


4.2:设备文件的write操作
设备文件的write操作对应tty_fops->write即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;
}
在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.
如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.
Do_tty_write代码分段分析如下:
static inline ssize_t do_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)
{
         ssize_t ret, written = 0;
         unsigned int chunk;

         ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
         if (ret
                   return ret;

         /*
          * We chunk up writes into a temporary buffer. This
          * simplifies low-level drivers immensely, since they
          * don't have locking issues and user mode accesses.
          *
          * But if TTY_NO_WRITE_SPLIT is set, we should use a
          * big chunk-size..
          *
          * The default chunk-size is 2kB, because the NTTY
          * layer has problems with bigger chunks. It will
          * claim to be able to handle more characters than
          * it actually does.
          *
          * FIXME: This can probably go away now except that 64K chunks
          * are too likely to fail unless switched to vmalloc...
          */
         chunk = 2048;
         if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
                   chunk = 65536;
         if (count
                   chunk = count;

         /* write_buf/write_cnt is protected by the atomic_write_lock mutex */
         if (tty->write_cnt
                   unsigned char *buf;

                   if (chunk
                            chunk = 1024;

                   buf = kmalloc(chunk, GFP_KERNEL);
                   if (!buf) {
                            ret = -ENOMEM;
                            goto out;
                   }
                   kfree(tty->write_buf);
                   tty->write_cnt = chunk;
                   tty->write_buf = buf;
         }
默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.
Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里
Tty->write_cnt是临时缓存区的大小。
在这里,必须要根据一次写的数据量对这个临时缓存区做调整

         /* Do the write .. */
         for (;;) {
                   size_t size = count;
                   if (size > chunk)
                            size = chunk;
                   ret = -EFAULT;
                   if (copy_from_user(tty->write_buf, buf, size))
                            break;
                   lock_kernel();
                   ret = write(tty, file, tty->write_buf, size);
                   unlock_kernel();
                   if (ret
                            break;
                   written += ret;
                   buf += ret;
                   count -= ret;
                   if (!count)
                            break;
                   ret = -ERESTARTSYS;
                   if (signal_pending(current))
                            break;
                   cond_resched();
         }
         if (written) {
                   struct inode *inode = file->f_path.dentry->d_inode;
                   inode->i_mtime = current_fs_time(inode->i_sb);
                   ret = written;
         }
out:
         tty_write_unlock(tty);
         return ret;
}
后面的操作就比较简单了。先将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的时间戳.
Write操作的流程图如下示:


在这里,我们只看到将数据写放到了ldisc->write().没有看到与tty_driver相关的部份。实际上在ldisc中对写入的数据做预处理过后,还是会调用tty_driver->write()将其写入硬件.

4.3:设备文件的read操作
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 > 0)
                   inode->i_atime = current_fs_time(inode->i_sb);
         return i;
}
这个read操作就更简单。直接调用ldsic->read()完成工作
流程图如下:


五:小结
在tty设备文件的操作中。Open操作会进行一系统初始化。然后调用ldsic->open tty_driver->open。在write和read调用中只tty_core只会用到ldisc->wirte/ldisc->read.除了上面分析的几个操作之外,还有一个ioctl操作,以及它封装的几个termios。这些ioctl类的操作会直接和tty_driver相关联.
在这一节里,只对tty的构造做一个分析,具体ldisc的操作我们之后以控制终端为例进行分析.




linux设备模型之uart驱动架构分析
一:前言
接着前面的终端控制台分析,接下来分析serial的驱动.在linux中,serial也对应着终端,通常被称为串口终端.在shell上,我们看到的/dev/ttyS*就是串口终端所对应的设备节点.
在分析具体的serial驱动之前.有必要先分析uart驱动架构.uart是Universal Asynchronous Receiver and Transmitter的缩写.翻译成中文即为”通用异步收发器”.它是串口设备驱动的封装层.
二:uart驱动架构概貌
如下图所示:

上图中红色部份标识即为uart部份的操作.
从上图可以看到,uart设备是继tty_driver的又一层封装.实际上uart_driver就是对应tty_driver.在它的操作函数中,将操作转入uart_port.
在写操作的时候,先将数据放入一个叫做circ_buf的环形缓存区.然后uart_port从缓存区中取数据,将其写入到串口设备中.
当uart_port从serial设备接收到数据时,会将设备放入对应line discipline的缓存区中.
这样.用户在编写串口驱动的时候,只先要注册一个uart_driver.它的主要作用是定义设备节点号.然后将对设备的各项操作封装在uart_port.驱动工程师没必要关心上层的流程,只需按硬件规范将uart_port中的接口函数完成就可以了.
三:uart驱动中重要的数据结构及其关联
我们可以自己考虑下,基于上面的架构代码应该要怎么写.首先考虑以下几点:
1: 一个uart_driver通常会注册一段设备号.即在用户空间会看到uart_driver对应有多个设备节点.例如:
/dev/ttyS0 /dev/ttyS1
每个设备节点是对应一个具体硬件的,从上面的架构来看,每个设备文件应该对应一个uart_port.
也就是说:uart_device怎么同多个uart_port关系起来?怎么去区分操作的是哪一个设备文件?
2:每个uart_port对应一个circ_buf,所以uart_port必须要和这个缓存区关系起来
回忆tty驱动架构中.tty_driver有一个叫成员指向一个数组,即tty->ttys.每个设备文件对应设数组中的一项.而这个数组所代码的数据结构为tty_struct. 相应的tty_struct会将tty_driver和ldisc关联起来.
那在uart驱动中,是否也可用相同的方式来处理呢?
将uart驱动常用的数据结构表示如下:


结合上面提出的疑问.可以很清楚的看懂这些结构的设计.
四:uart_driver的注册操作
Uart_driver注册对应的函数为: uart_register_driver()代码如下:
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 = 9600;
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 = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
state->close_delay = 500; /* .5 seconds */
state->closing_wait = 30000; /* 30 seconds */
mutex_init(&state->mutex);
}
retval = tty_register_driver(normal);
out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}
从上面代码可以看出.uart_driver中很多数据结构其实就是tty_driver中的.将数据转换为tty_driver之后,注册tty_driver.然后初始化uart_driver->state的存储空间.
这样,就会注册uart_driver->nr个设备节点.主设备号为uart_driver-> major. 开始的次设备号为uart_driver-> minor.
值得注意的是.在这里将tty_driver的操作集统一设为了uart_ops.其次,在tty_driver-> driver_state保存了这个uart_driver.这样做是为了在用户空间对设备文件的操作时,很容易转到对应的uart_driver.
另外:tty_driver的flags成员值为: TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV.里面包含有TTY_DRIVER_DYNAMIC_DEV标志.结合之前对tty的分析.如果包含有这个标志,是不会在初始化的时候去注册device.也就是说在/dev/下没有动态生成结点(如果是/dev下静态创建了这个结点就另当别论了^_^).
流程图如下:


五: uart_add_one_port()操作
在前面提到.在对uart设备文件过程中.会将操作转换到对应的port上,这个port跟uart_driver是怎么关联起来的呢?这就是uart_add_ont_port()的主要工作了.
顾名思义,这个函数是在uart_driver增加一个port.代码如下:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
struct uart_state *state;
int ret = 0;
struct device *tty_dev;
BUG_ON(in_interrupt());
if (port->line >= drv->nr)
return -EINVAL;
state = drv->state + port->line;
mutex_lock(&port_mutex);
mutex_lock(&state->mutex);
if (state->port) {
ret = -EINVAL;
goto out;
}
state->port = port;
state->pm_state = -1;
port->cons = drv->cons;
port->info = state->info;
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
spin_lock_init(&port->lock);
lockdep_set_class(&port->lock, &port_lock_key);
}
uart_configure_port(drv, state, port);
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this ports parameters.
*/
tty_dev = tty_register_device(drv->tty_driver, port->line, port->dev);
if (likely(!IS_ERR(tty_dev))) {
device_can_wakeup(tty_dev) = 1;
device_set_wakeup_enable(tty_dev, 0);
} else
printk(KERN_ERR "Cannot register tty device on line %d\n",
port->line);
/*
* Ensure UPF_DEAD is not set.
*/
port->flags &= ~UPF_DEAD;
out:
mutex_unlock(&state->mutex);
mutex_unlock(&port_mutex);
return ret;
}
首先这个函数不能在中断环境中使用. Uart_port->line就是对uart设备文件序号.它对应的也就是uart_driver->state数组中的uart_port->line项.
它主要初始化对应uart_driver->state项.接着调用uart_configure_port()进行port的自动配置.然后注册tty_device.如果用户空间运行了udev或者已经配置好了hotplug.就会在/dev下自动生成设备文件了.
操作流程图如下所示:

六:设备节点的open操作
在用户空间执行open操作的时候,就会执行uart_ops->open. Uart_ops的定义如下:
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.read_proc = uart_read_proc,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
};
对应open的操作接口为uart_open.代码如下:
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
struct uart_state *state;
int retval, line = tty->index;
BUG_ON(!kernel_locked());
pr_debug("uart_open(%d) called\n", line);
/*
* tty->driver->num won't change, so we won't fail here with
* tty->driver_data set to something non-NULL (and therefore
* we won't get caught by uart_close()).
*/
retval = -ENODEV;
if (line >= tty->driver->num)
goto fail;
/*
* We take the semaphore inside uart_get to guarantee that we won't
* be re-entered while allocating the info structure, or while we
* request any IRQs that the driver may need. This also has the nice
* side-effect that it delays the action of uart_hangup, so we can
* guarantee that info->tty will always contain something reasonable.
*/
state = uart_get(drv, line);
if (IS_ERR(state)) {
retval = PTR_ERR(state);
goto fail;
}
/*
* Once we set tty->driver_data here, we are guaranteed that
* uart_close() will decrement the driver module use count.
* Any failures from here onwards should not touch the count.
*/
tty->driver_data = state;
tty->low_latency = (state->port->flags & UPF_LOW_LATENCY) ? 1 : 0;
tty->alt_speed = 0;
state->info->tty = tty;
/*
* If the port is in the middle of closing, bail out now.
*/
if (tty_hung_up_p(filp)) {
retval = -EAGAIN;
state->count--;
mutex_unlock(&state->mutex);
goto fail;
}
/*
* Make sure the device is in D0 state.
*/
if (state->count == 1)
uart_change_pm(state, 0);
/*
* Start up the serial port.
*/
retval = uart_startup(state, 0);
/*
* If we succeeded, wait until the port is ready.
*/
if (retval == 0)
retval = uart_block_til_ready(filp, state);
mutex_unlock(&state->mutex);
/*
* If this is the first open to succeed, adjust things to suit.
*/
if (retval == 0 && !(state->info->flags & UIF_NORMAL_ACTIVE)) {
state->info->flags |= UIF_NORMAL_ACTIVE;
uart_update_termios(state);
}
fail:
return retval;
}
在这里函数里,继续完成操作的设备文件所对应state初始化.现在用户空间open这个设备了.即要对这个文件进行操作了.那uart_port也要开始工作了.即调用uart_startup()使其进入工作状态.当然,也需要初始化uart_port所对应的环形缓冲区circ_buf.即state->info-> xmit.
特别要注意,在这里将tty->driver_data = state;这是因为以后的操作只有port相关了,不需要去了解uart_driver的相关信息.
跟踪看一下里面调用的两个重要的子函数. uart_get()和uart_startup().先分析uart_get().代码如下:
static struct uart_state *uart_get(struct uart_driver *drv, int line)
{
struct uart_state *state;
int ret = 0;
state = drv->state + line;
if (mutex_lock_interruptible(&state->mutex)) {
ret = -ERESTARTSYS;
goto err;
}
state->count++;
if (!state->port || state->port->flags & UPF_DEAD) {
ret = -ENXIO;
goto err_unlock;
}
if (!state->info) {
state->info = kzalloc(sizeof(struct uart_info), GFP_KERNEL);
if (state->info) {
init_waitqueue_head(&state->info->open_wait);
init_waitqueue_head(&state->info->delta_msr_wait);
/*
* Link the info into the other structures.
*/
state->port->info = state->info;
tasklet_init(&state->info->tlet, uart_tasklet_action,
(unsigned long)state);
} else {
ret = -ENOMEM;
goto err_unlock;
}
}
return state;
err_unlock:
state->count--;
mutex_unlock(&state->mutex);
err:
return ERR_PTR(ret);
}
从代码中可以看出.这里注要是操作是初始化state->info.注意port->info就是state->info的一个副本.即port直接通过port->info可以找到它要操作的缓存区.
uart_startup()代码如下:
static int uart_startup(struct uart_state *state, int init_hw)
{
struct uart_info *info = state->info;
struct uart_port *port = state->port;
unsigned long page;
int retval = 0;
if (info->flags & UIF_INITIALIZED)
return 0;
/*
* Set the TTY IO error marker - we will only clear this
* once we have successfully opened the port. Also set
* up the tty->alt_speed kludge
*/
set_bit(TTY_IO_ERROR, &info->tty->flags);
if (port->type == PORT_UNKNOWN)
return 0;
/*
* Initialise and allocate the transmit and temporary
* buffer.
*/
if (!info->xmit.buf) {
page = get_zeroed_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
info->xmit.buf = (unsigned char *) page;
uart_circ_clear(&info->xmit);
}
retval = port->ops->startup(port);
if (retval == 0) {
if (init_hw) {
/*
* Initialise the hardware port settings.
*/
uart_change_speed(state, NULL);
/*
* Setup the RTS and DTR signals once the
* port is open and ready to respond.
*/
if (info->tty->termios->c_cflag & CBAUD)
uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
}
if (info->flags & UIF_CTS_FLOW) {
spin_lock_irq(&port->lock);
if (!(port->ops->get_mctrl(port) & TIOCM_CTS))
info->tty->hw_stopped = 1;
spin_unlock_irq(&port->lock);
}
info->flags |= UIF_INITIALIZED;
clear_bit(TTY_IO_ERROR, &info->tty->flags);
}
if (retval && capable(CAP_SYS_ADMIN))
retval = 0;
return retval;
}
在这里,注要完成对环形缓冲,即info->xmit的初始化.然后调用port->ops->startup( )将这个port带入到工作状态.其它的是一个可调参数的设置,就不详细讲解了.
七:设备节点的write操作
Write操作对应的操作接口为uart_write( ).代码如下:
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 || !state->info) {
WARN_ON(1);
return -EL3HLT;
}
port = state->port;
circ = &state->info->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);
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;
}
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty);
return ret;
}
Uart_start()代码如下:
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->port;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
__uart_start(tty);
spin_unlock_irqrestore(&port->lock, flags);
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->port;
if (!uart_circ_empty(&state->info->xmit) && state->info->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
port->ops->start_tx(port);
}
显然,对于write操作而言,它就是将数据copy到环形缓存区.然后调用port->ops->start_tx()将数据写到硬件寄存器.
八:Read操作
Uart的read操作同Tty的read操作相同,即都是调用ldsic->read()读取read_buf中的内容.有对这部份内容不太清楚的,参阅<< linux设备模型之tty驱动架构>>.
九:小结
本小节是分析serial驱动的基础.在理解了tty驱动架构之后,再来理解uart驱动架构应该不是很难.随着我们在linux设备驱动分析的深入,越来越深刻的体会到,linux的设备驱动架构很多都是相通的.只要深刻理解了一种驱动架构.举一反三.也就很容易分析出其它架构的驱动了.

原创粉丝点击