linux中pl011串口驱动的简单分析

背景

需要用 rust 重写串口驱动,那么第一步就是对其进行分析。

代码链接

Amba 总线

pl011 作为 arm 的片内外设,可以使用 amba 总线相关的函数进行驱动注册。

static int __init pl011_init(void)
{
	printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
	return amba_driver_register(&pl011_driver);
}
 
static void __exit pl011_exit(void)
{
	amba_driver_unregister(&pl011_driver);
}
 
arch_initcall(pl011_init);
module_exit(pl011_exit);
 

arch_initcall 表示初始化代码与体系结构相关,确保该 pl011_init 在体系结构特定的初始化代码后运行。

amba_driver结构体

static struct vendor_data vendor_arm = {
	.reg_offset		= pl011_std_offsets,
	.ifls			= UART011_IFLS_RX4_8|UART011_IFLS_TX4_8,
	.fr_busy		= UART01x_FR_BUSY,
	.fr_dsr			= UART01x_FR_DSR,
	.fr_cts			= UART01x_FR_CTS,
	.fr_ri			= UART011_FR_RI,
	.oversampling		= false,
	.dma_threshold		= false,
	.cts_event_workaround	= false,
	.always_enabled		= false,
	.fixed_options		= false,
	.get_fifosize		= get_fifosize_arm,
};
static const struct amba_id pl011_ids[] = {
	{
		.id	= 0x00041011, // 表示设备的 id,与设备树中arm,primecell-periphid所定义的值对应
		.mask	= 0x000fffff, // kernel 在匹配设备时,仅会注意 mask 二进制位为 1 的位,忽略为 0 的位
		.data	= &vendor_arm,
	},
	{ 0, 0 },
};
static struct amba_driver pl011_driver = {
	.drv = {
		.name	= "uart-pl011",
		.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011),
	},
	.id_table	= pl011_ids,
	.probe		= pl011_probe,
	.remove		= pl011_remove,
};

在 init 中,通过 amba_driver_register 函数将 pl011_driver 结构体注册到了总线上。

在 kernel 发现有与 id_table 相匹配的设备时,将调用 probe 函数。

我们需要在 probe 中进行设备的初始化和 uart_driver 的注册。

probe 函数

// uart_amba_port 存放封装了的 uart_port 串口信息
struct uart_amba_port {
	struct uart_port	port;    // 所对应的串口端口
	const u16		*reg_offset;    // 寄存器偏移量数组
	struct clk		*clk; // 时钟结构体
	const struct vendor_data *vendor; // 设备相关的数据
	unsigned int		im;		/* interrupt mask */
	unsigned int		old_status;
	unsigned int		fifosize;	/* vendor-specific */
	unsigned int		fixed_baud;	/* vendor-set fixed baud rate */
	char			type[12];
	bool			rs485_tx_started;
	unsigned int		rs485_tx_drain_interval; /* usecs */
};
 
static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
{
	struct uart_amba_port *uap;
    // vendor_data 源于 amba_id 结构体中设置的 data
	struct vendor_data *vendor = id->data;
	int portnr, ret;
 
    // 在本文件中定义了*uart_amba_port的数组,在这里寻找一个空位
	portnr = pl011_find_free_port();
	if (portnr < 0)
		return portnr;
 
    // 分配内存
	uap = devm_kzalloc(&dev->dev, sizeof(struct uart_amba_port),
			   GFP_KERNEL);
	if (!uap)
		return -ENOMEM;
 
    // 获取指针
	uap->clk = devm_clk_get(&dev->dev, NULL);
	if (IS_ERR(uap->clk))
		return PTR_ERR(uap->clk);
 
    // 如果设备树中 cts-event-workaround 被设定,在 vendor 中进行相应设置
	if (of_property_read_bool(dev->dev.of_node, "cts-event-workaround")) {
	    vendor->cts_event_workaround = true;
	    dev_info(&dev->dev, "cts_event_workaround enabled\n");
	}
 
	uap->reg_offset = vendor->reg_offset;    // 设置寄存器偏移量
	uap->vendor = vendor;    // 设置设备相关数据
	uap->fifosize = vendor->get_fifosize(dev);    //设置最大缓冲区大小,其中get_fifosize函数在vendor中定义
	uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;    //寄存器访问是否是 32 位
	uap->port.irq = dev->irq[0];    // 中断号
	uap->port.ops = &amba_pl011_pops;    //设备使用的相关函数,**需要实现**,详见后
	snprintf(uap->type, sizeof(uap->type), "PL011 rev%u", amba_rev(dev));    //设置 uap 的 type
 
	ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr);    // uart_port 内存相关的设置,详细的在后面
	if (ret)
		return ret;
 
	amba_set_drvdata(dev, uap);    // 将设备结构体中的 data 设置为 uap
 
	return pl011_register_port(uap); // 注册串口
}

pl011_setup_port函数

static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap,
			    struct resource *mmiobase, int index)
{
	void __iomem *base;
 
    // 映射资源
	base = devm_ioremap_resource(dev, mmiobase);
	if (IS_ERR(base))
		return PTR_ERR(base);
 
	// 通过设备树别名进行索引探测
	index = pl011_probe_dt_alias(index, dev);
 
	uap->port.dev = dev;    //设置设备
	uap->port.mapbase = mmiobase->start;    //设置物理基地址
	uap->port.membase = base;    // 设置基地址
	uap->port.fifosize = uap->fifosize;    // 设置最大缓冲区大小
	uap->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_AMBA_PL011_CONSOLE);    // 是否启用 SysRq 功能
	uap->port.flags = UPF_BOOT_AUTOCONF;    // 标志位,自动配置
	uap->port.line = index;    // 端口索引
 
	amba_ports[index] = uap;    // 将 uap 存储到 amba_port 中
 
	return 0;
}

pl011_register_port函数

//控制台结构体
static struct console amba_console = {
	.name		= "ttyAMA",    //控制台名称
	.write		= pl011_console_write,    //控制台写入函数
	.device		= uart_console_device,    //控制台设备,通过console和index获取tty设备
	.setup		= pl011_console_setup,    //控制台设置函数
	.match		= pl011_console_match,    //控制台匹配函数
	.flags		= CON_PRINTBUFFER | CON_ANYTIME, //CON_PRINTBUFFER表示在缓冲区中打印,CON_ANYTIME 表示控制台在任何时候都可用
	.index		= -1,    //表示当前未指定特定索引
	.data		= &amba_reg,    //指向串口驱动结构体
};
 
#define AMBA_CONSOLE	(&amba_console)
// 串口驱动结构体
static struct uart_driver amba_reg = {
	.owner			= THIS_MODULE,
	.driver_name	= "ttyAMA",    // 驱动名
	.dev_name		= "ttyAMA",    //设备名
	.major			= SERIAL_AMBA_MAJOR,
	.minor			= SERIAL_AMBA_MINOR,
	.nr			    = UART_NR,
	.cons			= AMBA_CONSOLE,    // 控制台
};
static int pl011_register_port(struct uart_amba_port *uap)
{
	int ret, i;
 
	/* 确保来自该UART的中断被屏蔽和清除 */
	pl011_write(0, uap, REG_IMSC);
	pl011_write(0xffff, uap, REG_ICR);
 
    // 如果驱动没有被注册
	if (!amba_reg.state) {
		ret = uart_register_driver(&amba_reg);    // 注册串口驱动
		if (ret < 0) {
			dev_err(uap->port.dev,
				"Failed to register AMBA-PL011 driver\n");
			for (i = 0; i < ARRAY_SIZE(amba_ports); i++)
				if (amba_ports[i] == uap)
					amba_ports[i] = NULL;
			return ret;
		}
	}
 
    // 将串口端口添加到驱动上
	ret = uart_add_one_port(&amba_reg, &uap->port);
	if (ret)
		pl011_unregister_port(uap);
 
	return ret;
}

amba_pl011_pops结构体

工作量最大的地方(

static const struct uart_ops amba_pl011_pops = {
	.tx_empty	= pl011_tx_empty,
	.set_mctrl	= pl011_set_mctrl,
	.get_mctrl	= pl011_get_mctrl,
	.stop_tx	= pl011_stop_tx,
	.start_tx	= pl011_start_tx,
	.stop_rx	= pl011_stop_rx,
	.throttle	= pl011_throttle_rx,
	.unthrottle	= pl011_unthrottle_rx,
	.enable_ms	= pl011_enable_ms,
	.break_ctl	= pl011_break_ctl,
	.startup	= pl011_startup,
	.shutdown	= pl011_shutdown,
	.set_termios	= pl011_set_termios,
	.type		= pl011_type,
	.config_port	= pl011_config_port,
	.verify_port	= pl011_verify_port,
#ifdef CONFIG_CONSOLE_POLL
	.poll_init     = pl011_hwinit,
	.poll_get_char = pl011_get_poll_char,
	.poll_put_char = pl011_put_poll_char,
#endif
};

这个结构体 uart_ops 包含了一组函数指针,用于定义 UART 驱动的操作。每个函数指针对应特定的 UART 操作。下面是每个成员的作用:

基本操作

  1. tx_emptypl011_tx_empty

    • 检查传输缓冲区是否为空。用于确定是否可以发送新的数据。
  2. set_mctrlpl011_set_mctrl

    • 设置调制控制(Modem Control)信号,如 RTS(请求发送)和 DTR(数据终端就绪)。
  3. get_mctrlpl011_get_mctrl

    • 获取当前调制控制信号的状态。
  4. stop_txpl011_stop_tx

    • 停止数据传输。当不再需要传输数据时调用。
  5. start_txpl011_start_tx

    • 开始数据传输。准备发送数据时调用。
  6. stop_rxpl011_stop_rx

    • 停止接收数据。当不再需要接收数据时调用。
  7. throttlepl011_throttle_rx

    • 节流接收数据。通常用于流控制(flow control),防止接收端过载。
  8. unthrottlepl011_unthrottle_rx

    • 解除节流,重新允许接收数据。
  9. enable_mspl011_enable_ms

    • 启用调制信号(Modem Status)中断,以监控调制信号的变化。
  10. break_ctlpl011_break_ctl

    • 控制 UART 的中断信号。通常用于发送 Break 信号。

初始化和配置

  1. startuppl011_startup

    • 启动 UART 端口。在端口打开时调用,进行初始化。
  2. shutdownpl011_shutdown

    • 关闭 UART 端口。在端口关闭时调用,进行清理工作。
  3. set_termiospl011_set_termios

    • 设置端口的串行参数,如波特率、数据位、停止位、校验位等。
  4. typepl011_type

    • 返回 UART 端口的类型。用于识别端口类型。
  5. config_portpl011_config_port

    • 配置 UART 端口。用于端口的特定配置操作。
  6. verify_portpl011_verify_port

    • 验证 UART 端口配置。检查配置是否有效。

控制台轮询(仅在启用 CONFIG_CONSOLE_POLL 时有效)

  1. poll_initpl011_hwinit

    • 初始化轮询模式。在控制台轮询模式下使用。
  2. poll_get_charpl011_get_poll_char

    • 从 UART 端口读取一个字符。在轮询模式下获取输入。
  3. poll_put_charpl011_put_poll_char

    • 向 UART 端口写入一个字符。在轮询模式下输出字符。

这些函数指针定义了 UART 驱动的各个操作,实现了 UART 的初始化、配置、传输控制、状态获取等功能。每个函数对应一个特定的操作,通过这些操作可以全面管理 UART 端口的工作。