文章目录
前言
最近分析了I.MX6ULL的UART驱动源码,查了很多资料,终于算是了解了其中大部分内容,所以想在这里记下笔记分享给大家。但是由于自身能力有限,难免会有一些地方错误,希望大家谅解。如果有不对的地方,大家可以在评论区指出,我会虚心改正。最后,在分析这份源码时,重点参考了一口Linux公众号的文章,这份笔记用的图也是该公众号的图,大家可以去看下,里面很多文章都写的很棒。
一、I.MX6ULL串口接收和发送方式
UART驱动框架最核心的工作是实现串口的接收和发送,而串口的接收和发送无非跟硬件的底层相关。所以,先了解下I.MX6ULL底层串口实现接发收方式对整个源码理解是有很大帮助的。而串口实现接收和发送主要有两种方式,一种是非DMA方式,一种是DMA方式。可能看了下面这两种方式有点迷迷糊糊,但是没关系,等后面分析了源码再回来看,就比较清晰了。
1.非DMA方式
1.1.接收方式
1、设置uart的rxfifo中断方式,即rxfifo接收到一定数量的字节数据后产生一次USR1_RRDY中断。在imx_setup_ufcr()函数中完成设置。
2、设置uart的rxfifo空闲中断方式,即rxfifo已空闲8个字符长度的时间没有接收到数据就会产生一次USR1_AGTIM中断。(这个例程没有采取这种方式)
1.2 发送方式
1、设置uart的txfifo中断方式,即txfifo少于一定数量的字节数据后产生一次USR1_TRDY中断。在imx_setup_ufcr()函数中完成设置。但该方式还需要使能UCR1_TXMPTYEN(txfifo空中断)位。
在以上方式中,接收和发送都会产生串口中断,调用imx_int中断函数,对于接收中断,执行imx_rxint()函数完成数据接收;对于发送中断,执行imx_txint()函数完成数据发送。
2.DMA方式
2.1.接收方式
1、初始化串口接收DMA。首先初始化DMA接收通道,再初始化串口接收缓冲区。在imx_uart_dma_init()函数中完成。
2、串口DMA使能。使能接收DMA请求,DMA空闲状态检测和DMA空闲中断。即RX DMA buffer存在数据且超过32帧时间没有再接收到数据就会产生一次DMA请求中断。在imx_enable_dma()函数中完成。
3、串口DMA接收启动。设置串口DMA接收完成回调函数和回调函数触发方式。回调函数触发方式有两种,一种是DMA空闲中断(AGTIM DMA请求),一种是DMA传输一定数量数据后,触发一次回调函数(接收DMA请求)。在start_rx_dma()函数中完成。
2.2 发送方式
1、初始化串口DMA。初始化DMA发送通道。在imx_uart_dma_init()函数中完成。
2、串口DMA使能。使能发送DMA请求。在imx_enable_dma()函数中完成。
3、串口DMA发送启动。设置串口DMA发送完成回调函数。将发送缓冲区映射到连续的虚拟地址,再通过串口DMA进行传输,传输完成后产生DMA发送请求中断,最后会调用该回调函数。在dma_tx_work()函数中完成。
二、UART驱动注册
我用的是正点原子提供的内核源码。I.MX6ULL的驱动源码路径在drivers\tty\serial\imx.c下。源码分析时,我会把重点的函数和重点的内容讲解,否则整个讲解下来太繁琐。建议大家能对着源码看。注册流程如下图所示:
1.uart_register_driver()函数解析
代码如下:(我只把程序重点部分提取了出来,主要是为了告诉这个函数主要做了什么工作,注释都在每行代码后面写清楚了,后面的函数解析也是一样)
int uart_register_driver(struct uart_driver *drv)
{
/* 分配内存空间 */
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); //申请nr个uart_state空间
normal = alloc_tty_driver(drv->nr); //分配并初始化tty driver
drv->tty_driver = normal; //将tty_driver赋值给drv->tty_driver进行管理
/* tty driver初始化 */
normal->driver_name = drv->driver_name; //tty driver名设置为("IMX-uart")
normal->name = drv->dev_name; //tty driver名设置为("ttymxc")
normal->major = drv->major; //tty driver主设备号设置为(207)
normal->minor_start = drv->minor; //tty driver次设备号起始设置为(16)
normal->type = TTY_DRIVER_TYPE_SERIAL; //tty driver类型设置为TTY_DRIVER_TYPE_SERIAL
normal->subtype = SERIAL_TYPE_NORMAL; //tty driver子类型设置为SERIAL_TYPE_NORMAL
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; //波特率9600|字符长度8|使用接收器|关闭设备时挂起|忽略调制解调器线路状态
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; //c_ispeed=0
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; //|动态设备管理
normal->driver_state = drv; //tty driver和uart driver互相绑定
tty_set_operations(normal, &uart_ops); //设置tty driver的操作集(该操作集由tty字符设备操作集调用)
/* 初始化tty port */
tty_port_init(port); //初始化tty port,tty port处于uart_state中
port->ops = &uart_port_ops; //设置tty port的操作集
/* 注册tty driver */
retval = tty_register_driver(normal); //注册tty_driver字符设备,并将新注册的tty driver添加到tty_drivers链表上
}
1、 init_termios的结构类型为ktermios ,该结构体我的理解为是与控制终端的输入输出有关,关于该结构体各个参数的介绍可以参考链接:参考链接
2、 tty_register_driver函数完成了字符设备的创建,该字符设备使用的文件操作集如下:
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_driver挂接到tty_drivers全局链表上进行管理。
3、 操作集调用顺序,如应用层向串口写数据,先是在应用层调用write函数,再在write函数中调用tty_fops操作集中的tty_write函数,再在tty_write函数调用uart_ops操作集中的uart_write函数,最后再调用imx_pops操作集中的相关控制串口的函数。
2.serial_imx_probe()函数解析
static int serial_imx_probe(struct imx_port *sport, struct platform_device *pdev)
{
/* serial_imx_probe_dt()函数中完成 */
sport->port.line = ret; //记录该设备节点的串口设备号
sport->have_rtscts = 0; //imx6ul没有RTS和CTS, 所以此处为0
sport->dte_mode = 0; //不支持数据终端设备,所以此处为0
sport->devdata = of_id->data; //将匹配表兼容的那一项附带的数据交给devdata(也就是测试寄存器偏移地址和串口类型)
/* uart port初始化 */
sport->port.dev = &pdev->dev; //将平台设备的device结构体赋值给uart_port->dev
sport->port.mapbase = res->start; //将串口的起始地址(物理地址)赋值给uart_port->mapbase
sport->port.membase = base; //将串口的起始地址(虚拟地址)赋值给uart_port->membase
sport->port.type = PORT_IMX, //表示该串口类型为IMX端口
sport->port.iotype = UPIO_MEM; //串口接口寄存器的地址类型为8位的内存地址
sport->port.irq = rxirq; //记录串口的fifo溢出中断号
sport->port.fifosize = 32; //串口接收发送fifo大小为32
sport->port.ops = &imx_pops; //该串口设备使用的操作设备集为imx_pops,
sport->port.rs485_config = imx_rs485_config; //485接口配置函数
sport->port.rs485.flags = SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
sport->port.flags = UPF_BOOT_AUTOCONF; //自动探测串口类型
init_timer(&sport->timer); //初始化定时器
sport->timer.function = imx_timeout; //定时器超时后调用的函数
sport->timer.data = (unsigned long)sport; //定时器超时函数的传入参数
sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); //解析设备树,得到串口的ipg时钟
sport->clk_per = devm_clk_get(&pdev->dev, "per"); //解析设备树,得到串口的per时钟
sport->port.uartclk = clk_get_rate(sport->clk_per); //得到串口时钟频率
devm_request_irq(&pdev->dev, rxirq, imx_int, 0, dev_name(&pdev->dev), sport); //注册串口中断函数imx_int
return uart_add_one_port(&imx_reg, &sport->port); //sport->port就代表了一个串口设备
}
3.uart_add_one_port()函数解析
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
uport->minor = drv->tty_driver->minor_start + uport->line; //设置该串口设备的次设备号
/* 1、串口类型设置为PORT_IMX
2、取消流控制和串口自检测 */
uart_configure_port(drv, state, uport); //串口配置
/*sysfs文件系统创建和字符设备创建 */
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL);
uport->tty_groups[0] = &tty_dev_attr_group; //sysfs文件系统属性操作集
tty_dev = tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups); //完成字符设备注册和sysfs文件系统注册
uport->flags &= ~UPF_DEAD; //表示串口可以开始工作
}
4.注册流程总结
1、uart_register_driver()函数中完成了tty driver字符设备的注册。
2、serial_imx_probe()函数中完成一些对串口参数的设置和填充,并注册了定时器中断回调函数imx_timeout和串口中断回调函数imx_int。
3、uart_add_one_port()函数中完成关于串口的sysfs文件系统创建。(看了代码,个人认为在这一步才在完成字符设备创建,在1中,并没有注册)
4、以上函数执行过后,uart_driver结构体各个参数如下所示(只把重要的写了出来):
struct uart_driver imx_reg =
{
.driver_name = "IMX-uart"; /*驱动串口名,串口设备名以驱动名为基础*/
.name = "ttymxc"; /*串口设备名*/
.nr = 8; /*该uart_driver支持的串口数*/
.tty_driver tty_driver[8] =
{
.major = 207; /*tty driver主设备号设置为(207)*/
.minor_start = 16; /*tty driver次设备号起始设置为(16)*/
.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; /* |动态设备管理*/
.tty_operations uart_ops = &uart_ops; /*tty driver的操作集(该操作集由tty字符设备操作集调用)*/
}
.uart_state state[8] =
{
.tty_port port[8] =
{
.tty_port_operations ops = &uart_port_ops; /*tty port的操作集*/
}
.uart_port uart_port[8]=
{
.type = PORT_IMX;
.state = uart_state;
.mapbase = /*串口寄存器起始物理地址*/
.membase = /*串口寄存器起始虚拟地址*/
.irq = platform_get_irq(pdev, 0); /*串口fifo溢出中断号*/
.fifosize = 32;
.uart_ops ops = &imx_pops; /*串口设备使用的操作设备集*/
.uartclk = clk_get_rate(sport->clk_per); /*串口时钟频率*/
.attribute_group tty_groups = &tty_dev_attr_group; /*sysfs文件系统属性操作集*/
.timeout = (HZ * bits) / baud + HZ/50; //计算串口发送以上所有字符长度时间再加上0.02s的时间间隙,当定时器发生中断,表示数据已经接收完成
}
.circ_buf xmit
{
.buf = get_zeroed_page(GFP_KERNEL); //串口发送缓冲区
}
}
}
三、打开设备(open操作)
uart_open函数的源码路径在drivers\tty\serial\serial_core.c下。open流程如下图所示:
1.uart_open()函数解析
static int uart_open(struct tty_struct *tty, struct file *filp)
{
/*uart_port_startup()函数中完成*/
page = get_zeroed_page(GFP_KERNEL); //开辟发送缓冲区
state->xmit.buf = (unsigned char *) page; //创建串口发送缓冲区
/* imx_startup()函数中完成 */
retval = clk_prepare_enable(sport->clk_per); //使能per时钟
retval = clk_prepare_enable(sport->clk_ipg); //使能ipg时钟
imx_setup_ufcr(sport, 0); //设置uart的fifo中断方式:当txfifo字节数小于等于2时产生一次fifo发送中断,rxfifo字节数为1时或者为32时,产生一次fifo接收中断
imx_uart_dma_init(sport); //初始化uart_dma
INIT_DELAYED_WORK(&sport->tsk_dma_tx, dma_tx_work); //初始化工作队列,DMA传输函数dma_tx_work
temp |= UCR1_UARTEN; //UART使能
temp |= (UCR2_RXEN | UCR2_TXEN); //使能串口接收和发送功能
imx_enable_ms(&sport->port); //启动定时器,立刻执行定时器中断函数imx_timeout
uart_change_speed(tty, state, NULL); //设置串口波特率等属性
}
2.imx_uart_dma_init()函数解析
static int imx_uart_dma_init(struct imx_port *sport)
{
struct dma_slave_config slave_config = {}; //大多数slave DMA使用到的通用信息都在结构体dma_slave_config中。它允许客户端对外设指定DMA的方向、DMA地址、总线宽度、DMA突发长度等等。
/* DMA接收通道配置 */
sport->dma_chan_rx = dma_request_slave_channel(dev, "rx"); //得到串口接收的dma通道
slave_config.direction = DMA_DEV_TO_MEM; //方向为设备到内存
slave_config.src_addr = sport->port.mapbase + URXD0; //源地址为串口接收寄存器
slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; //源地址总线宽度为8bit
slave_config.src_maxburst = RXTL_UART; //增量突发模式为16
ret = dmaengine_slave_config(sport->dma_chan_rx, &slave_config); //设置slave和controller特定的参数
/* 串口接收缓冲区初始化配置 */
sport->rx_buf.buf = dma_alloc_coherent(NULL, IMX_RXBD_NUM * RX_BUF_SIZE, //为DMA接收缓冲分配空间,并将首地址转换为虚拟地址,sport->rx_buf.dmaaddr是返回的内存物理地址,dma就可以用。
&sport->rx_buf.dmaaddr, GFP_KERNEL);
for (i = 0; i < IMX_RXBD_NUM; i++) {
sport->rx_buf.buf_info[i].rx_bytes = 0; //初始化当前内存空间接收字节数为0
sport->rx_buf.buf_info[i].filled = false; //初始化当前内存空间没被使用
}
/* DMA发送通道配置 */
sport->dma_chan_tx = dma_request_slave_channel(dev, "tx"); //得到串口发送的dma通道
slave_config.direction = DMA_MEM_TO_DEV; //方向为内存到设备
slave_config.dst_addr = sport->port.mapbase + URTX0; //目标地址为串口发送寄存器
slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; //目标地址总线宽度为8bit
slave_config.dst_maxburst = TXTL; //增量突发模式为2
ret = dmaengine_slave_config(sport->dma_chan_tx, &slave_config); //设置slave和controller特定的参数
sport->dma_is_inited = 1; //表示dma初始化成功
}
3.uart_change_speed()函数解析
static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, struct ktermios *old_termios)
{
/* imx_set_termios()函数中完成 */
ucr2 = UCR2_WS | UCR2_SRST | UCR2_IRTS; //8位发送和接收字符长度|重置发送、接收状态机和所有FIFO,并注册USR1,USR2,UBIR,UBMR,UBRC,URXD,UTXD和UTS [6-3]|忽略RTS引脚
del_timer_sync(&sport->timer); //删除定时器
baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16); //通过解码termios结构体来获取指定串口的波特率
quot = uart_get_divisor(port, baud); //用于计算某一波特率的串口时钟分频数(串口波特率除数)
uart_update_timeout(port, termios->c_cflag, baud); //用于更新(设置)串口FIFO超出时间
div = sport->port.uartclk / (baud * 16); //imx6ul波特率公式:BaudRate=Ref Freq/(16*(UBMR+1)/(UBIR+1)),这一步是在计算(UBMR+1)/(UBIR+1)的比值
rational_best_approximation(16 * div * baud, sport->port.uartclk,
1 << 16, 1 << 16, &num, &denom); //计算波特率的分频值,即denom代表UBMR的值,num代表UBIR的值
ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div); //设置分频
writel(ufcr, sport->port.membase + UFCR);
writel(num, sport->port.membase + UBIR); //设置UBIR
writel(denom, sport->port.membase + UBMR); //设置UBMR
/* 1、使能接收DMA请求|使能发送DMA请求|使能AGTIM DMA请求|空闲状态检测(超过32帧时间)
2、DMA空闲中断使能
3、sport->dma_is_enabled=1 */
imx_enable_dma(sport);
/* 1、设置DMA传输完成回调函数dma_rx_callback
2、设置DMA传输完成回调函数触发方式
3、sport->dma_is_rxing = 1 */
start_rx_dma(sport);
}
3.1 uart_update_timeout()函数解析
void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud)
{
bits = 10; //1个起始位+8个字符长度+1个停止位
bits = bits * port->fifosize; //fifo大小为32,一个fifo存10个bit,所以一共接收320bit
port->timeout = (HZ * bits) / baud + HZ/50; //计算串口发送以上所有字符长度时间再加上0.02s的时间间隙,当定时器发生中断,表示数据已经接收完成
}
3.2 start_rx_dma()函数解析
static int start_rx_dma(struct imx_port *sport)
{
sport->rx_buf.periods = IMX_RXBD_NUM; //接收buffer数量
sport->rx_buf.period_len = RX_BUF_SIZE; //每个buffer大小,DMA每传输period_len长度就会调用一次回调函数
sport->rx_buf.buf_len = IMX_RXBD_NUM * RX_BUF_SIZE; //dma总传输长度
sport->rx_buf.cur_idx = 0;
sport->rx_buf.last_completed_idx = -1;
desc = dmaengine_prep_dma_cyclic(chan, sport->rx_buf.dmaaddr, //获取一个接收描述符
sport->rx_buf.buf_len, sport->rx_buf.period_len, //rx_buf.buf_len为dma总传输长度,rx_buf.period_len为dma每传输period_len长度就会调用一次回调函数
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
desc->callback = dma_rx_callback; //设置dma接收完成回调函数
desc->callback_param = sport;
sport->rx_buf.cookie = dmaengine_submit(desc); //提交传输描述符,把传输描述符加入到DMA engine驱动的等待队列返回值是一个cookie,主要用来检查DMA engine活动的状态过程
dma_async_issue_pending(chan); //发起等待的请求并等待回调通知
sport->dma_is_rxing = 1; //表示dma处于接收状态
}
4.使用到的回调函数
以上函数使用到的回调函数:
imx_timeout; //定时器超时中断回调函数
imx_int //串口中断回调函数
dma_tx_work //启动串口DMA发送回调函数
dma_tx_callback //串口DMA发送完成回调函数
dma_rx_callback //串口DMA接收完成回调函数
关于imx_timeout函数,个人理解为是与流控控制相关的代码,而我没用过流控的串口设备,所以也就不清楚这部分原理。
5.设备打开流程总结
1、使能串口时钟,初始化发送缓冲区。
2、设置uart的fifo中断方式.
3、在imx_uart_dma_init()函数中初始化串口DMA。
4、初始化串口DMA发送工作队列。
5、串口使能,串口接收使能和串口发送使能。
6、串口波特率设置。
7、串口接收DMA使能。
8、串口DMA接收启动。
四、数据发送流程(write操作)
uart_write函数的源码路径在drivers\tty\serial\serial_core.c下。write流程如下图所示:
1.uart_write()函数解析
static int uart_write(struct tty_struct *tty,const unsigned char *buf, int count)
{
circ = &state->xmit;
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); //计算发送缓冲区的空闲空间
memcpy(circ->buf + circ->head, buf, c); //将应用层传来的数据拷贝到发送缓冲区空间中
/* imx_start_tx()函数中完成 */
schedule_delayed_work(&sport->tsk_dma_tx, 0); //调用串口DMA发送回调函数(dma_tx_work),延时时间为0,即立即执行
}
2.dma_tx_work()函数解析
static void dma_tx_work(struct work_struct *w)
{
if (xmit->tail > xmit->head && xmit->head > 0) {
sport->dma_tx_nents = 2; //表示有两个scatterlist
sg_init_table(sgl, 2); //初始化SG table,相当于此函数可以初始化多个scatterlist
sg_set_buf(sgl, xmit->buf + xmit->tail,UART_XMIT_SIZE - xmit->tail); //将xmit->tail以后的缓冲区buffer赋给第一个scatterlist
sg_set_buf(sgl + 1, xmit->buf, xmit->head); //将0到xmit->head之间的缓冲区buffer赋给第二个scatterlist
} else {
sport->dma_tx_nents = 1; //表示有1个scatterlist
sg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes); //初始化1个scatterlist,并将xmit->tail到xmit->tail+sport->tx_bytes之间的缓冲buffer赋给scatterlist
}
ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); //将不连续nents个物理内存区域的sgl映射到连续的虚拟地址中,其中DMA_TO_DEVICE为方向,表示dma传输方向为发送
desc = dmaengine_prep_slave_sg(chan, sgl, sport->dma_tx_nents, //获取一个传输描述符
DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
desc->callback = dma_tx_callback; //设置DMA传输完成回调函数
desc->callback_param = sport;
sport->dma_is_txing = 1; //表示dma正在发送
dmaengine_submit(desc); //提交传输描述符,把传输描述符加入到DMA engine驱动的等待队列(仅仅提交描述符到DMA engine的等待队列,它不会启动DMA操作)
dma_async_issue_pending(chan); //发起等待的请求并等待回调通知
}
3.dma_tx_callback()函数解析
static void dma_tx_callback(void *data)
{
dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); //解除映射关系
sport->dma_is_txing = 0; //表示此时dma传输完成
xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1); //调整发送缓冲区的xmit->tail,xmit->tail指向已发送空间的末尾
sport->port.icount.tx += sport->tx_bytes; //记录此uart_port已经发送多少个字节
uart_write_wakeup(&sport->port); //唤醒tty写等待工作队列 ,唤醒上层因串口端口写数据而堵塞的进程,通常在串口发送中断处理函数中调用该函数
schedule_delayed_work(&sport->tsk_dma_tx, msecs_to_jiffies(1)); //如果一次dma传输没有将串口数据发送完,将继续调用dma_tx_work函数,延时时间为1ms
if (waitqueue_active(&sport->dma_wait)) { //判断一个等待队列是否为空,不为空if成立
wake_up(&sport->dma_wait); //唤醒dma等待工作队列,比如在关闭串口时需要等待dma传输完成,完成后,再唤醒由于dma正在传输而休眠的任务
}
}
4.参考链接
1、关于scatterlist介绍和学习,可参考这篇文章:链接地址
2、关于dmaengine介绍和学习,可参考这篇文章:链接地址
3、关于函数dma_map_sg()介绍和学习,可参考这篇文章:链接地址
五、数据接收流程(read操作)
uart_read函数的源码路径在drivers\tty\serial\serial_core.c下。read流程如下图所示:
1.dma_rx_callback()函数解析
static void dma_rx_callback(void *data)
{
status = dmaengine_tx_status(chan, sport->rx_buf.cookie, &state); //获取当前DMA状态(完成|停止|正在传输)和得到DMA还剩多少数据没有传输
count = RX_BUF_SIZE - state.residue; //得到DMA已经传输了多少个字节
sport->rx_buf.buf_info[sport->rx_buf.cur_idx].filled = true; //表示该buffer已经被使用
sport->rx_buf.buf_info[sport->rx_buf.cur_idx].rx_bytes = count; //记录该buffer接收了多少数据
sport->rx_buf.cur_idx++; //记录当当前接收数据的buffer的编号
sport->rx_buf.cur_idx %= IMX_RXBD_NUM;
dma_rx_work(sport); //上传数据给应用层
}
2.dma_rx_work()函数解析
static void dma_rx_work(struct imx_port *sport)
{
if (sport->rx_buf.last_completed_idx < cur_idx) { //last_completed_idx初始值为-1,因为第0个内存块的上一个就是-1
dma_rx_push_data(sport, tty, sport->rx_buf.last_completed_idx + 1, cur_idx);
} else if (sport->rx_buf.last_completed_idx == (IMX_RXBD_NUM - 1)) { //cur_idx<last_completed_idx,且last_completed_idx=19,则读取将0到cur_idx内存块
dma_rx_push_data(sport, tty, 0, cur_idx);
} else { //cur_idx<last_completed_idx,且last_completed_idx<19
dma_rx_push_data(sport, tty, sport->rx_buf.last_completed_idx + 1,IMX_RXBD_NUM);
dma_rx_push_data(sport, tty, 0, cur_idx);
}
}
3.dma_rx_push_data()函数解析
static void dma_rx_push_data(struct imx_port *sport, struct tty_struct *tty, unsigned int start, unsigned int end)
{
for (i = start; i < end; i++) {
if (sport->rx_buf.buf_info[i].filled) {
tty_insert_flip_string(port, sport->rx_buf.buf + (i * RX_BUF_SIZE), sport->rx_buf.buf_info[i].rx_bytes); //将读取的数据放到tty buffer中
tty_flip_buffer_push(port); //将tty数据块的数据推到线路规程当中,这个函数的作用就类似于通知tty去线路规程获取从串口过来的数据
sport->rx_buf.buf_info[i].filled = false; //清除该内存块使用标志
sport->rx_buf.last_completed_idx++;
sport->rx_buf.last_completed_idx %= IMX_RXBD_NUM;
sport->port.icount.rx += sport->rx_buf.buf_info[i].rx_bytes; //记录一共读取了多少字节的数据
}
}
}
4.接收buffer的理解
关于接收缓冲理解:在创建接收缓冲时,创建的总大小为20*buffer大小,即一共有20个buffer用来接收串口数据。而每个buffer大小恰恰是一次串口DMA传输的大小。关于函数dmaengine_prep_dma_cyclic()实现了一个循环的DMA操作。即一共20个buffer,这20个buffer轮流用来接收串口数据。所以每个buffer都有自己的一个编号,根据last_completed_idx和cur_idx这两个编号,用来确定将那些编号的buffer里面的数据传递给应用层。
六、关闭设备(close操作)
uart_close函数的源码路径在drivers\tty\serial\serial_core.c下。close流程如下图所示:
1.uart_close()函数解析
static void uart_close(struct tty_struct *tty, struct file *filp)
{
/* uart_shutdown()函数内实现 */
ret = wait_event_interruptible_timeout(sport->dma_wait,!sport->dma_is_rxing && !sport->dma_is_txing, msecs_to_jiffies(1)); //等待DMA传输完成,等待时间1ms
dmaengine_terminate_all(sport->dma_chan_tx); //串口DMA发送通道的所有活动停止
dmaengine_terminate_all(sport->dma_chan_rx); //串口DMA接收通道的所有活动停止
cancel_delayed_work_sync(&sport->tsk_dma_tx); //取消串口DMA发送回调函数
imx_stop_tx(port); //禁用串口发送中断
imx_stop_rx(port); //禁用串口接收中断
imx_disable_dma(sport); //禁用DMA
/*imx_uart_dma_exit()函数中完成*/
dma_release_channel(sport->dma_chan_rx); //释放串口DMA接收通道
dma_release_channel(sport->dma_chan_tx); //释放串口DMA发送通道
dma_free_coherent(NULL, IMX_RXBD_NUM * RX_BUF_SIZE,(void *)sport->rx_buf.buf, sport->rx_buf.dmaaddr); //释放接收缓冲区
free_page((unsigned long)state->xmit.buf); //释放发送缓冲区
temp &= ~(UCR2_TXEN); //禁用串口发送
temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN); //禁用串口中断和串口
clk_disable_unprepare(sport->clk_per); //禁用串口per时钟
clk_disable_unprepare(sport->clk_ipg); //禁用串口ipg时钟
sport->dma_is_rxing = 0;
sport->dma_is_txing = 0;
sport->dma_is_enabled = 0;
sport->dma_is_inited = 0;
}
总结
这份笔记更多的是对难点的函数进行解析,给大家一个参考的思路。所以,如果有人想真正搞明白源码实现原理,还是得自己从头到尾去分析一遍。另外,UART驱动源码重点就是在实现串口的接收和发送,只要抓住这个重点,就能明白每一个函数的目的是干嘛。所以,再回去看我最开始写的串口接发收方式,就会发现思路更加的清晰了。