写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。
串口是很常用的一个外设,在 Linux 下通常通过串口和其他设备或传感器进行通信,根据电平的不同,串口分为 TTL , RS232和RS485。不管是什么样的接口电平,其驱动程序都是一样的,通过外接 RS485 这样的芯片就可以将串口转换为 RS485 信号。
同 I2C、SPI 一样,Linux 也提供了串口驱动框架,我们只需要按照相应的串口框架编写驱动程序即可。串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由 soc厂家 编写好在内核中了,我们真正要做的就是在arch/arm/mach-s5p6818/dev-uart.c中通过platform_device结构体描述设备的信息并注册进内核,如果是使用设备树来描述设备信息的,就到对应的.dts文件中添加所要使用的串口节点信息,当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttySACX(X=0….n)文件。
在编写 UART t驱动程序中,一共有三个结构体比较重要,分别为:uart_driver, uart_port, uart_ops。
uart_driver 结构体表示 UART 驱动, 它定义在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;
};
每个串口驱动都需要定义一个 uart_driver,加载驱动的时候通过uart_register_driver 函数向系统注册这个 uart_driver,此函数原型如下:
int uart_register_driver(struct uart_driver *drv)
函数参数和返回值含义如下:
drv:要注册的 uart_driver。
返回值:0,成功;负值,失败。
注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数,函数原型如下:
void uart_unregister_driver(struct uart_driver *drv)
函数参数和返回值含义如下:
drv:要注销的 uart_driver。
返回值:无。
uart_port 表示一个具体的 port,uart_port 定义在 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] */
..........
const struct uart_ops *ops;
unsigned int custom_divisor;
unsigned int line; /* port index */
resource_size_t mapbase; /* for ioremap */
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
.........
};
uart_port 中最主要的就是 ops 成员,它是一个 uart_ops 结构体类型的变量,ops 包含了串口的具体驱动函数,每个 UART 都有一个 uart_port,那么 uart_port 是怎么和 uart_driver 结合起来的呢?这里要用到 uart_add_one_port 函数,函数原型如下:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
函数参数和返回值含义如下:
drv:此 port 对应的 uart_driver。
uport:要添加到 uart_driver 中的 port。
返回值:0,成功;负值,失败。
卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到uart_remove_one_port 函数,函数原型如下:
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
函数参数和返回值含义如下:
drv:要卸载的 port 所对应的 uart_driver。
uport:要卸载的 uart_port。
返回值:0,成功;负值,失败。
在上面讲解 uart_port 的时候说过,uart_port 中的 ops 成员变量很重要,因为 ops 包含了针对 UART 具体的驱动函数,Linux 系统收发数据最终调用的都是 ops 中的函数。ops 是 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 (*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 *, int new);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
void (*wake_peer)(struct uart_port *);
/*
* 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
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
UART 驱动编写人员需要实现 uart_ops,因为 uart_ops 是最底层的 UART 驱动接口,是实实在在的和 UART 寄存器打交道的。
.
.
因为我的板子soc是三星公司的,所以内核中的 UART 驱动在 drivers/tty/serial/nxp-s3c.c 文件中,不同的soc,其UART驱动都不同。
在 nxp-s3c.c 中,大致了解到驱动的框架,框架如下:
/* 操作函数集合 */
static struct uart_ops s3c24xx_serial_ops = {
.pm = s3c24xx_serial_pm,
.tx_empty = s3c24xx_serial_tx_empty,
.get_mctrl = s3c24xx_serial_get_mctrl,
.set_mctrl = s3c24xx_serial_set_mctrl,
.stop_tx = s3c24xx_serial_stop_tx,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
.enable_ms = s3c24xx_serial_enable_ms,
.break_ctl = s3c24xx_serial_break_ctl,
.startup = s3c24xx_serial_startup,
.shutdown = s3c24xx_serial_shutdown,
.set_termios = s3c24xx_serial_set_termios,
.type = s3c24xx_serial_type,
.release_port = s3c24xx_serial_release_port,
.request_port = s3c24xx_serial_request_port,
.config_port = s3c24xx_serial_config_port,
.verify_port = s3c24xx_serial_verify_port,
.wake_peer = s3c24xx_serial_wake_peer,
.flush_buffer = pl011_dma_flush_buffer,
};
/*定义 uart_driver 结构体 */
static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.driver_name = S3C24XX_SERIAL_NAME,
.dev_name = S3C24XX_SERIAL_NAME,
.nr = UART_NR,
.cons = S3C24XX_SERIAL_CONSOLE,
.major = S3C24XX_SERIAL_MAJOR,
.minor = S3C24XX_SERIAL_MINOR,
};
/* probe函数 */
static int s3c24xx_serial_probe(struct platform_device *pdev)
{
/* 定义一个s3c24xx_uart_port 结构体,里面的port成员就是uart_port */
struct s3c24xx_uart_port *uport;
...........
/* 收发数据最终调用的操作函数集合 */
uport->port.ops = &s3c24xx_serial_ops;
.......
/* 把uart_port添加入uart_driver中*/
uart_add_one_port(&s3c24xx_uart_drv, &uport->port);
return 0;
........
}
/* remove函数 */
static int __devexit s3c24xx_serial_remove(struct platform_device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
......
uart_remove_one_port(&s3c24xx_uart_drv, port);
.......
return 0;
}
/* 使用设备树配对的匹配表 */
static const struct of_device_id s3c24xx_uart_dt_match[] = {
{ .compatible = "Nexell,s3c-uart",
.data = (void *)EXYNOS4210_SERIAL_DRV_DATA },
{},
};
MODULE_DEVICE_TABLE(of, s3c24xx_uart_dt_match);
static struct platform_driver samsung_serial_driver = {
.probe = s3c24xx_serial_probe, /* 熟悉的probe函数,配对成功就会调用 */
.remove = __devexit_p(s3c24xx_serial_remove),
.driver = {
.name = "nxp-uart", /* 配对用的名字 */
.owner = THIS_MODULE,
.pm = SERIAL_SAMSUNG_PM_OPS,
.of_match_table = s3c24xx_uart_dt_match,/* 使用设备树配对用的匹配表 */
},
};
static int __init s3c24xx_serial_modinit(void)
{
/* 向内核注册uart_driver */
uart_register_driver(&s3c24xx_uart_drv);
/* 注册平台设备 */
return platform_driver_register(&samsung_serial_driver);
}
static void __exit s3c24xx_serial_modexit(void)
{
/* 注销uart_driver */
uart_unregister_driver(&s3c24xx_uart_drv);
}
module_init(s3c24xx_serial_modinit);
module_exit(s3c24xx_serial_modexit);
可以看出内核中的 的 UART 驱动本质上是一个 platform 驱动
UART驱动有了,要想实现串口收发功能,还差串口设备,有设备,有驱动,设备跟驱动配对成功,才是真正实现串口收发功能。那么内核中的串口设备信息描述是放在哪里呢?在上面分析驱动框架时,里面 platform_driver 结构体的 name 字段就起到这个匹配作用的,
.name = "nxp-uart",
它还能帮助我们找到设备信息描述存放的位置,在 ubuntu 终端中进入kernel的根目录,使用指令 grep -nR “nxp-uart” ,就能找到 uart 设备信息描述在哪个位置了,它在 arch/arm/mach-s5p6818/dev-uart.c 中,内面有如下内容:
#if defined(CONFIG_SERIAL_NXP_UART0)
void uport0_weak_alias_init(int hwport) __attribute__((weak, alias("uart_device_init")));
void uport0_weak_alias_exit(int hwport) __attribute__((weak, alias("uart_device_exit")));
void uport0_weak_alias_wake_peer(struct uart_port *uport)
__attribute__((weak, alias("uart_device_wake_peer")));
/* 存放在 platform_data 中的数据 */
static struct s3c24xx_uart_platdata uart0_data = {
.hwport = 0,
.init = uport0_weak_alias_init,
.exit = uport0_weak_alias_exit,
.wake_peer = uport0_weak_alias_wake_peer,
.ucon = S5PV210_UCON_DEFAULT,
.ufcon = S5PV210_UFCON_DEFAULT,
.has_fracval = 1,
#if defined(CONFIG_SERIAL_NXP_UART0_DMA)
.enable_dma = 1,
.dma_filter = pl08x_filter_id,
.dma_rx_param = (void *) DMA_PERIPHERAL_NAME_UART0_RX,
.dma_tx_param = (void *) DMA_PERIPHERAL_NAME_UART0_TX,
#else
.enable_dma = 0,
#endif
};
/* UART_RESOURCE是一个宏,用来设置resource资源 */
static UART_RESOURCE(uart0, PHY_BASEADDR_UART0, IRQ_PHY_UART0);
/* UART_RESOURCE是一个宏,向内核注册 platform_device 结构体 */
static UART_PLDEVICE(uart0, "nxp-uart", 0, uart0_resource, &uart0_data);
#endif
platform_device 的注册可以看出,它就是对应前面驱动程序中的 platform_driver,所以 uart 设备和驱动也就是一个 flatform总线上的设备和驱动。