Linux驱动-内核uart串口驱动分析

前言

写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。

一、简介

串口是很常用的一个外设,在 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 驱动的几个重要结构体

在编写 UART t驱动程序中,一共有三个结构体比较重要,分别为:uart_driver, uart_port, uart_ops

1、uart_driver 结构体

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。
返回值:无。

2、uart_port 结构体

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,成功;负值,失败。

3、uart_ops 结构体

在上面讲解 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 寄存器打交道的。
.
.

三、 Linux 下 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 驱动

四、 Linux 下 UART 设备信息描述

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总线上的设备和驱动。

你可能感兴趣的:(ARM,linux)