文章目录
- 前言
- 一、I.MX6ULL串口接收和发送方式
-
- 二、UART驱动注册
- 1.uart_register_driver()函数解析
- 2.serial_imx_probe()函数解析
- 3.uart_add_one_port()函数解析
- 4.注册流程总结
- 三、打开设备(open操作)
- 1.uart_open()函数解析
- 2.imx_uart_dma_init()函数解析
- 3.uart_change_speed()函数解析
- 3.1 uart_update_timeout()函数解析
- 3.2 start_rx_dma()函数解析
- 4.使用到的回调函数
- 5.设备打开流程总结
- 四、数据发送流程(write操作)
- 1.uart_write()函数解析
- 2.dma_tx_work()函数解析
- 3.dma_tx_callback()函数解析
- 4.参考链接
- 五、数据接收流程(read操作)
- 1.dma_rx_callback()函数解析
- 2.dma_rx_work()函数解析
- 3.dma_rx_push_data()函数解析
- 4.接收buffer的理解
- 六、关闭设备(close操作)
-
- 总结
前言
最近分析了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);
normal = alloc_tty_driver(drv->nr);
drv->tty_driver = normal;
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;
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_port_init(port);
port->ops = &uart_port_ops;
retval = tty_register_driver(normal);
}
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)
{
sport->port.line = ret;
sport->have_rtscts = 0;
sport->dte_mode = 0;
sport->devdata = of_id->data;
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.ops = &imx_pops;
sport->port.rs485_config = imx_rs485_config;
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");
sport->clk_per = devm_clk_get(&pdev->dev, "per");
sport->port.uartclk = clk_get_rate(sport->clk_per);
devm_request_irq(&pdev->dev, rxirq, imx_int, 0, dev_name(&pdev->dev), sport);
return uart_add_one_port(&imx_reg, &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;
uart_configure_port(drv, state, uport);
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL);
uport->tty_groups[0] = &tty_dev_attr_group;
tty_dev = tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
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;
.tty_driver tty_driver[8] =
{
.major = 207;
.minor_start = 16;
.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
.tty_operations uart_ops = &uart_ops;
}
.uart_state state[8] =
{
.tty_port port[8] =
{
.tty_port_operations ops = &uart_port_ops;
}
.uart_port uart_port[8]=
{
.type = PORT_IMX;
.state = uart_state;
.mapbase =
.membase =
.irq = platform_get_irq(pdev, 0);
.fifosize = 32;
.uart_ops ops = &imx_pops;
.uartclk = clk_get_rate(sport->clk_per);
.attribute_group tty_groups = &tty_dev_attr_group;
.timeout = (HZ * bits) / baud + HZ/50;
}
.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)
{
page = get_zeroed_page(GFP_KERNEL);
state->xmit.buf = (unsigned char *) page;
retval = clk_prepare_enable(sport->clk_per);
retval = clk_prepare_enable(sport->clk_ipg);
imx_setup_ufcr(sport, 0);
imx_uart_dma_init(sport);
INIT_DELAYED_WORK(&sport->tsk_dma_tx, dma_tx_work);
temp |= UCR1_UARTEN;
temp |= (UCR2_RXEN | UCR2_TXEN);
imx_enable_ms(&sport->port);
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 = {};
sport->dma_chan_rx = dma_request_slave_channel(dev, "rx");
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;
slave_config.src_maxburst = RXTL_UART;
ret = dmaengine_slave_config(sport->dma_chan_rx, &slave_config);
sport->rx_buf.buf = dma_alloc_coherent(NULL, IMX_RXBD_NUM * RX_BUF_SIZE,
&sport->rx_buf.dmaaddr, GFP_KERNEL);
for (i = 0; i < IMX_RXBD_NUM; i++) {
sport->rx_buf.buf_info[i].rx_bytes = 0;
sport->rx_buf.buf_info[i].filled = false;
}
sport->dma_chan_tx = dma_request_slave_channel(dev, "tx");
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;
slave_config.dst_maxburst = TXTL;
ret = dmaengine_slave_config(sport->dma_chan_tx, &slave_config);
sport->dma_is_inited = 1;
}
3.uart_change_speed()函数解析
static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, struct ktermios *old_termios)
{
ucr2 = UCR2_WS | UCR2_SRST | UCR2_IRTS;
del_timer_sync(&sport->timer);
baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16);
quot = uart_get_divisor(port, baud);
uart_update_timeout(port, termios->c_cflag, baud);
div = sport->port.uartclk / (baud * 16);
rational_best_approximation(16 * div * baud, sport->port.uartclk,
1 << 16, 1 << 16, &num, &denom);
ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
writel(ufcr, sport->port.membase + UFCR);
writel(num, sport->port.membase + UBIR);
writel(denom, sport->port.membase + UBMR);
imx_enable_dma(sport);
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;
bits = bits * port->fifosize;
port->timeout = (HZ * bits) / baud + HZ/50;
}
3.2 start_rx_dma()函数解析
static int start_rx_dma(struct imx_port *sport)
{
sport->rx_buf.periods = IMX_RXBD_NUM;
sport->rx_buf.period_len = RX_BUF_SIZE;
sport->rx_buf.buf_len = IMX_RXBD_NUM * RX_BUF_SIZE;
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,
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
desc->callback = dma_rx_callback;
desc->callback_param = sport;
sport->rx_buf.cookie = dmaengine_submit(desc);
dma_async_issue_pending(chan);
sport->dma_is_rxing = 1;
}
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);
schedule_delayed_work(&sport->tsk_dma_tx, 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;
sg_init_table(sgl, 2);
sg_set_buf(sgl, xmit->buf + xmit->tail,UART_XMIT_SIZE - xmit->tail);
sg_set_buf(sgl + 1, xmit->buf, xmit->head);
} else {
sport->dma_tx_nents = 1;
sg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes);
}
ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);
desc = dmaengine_prep_slave_sg(chan, sgl, sport->dma_tx_nents,
DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
desc->callback = dma_tx_callback;
desc->callback_param = sport;
sport->dma_is_txing = 1;
dmaengine_submit(desc);
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;
xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);
sport->port.icount.tx += sport->tx_bytes;
uart_write_wakeup(&sport->port);
schedule_delayed_work(&sport->tsk_dma_tx, msecs_to_jiffies(1));
if (waitqueue_active(&sport->dma_wait)) {
wake_up(&sport->dma_wait);
}
}
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);
count = RX_BUF_SIZE - state.residue;
sport->rx_buf.buf_info[sport->rx_buf.cur_idx].filled = true;
sport->rx_buf.buf_info[sport->rx_buf.cur_idx].rx_bytes = count;
sport->rx_buf.cur_idx++;
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) {
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)) {
dma_rx_push_data(sport, tty, 0, cur_idx);
} else {
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_flip_buffer_push(port);
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)
{
ret = wait_event_interruptible_timeout(sport->dma_wait,!sport->dma_is_rxing && !sport->dma_is_txing, msecs_to_jiffies(1));
dmaengine_terminate_all(sport->dma_chan_tx);
dmaengine_terminate_all(sport->dma_chan_rx);
cancel_delayed_work_sync(&sport->tsk_dma_tx);
imx_stop_tx(port);
imx_stop_rx(port);
imx_disable_dma(sport);
dma_release_channel(sport->dma_chan_rx);
dma_release_channel(sport->dma_chan_tx);
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);
clk_disable_unprepare(sport->clk_ipg);
sport->dma_is_rxing = 0;
sport->dma_is_txing = 0;
sport->dma_is_enabled = 0;
sport->dma_is_inited = 0;
}
总结
这份笔记更多的是对难点的函数进行解析,给大家一个参考的思路。所以,如果有人想真正搞明白源码实现原理,还是得自己从头到尾去分析一遍。另外,UART驱动源码重点就是在实现串口的接收和发送,只要抓住这个重点,就能明白每一个函数的目的是干嘛。所以,再回去看我最开始写的串口接发收方式,就会发现思路更加的清晰了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)