47.Linux UART 驱动

47.Linux UART 驱动

串口是很常用的一个外设,在 Linux 下通常通过串口和其他设备或传感器进行通信,根据电平的不同,串口分为 TTL 和 RS232。不管是什么样的接口电平,其驱动程序都是一样的,通过外接 RS485 这样的芯片就可以将串口转换为 RS485 信号,正点原子的 I.MX6U-ALPHA 开发板就是这么做的。对于正点原子的 I.MX6U-ALPHA 开发板而言, RS232、 RS485 以及 GPS 模块接口通通连接到了 I.MX6U 的 UART3 接口上,因此这些外设最终都归结为 UART3 的串口驱动。本章我们就来学习一下如何驱动 I.MX6U-ALPHA 开发板上的 UART3 串口,进而实现 RS232、RS485 以及 GSP 驱动。

在内核中的体现:

c 复制代码
/dev/ttymcx0   ->  uart1
/dev/ttymcx1   ->  uart2 
/dev/ttymcx2   ->  uart3   

最重要的是uart_driver和uart_port这两个结构体

uart_driver

uart_driver 结构体表示 UART 驱动, uart_driver 定义在 include/linux/serial_core.h 文件中,内容如下:

c 复制代码
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,此函数原型如下:

c 复制代码
int uart_register_driver(struct uart_driver *drv)

drv: 要注册的 uart_driver。

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

注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数,函数原型如下:

c 复制代码
void uart_unregister_driver(struct uart_driver *drv)

函数参数和返回值含义如下: drv: 要注销的 uart_driver。

返回值: 无。

2、 uart_port 的添加与移除

uart_port 表示一个具体的 port,如同不能i2c_device、spi_device. uart_port 定义在 include/linux/serial_core.h 文件,内容如下(有省略):

c 复制代码
 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 */
     unsigned int minor;
     resource_size_t mapbase; /* for ioremap */
     resource_size_t mapsize;
     struct device *dev; /* parent device */
    	...
 }

const struct uart_ops *ops;是其中最重要的参数,ops包含了串口的具体驱动函数,

每个 UART 都有一个 uart_port,那么 uart_port 是怎么和 uart_driver 结合起来的呢?这里要用到 uart_add_one_port 函数,函数原型如下:

c 复制代码
int uart_add_one_port(struct uart_driver *reg, struct uart_port *port);

drv:此 port 对应的 uart_driver。

uport: 要添加到 uart_driver 中的 port。

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

卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到uart_remove_one_port 函数,函数原型如下:

c 复制代码
int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port);

uart_ops

const struct uart_ops *ops;是uart_port最重要的参数

因为 ops 包含了针对 UART 具体的驱动函数, Linux 系统收发数据最终调用的都是 ops 中的函数。 ops 是 uart_ops类型的结构体指针变量, uart_ops 定义在 include/linux/serial_core.h 文件中,内容如下:

c 复制代码
/*
 * This structure describes all the operations that can be done on the
 * physical hardware.  See Documentation/serial/driver for details.
 */
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 驱动编写人员需要实现 uart_ops,因为 uart_ops 是最底层的 UART 驱动接口,是实实在在的和 UART 寄存器打交道的。关于 uart_ops 结构体中的这些函数的具体含义请参考Documentation/serial/driver 这个文档。

驱动编写

以uart3为例,来讲解开发过程

1.从设备树出发,剖析开发驱动

c 复制代码
			uart3: serial@021ec000 {
				compatible = "fsl,imx6ul-uart",
					     "fsl,imx6q-uart", "fsl,imx21-uart";
				reg = <0x021ec000 0x4000>;
				interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_UART3_IPG>,
					 <&clks IMX6UL_CLK_UART3_SERIAL>;
				clock-names = "ipg", "per";
				dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
				dma-names = "rx", "tx";
				status = "disabled";
			};

找到具体的驱动文件drivers/tty/serial/imx.c

可以看到uart3的驱动文件在tty结合起来的

c 复制代码
#define SERIAL_IMX_MAJOR	207
#define MINOR_START		16
#define DEV_NAME		"ttymxc"
#define MCTRL_TIMEOUT	(250*HZ/1000)

#define DRIVER_NAME "IMX-uart"

#define UART_NR 8
#define IMX_RXBD_NUM 20
#define IMX_MODULE_MAX_CLK_RATE	80000000



static struct uart_driver imx_reg = {
	.owner          = THIS_MODULE,
	.driver_name    = DRIVER_NAME,
	.dev_name       = DEV_NAME,
	.major          = SERIAL_IMX_MAJOR,
	.minor          = MINOR_START,
	.nr             = ARRAY_SIZE(imx_ports),
	.cons           = IMX_CONSOLE,
};
...
static int __init imx_serial_init(void)
{
	int ret = uart_register_driver(&imx_reg);

	if (ret)
		return ret;

	ret = platform_driver_register(&serial_imx_driver);
	if (ret != 0)
		uart_unregister_driver(&imx_reg);

	return ret;
}

static void __exit imx_serial_exit(void)
{
	platform_driver_unregister(&serial_imx_driver);
	uart_unregister_driver(&imx_reg);
}

module_init(imx_serial_init);
module_exit(imx_serial_exit);

MODULE_AUTHOR("Sascha Hauer");
MODULE_DESCRIPTION("IMX generic serial port driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx-uart");

NXP官方驱动入口imx_serial_init

​ -> uart_register_driver 先向内核注册uart_driver,为imx_reg

​ -> platform_driver_register注册platform驱动,匹配后运行platform_driver->serial_imx_probe

​ -> serial_imx_probe,在这里面定义了NXP根据自己的芯片封装了uart_port的struct imx_port *sport;

​ serial_imx_probe主要工作是1.申请imx_prot内存,2.获取设备树中的节点信息,3.获取uart3寄存器地址,4.初始化uart_port,(主要初始化了ops)5.中断处理,串口接收中断处理函数imx_rxint获取到串口接收到的数据,然后使用tty_inert_filp_char将其放到tty中

修改设备树使能UART3

c 复制代码
	
&uart3 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart3>;
	status = "okay";
};


		pinctrl_uart3: uart3grp {
			fsl,pins = <
				MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX	0x1b0b1
				MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX	0x1b0b1
			>;
		};

编译,复制,启动

c 复制代码
ls /dev/ttym*
/dev/ttymxc0  /dev/ttymxc2

可以在/dev/ttymxc2看到该uart3已经成功启动

我们不需要自己写应用程序来操作uart3

有现成的uart测试APP minicom

minicom移植

与dtb移植步骤类似

1.配置。2.编译。3.安装

c 复制代码
./configure --prefix=/home/alientek/linux/tool/ncurses --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf --with-shared --without-profile --disable-stripping --without-progs --with-manpages --without-tests

    
    make
    
    make install
    
    //就可以在--prefix目录下看到安装的内容了 
    
    //将安装内容中的库文件放到开发板上去
    sudo cp * ~/Desktop/nfs/rootfs/usr/lib/ -rfa
c 复制代码
./configure CC=arm-linux-gnueabihf-gcc --prefix=/home/alientek/linux/tool/minicom --host=arm-linux-gnueabihf CPPFLAGS=-I/home/alientek/linux/tool/ncurses/include LDFLAGS=-L/home/alientek/linux/tool/ncurses/lib -enable-cfg-dir=/etc/minicom
    
make 
    
make install

sudo cp /home/alientek/linux/tool/minicom/bin/* 开发板的/usr/bin
c 复制代码
minicom -v
minicom version 2.7.1 (compiled Sep  6 2025)
Copyright (C) Miquel van Smoorenburg.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version
2 of the License, or (at your option) any later version.
c 复制代码
vi /etc/passwd
#内容如下
root:x:0:0:root:/root:/bin/sh
    
    
//保存退出
export TERMINFO=/usr/share/terminfo
echo $TERMINFO

成功移植minicom

c 复制代码
            ┌─────[configuration]──────┐
            │ Filenames and paths      │
            │ File transfer protocols  │
            │ Serial port setup        │
            │ Modem and dialing        │
            │ Screen and keyboard      │
            │ Save setup as dfl        │
            │ Save setup as..          │
            │ Exit                     │
            │ Exit from Minicom        │
            └──────────────────────────┘
相关推荐
w***15311 小时前
四、nginx的优化和location匹配规则
运维·nginx
little_kid_pea1 小时前
Oracle:从收费明细中扣减退费数据
java·服务器·数据库
我不是张鸭鸭1 小时前
nginx的https的搭建
运维·nginx·https
汽车仪器仪表相关领域1 小时前
PSN-1:氮气加速 + 空燃比双控仪 ——NOS 系统的 “安全性能双管家”
大数据·linux·服务器·人工智能·功能测试·汽车·可用性测试
遇到困难睡大觉哈哈1 小时前
Harmony os 卡片传递消息给应用(message 事件)详细介绍
java·服务器·javascript·harmonyos·鸿蒙
杰 .1 小时前
Linux vim
linux·服务器
007php0071 小时前
nginx加速缓存导致Event-Stream消息延迟问题的解决方案
运维·网络·数据库·nginx·缓存·面试·职场和发展
Evan芙1 小时前
OpenEuler系统网卡地址定制
运维·服务器·网络