瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】258811263(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十七篇 串口_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第200章uart_driver注册流程分析
在上一章节中,我们详细介绍了 Linux 内核中的串口子系统框架,接下来,我们将深入探讨 uart_driver 的注册流程,以便更好地理解串口驱动程序是如何与 tty 核心层进行交互的。
200.1 uart相关底层结构体
uart相关底层结构体之间的关系如下所示:
接下来我们依次学习一下这几个结构体。
200.1.1 uart_driver
uart_driver 结构体代 表 UART 驱动,uart_driver 定义在 include/linux/serial_core.h 文件中,内容如下:
/*
* 结构体定义 UART 驱动程序。
*/
struct uart_driver {
struct module *owner; // 模块所有者
const char *driver_name; // 驱动程序名称
const char *dev_name; // 设备名称
int major; // 设备分配的主设备号
int minor; // 设备分配的次设备号
int nr; // 设备的唯一标识符
struct console *cons; // 控制台的指针
/*
* 这些是私有的; 低级驱动程序不应该访问这些; 它们应该被初始化为 NULL
*/
struct uart_state *state; // 指向 UART 驱动程序状态的指针
struct tty_driver *tty_driver; // 指向 TTY 驱动程序的指针
};
struct uart_driver封装了tty_driver,使得底层的UART驱动不需要关心tty_driver。
200.1.2 uart_port
uart_port是针对一个串口的抽象,定义在kernel/include/linux/serial_core.h,其内部包含一个tty_port类型的成员变量,内容如下:
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* io端口基地址(物理) */
unsigned char __iomem *membase; /* io内存基地址(虚拟) */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* 串口缓冲区大小 */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* 寄存器位移 */
unsigned char iotype; /* IO访问方式 */
unsigned char unused1;
unsigned int read_status_mask; /* 关心 Rx error status */
unsigned int ignore_status_mask; /* 忽略 Rx error status */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* 串口信息计数器 */
struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif
upf_t flags;
unsigned int mctrl; /* 当前的Moden 设置 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* 端口类型 */
const struct uart_ops *ops; /* 串口端口操作函数 */
unsigned int custom_divisor;
unsigned int line; /* 端口索引 */
resource_size_t mapbase; /* io内存物理基地址 */
struct device *dev; /* 父设备 */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};
200.1.3 uart_state
struct uart_state是一个结构体,定义在kernel/include/linux/serial_core.h,通常用于表示UART驱动程序的状态信息。通过uart_driver结构体中的state成员指针,可以访问和操作与UART设备状态有关的数据。struct uart_state内容如下:
/*
* 结构体定义 UART 驱动程序的状态。
*/
struct uart_state {
struct tty_port port; // tty端口结构体
enum uart_pm_state pm_state; // UART 电源管理状态
struct circ_buf xmit; // 待发送数据的循环缓冲区
atomic_t refcount; // 引用计数
wait_queue_head_t remove_wait; // 等待队列头用于移除操作
struct uart_port *uart_port; // UART 端口结构体指针
};
200.1.4 uart_ops
结构体 uart_ops 包含了一系列函数指针,这些函数指针定义了对 UART 端口进行操作的接口。每个函数指针对应一个特定的操作,例如发送数据、设置控制信号、启动或停止传输等。通过 uart_ops 结构体,上层应用程序或驱动程序可以调用这些函数指针来操作 UART 端口,实现数据传输和控制操作。
/*
* 结构体定义 UART 操作函数集合。
*/
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); // 检查发送缓冲区是否为空的函数指针
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); // 设置modem控制信号的函数指针
unsigned int (*get_mctrl)(struct uart_port *); // 获取modem控制信号的函数指针
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 *); // 启用RTS/CTS(硬件流控)函数指针
void (*break_ctl)(struct uart_port *, int ctl); // 控制发送BREAK信号函数指针
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); // 电源管理函数指针
/*
* 返回描述端口类型的字符串
*/
const char *(*type)(struct uart_port *); // 获取端口类型的函数指针
/*
* 释放端口使用的IO和内存资源。
* 这包括必要时的iounmap。
*/
void (*release_port)(struct uart_port *); // 释放端口函数指针
/*
* 请求端口使用的IO和内存资源。
* 这包括必要时的iomap端口。
*/
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
};
200.2 uart_driver注册流程分析
我们打开Linux内核源码,kernel/drivers/tty/serial/8250/8250_core.c 文件中的函数主要是与 8250 系列 UART 驱动程序的核心功能相关的。这个文件实现了 8250 这种串行通信设备的核心操作,包括初始化、配置、中断处理、数据传输等功能。
打开kernel/drivers/tty/serial/8250/8250_core.c文件,如下所示
#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT
rootfs_initcall(serial8250_init);
#else
module_init(serial8250_init);
#endif
module_exit(serial8250_exit);
这段代码根据是否定义了 CONFIG_ROCKCHIP_THUNDER_BOOT 宏来选择不同的初始化方式:如果定义了,则在 rootfs 阶段初始化串口设备;否则,在模块加载时进行初始化。无论哪种方式,模块卸载时都会执行相应的退出函数。serial8250_init(void)函数如下所示:
static int __init serial8250_init(void)
{
int ret;
// 检查是否有UART端口被定义,如果没有则返回-ENODEV错误
if (nr_uarts == 0)
return -ENODEV;
// 初始化8250/16550串口
serial8250_isa_init_ports();
// 打印串口驱动信息,包括端口数量和IRQ共享状态
pr_info("Serial: 8250/16550 driver, %d ports, IRQ sharing %sabled\n",
nr_uarts, share_irqs ? "en" : "dis");
#ifdef CONFIG_SPARC
// 如果是SPARC架构,注册串口设备的次设备号
ret = sunserial_register_minors(&serial8250_reg, UART_NR);
#else
// 否则,在当前平台上注册UART驱动程序
serial8250_reg.nr = UART_NR;
ret = uart_register_driver(&serial8250_reg);
#endif
if (ret)
goto out;
// 初始化PNP设备(如果存在)
ret = serial8250_pnp_init();
if (ret)
goto unreg_uart_drv;
// 分配ISA平台设备结构体并注册
serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_pnp;
}
// 将平台设备添加到系统
ret = platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
// 注册串口端口
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
// 注册平台驱动程序
ret = platform_driver_register(&serial8250_isa_driver);
if (ret == 0)
goto out;
// 如果注册失败,删除已添加的ISA平台设备
platform_device_del(serial8250_isa_devs);
put_dev:
platform_device_put(serial8250_isa_devs);
unreg_pnp:
// 退出时释放PNP设备
serial8250_pnp_exit();
unreg_uart_drv:
#ifdef CONFIG_SPARC
// 如果是SPARC架构,注销串口设备的次设备号
sunserial_unregister_minors(&serial8250_reg, UART_NR);
#else
// 否则,在当前平台上注销UART驱动程序
uart_unregister_driver(&serial8250_reg);
#endif
out:
// 返回初始化结果
return ret;
}
接下来的章节我们依次分析serial8250_init函数中重要的代码。
200.2.1 初始化8250串口
serial8250_init(void)函数主要负责在系统启动时初始化和注册8250/16550串口设备驱动,其中第10行的serial8250_isa_init_ports();函数初始化8250串口,函数如下所示:
static void __init serial8250_isa_init_ports(void)
{
struct uart_8250_port *up; // UART 8250端口结构体指针
static int first = 1; // 静态变量,用于标记是否首次初始化
int i, irqflag = 0; // 循环变量和IRQ标志位初始化为0
// 如果不是首次初始化,则直接返回,避免重复初始化
if (!first)
return;
first = 0; // 标记为不是首次初始化
// 如果系统定义的UART端口数量大于硬件支持的最大数量,将其限制为最大支持数量
if (nr_uarts > UART_NR)
nr_uarts = UART_NR;
// 遍历所有定义的UART端口数量,初始化每个端口
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port up = &serial8250_ports[i]; // 获取第i个UART端口结构体指针
struct uart_port *port = &up->port; // 获取端口的通用UART端口结构体
port->line = i; // 设置UART端口的逻辑线号
serial8250_init_port(up); // 初始化UART端口
// 如果base_ops尚未设置,则使用当前端口的操作函数作为基本操作函数
if (!base_ops)
base_ops = port->ops;
port->ops = &univ8250_port_ops; // 设置UART端口的操作函数为univ8250_port_ops
// 初始化定时器
timer_setup(&up->timer, serial8250_timeout, 0);
up->ops = &univ8250_driver_ops; // 设置UART端口的驱动操作函数为univ8250_driver_ops
/*
* ALPHA_KLUDGE_MCR needs to be killed.
*/
up->mcr_mask = ~ALPHA_KLUDGE_MCR; // 设置UART端口的MCR屏蔽位,屏蔽ALPHA_KLUDGE_MCR
up->mcr_force = ALPHA_KLUDGE_MCR; // 设置UART端口的MCR强制位,设置为ALPHA_KLUDGE_MCR
serial8250_set_defaults(up); // 设置UART端口的默认参数
}
// 将基本端口操作链到支持远程监控适配器(RSA)
univ8250_port_ops = *base_ops;
univ8250_rsa_support(&univ8250_port_ops);
// 如果设置了共享IRQ标志,设置IRQ标志位
if (share_irqs)
irqflag = IRQF_SHARED;
// 遍历旧的串口端口数组,初始化对应的UART端口
for (i = 0, up = serial8250_ports;
i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
i++, up++) {
struct uart_port *port = &up->port;
// 设置UART端口的IO基地址、IRQ、IRQ标志、时钟频率、标志、hub6等参数
port->iobase = old_serial_port[i].port;
port->irq = irq_canonicalize(old_serial_port[i].irq); // 规范化IRQ
port->irqflags = 0;
port->uartclk = old_serial_port[i].baud_base * 16; // 设置UART时钟频率
port->flags = old_serial_port[i].flags; // 设置UART端口标志
port->hub6 = 0;
port->membase = old_serial_port[i].iomem_base;
port->iotype = old_serial_port[i].io_type;
port->regshift = old_serial_port[i].iomem_reg_shift;
port->irqflags |= irqflag; // 设置IRQ标志位
// 如果定义了ISA配置函数,调用配置函数进行额外的配置
if (serial8250_isa_config != NULL)
serial8250_isa_config(i, &up->port, &up->capabilities);
}
}
在上面的第3行代码 struct uart_8250_port *up;定义了 UART 8250端口结构体指针,struct uart_8250_port定义如下所示:
struct uart_8250_port {
struct uart_port port; /*在上一小节讲过uart_port*/
struct timer_list timer; /* "no irq" timer */
struct list_head list; /* ports on this IRQ */
.......
};
在上面的第18行代码serial8250_ports[i]这声明了一个静态的数组 serial8250_ports,数组元素类型为 struct uart_8250_port,这个数组的大小为 UART_NR。如下所示:
static struct uart_8250_port serial8250_ports[UART_NR];
在上面的第22行代码执行了serial8250_init_port函数,函数实现如下所示:
/*
* 初始化 UART 8250 端口的函数。
*/
void serial8250_init_port(struct uart_8250_port *up)
{
struct uart_port *port = &up->port; // 获取 UART 8250 端口结构体
spin_lock_init(&port->lock); // 初始化端口锁
port->ops = &serial8250_pops; // 设置端口操作函数为 serial8250_pops
up->cur_iotype = 0xFF; // 设置当前端口的IO类型为 0xFF
}
serial8250_pops结构体如下所示:
/*
* 包含了串行8250端口操作函数集合的结构体
*/
static const struct uart_ops serial8250_pops = {
.tx_empty = serial8250_tx_empty, // 传输缓冲区是否为空
.set_mctrl = serial8250_set_mctrl, // 设置控制信号
.get_mctrl = serial8250_get_mctrl, // 获取控制信号
.stop_tx = serial8250_stop_tx, // 停止发送
.start_tx = serial8250_start_tx, // 启动发送
.throttle = serial8250_throttle, // 限流
.unthrottle = serial8250_unthrottle, // 解除限流
.stop_rx = serial8250_stop_rx, // 停止接收
.enable_ms = serial8250_enable_ms, // 启用RTS/CTS
.break_ctl = serial8250_break_ctl, // 控制发送BREAK信号
.startup = serial8250_startup, // 启动
.shutdown = serial8250_shutdown, // 关闭
.set_termios = serial8250_set_termios, // 设置终端参数
.set_ldisc = serial8250_set_ldisc, // 设置行规则
.pm = serial8250_pm, // 电源管理
.type = serial8250_type, // 返回端口类型的字符串
.release_port = serial8250_release_port, // 释放端口资源
.request_port = serial8250_request_port, // 请求端口资源
.config_port = serial8250_config_port, // 配置端口
.verify_port = serial8250_verify_port, // 验证端口
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = serial8250_get_poll_char, // 获取字符(用于轮询)
.poll_put_char = serial8250_put_poll_char, // 发送字符(用于轮询)
#endif
};
200.2.2 注册UART驱动程序
serial8250_init(void)函数主要负责在系统启动时初始化和注册8250/16550串口设备驱动,其中第22行的uart_register_driver(&serial8250_reg),通过 uart_register_driver 函数向系统注册这个 uart_driver,函数原型如下:
int uart_register_driver(struct uart_driver *uart)
函数参数和返回值含义如下所示:
- uart:要注册的 uart_driver。
- 返回值:0,成功;负值,失败。
注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数原型如下:
void uart_unregister_driver(struct uart_driver *uart)
函数参数和返回值含义如下所示:
- uart:要注销的 uart_driver。
- 返回值:无
第22行中uart_register_driver 函数传入的参数是serial8250_reg
/*
* 定义了一个名为 serial8250_reg 的 UART 驱动程序结构体
*/
static struct uart_driver serial8250_reg = {
.owner = THIS_MODULE, // 模块的所有者
.driver_name = "serial", // 驱动程序名称
.dev_name = "ttyS", // 设备名称
.major = TTY_MAJOR, // 主设备号
.minor = 64, // 次设备号
.cons = SERIAL8250_CONSOLE, // 控制台
};
uart_register_driver函数内容如下所示,定义在在源码kernel/drivers/tty/serial/serial_core.c中。
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
// 检查驱动状态是否已被占用
BUG_ON(drv->state);
/*
* 或许我们应该为此使用一个slab cache,特别是如果我们要处理大量的端口。
*/
// 为UART状态分配内存空间
drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
if (!drv->state)
goto out;
// 分配tty驱动器
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out_kfree;
drv->tty_driver = normal;
// 设置tty驱动器的属性
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* 初始化UART状态。
*/
// 遍历每个UART状态并初始化对应的tty端口
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
}
// 注册tty驱动器
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
// 注册失败时,销毁已初始化的tty端口并释放内存
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
uart_register_driver函数中的第36行代码设置tty驱动器的操作函数,这些操作函数定义了与特定设备相关的操作,如读写、控制等。
// 定义了UART设备的操作函数指针结构体
static const struct tty_operations uart_ops = {
.install = uart_install, // 安装tty设备
.open = uart_open, // 打开tty设备
.close = uart_close, // 关闭tty设备
.write = uart_write, // 写入数据到tty设备
.put_char = uart_put_char, // 将字符写入tty设备输出缓冲区
.flush_chars = uart_flush_chars, // 刷新tty设备输出缓冲区
.write_room = uart_write_room, // 获取tty设备输出缓冲区剩余空间大小
.chars_in_buffer= uart_chars_in_buffer, // 获取tty设备输入缓冲区中的字符数量
.flush_buffer = uart_flush_buffer, // 刷新tty设备输入缓冲区
.ioctl = uart_ioctl, // 控制tty设备的操作
.throttle = uart_throttle, // 控制tty设备的流控状态
.unthrottle = uart_unthrottle, // 控制tty设备的流控状态
.send_xchar = uart_send_xchar, // 发送特殊字符到tty设备
.set_termios = uart_set_termios, // 设置tty设备的终端参数
.set_ldisc = uart_set_ldisc, // 设置tty设备的行规则
.stop = uart_stop, // 停止tty设备
.start = uart_start, // 启动tty设备
.hangup = uart_hangup, // 关闭tty设备的连接
.break_ctl = uart_break_ctl, // 控制tty设备的发送中断
.wait_until_sent= uart_wait_until_sent, // 等待tty设备发送完所有数据
#ifdef CONFIG_PROC_FS
.proc_show = uart_proc_show, // 显示与uart相关的proc文件系统信息
#endif
.tiocmget = uart_tiocmget, // 获取tty设备的modem状态
.tiocmset = uart_tiocmset, // 设置tty设备的modem状态
.get_icount = uart_get_icount, // 获取tty设备的计数信息
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init, // 初始化tty设备的轮询模式
.poll_get_char = uart_poll_get_char, // 从tty设备获取字符(轮询模式)
.poll_put_char = uart_poll_put_char, // 将字符写入tty设备(轮询模式)
#endif
};
uart_register_driver函数中的第51行代码tty_register_driver函数如下所示,定义在kernel/drivers/tty/tty_io.c
/*
* 当tty驱动程序调用此函数以注册自身时被调用。
*/
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
struct device *d;
// 如果驱动程序没有指定主设备号,则分配一个
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < 0)
goto err;
// 如果驱动标志为TTY_DRIVER_DYNAMIC_ALLOC,则动态添加tty字符设备
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
error = tty_cdev_add(driver, dev, 0, driver->num);
if (error)
goto err_unreg_char;
}
mutex_lock(&tty_mutex);
// 将驱动程序添加到tty驱动程序列表中
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex);
// 如果驱动标志没有设置为TTY_DRIVER_DYNAMIC_DEV,则注册tty设备
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
if (IS_ERR(d)) {
error = PTR_ERR(d);
goto err_unreg_devs;
}
}
}
// 注册TTY驱动程序到proc文件系统
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
err_unreg_devs:
// 注销已注册的tty设备
for (i--; i >= 0; i--)
tty_unregister_device(driver, i);
mutex_lock(&tty_mutex);
// 从tty驱动程序列表中移除驱动程序
list_del(&driver->tty_drivers);
mutex_unlock(&tty_mutex);
err_unreg_char:
// 注销已注册的字符设备
unregister_chrdev_region(dev, driver->num);
err:
return error;
}
200.2.3 分配平台设备结构体并注册
serial8250_init(void)函数主要负责在系统启动时初始化和注册8250/16550串口设备驱动,其中第33-40行代码,如下所示:
// 分配平台设备结构体并注册
serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_pnp;
}
// 将平台设备添加到系统
ret = platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
platform_device_alloc函数用于分配一个平台设备结构体,但不会将其注册到平台总线上。返回的结构体可以在之后使用函数platform_device_add()来将其注册到平台总线上。
200.2.5 注册串口端口
serial8250_init(void)函数主要负责在系统启动时初始化和注册8250/16550串口设备驱动,其中第45行代码,如下所示:
// 注册串口端口
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
serial8250_register_ports函数内容如下所示:
static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
#ifndef CONFIG_ARCH_ROCKCHIP
int i;
// 遍历所有串口
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i];
// 如果是CIR端口,则跳过
if (up->port.type == PORT_8250_CIR)
continue;
// 如果端口已经分配了设备,则跳过
if (up->port.dev)
continue;
// 将设备指针与端口关联起来
up->port.dev = dev;
// 应用串口8250的特殊处理
serial8250_apply_quirks(up);
// 向UART驱动注册一个串口
uart_add_one_port(drv, &up->port);
}
#endif
}
207.2.6 注册platform_driver驱动
serial8250_init(void)函数主要负责在系统启动时初始化和注册8250/16550串口设备驱动,其中第48行代码,如下所示:
// 注册平台驱动程序
ret = platform_driver_register(&serial8250_isa_driver);
if (ret == 0)
goto out;
至此,uart_driver注册流程分析完毕!