本文共 4724 字,大约阅读时间需要 15 分钟。
X-017-KERNEL-串口驱动开发之uart driver框架
作者:wowo 发布于:2016-11-16 22:09
分类:X Project
1. 前言
本文是“X Project”串口驱动开发的第二篇,将以“bubblegum-96”开发板为例,介绍在linux serial framework的框架下,编写串口driver以及console driver的方法和步骤(暂不涉及实现细节)。
注1:有关串口、TTY、console等概念,可参考本站“TTY子系统[1]”的文章。Linux serial framework的分析,会在后续的文档中补充(这里故意颠倒,以便让大家理解kernel framework的妙处)。
2. 硬件说明
我们在前面“u-boot串口驱动移植[2]”以及"linux serial early console移植[3]”的时候,主要以“bubblegum-96”开发板的UART5为例。为了简单,本文也基于UART5,并做到和其它串口兼容。有关“bubblegum-96”开发板UART5的硬件介绍,可以参考[2],这里不再详细说明了。
3. 串口驱动的移植步骤
3.1 定义并注册uart driver
在linux serial framework中,uart driver是一个平行于platform driver的概念,用于驱动“虚拟”的“串口”设备。举例来说:
假如一个soc中有5个串口控制器(也可称作uart控制器,后面我们不再区分),每个uart控制器都可引出一个串口(uart port)。那么:
每个uart控制器,都是一个platform device,由[5]中介绍的dts文件的一个node描述。而这5个platform device,可由同一个driver驱动,即[5]介绍的platform driver。
相对于uart控制器实实在在的存在,我们更为熟悉的串口(uart port),则是虚拟的设备,它们由“struct uart_port”描述(后面会介绍),并在platform driver的probe接口中,注册到kernel。它们可由同一个driver驱动,即这里所说的uart driver。
uart driver一般长这个样子(AAA和BBB的含义可参考[5]):
#define aaa_bbb_MAXIMUM 5
static struct uart_driver AAA_BBB_driver = {
.owner = THIS_MODULE,
.driver_name = "AAA_BBB",
.dev_name = "ttyS",
.cons = NULL,
.nr = aaa_bbb_MAXIMUM,
};
其中.cons表示该driver对应的console driver,第4章会补充介绍。.nr表示该uart driver最大支持的uart port个数,根据硬件情况,我们这里定义为5。
uart driver的注册和注销和[5]中介绍的platform driver一样,具体可参考后面的patch文件。
3.2 注册uart port
前面提到过,platform device代表uart控制器,是实体抽象。对应的,uart port代表“串口”,是虚拟抽象。因此,我们需要在platform device probe的时候(platform driver的probe接口),动态分配并注册一个uart port(struct uart_port)。
由于在后续的串口操作中,都是以uart port指针为对象,所以在通常情况下,为了保存一些自定义的信息,我们会定义一个包含struct uart_port的数据结构----struct AAA_uart_port,如下:
struct AAA_uart_port {
struct uart_port port;
/* others, TODO */
};
在probe的时候,会分配struct AAA_uart_port类型的指针,然后初始化并注册其中的port变量(这是driver开发的惯用伎俩,大家慢慢就会熟悉),例如:
struct AAA_uart_port *Aup;
struct uart_port *port;
Aup = devm_kzalloc(&pdev->dev, sizeof(*Aup), GFP_KERNEL);
if (Aup == NULL) {
dev_err(&pdev->dev, "Failed to allocate memory for Aup\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, Aup);
port = &Aup->port;
port->dev = &pdev->dev;
port->type = PORT_AAA;
port->ops = &AAA_uart_ops;
注2:这里使用devm_kzalloc,有关devm_xxx的说明,可参考[6]。另外我们会调用platform_set_drvdata将新申请的Aup指针保持在pdev的drvdata中,以便后续需要使用的时候再取出(例如remove的时候),这也是driver开发的惯用伎俩。
struct uart_port结构在抽象一个虚拟的“串口”的同时,也保存了该“串口”的一些常用属性,我们需要在probe的时候将这些属性保存在新申请的指针中,包括:
port->dev,对应的struct device指针;
port->type,该串口的类型,是以PORT_为前缀的一个宏定义,大家可以根据需要在include/uapi/linux/serial_core.h中定义;
port->ops,该串口的操作函数集,后面会介绍;
port->iotype,该串口的IO类型,我们常用的通过寄存器访问的uart控制器,选用UPIO_MEM32即可;
port->membase,对应MEM类型的串口,保持它的寄存器基址,一般是从DTS中解析得到的;
port->irq,该串口对应的中断号, 一般是从DTS中解析得到的;
port->line,该串口的编号,和串口字符设备的次设备号等有关,后面文章会重点介绍;
等等。
本文先定义一些必要的,其它的会随着功能的完善,逐步添加,具体可参考后面的patch文件[8]。
初始化完之后,直接调用uart_add_one_port接口,将该port添加到kernel serial core即可,如下(第一个参数为上面3.1注册的uart driver指针):
ret = uart_add_one_port(&AAA_BBB_driver, port);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to add uart port, err %d\n", ret);
return ret;
}
3.3 定义并实现uart ops
struct uart_ops结构包含了各式各样的uart端口的操作函数,需要在添加uart port的时候提供。开始移植的时候,我们可以只实现部分接口(暂时留空都可以),至少应包括:
static struct uart_ops AAA_uart_ops = {
.startup = AAA_BBB_startup,
.shutdown = AAA_BBB_shutdown,
.start_tx = AAA_BBB_start_tx,
.stop_tx = AAA_BBB_stop_tx,
.stop_rx = AAA_BBB_stop_rx,
.tx_empty = AAA_BBB_tx_empty,
.set_mctrl = AAA_BBB_set_mctrl,
.set_termios = AAA_BBB_set_termios,
};
有关这些操作函数的具体含义,我会在下一篇文章中介绍。
4. console驱动的移植步骤
在嵌入式开发的过程中,我们通常会从SOC上众多串口中选择一个,当作console设备,以方便开发和调试。在Linux serial framework的框架下,编写一个console驱动是非常简单的。步骤如下:
1)定义struct console变量
有关struct console结构的描述可参考[7],下面是一个例子:
static struct console AAA_console = {
.name = "ttyS",
.write = AAA_console_write,
.device = uart_console_device,
.setup = AAA_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &AAA_BBB_driver,
};
重点字段的解释如下:
.name字段决定console的名称,command line传入的时候,需要和该名称匹配,例如“console=ttyS0”;
.index用来指定该console使用哪一个uart port(对应3.2小节中的port->line),这里使用-1,kernel会自动选择第一个uart port。后面有需要的时候我们再改;
.data可以用来保存console的私有数据,这里我们把struct uart_driver指针保存下来,后面有用;
.setup和.write是两个需要实现的回调函数,我们可以先留空,具体请参考[8]中的patch。
2)将console变量的指针保存在struct uart_driver变量的.cons字段中,如下:
static struct console AAA_console;
static struct uart_driver AAA_BBB_driver = {
…
.cons = &AAA_console,
…
};
3)OK,移植完成。当我们执行3.2小节所介绍的add port的时候,serial core会自动比较uart driver---->cons---->index和uart port---->line,如果匹配,则调用register_console帮忙注册console驱动。
5. 参考文档
[4] Documentation/serial/driver
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。
评论:
Sky
2017-03-09 14:35
版主,请问platform的设备层要怎么写,有例子吗?
uart_ops 结构体中的函数什么时候会被调用?
上层的用户程序如何操作serial 这个设备?是以open()的方式吗?还是别的。
2017-03-09 15:55
@Sky:这篇文章就是一个例子啊:
[8] 和本文对应的patch文件,https://github.com/wowotechX/linux/commit/d072d177c9a88e57eb7c5f18c424b96f0ce6d2d5
你可以自己先试试,有具体问题的话再提出来讨论。
上层open就行了。
发表评论:
昵称
邮件地址 (选填)
个人主页 (选填)
转载地址:http://qofkp.baihongyu.com/