linux串口驱动分析——发送数据
一、应用程序中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串口驱动分析——发送数据的更多相关文章
- linux串口驱动分析
linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...
- linux串口驱动分析——打开设备
串口驱动是由tty_driver架构实现的.一个应用程序中的函数要操作硬件,首先会经过tty,级级调用之后才会到达驱动之中.本文先介绍应用程序中打开设备的open函数的整个历程. 首先在串口初始化中会 ...
- linux串口驱动分析【转】
转自:http://blog.csdn.net/hanmengaidudu/article/details/11946591 硬件资源及描述 s3c2440A 通用异步接收器和发送器(UART)提供了 ...
- linux的串口驱动分析
1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver 驱动 • UART端口结构: struct uart_port 串口 • UART相关操作函数结构: st ...
- Smart210学习记录------linux串口驱动
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...
- Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】
本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...
- Linux spi驱动分析(二)----SPI核心(bus、device_driver和device)
一.spi总线注册 这里所说的SPI核心,就是指/drivers/spi/目录下spi.c文件中提供给其他文件的函数,首先看下spi核心的初始化函数spi_init(void).程序如下: 点击(此处 ...
- 基于335X的Linux网口驱动分析
基于335X的linux网口驱动分析 一. 系统构成 1. 硬件平台 AM335X 2. LINUX内核版本 4.4.12 二. 网口驱动构架(mdio部分) mdio网口驱动部分 使用 总线.设 ...
- linux 串口驱动(二)初始化 【转】
转自:http://blog.chinaunix.net/uid-27717694-id-3493611.html 8250串口的初始化: (1)定义uart_driver.uart_ops.uart ...
随机推荐
- Linux查看端口信息命令
netstat -tlnp|grep 端口 eg: netstat -tlnp|grep 9889
- android改动tab 导航 指示器颜色
我事实上想改动的上面的蓝色条条,改成红色. 这个问题实在是困扰我了太长时间.之前參照google的这个文章: https://developer.android.com/training/basics ...
- AutoCompleteTextView ArrayAdapter
AutoCompleteTextView 继承于EditText,拥有EditText所有属性和方法 在输入框中输入我们想要输入的信息就会出现其他与其相关的提示信息 首先在activity_mai ...
- python复制--笔记
对象引用: >>> songs = ["Bee","Core","Love"] >>> bat = so ...
- 06-自定义Attribute标记案例
自定义Attribute: 1)Attribute都从System. Attribute类继承,类名一般以Attribute结尾 2) 标记类的用途—AttributeUsage标记(标记的标记):A ...
- 小学生之Map集合框架的使用
Map用于保存具有映射关系的数据(key-vlaue).Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false Map中包含了一个keySet()方法, ...
- adb服务无法启动
今天学习android编程发现调试出错 The connection to adb is down, and a severe error has occured. You must restart ...
- asp.mvc获取checkbox、radio、select的值
记录一下在asp.mvc中,提交表单时后台获取checkbox.radio.select值的方法. 1.获取select的值 <select name="type"> ...
- ajax调用webservice(二) 跨域。
所需工具与项目结构同(一). service.asmx中代码如下: using System; using System.Collections.Generic; using System.Web; ...
- linux FILE 类型.
stdio.h 头文件中,有以下内容(用内部行号解释): /* The opaque type of streams. This is the definition used elsewhere. * ...