×
单片机 > 单片机程序设计 > 详情

Linux下的串口总线驱动(二)

发布时间:2020-08-31 发布时间:
|
四.TTY层内核代码

TTY驱动程序有三种:控制台、串口和pty。在此我们主要分析Mini2440串口驱动。

我们现在跟踪uart_register_driver和uart_add_one_port发现,他们的原函数定义在TTY层驱动serial_core.o中。

int uart_register_driver(struct uart_driver *drv)

{

       struct tty_driver *normal = NULL;

       int i, retval;

       BUG_ON(drv->state);

      

       drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

       retval = -ENOMEM;

       if (!drv->state)

              goto out;

       normal  = alloc_tty_driver(drv->nr);  //分配TTY驱动

       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;        //初始的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);  //设置TTY驱动操作

       for (i = 0; i < drv->nr; i++) {       //初始化UART状态

              struct uart_state *state = drv->state + i;

              struct tty_port *port = &state->port;

              tty_port_init(port);

              port->close_delay     = 500;     

              port->closing_wait    = 30000;  

              tasklet_init(&state->tlet, uart_tasklet_action,

                          (unsigned long)state);

       }

       retval = tty_register_driver(normal);  //注册TTY驱动

 out:

       if (retval < 0) {

              put_tty_driver(normal);

              kfree(drv->state);

       }

       return retval;

}

 

在上面uart_register_driver这个函数里我们首先分配了TTY驱动,然后对其进行填充,初始的termios,并设置TTY驱动操作,最后注册TTY驱动。其中设置TTY驱动操作时用到uart_ops,我们看看这个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,

       .set_ldisc = uart_set_ldisc,

       .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

       .proc_fops      = &uart_proc_fops,

#endif

       .tiocmget = uart_tiocmget,

       .tiocmset = uart_tiocmset,

#ifdef CONFIG_CONSOLE_POLL

       .poll_init  = uart_poll_init,

       .poll_get_char = uart_poll_get_char,

       .poll_put_char = uart_poll_put_char,

#endif

};

 

 

终端设备可以完成收发数据的功能,当用户在有数据发送给终端设备时候,通过”write()系统调用—tty核心—线路规程”的层层调用,最终调用tty_driver结构体中的write()函数完成发送。因为传输速度和tty硬件缓冲区容量的原因,不是所有的写程序要求的字符都可以在调用写函数时候被发送出去,因此,写函数应当返回能够发给硬件的字节数以便用户程序检查是否所有的数据被真正写入。如果在write()调用期间发生任何错误,一个负的错误码应当被返回。在上面的uart_ops结构体中,我们先看看写函数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;

       if (!state) {

              WARN_ON(1);

              return -EL3HLT;

       }

       port = state->uart_port;   //UART端口

       circ = &state->xmit;  //数据缓冲区

       if (!circ->buf)

              return 0;

       spin_lock_irqsave(&port->lock, flags);  //获取UART端口操作的锁

       while (1) {

              //返回可用缓存空间的大小

              c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);

              if (count < c)

                     c = count;

              if (c <= 0)  //缓存区太小则退出

                     break;

//将用户空间buf中大小为c的内容拷贝到缓存中

              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端口操作的锁

       uart_start(tty);  //开始发送

       return ret;

}

 

根据上面对uart_write的分析,我们知道tty_driver的write()函数接收三个参数tty_struct,发送数据指针和要发送的字节数。uart_state作为这个驱动tty的私有数据,其中circ_buf定义了缓冲区,我们向这个缓冲区拷贝待发送的内容后,执行uart_start(tty)进行发送数据。那我们继续看跟踪uart_start函数

static void uart_start(struct tty_struct *tty)

{

       struct uart_state *state = tty->driver_data;

       struct uart_port *port = state->uart_port;

       unsigned long flags;

       spin_lock_irqsave(&port->lock, flags);  //获取UART端口操作的锁

       __uart_start(tty); 

       spin_unlock_irqrestore(&port->lock, flags); //释放UART端口操作的锁

}

 

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_ops下的start_tx,即s3c24xx_serial_start_tx

}

 

注意__uart_start函数中的port->ops->start_tx(port)便实现了tty层和uart层的相连,由tty层的write()调用uart层的write()。

 

好了,上面讲的是发送数据,读者可能注意到struct tty_operations uart_ops中没有提到read()函数。因为发送是用户主动的,而接收拾用户调用read()从一片缓冲区读取已经放好的数据,这个缓冲区由struct tty_flip_buffer结构体实现。因为tty核提供了这样的缓冲逻辑,所以每个tty驱动并非一定要实现它自身的缓冲逻辑。Tty驱动不需要关注struct tty_flip_buffe的细节,从tty驱动接收到的来自硬件层的字符将被tty_insert_filp_char()函数插入filp缓冲区。如果传输的字节数count大于或等于TTY_FLIPBUF_SIEZE,这个flip缓冲区就需要被刷新到用户,刷新是通过调用tty_flip_buffer_push()实现的。

 

接着,我们继续看struct tty_operations uart_ops中对termios的设置函数set_termios,即uart_set_termios。这个set_termios需要根据用户对termios的设置完成实际的硬件设置。新的设置被保存在tty_struct中,旧的设置被保存在old参数中,若新旧参数相同,则什么都不需要做,对于被改的设置,需要完成硬件上的设置。好了,下面我们还是看看uart_set_termios的实现吧。

static void uart_set_termios(struct tty_struct *tty,

                                          struct ktermios *old_termios)

{

       struct uart_state *state = tty->driver_data;  //获取私有数据

       unsigned long flags;

       unsigned int cflag = tty->termios->c_cflag;  //获取当前线路设置

#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

//如果新旧线路设置的控制状态,输入输出速度等信息一样,则退出

       if ((cflag ^ old_termios->c_cflag) == 0 &&

           tty->termios->c_ospeed == old_termios->c_ospeed &&

           tty->termios->c_ispeed == old_termios->c_ispeed &&

           RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) {

              return;

       }

       uart_change_speed(state, old_termios);  //用新的线路规程的速度更新旧的线路规程

      //处理波特率为B0情况

       if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))

              uart_clear_mctrl(state->uart_port, TIOCM_RTS | TIOCM_DTR);

       /处理波特率为非B0情况

       if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {

              unsigned int mask = TIOCM_DTR;

              if (!(cflag & CRTSCTS) ||

                  !test_bit(TTY_THROTTLED, &tty->flags))

                     mask |= TIOCM_RTS;

              uart_set_mctrl(state->uart_port, mask); //设置modem控制

       }

       //处理无数据流控制情况

       if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {

              spin_lock_irqsave(&state->uart_port->lock, flags);

              tty->hw_stopped = 0;

              __uart_start(tty);

              spin_unlock_irqrestore(&state->uart_port->lock, flags);

       }

       //处理有数据流控制情况

       if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {

              spin_lock_irqsave(&state->uart_port->lock, flags);

              if (!(state->uart_port->ops->get_mctrl(state->uart_port) & TIOCM_CTS)) {

                     tty->hw_stopped = 1;

                     state->uart_port->ops->stop_tx(state->uart_port);

              }

              spin_unlock_irqrestore(&state->uart_port->lock, flags);

       }

}

 

好了我们已经讲解了write,set_termiox,下面我们讲讲tiocmget和tiocmset。Tiocmget()函数用于获取tty设备的线路设置,对应的tiocmset()用于设置tty设备的线路设置。

static int uart_tiocmget(struct tty_struct *tty, struct file *file)

{

       struct uart_state *state = tty->driver_data;

       struct tty_port *port = &state->port;

       struct uart_port *uport = state->uart_port;

       int result = -EIO;

       mutex_lock(&port->mutex); //获取对tty_port操作的锁

       if ((!file || !tty_hung_up_p(file)) &&

           !(tty->flags & (1 << TTY_IO_ERROR))) {

              result = uport->mctrl;

              spin_lock_irq(&uport->lock);

              result |= uport->ops->get_mctrl(uport);  //调用UART层get_mctrl获取modem控制

              spin_unlock_irq(&uport->lock);

       }

       mutex_unlock(&port->mutex); //释放对tty_port操作的锁

       return result;

}

 

static int uart_tiocmset(struct tty_struct *tty, struct file *file,

             unsigned int set, unsigned int clear)

{

       struct uart_state *state = tty->driver_data;

       struct uart_port *uport = state->uart_port;

       struct tty_port *port = &state->port;

       int ret = -EIO;

       mutex_lock(&port->mutex);  //获取对tty_port操作的锁

       if ((!file || !tty_hung_up_p(file)) &&

           !(tty->flags & (1 << TTY_IO_ERROR))) {

              uart_update_mctrl(uport, set, clear);  //获取modem控制

              ret = 0;

       }

       mutex_unlock(&port->mutex);  //释放对tty_port操作的锁

       return ret;

}

 

上面uart_tiocmset中调用了uart_update_mctrl(uport, set, clear)函数,它最终是通过调用port->ops->set_mctrl(port, port->mctrl)完成,而set_mctrl在UART层的uart_ops实现了。

综上,TTY层的ops中的uart_tiocmget和uart_tiocmset其实最终是调用UART层uart_ops中的get_mctrl和set_mctrl实现的。

 

当用户在tty设备节点上进行ioctl调用时,tty_operations中的ioctl()函数会被tty核心调用。我们接下来看看struct tty_operations uart_ops下的.ioctl也就是uart_ioctl。

static int uart_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd,

          unsigned long arg)

{

       struct uart_state *state = tty->driver_data;

       struct tty_port *port = &state->port;

       void __user *uarg = (void __user *)arg;

       int ret = -ENOIOCTLCMD;

       switch (cmd) { //这些ioctl不依赖硬件

       case TIOCGSERIAL:    //获得串口线信息

              ret = uart_get_info(state, uarg);

              break;

       case TIOCSSERIAL: //设置串口线信息

              ret = uart_set_info(state, uarg);

              break;

       case TIOCSERCONFIG: //自动配置

              ret = uart_do_autoconfig(state);

              break;

       case TIOCSERGWILD:

       case TIOCSERSWILD:

              ret = 0;

              break;

       }

       if (ret != -ENOIOCTLCMD)

              goto out;

       if (tty->flags & (1 << TTY_IO_ERROR)) {

              ret = -EIO;

              goto out;

       }

       switch (cmd) { //这些ioctl依赖硬件

       case TIOCMIWAIT:  //等待MSR改变

              ret = uart_wait_modem_status(state, arg);

              break;

       case TIOCGICOUNT:   //获得中断计数

              ret = uart_get_count(state, uarg);

              break;

       }

       if (ret != -ENOIOCTLCMD)

              goto out;

       mutex_lock(&port->mutex);

       if (tty_hung_up_p(filp)) {

              ret = -EIO;

              goto out_up;

       }

       switch (cmd) { //这些ioctl依赖硬件,并且需要保护,房子tty被挂起

       case TIOCSERGETLSR:  //获得这个tty设备的线路状态寄存器LSR的值

              ret = uart_get_lsr_info(state, uarg);

              break;

       default: {

              struct uart_port *uport = state->uart_port;

              if (uport->ops->ioctl)

                     ret = uport->ops->ioctl(uport, cmd, arg);

              break;

       }

       }

out_up:

       mutex_unlock(&port->mutex);

out:

       return ret;

}

当TTY核心想知道由TTY驱动程序提供的可用写入缓冲区的大小时,就会调用write_room。在清空写缓冲区,或者调用write函数向缓冲区添加数据时,该值是变化的。接下来我们看看TTY层ops中write_room,也就是uart_write_room。跟踪发现其实这个函数实现主要是把缓冲区头尾相减得到剩余空间大小。

除了write_room外,还有其他一些缓冲函数,例如TTY层ops中chars_in_buffer,也就是uart_chars_in_buffer,当tty核心在tty驱动程序的写缓冲区中还有多少个需要传输的字符时调用该函数。

除此之外TTY层ops中还有三个回调函数,用来刷新驱动程序保存的任何数据,并不一定要实现,但是如果tty驱动程序能在发送给硬件前缓冲数据,还是推荐实现它们的,它们分别是flush_buffer,wait_until_sent,flush_buffer。

回顾一下,我们在TTY层的ops中,主要讲了write,set_termiox, tiocmget,tiocmset,ioctl,五个函数,还简单介绍了write_room,chars_in_buffer,flush_buffer,wait_until_sent,flush_buffer五个函数。到目前为止,我们已经分析好了uart_register_driver函数,现在该分析uart_add_one_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;

       BUG_ON(in_interrupt());

       if (uport->line >= drv->nr)

              return -EINVAL;

       state = drv->state + uport->line;

       port = &state->port;

       mutex_lock(&port_mutex);

       mutex_lock(&port->mutex);

       if (state->uart_port) {

              ret = -EINVAL;

              goto out;

       }

       state->uart_port = uport;

       state->pm_state = -1;

       uport->cons = drv->cons;

       uport->state = state;

       //如果这个端口是控制台,那么这个锁就已经初始化了

       if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {

              spin_lock_init(&uport->lock);

              lockdep_set_class(&uport->lock, &port_lock_key);

       }

       uart_configure_port(drv, state, uport);  //配置端口

       tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);//注册端口

       if (likely(!IS_ERR(tty_dev))) {

              device_init_wakeup(tty_dev, 1);

              device_set_wakeup_enable(tty_dev, 0);

       } else

              printk(KERN_ERR "Cannot register tty device on line %d\n",

                     uport->line);

       //确保UPF_DEAD没有被置位

       uport->flags &= ~UPF_DEAD;

 out:

       mutex_unlock(&port->mutex);

       mutex_unlock(&port_mutex);

       return ret;

}

对于uart_add_one_port,我们发现其中最核心的一句代码就是tty_register_device,仅有tty_driver是不够的,驱动必须依附于设备,tty_register_device函数用于注册关联于tty_driver的设备。

 

总结下,TTY层的uart_register_driver和uart_register_port最终调用线路规程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的实现在线路规程中。

对于TTY驱动主要涉及如下几个重要结构体,struct tty_struct包含了和打开的tty相关的所有状态信息。其中一个重要的成员就是struct tty_bufhead buf,它是数据收集和处理机制的中枢,其定义如下

struct tty_bufhead {

       struct delayed_work work;

       spinlock_t lock;

       struct tty_buffer *head;

       struct tty_buffer *tail;   

       struct tty_buffer *free; 

       int memory_used;         

};

另一个重要结构体是struct tty_driver,它规定了tty驱动程序和高层之间的编程接口。在我们这个TTY层,由uart_register_driver下的tty_register_driver注册入内核,其中这个结构体中的成员部分是通过拷贝uart_driver中的参数得到。

 

好了,对于TTY层驱动,一般而言,我们需要完成如下两个任务:

其一,终端设备驱动模块的加载函数和卸载函数,完成注册和注销tty_driver,初始化和释放终端设备对应的tty_driver结构体成员和硬件资源。

其二,实现tty_operations结构体中的一系列成员函数,主要的是实现open()、close()、 write()、 tiocmget()、 tiocmset()。

关键字:Linux  串口  总线驱动 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
单片机中高阻态的实质及意义