串口驱动#

串口是常用的外设,和其他mcu通讯、和传感器2G、 GPS等模块通讯,都经常会选择串口。在Linux系统下也不例外,我们通过串口工具来和开发板进行交互,无疑就是用到了串口。串口有很多电平标准,TTL、232、485等等,但他们的驱动程序都是一样的。在嵌入式Linux系统中,串口被看成终端设备(tty),包括3个结构体:uart_driver、uart_port、uart_ops定义在文件include/serial_core.h中。实现一个uart驱动程序只要实现这3个结构体即可。

uart的驱动一般也是由芯片厂家提供。xilinx提供的uart驱动在drivers/tty/serial/xilinx_uartps.c中。我们只要在设备树中添加相应的串口节点,就可以使用串口外设了。这章就来学习Linux中串口驱动框架,并对xilinx的串口驱动实现稍做了解。

uart驱动框架#

  1. usb_driver

Linux内核中使用usb_driver来表示,uart_driver包含了串口设备名、串口驱动名、主次设备号等信息,并且包含了tty_driver使得底层串口驱动无需关心tty_driver。

uart_driver 定义在文件include/linux/serial_core.h中,如下:

struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;

/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};

onwer一般为THIS_MODULE。

driver_name为驱动名。

dev_name为设备名。

cons为控制台。

定义好uart_driver后使用下面你的函数来向内核注册:

int uart_register_driver(struct uart_driver *drv)

drv:要注册的 uart_driver。

返回值:0成功,负值失败。

相对的注销uart_driver使用下面的函数:

void uart_unregister_driver(struct uart_driver *drv)

drv为要注销的uart_driver。

  1. uart_prot

uart_port表示一个具体的uart端口,用于描述一个uart端口的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。定义在文件include/linux/serial_core.h中,如下:

struct uart_port {
 spinlock_t lock; /* port lock */
 unsigned long iobase; /* in/out[bwl] */
 unsigned char __iomem *membase; /* read/write[bwl] */
 unsigned int (*serial_in)(struct uart_port *, int);
 void (*serial_out)(struct uart_port *, int, int);
 void (*set_termios)(struct uart_port *,
 struct ktermios *new,
 struct ktermios *old);
 unsigned int (*get_mctrl)(struct uart_port *);
 void (*set_mctrl)(struct uart_port *, unsigned int);
 int (*startup)(struct uart_port *port);
 void (*shutdown)(struct uart_port *port);
 …………
 const struct uart_ops *ops;
 unsigned int custom_divisor;
 unsigned int line; /* port index */
 unsigned int minor;
 resource_size_t mapbase; /* for ioremap */
 resource_size_t mapsize;
 struct device *dev; /* parent device */
 unsigned char hub6; /* this should be in the 8250 driver */
 unsigned char suspended;
 unsigned char irq_wake;
 unsigned char unused[2];
 struct attribute_group *attr_group; /* port specific attributes */
 const struct attribute_group **tty_groups; /* all attributes (serial core use only) *
  struct serial_rs485 rs485;
  void *private_data; /* generic platform data pointer */
};

其中最主要的就是ops串口驱动操作函数集。urat_port定义后需要和uart_driver关联。使用下面的函数把uart_port添加到uart_driver中:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

drv:注册目标uart_port对应的uart_driver。

uport:要添加到uart_driver的uart_port。

返回值: 0成功;负值失败。

相对的使用下面的函数把uart_port从uart_driver中删除:

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

drv:卸载目标uart_port对应的 uart_driver。

uport:要卸载的 uart_port。

返回值: 0成功;负值失败。

  1. uart_ops

uart_ops是uart_port中重要的成员,是uart具体驱动函数的集合。内核使用串口收发数据最终都是调用ops中的函数。uart_ops定义在文件include/linux/serial_core.h中,如下:

struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);

/*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *);

/*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *);

/*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};

uart_ops中的函数需要底层开发人员去实现,是直接和寄存器打交道的部分。uart_ops结构体中的这些函数的具体含义可以参考文档Documentation/serial/driver。

总结一下,一个串口驱动要完成的主要工作为:

  1. 定义uart_driver、uart_ops、uart_port等结构体变量并初始化。

  2. 驱动模块加载时使用uart_register_driver()和uart_add_one_port()函数注册uart_driver并添加端口。驱动模块卸载时使用函数uart_unregister_driver()和uart_remove_one_port()来注销uart_driver并移除端口。

  3. 根据具体硬件的datasheet实现uart_ops中的成员函数。

然后我们再对应到具体的驱动代码来看。看看xilinx的uart实现。

xilinx的uart驱动#

先看看设备树中uart的节点描述,打开文件zynq-7000.dtsi。找到uart相关节点,如下:

uart0: serial@e0000000 {
compatible = "xlnx,xuartps", "cdns,uart-r1p8";
status = "disabled";
clocks = <&clkc 23>, <&clkc 40>;
clock-names = "uart_clk", "pclk";
reg = <0xE0000000 0x1000>;
interrupts = <0 27 4>;
};

uart1: serial@e0001000 {
compatible = "xlnx,xuartps", "cdns,uart-r1p8";
status = "disabled";
clocks = <&clkc 24>, <&clkc 41>;
clock-names = "uart_clk", "pclk";
reg = <0xE0001000 0x1000>;
interrupts = <0 50 4>;
};

节点中两个uart节点都是ps端的串口,zynq的ps端就只有两路串口,如果需要更多,就需要借助pl端的资源。

首先根据compatible属性找到对应的驱动代码,为文件drivers/tty/serial/xilinx_uartps.c。其中of_device_id如下:

/* Match table for of_platform binding */
static const struct of_device_id cdns_uart_of_match[] = {
{ .compatible = "xlnx,xuartps", },
{ .compatible = "cdns,uart-r1p8", },
{ .compatible = "cdns,uart-r1p12", .data = &zynqmp_uart_def },
{ .compatible = "xlnx,zynqmp-uart", .data = &zynqmp_uart_def },
{}
};

在驱动代码xilinx_uartps.c中1614行,会发现如下代码:

static struct platform_driver cdns_uart_platform_driver = {
.probe = cdns_uart_probe,
.remove = cdns_uart_remove,
.driver = {
.name = CDNS_UART_NAME,
.of_match_table = cdns_uart_of_match,
.pm = &cdns_uart_dev_pm_ops,
},
};

可见,uart本质上是一个platform驱动。

然后根据前面说的uart框架,来对应到这个驱动代码中去。

  1. uart_driver

static struct uart_driver cdns_uart_uart_driver = {
.owner = THIS_MODULE,
.driver_name = CDNS_UART_NAME,
.dev_name = CDNS_UART_TTY_NAME,
.major = CDNS_UART_MAJOR,
.minor = CDNS_UART_MINOR,
.nr = CDNS_UART_NR_PORTS,
#ifdef CONFIG_SERIAL_XILINX_PS_UART_CONSOLE
.cons = &cdns_uart_console,
#endif
};
……
static int __init cdns_uart_init(void)
{
int retval = 0;

/* Register the cdns_uart driver with the serial core */
retval = uart_register_driver(&cdns_uart_uart_driver);
if (retval)
return retval;
……
return retval;
}

static void __exit cdns_uart_exit(void)
{
/* Unregister the platform driver */
platform_driver_unregister(&cdns_uart_platform_driver);
……
}

可以找到uart_driver类型的变量cdns_uart_uart_driver,并且初始化了。

之后在驱动入口函数和出口函数中,分别有对应的注册和注销方法。

  1. uart_port

static struct uart_port cdns_uart_port[CDNS_UART_NR_PORTS];

/**
* cdns_uart_get_port - Configure the port from platform device resource info
* @id: Port id
*
* Return: a pointer to a uart_port or NULL for failure
*/
static struct uart_port *cdns_uart_get_port(int id)
{
struct uart_port *port;

/* Try the given port id if failed use default method */
if (cdns_uart_port[id].mapbase != 0) {
/* Find the next unused port */
for (id = 0; id < CDNS_UART_NR_PORTS; id++)
if (cdns_uart_port[id].mapbase == 0)
break;
}

if (id >= CDNS_UART_NR_PORTS)
return NULL;

port = &cdns_uart_port[id];

/* At this point, we've got an empty uart_port struct, initialize it */
spin_lock_init(&port->lock);
port->membase = NULL;
port->irq = 0;
port->type = PORT_UNKNOWN;
port->iotype = UPIO_MEM32;
port->flags = UPF_BOOT_AUTOCONF;
port->ops = &cdns_uart_ops;
port->fifosize = CDNS_UART_FIFO_SIZE;
port->line = id;
port->dev = NULL;
return port;
}

static int cdns_uart_probe(struct platform_device *pdev)
{
int rc, id, irq;
struct uart_port *port;
struct resource *res;
struct cdns_uart *cdns_uart_data;
const struct of_device_id *match;
……
/* Look for a serialN alias */
id = of_alias_get_id(pdev->dev.of_node, "serial");
if (id < 0)
id = 0;

/* Initialize the port structure */
port = cdns_uart_get_port(id);

if (!port) {
dev_err(&pdev->dev, "Cannot get uart_port structure\n");
rc = -ENODEV;
goto err_out_notif_unreg;
}

/*
* Register the port.
* This function also registers this device with the tty layer
* and triggers invocation of the config_port() entry point.
*/
port->mapbase = res->start;
port->irq = irq;
port->dev = &pdev->dev;
port->uartclk = clk_get_rate(cdns_uart_data->uartclk);
port->private_data = cdns_uart_data;
cdns_uart_data->port = port;
platform_set_drvdata(pdev, port);

……
rc = uart_add_one_port(&cdns_uart_uart_driver, port);
if (rc) {
dev_err(&pdev->dev,
"uart_add_one_port() failed; err=%i\n", rc);
goto err_out_pm_disable;
}

……
}

在程序中,可以找到如上的代码片段,首先定义了uart_port型的数组cdns_uart_port[CDNS_UART_NR_PORTS]。

又实现了函数cdns_uart_get_port来对uart_port实现初始化。

在probe函数中73行(实际源码中1554行),把uart_port添加到设备驱动结构体的私有数据中,以便于在之后ops函数实现时调用。

同样在probe函数76行(实际源码1561行),调用uart_add_one_port把uart_port添加到uart_driver中。

  1. uart_ops

uart_ops变量定义在1081行,名为cdns_uart_ops,如下:

static const struct uart_ops cdns_uart_ops = {
.set_mctrl = cdns_uart_set_mctrl,
.get_mctrl = cdns_uart_get_mctrl,
.start_tx = cdns_uart_start_tx,
.stop_tx = cdns_uart_stop_tx,
.stop_rx = cdns_uart_stop_rx,
.tx_empty = cdns_uart_tx_empty,
.break_ctl = cdns_uart_break_ctl,
.set_termios = cdns_uart_set_termios,
.startup = cdns_uart_startup,
.shutdown = cdns_uart_shutdown,
.pm = cdns_uart_pm,
.type = cdns_uart_type,
.verify_port = cdns_uart_verify_port,
.request_port = cdns_uart_request_port,
.release_port = cdns_uart_release_port,
.config_port = cdns_uart_config_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = cdns_uart_poll_get_char,
.poll_put_char = cdns_uart_poll_put_char,
#endif
};

在1112行的uart_port初始化函数中,其中的ops就是赋值为cdns_uart_ops。

cdns_uart_ops中的函数,也就是uart具体的驱动函数了。