您的位置 首页 主动

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

四.TTY层内核代码TTY驱动程序有三种:控制台、串口和pty。在此我们主要分析Mini2440串口驱动。我们现在跟踪uart_register_driver和uart_ad

四.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()。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ziliao/zhudong/262308.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部