一、应用程序中write函数到底层驱动历程

  和前文提到的一样,首先先注册串口,使用uart_register_driver函数,依次分别为tty_register_driver,cdev_init函数,找到使用的file_operations,即应用程序与tty架构的统一接口。步骤不再赘述。

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_write函数

static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
...
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
...
}

  这里通过do_tty_write函数调用到了线路规程(ldisc)中的函数,结构名为tty_ldisc_N_TTY。 

struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};

  n_tty_write函数

static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = ;
  ...
  c = tty->ops->write(tty, b, nr);
  ...
}

  ops为struct tty_operations类型,由上文可知该结构名为

static const struct tty_operations uart_ops = {
...
.write = uart_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 = ;
  ...
  uart_start(tty);
  ...
}

  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);
}

  这里的port就是uart_port类型的了,终于到达底层驱动了,好累。。又是这个数组,同样的函数操作集

  

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX0,
.uartclk = ,
.fifosize = ,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = ,
}
},
  ...
}

  所以在底层驱动与之对应的就是s3c24xx_serial_start_tx这个函数。层层追溯下来,最终与应用程序中的write函数千里相会。。。

static void s3c24xx_serial_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port); if (!tx_enabled(port)) {
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port); enable_irq(ourport->tx_irq);
tx_enabled(port) = 1;
}
}

  但是要让各位看官失望了。这个函数很简单,功能上来说就是如果没有打开发送使能就去打开。那么串口驱动又是在什么地方去完成发送数据相关的操作呢?

二、底层驱动发送中断处理函数

  看到这个标题大家应该就明白了。start_tx函数只是用来打开发送中断,而真正进行发送操作的却是在中断处理函数中。在驱动文件中查找注册中断函数request_irq,最终找到了发送中断处理函数s3c24xx_serial_tx_chars,我们分段来看

struct s3c24xx_uart_port *ourport = id;
struct uart_port *port = &ourport->port;
struct circ_buf *xmit = &port->state->xmit;
int count = ; if (port->x_char) {
wr_regb(port, S3C2410_UTXH, port->x_char);
port->icount.tx++;
port->x_char = ;
goto out;
}

  首先定义了一些结构和变量。接下来一个if分支,判断是否有需要发送的x_char。x_char用于两个硬件之间的通信,若接收方需要接收数据则向发送方发送x_on,否则发送x_off。只需将该变量写入寄存器UTXH中即可发送。当然在发送完毕后要将其置零。

if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
s3c24xx_serial_stop_tx(port);
goto out;
}

  检查循环缓冲是否为空或停止了串口发送,满足任一条件即关闭发送使能。

while (!uart_circ_empty(xmit) && count-- > ) {
if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
break; wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + ) & (UART_XMIT_SIZE - );
port->icount.tx++;
}

  这段就是名副其实的发送函数了。首先看循环条件:其一,循环缓冲不能为空。这是自然,因为发送的数据是从循环缓冲中取得,空了还怎么发。其二:count自减结果不能小于0。在前面定义了count初值为256,也就是说一次中断最多能发送256个数据量。这是linux的一种保护机制,如果说待发送的数据过多,那么就会长时间停留在这个中断中,linux不能做其他事情,显然这是不合理的。进入循环体,又是一个判断分支。UFSTAT寄存器是记录发送FIFO状态的寄存器,ourport->info->tx_fifofull在linux内核中查出它的值是(1<<14)

  

也就是说,当发送FIFO中数据存满时,也要退出循环。接下来就是将循环缓冲中的数据写入发送寄存器UTXH,在调整tail指针的位置。注意由于tail和head的值不能超过循环缓冲的空间,所以超出时将其置零。

if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port); if (uart_circ_empty(xmit))
s3c24xx_serial_stop_tx(port); out:
return IRQ_HANDLED;

  第一个分支,如果循环缓冲中的数据量小于WAKEUP_CHARS(256),则唤醒之前向循环缓冲中写入数据的函数。第二个分支,如果循环缓冲为空,则停止发送。

  这就是整个发送数据的函数。

三、补充

  1、循环缓冲

  循环缓冲是linux内核定义的一种数据结构

struct circ_buf {
char *buf;
int head;
int tail;
};

  工作机制:

    收入n个数据,head=head+n,发送n个数据,tail=tail+n,相当于一个循环队列,先进先出。  

  write函数中的数据并不是直接写入UTXH寄存器中,而是先写入循环缓冲。具体实现是在uart_write函数中。

  memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - );

  这两句代码将buf中的数据写入循环缓冲中,并修改了head的位置。

  2、发送FIFO

  为了处理器的工作效率,发送出的数据也不是直接到达相应的硬件,而是存入发送FIFO中,当处理器闲置时在进行发送。

至此,linux串口驱动程序发送数据的实现已分析完毕。如果有疑问或错误,欢迎指出。

linux串口驱动分析——发送数据的更多相关文章

  1. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  2. linux串口驱动分析——打开设备

    串口驱动是由tty_driver架构实现的.一个应用程序中的函数要操作硬件,首先会经过tty,级级调用之后才会到达驱动之中.本文先介绍应用程序中打开设备的open函数的整个历程. 首先在串口初始化中会 ...

  3. linux串口驱动分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/11946591 硬件资源及描述 s3c2440A 通用异步接收器和发送器(UART)提供了 ...

  4. linux的串口驱动分析

    1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver  驱动 • UART端口结构: struct uart_port  串口 • UART相关操作函数结构: st ...

  5. Smart210学习记录------linux串口驱动

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...

  6. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

  7. Linux spi驱动分析(二)----SPI核心(bus、device_driver和device)

    一.spi总线注册 这里所说的SPI核心,就是指/drivers/spi/目录下spi.c文件中提供给其他文件的函数,首先看下spi核心的初始化函数spi_init(void).程序如下: 点击(此处 ...

  8. 基于335X的Linux网口驱动分析

    基于335X的linux网口驱动分析 一. 系统构成 1.  硬件平台 AM335X 2.  LINUX内核版本 4.4.12 二. 网口驱动构架(mdio部分) mdio网口驱动部分 使用 总线.设 ...

  9. linux 串口驱动(二)初始化 【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3493611.html 8250串口的初始化: (1)定义uart_driver.uart_ops.uart ...

随机推荐

  1. [Docker] Docker Client in Action

    Pull the docker image: docker pull hello-world Show all the images: docker images Remove the image: ...

  2. [AngularJS] ngPluralize

    ngPluralize is a directive that displays messages according to en-US localization rules. <script& ...

  3. Android四大组件——Activity

    Activity作为Android四大组件之一,也是其中最重要的一个组件.作为一个与用户交互的组件,我们可以把Activity比较成为windows系统上的一个文件夹窗口,是一个与用户交互的界面.再进 ...

  4. 如何完全退出android应用程序

    当一个android应用程序包含多个activity时,要完全退出android应用程序,便要销毁掉所有的activity,下面是一种网上流传的比较经典完美的方法: 首先要定义一个继承Applicat ...

  5. Sorting File Contents and Output with sort

     Sorting File Contents and Output with sort   Another very useful command to use on text file is  so ...

  6. mockServer学习

    mockServer学习 很喜欢mockserver官方主页的背景颜色和格式 官方主页如下: http://www.mock-server.com/

  7. C# Html网页生成图片解决方案1

    1.使用System.Windows.Forms命名空间下的WebBrowser控件加载网页并生成图片 GiHub参考地址: https://github.com/tianma3798/FileOpa ...

  8. form表单提交

    1.form表单提交.html页面失败 <%--客户端form--%> <form id="form2" action="LoginOne.html&q ...

  9. Debugging Failed Because Integrated Windows Authentication Is Not Enabled

    To enable integrated Windows authentication Log onto the Web server using an administrator account. ...

  10. mysql学习(用户权限管理)

    1. 添加数据库用户 create user 'username'@'host' identified by 'password'; 提示: 如果想让该用户可以从其他主机登陆,host可以设置为'%' ...