串口驱动#
串口是常用的外设,和其他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驱动框架#
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。
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成功;负值失败。
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。
总结一下,一个串口驱动要完成的主要工作为:
定义uart_driver、uart_ops、uart_port等结构体变量并初始化。
驱动模块加载时使用uart_register_driver()和uart_add_one_port()函数注册uart_driver并添加端口。驱动模块卸载时使用函数uart_unregister_driver()和uart_remove_one_port()来注销uart_driver并移除端口。
根据具体硬件的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框架,来对应到这个驱动代码中去。
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,并且初始化了。
之后在驱动入口函数和出口函数中,分别有对应的注册和注销方法。
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中。
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具体的驱动函数了。