UART驱动情景分析-注册

一、tty串口驱动框架

应用程序通过某一个设备节点来访问驱动程序,设备节点都对应了某些驱动程序。
在这些驱动程序里面,肯定会有一个cdev与驱动对应。这个cdev里面肯定会有一个file_operations结构体。
在这里插入图片描述

我们可以从最低层开始,对于imx6ull是drivers/tty/serial/imx.c文件。

  • APP:应用程序open、write
  • tty驱动层:tty_io.c文件设置了cdev -> file_operations
  • 对应的tty端口:serial_core.c(串口)、vt.c(键盘),会向上注册一个tty_driver
  • 具体芯片的串口:比如imx6ull相关的驱动程序分为两部分
    • imx6ull串口控制器通用的代码,.tx命令,.rx命令。读状态写数据
    • 硬件信息,比如UART0的寄存器地址、中断号;UART1的寄存器地址、中断号

与串口相关的代码driver/tty/serial/imx.c
串口的通用代码是用结构体uart_driver开头的,串口的硬件相关的代码是根据设备树的信息创建一个uart_port开头的。
uart_port相关的信息又分为platform_driver和设备设备树上的信息。
当platform_driver和设备树匹配后,就会在probe函数里构造一个uart_port,并向上注册。(把uart_port注册进uart_driver)
设备树里的信息

假设imx6ull有两个串口,那就会有两个uart_port来对应。

二、串口驱动分析

driver/tty/serial/imx.c文件里的初始化函数:

static int __init imx_uart_init(void)
{
    int ret = uart_register_driver(&imx_uart_uart_driver);	//注册一个uart_driver
//...
    ret = platform_driver_register(&imx_uart_platform_driver);	//注册一个platfrom_driver
//...
    return ret;
}

//这里的uart_driver,比较简单。没有什么操作函数
//只定义了设备名、主次设备号,支持的多少个串口
static struct uart_driver imx_uart_uart_driver = {
	.owner          = THIS_MODULE,
	.driver_name    = DRIVER_NAME,
	.dev_name       = DEV_NAME,
	.major          = SERIAL_IMX_MAJOR,
	.minor          = MINOR_START,
	.nr             = ARRAY_SIZE(imx_uart_ports),
	.cons           = IMX_CONSOLE,
};

//注册的平台driver,用来处理设备树中的信息的
static struct platform_driver imx_uart_platform_driver = {
	.probe = imx_uart_probe,
	.remove = imx_uart_remove,

	.id_table = imx_uart_devtype,
	.driver = {
		.name = "imx-uart",
		.of_match_table = imx_uart_dt_ids,
		.pm = &imx_uart_pm_ops,
	},
};

继续查看probe函数

static int imx_uart_probe(struct platform_device *pdev)
{
	//...
	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
	//...
//解析设备树
	ret = imx_uart_probe_dt(sport, pdev);
	//.....
//获得设备树内的资源,获得寄存器地址
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);
//获得接收中断、发送中断的中断号等等
	rxirq = platform_get_irq(pdev, 0);
	txirq = platform_get_irq(pdev, 1);
	rtsirq = platform_get_irq(pdev, 2);
//这里的port类型就是struct uart_port,这里设置的uart_port的信息
	sport->port.dev = &pdev->dev;
	sport->port.mapbase = res->start;
	sport->port.membase = base;
	sport->port.type = PORT_IMX,
	sport->port.iotype = UPIO_MEM;
	sport->port.irq = rxirq;
	sport->port.fifosize = 32;
	sport->port.ops = &imx_uart_pops;		//这个operation操作函数在uart_port里
	sport->port.rs485_config = imx_uart_rs485_config;
	sport->port.flags = UPF_BOOT_AUTOCONF;
	timer_setup(&sport->timer, imx_uart_timeout, 0);
	//...硬件相关的初始化

//向imx_uart_uart_driver注册uart_port结构体
	return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}

static const struct uart_ops imx_uart_pops = {
	.tx_empty	= imx_uart_tx_empty,
	.set_mctrl	= imx_uart_set_mctrl,
	.get_mctrl	= imx_uart_get_mctrl,
	.stop_tx	= imx_uart_stop_tx,			//停止发送
	.start_tx	= imx_uart_start_tx,		//开始发送
	.stop_rx	= imx_uart_stop_rx,
	.enable_ms	= imx_uart_enable_ms,
	.break_ctl	= imx_uart_break_ctl,
	.startup	= imx_uart_startup,			//启动串口
	.shutdown	= imx_uart_shutdown,		//关闭窗口
	.flush_buffer	= imx_uart_flush_buffer,	//清空缓存
	.set_termios	= imx_uart_set_termios,
	.type		= imx_uart_type,
	.config_port	= imx_uart_config_port,
	.verify_port	= imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
	.poll_init      = imx_uart_poll_init,
	.poll_get_char  = imx_uart_poll_get_char,
	.poll_put_char  = imx_uart_poll_put_char,
#endif
};

对于一个芯片,它有多个芯片,但是对于不同的串口,串口的读写函数可能不一样。
所以如果字节写驱动函数,主要是要编写imx_uart_platform_driver。

三、如何去注册driver

uart_driver的注册uart_register_driverdrivers/tty/serial/serial_core.c文件里

//drivers/tty/serial/serial_core.c
int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal;		//使用uart_driever去构造tty_driver
	int i, retval;
	//...
	drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
	//...
	normal = alloc_tty_driver(drv->nr);		//分配一个tty_driver
	//...
	drv->tty_driver = normal;
//设置uart_driver的信息,直接复制
	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);	//给tty提供了uart的operations函数
//不同的tty设备,要提供不同的操作函数
	/*
	 * Initialise the UART state(s).
	 */
	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_driver
	retval = tty_register_driver(normal);
	//...
}

对于每一个uart_driver结构体,都有一个usart_state指针,用来指向每个串口的state,个数是针对uart_driver的nr参数决定的。

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;		//每个uart_driver都有一组state,根据nr来决定个数
    struct tty_driver   *tty_driver;
};    

//每一个port对应一个uart_state
struct uart_state {
	struct tty_port		port;

	enum uart_pm_state	pm_state;
	struct circ_buf		xmit;

	atomic_t		refcount;
	wait_queue_head_t	remove_wait;
	struct uart_port	*uart_port;
};

这里开始分析alloc_tty_driver函数,是分配tty_driver的过程

//每一个串口的个数,对应一个line
static inline struct tty_driver *alloc_tty_driver(unsigned int lines)
{
	struct tty_driver *ret = tty_alloc_driver(lines, 0);
	if (IS_ERR(ret))
		return NULL;
	return ret;
}

#define tty_alloc_driver(lines, flags) \
		__tty_alloc_driver(lines, THIS_MODULE, flags)

struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
		unsigned long flags)
{
	//...
//首先分配了一个tty_driver
	driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
	//...
	kref_init(&driver->kref);
	driver->magic = TTY_DRIVER_MAGIC;
	driver->num = lines;		//支持多少个串口
	driver->owner = owner;
	driver->flags = flags;

	if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
		driver->ttys = kcalloc(lines, sizeof(*driver->ttys),
				GFP_KERNEL);
		driver->termios = kcalloc(lines, sizeof(*driver->termios),
				GFP_KERNEL);	//为每个串口分配了ttys和termios
		//...
	}

	if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		driver->ports = kcalloc(lines, sizeof(*driver->ports),
				GFP_KERNEL);	//对于每一个串口,都会分配一个tty_port指针
		//...
		cdevs = lines;		//cdevs等于串口的个数
	}

	driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
	//...
}

struct tty_driver {
	int	magic;		/* magic number for this structure */
	struct kref kref;	/* Reference management */
	struct cdev **cdevs;		//为每一个串口分配cdev,在uart_add_one_port会去注册cdev
	//...
	/*
	 * Pointer to the tty data structures
	 */
	struct tty_struct **ttys;		//为每一个串口分配了对应的指针数组
	struct tty_port **ports;		//这里的每一个tty_port一项对应上面的uart_state里的port指针
	struct ktermios **termios;
	void *driver_state;

	/*
	 * Driver methods
	 */

	const struct tty_operations *ops;
	struct list_head tty_drivers;
} __randomize_layout;

四、分析uart_add_one_port

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
	struct uart_state *state;
	struct tty_port *port;
	int ret = 0;
	struct device *tty_dev;
	int num_groups;

	BUG_ON(in_interrupt());

	if (uport->line >= drv->nr)
		return -EINVAL;

	state = drv->state + uport->line;
	port = &state->port;

	mutex_lock(&port_mutex);
	mutex_lock(&port->mutex);
	if (state->uart_port) {
		ret = -EINVAL;
		goto out;
	}

	/* Link the port to the driver state table and vice versa */
	atomic_set(&state->refcount, 1);
	init_waitqueue_head(&state->remove_wait);
	state->uart_port = uport;
	uport->state = state;

	state->pm_state = UART_PM_STATE_UNDEFINED;
	uport->cons = drv->cons;
	uport->minor = drv->tty_driver->minor_start + uport->line;
	uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
				drv->tty_driver->name_base + uport->line);
	if (!uport->name) {
		ret = -ENOMEM;
		goto out;
	}

	/*
	 * If this port is a console, then the spinlock is already
	 * initialised.
	 */
	if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
		spin_lock_init(&uport->lock);
		lockdep_set_class(&uport->lock, &port_lock_key);
	}
	if (uport->cons && uport->dev)
		of_console_check(uport->dev->of_node, uport->cons->name, uport->line);

	uart_configure_port(drv, state, uport);		//配置port

	port->console = uart_console(uport);

	num_groups = 2;
	if (uport->attr_group)
		num_groups++;

	uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
				    GFP_KERNEL);
	if (!uport->tty_groups) {
		ret = -ENOMEM;
		goto out;
	}
	uport->tty_groups[0] = &tty_dev_attr_group;
	if (uport->attr_group)
		uport->tty_groups[1] = uport->attr_group;

	/*
	 * Register the port whether it's detected or not.  This allows
	 * setserial to be used to alter this port's parameters.
	 */
	tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
			uport->line, uport->dev, port, uport->tty_groups);		//这里注册port
	if (likely(!IS_ERR(tty_dev))) {
		device_set_wakeup_capable(tty_dev, 1);
	} else {
		dev_err(uport->dev, "Cannot register tty device on line %d\n",
		       uport->line);
	}

	/*
	 * Ensure UPF_DEAD is not set.
	 */
	uport->flags &= ~UPF_DEAD;

 out:
	mutex_unlock(&port->mutex);
	mutex_unlock(&port_mutex);

	return ret;
}

struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
		struct tty_driver *driver, unsigned index,
		struct device *device, void *drvdata,
		const struct attribute_group **attr_grp)
{
	struct device *dev;

	tty_port_link_device(port, driver, index);

	dev = serdev_tty_port_register(port, device, driver, index);
	if (PTR_ERR(dev) != -ENODEV) {
		/* Skip creating cdev if we registered a serdev device */
		return dev;
	}

	return tty_register_device_attr(driver, index, device, drvdata,
			attr_grp);
}

void tty_port_link_device(struct tty_port *port,
		struct tty_driver *driver, unsigned index)
{
	if (WARN_ON(index >= driver->num))
		return;
	driver->ports[index] = port;	//让tty_port指针指向uart_driver里面的
}

struct device *tty_register_device_attr(struct tty_driver *driver,
				   unsigned index, struct device *device,
				   void *drvdata,
				   const struct attribute_group **attr_grp)
{
	//...
	if (driver->type == TTY_DRIVER_TYPE_PTY)
		pty_line_name(driver, index, name);
	else
		tty_line_name(driver, index, name);		//使用uart_driver.dev_name来构造/dev/xxx

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	//...
		retval = tty_cdev_add(driver, devt, index, 1);		//这里构造cdev
	//...
}

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
		unsigned int index, unsigned int count)
{
	int err;

	/* init here, since reused cdevs cause crashes */
	driver->cdevs[index] = cdev_alloc();			//分配cdev
	if (!driver->cdevs[index])
		return -ENOMEM;
	driver->cdevs[index]->ops = &tty_fops;		//设置cdev->ops
	driver->cdevs[index]->owner = driver->owner;
	err = cdev_add(driver->cdevs[index], dev, count);	//cdev_add注册
	if (err)
		kobject_put(&driver->cdevs[index]->kobj);
	return err;
}

五、总结

在这里插入图片描述

在imx.c文件中,会注册一个uart_driver和一个platform_driver。
uart_driver只是包含了一些串口驱动的基本信息,包括驱动名称、主从设备号、支持多少个设备等。
而platform_driver就会根据设备树来获取必要的信息,然后去设置一个uart_port结构体。这个uart_port结构体会带一个操作的结构体,包含所有用来给串口进行各种操作的函数。最后通过uart_add_one_port把uart_driver和uart_port进行关联和注册。

在serial_core.c中,包含了uart_register_driver函数。通过分析这个函数,可以了解到uart_driver的注册过程。
它通过uart_driver来分配对应的tty_driver,通过alloc_tty_driver函数,给uart_driver支持的串口个数nr,每一个分配一个tty_driver的tty_struct、tty_port、ktermios、cdev结构体(指针数组)。并把uart_driver的信息复制给tty_driver。并且给tty驱动提供了uart_ops的操作函数集。(这个操作函数集和上面的uart_port的操作函数集是不一样的,上面的针对串口,这个是针对tty的),最后注册了tty_driver

因为提前注册了tty_driver,所以在注册platform_driver后,运行probe函数的uart_add_one_port时:
注册好的&imx_uart_uart_driver和设置好的imx_port相关联,把imx_port关联到tty_driver中(tty_port_link_device函数),给tty_driver注册cdev(tty_register_device_attr函数)。imx_port包含了uart_port,uart_port又包含了ops,就是针对硬件的操作函数。

通过这些关联,tty_driver就和uart_driver进行了绑定。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值