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        │
            └──────────────────────────┘
相关推荐
牢姐与蒯4 小时前
Linux信号(一).信号产生
linux·运维·服务器·ubuntu
FfHUCisI4 小时前
sync.Once 与 sync.Cond 源码与并发控制陷阱
服务器·开发语言·后端·golang
爱签AI电子合同5 小时前
电子合同上手成本怎么测?易用性维度专项测评
服务器·人工智能·智能合约·企业微信
aixingkong9217 小时前
Nvidia为例 XPU的Multi-Host、Socket Direct 、GPUDirect、RDMA 技术点分析
服务器·网络·硬件架构·硬件工程
雯宝10 小时前
mnt_init 函数
linux
律宏阔15 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔15 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴15 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千16 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei3716 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化