​【迅为电子】RK3568驱动指南|第十七篇 串口-第200章uart_driver注册流程分析

瑞芯微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)

函数参数和返回值含义如下所示:

  1. uart:要注册的 uart_driver。
  2. 返回值:0,成功;负值,失败。

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

void uart_unregister_driver(struct uart_driver *uart)

函数参数和返回值含义如下所示:

  1. uart:要注销的 uart_driver。
  2. 返回值:无

第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注册流程分析完毕!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值