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. 参考文档

[1] Linux kernel TTY子系统

[2] X-004-UBOOT-串口驱动移植(Bubblegum-96平台)

[3] X-012-KERNEL-serial early console的移植

[4] Documentation/serial/driver

[5] X-016-KERNEL-串口驱动开发之驱动框架

[6] Linux设备模型(9)_device resource management

[7] Linux TTY framework(5)_System console driver

[8] 和本文对应的patch文件,https://github.com/wowotechX/linux/commit/d072d177c9a88e57eb7c5f18c424b96f0ce6d2d5

 

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net

标签: Linux Kernel driver framework serial console uart_driver

评论:

Sky
2017-03-09 14:35
版主,请问platform的设备层要怎么写,有例子吗?
uart_ops 结构体中的函数什么时候会被调用?
上层的用户程序如何操作serial 这个设备?是以open()的方式吗?还是别的。
wowo
2017-03-09 15:55
@Sky:这篇文章就是一个例子啊:
[8] 和本文对应的patch文件,https://github.com/wowotechX/linux/commit/d072d177c9a88e57eb7c5f18c424b96f0ce6d2d5
你可以自己先试试,有具体问题的话再提出来讨论。

上层open就行了。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog