X-019-KERNEL-串口驱动开发之数据收发
作者:wowo 发布于:2016-11-29 21:55 分类:X Project
1. 前言
本文是“X Project”串口驱动开发的第四篇,在第二篇“uart driver框架”的基础上,实现基本的、可收发数据的uart驱动,并借助这个过程,学习如下知识:
中断的申请和使用;
利用中断发送和接收数据;
uart_ops中常用函数(.startup, .start_tx, etc.)的使用。
2. 中断的申请和使用
在linux kernel中,使用中断进行数据传输是最基本的要求(更进阶的是DMA,我们会在后续的文章中介绍),因为强悍的CPU无法忍受乌龟般的外设速度,忙等待只会自断生路。下面将会以Bubblegum-96平台的UART driver为例,介绍中断的使用。
2.1 准备工作
关于中断,在使用之前,我们至少需要先理清如下内容(以Bubblegum-96平台的UART5为例进行说明):
1)该外设和中断控制器之间通过哪些中断线(IRQ line,也就是我们常说的中断号)进行连接。
2)每个中断线的触发方式为何,电平?边沿?
由[2]可知,Bubblegum-96平台的UART5的中断线为SPI 35,触发方式为高电平触发(外部GPIO中断比较关心触发方式,其它的外设中断,一般都是电平触发)。
3)在设备内部(例如这里的UART控制器),哪些行为可以产生中断?产生中断的条件为何?如何控制中断的使能?如何清除中断的pending状态?
对Bubblegum-96平台的UART5控制器来说,由[1]可知:
有TX和RX两种中断;
TX的FIFO大小为32bytes,只要空闲空间(empty)大于等于16bytes,就会触发中断;
RX的FIFO大小也为32bytes,当接收的数据大于等于16bytes时,就会触发中断;
TX/RX中断使能与否,可由UARTx_CTL(offset=0x0000)的bit19、bit18控制;
UART5中断产生时,可通过UARTx_STAT(offset=0x000c)的bit1(TX IRQ Pending)、bit0(RX IRQ Pending)获取具体的中断原因(TX还是RX),这两个bit均写1清除。
注1,这里存在一个问题,需要大家思考(这个问题是串口驱动最常见的):基于上面的描述,只有RX接收到大于等于16bytes的数据时,才会产生中断,那么接收少于16bytes的数据时,怎么办?后面实际调试的时候,再细说。
4)中断发生时,要做哪些事情?这些事情是否比较耗时?是否可以在线程中处理?
以UART控制器为例:
TX中断产生时,表明可以向TX FIFO写入数据;
RX中断产生时,表明RX FIFO中有数据需要读取;
在Linux serial framework下,上面两个动作都不是耗时的动作,因此不需要在线程中进行处理。
2.2 中断申请
对uart driver来说,中断的申请,包括3个步骤:
1)在DTS中,通过interrupts字段,指定该设备需要使用的中断号
serial5: serial@e012a000 {
compatible = "actions,s900-serial";
reg = <0 0="" 0xe012a000="" 0x2000="">; /* UART5_BASE */
+ interrupts =;
};
2)在platform driver的probe接口中,通过platform_get_irq接口,将DTS中指定的中断号取出并保存下来
@@ -253,6 +268,12 @@ static int owl_serial_probe(struct platform_device *pdev)
}
port->iotype = UPIO_MEM32;+ port->irq = platform_get_irq(pdev, 0);
+ if (port->irq < 0) {
+ dev_err(&pdev->dev, "Failed to get irq\n");
+ return port->irq;
+ }
+
port->line = of_alias_get_id(pdev->dev.of_node, "serial");
platform_get_irq第二个参数是dts中interrupts字段指定的中断编号,我们只使用一个,因此这里为0即可。
3)在uart port的.startup接口中,调用devm_request_irq,申请并使能该中断线
注2:这里的使能,是指UART控制器和GIC之间的使能。
+static irqreturn_t owl_serial_irq_handle(int irq, void *data)
+{
+ struct uart_port *port = data;
+
+ return IRQ_HANDLED;
+}
+
static int owl_serial_startup(struct uart_port *port)
{
int ret = 0;dev_dbg(port->dev, "%s\n", __func__);
+ ret = devm_request_irq(port->dev, port->irq, owl_serial_irq_handle,
+ 0, "owl_serial", port);
+ if (ret < 0) {
+ dev_err(port->dev, "request irq(%d) failed(%d)\n",
+ port->irq, ret);
+ return ret;
+ }
+
return ret;
}
devm_request_irq有很多参数,其中“owl_serial_irq_handle”是中断的handler,0是flags(这里没有特殊的flag需要传递),"owl_serial"是名字(无关紧要),port是传入的私有数据,kernel irq core会在调用我们的handler的时候再传给我们(具体请参考上面owl_serial_irq_handle函数)。
2.3 RX和TX中断的使能
串口在启动(.startup被调用)之后,就要保证可以正常接收数据,因此RX中断需要在uart port的.startup中使能:
@@ -111,6 +120,9 @@ static int owl_serial_startup(struct uart_port *port)
return ret;
}+ /* RX irq enable */
+ __PORT_SET_BIT(port, UART_CTL, UART_CTL_RXIE);
+
return ret;
}
而TX中断可以放到uart port的.start_tx中再使能,如下:
static void owl_serial_start_tx(struct uart_port *port)
{
dev_dbg(port->dev, "%s\n", __func__);
+
+ /* TX irq enable */
+ __PORT_SET_BIT(port, UART_CTL, UART_CTL_TXIE);
}
2.4 中断处理
中断处理的逻辑比较简单:
1)读取UARTx_STAT寄存器,判断中断类型。
2)如果是RX中断,从UARTx_RXDAT中读取数据,并调用tty_insert_flip_char将读取的数据保存在uart port的RX buffer中。具体可参考第4章的介绍。
3)如果是TX中断,则检查uart port的TX buffer,是否还有未发送的数据,如果有,则将数据写入到UARTx_TXDAT,由UART控制器发送出去。具体可参考第3章的介绍。
3. 收据收发前的其它操作
在前面的章节提到过,uart driver需要实现由struct uart_ops变量所代表的各种回调函数,这里列举一些和数据收发直接相关的函数,如下:
1).startup,除去上面2.3中申请终端、使能RX中断等操作,我们还需要在startup的时候,使能串口控制器,如下:
@@ -123,6 +123,9 @@ static int owl_serial_startup(struct uart_port *port)
/* RX irq enable */
__PORT_SET_BIT(port, UART_CTL, UART_CTL_RXIE);+ /* enable serial port */
+ __PORT_SET_BIT(port, UART_CTL, UART_CTL_EN);
+
return ret;
}
2).shutdown,.startup的反动作,disable串口控制器、disable RX中断、注销中断等,不再贴代码了。
3).stop_tx,disable TX中断,以及其它和数据发送有关的内容(具体可参考第4章的描述)。
4).stop_rx,disable RX中断,以及其它和数据接收有关的内容(具体可参考第5章的描述)。
5).tx_empty,判断TX FIFO是否为空,如下(如果为空,需要返回TIOCSER_TEMT,否则返回0即可):
@@ -153,7 +167,10 @@ static unsigned int owl_serial_tx_empty(struct uart_port *port)
{
dev_dbg(port->dev, "%s\n", __func__);- return 0;
+ if (__PORT_TEST_BIT(port, UART_STAT, UART_STAT_TFES))
+ return TIOCSER_TEMT;
+ else
+ return 0;
}
3. 数据发送
上层软件需要通过uart port发送的数据时,会将数据暂存在一个struct circ_buf类型的环形缓冲区中(port->state->xmit),然后调用driver的.start_tx接口,我们可以在这个接口中从环形缓冲区读取数据要发送的数据,写入到UART控制器的TX FIFO。当然,TX FIFO可能很小,需要多次传输才能完成,因此我们需要在传输完成的中断中,再次从环形缓冲区读取数据,写入TX FIFO,直到所有数据发送完成为止。上述的发送流程图如下:
图片1 数据发送流程
该流程比较简单(具体的代码实现可参考本文档对应的patch文件),不过有一些地方需要说明一下:
1).start_tx实在关中断的情况下调用的,因此不用考虑同步问题。
2)由于TX FIFO的限制,大多数情况下,数据发送是由TX中断推动的。但存在一种特殊情况:TX buffer的数据已经发送完成了,此时串口控制器没有数据在发送,TX中断不会产生,此时需要在.start_tx中重新写FIFO。上面图片1的流程,可以覆盖这个场景。
4. 数据接收
数据接收的流程更简单,流程图如下:
图片2 数据接收流程
5. 串口数据发送和console之间的同步
由于console的write,和串口数据的发送,都是操作同一个TX FIFO,因此存在数据交叉的问题。要解决这个问题,一般遵守一个原则:保证一次console write的完整性,这样可以避免kernel输出日志被打乱。为了做到这一点,我们需要按照如下步骤改造console的write接口:
1)备份当前TX中断的状态,然后禁止TX中断。
2)按照原来的方式,调用uart_console_write将字符串通过串口发送出去。
3)恢复TX的中断状态。
代码如下:
static void owl_console_write(struct console *con, const char *s, unsigned n)
{
+ bool tx_irq_enabled;
+
struct uart_driver *driver = con->data;
struct uart_port *port = driver->state[con->index].uart_port;+ /* save TX IRQ status */
+ tx_irq_enabled = __PORT_TEST_BIT(port, UART_CTL, UART_CTL_TXIE);
+
+ /* TX irq disable */
+ __PORT_CLEAR_BIT(port, UART_CTL, UART_CTL_TXIE);
+
uart_console_write(port, s, n, owl_console_putchar);
+
+ /* restore TX IRQ status */
+ if (tx_irq_enabled)
+ __PORT_SET_BIT(port, UART_CTL, UART_CTL_TXIE);
}
6. 参考文档
[1] bubblegum-96/SoC_bubblegum96.pdf
[2] https://github.com/96boards-bubblegum/linux/blob/bubblegum96-3.10/arch/arm64/boot/dts/s900.dtsi
[3] Documentation/serial/driver
[4] patch文件,https://github.com/wowotechX/linux/commit/9328d8aa82dcf2c17eec03c1beccf9c5c0567a6a
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。
标签: Linux driver irq serial tx rx transmit

评论:
2017-05-22 11:39
2017-05-22 17:55
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2017-05-21 23:54
如果FIFO阈值可以修改,出于传输效率考虑,以下想法是否正确?
1、假设CPUa快于CPUb,则a的TX FIFO阈值要设的小于b,a的RX FIFO阈值要设的大于b(uart发送速率一样的前提下)?
2、是不是因为实际CPU速度相对外设来说都很快,所以TX FIFO阈值都要尽量设的低些,RX FIFO阈值尽量设备高些?