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        │
            └──────────────────────────┘
相关推荐
大树881 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz2 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工2 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智3 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩3 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_3 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化