tty驱动注册

本文详细解析了USB CDC ACM设备驱动中如何注册tty设备,包括分配内存alloc_tty_driver、注册tty驱动tty_register_driver的过程,以及动态生成tty设备节点的步骤。通过分析cdc_acm.c驱动源码,介绍了tty_driver初始化、设备号申请、cdev注册等关键环节,展示了tty设备从无到有的完整流程。

文章是作为笔记,可能有的地方理解不对。

1、注册tty设备驱动

我们以cdc_acm.c为例来介绍tty设备驱动的注册流程


1.1、首先是注册tty驱动

static struct tty_driver *acm_tty_driver;
acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
acm_tty_driver->driver_name = "acm",
acm_tty_driver->name = ACM_MAJOR_NAME,
acm_tty_driver->major = ACM_TTY_MAJOR,
acm_tty_driver->minor_start = 0,
acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
acm_tty_driver->init_termios = tty_std_termios;
acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
tty_set_operations(acm_tty_driver, &acm_ops);
retval = tty_register_driver(acm_tty_driver);

这是在cdc_acm.c的init函数中截取的有关tty的部分

行2 为tty_driver分配内存。
行3~12 对 tty_driver 进行初始化,其中包括默认波特率、校验方式等,还有一个重要的 Ops ,acm_ops ,它是tty核心与我们usb驱动通信的接口。因为是作为一个usb转串口的功能所以type和subtype选择是是正常的串口类型。
TTY_DRIVER_DYNAMIC_DEV表示设备节点不是初始化的时候生成的,是在cdc驱动的probe函数中调用tty_register_device生成,类似可以看下其他tty节点生成可能没有这个标志位
TTY_DRIVER_REAL_RAW 表示这个数据不会被特殊处理。
行13 最后注册tty的驱动。

分配内存 alloc_tty_driver

acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
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)
{
	struct tty_driver *driver;
	unsigned int cdevs = 1;
	int err;

	if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
		return ERR_PTR(-EINVAL);

	driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
	if (!driver)
		return ERR_PTR(-ENOMEM);

	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);
		if (!driver->ttys || !driver->termios) {
			err = -ENOMEM;
			goto err_free_all;
		}
	}

	if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		driver->ports = kcalloc(lines, sizeof(*driver->ports),
				GFP_KERNEL);
		if (!driver->ports) {
			err = -ENOMEM;
			goto err_free_all;
		}
		cdevs = lines;
	}

	driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
	if (!driver->cdevs) {
		err = -ENOMEM;
		goto err_free_all;
	}

	return driver;
err_free_all:
	kfree(driver->ports);
	kfree(driver->ttys);
	kfree(driver->termios);
	kfree(driver);
	return ERR_PTR(err);
}

这个函数的主要功能是为tty_driver和他的部分成变量分配内存同时初始化部分成员变量。

行1~12 调用流程什么好讲,主要flags为0,lines=ACM_TTY_MINORS。
行20~31 分配内存,driver->num = lines 这个驱动支持的最大设备数。
行33~42 应为flags为0,为线路规划和termios分配空间,这个ttys是个很重要的东西,对tty节点的fops就是通过ttys的fops来线路规划的。这里分配的内存是一个tty_struct *的数组。
行44~58 flags为0,表明没有采用动态分配内存的方法,这里给tty_driver的port和cdevs分配内存。port是对应具体cdevs表示一个设备。
TTY_DRIVER_DYNAMIC_ALLOC动态的为支持的每个设备分配内存,防止浪费空间

注册tty驱动tty_register_driver

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;

	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);
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);

	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;
			}
		}
	}
	proc_tty_register_driver(driver);
	driver->flags |= TTY_DRIVER_INSTALLED;
	return 0;

err_unreg_devs:
	for (i--; i >= 0; i--)
		tty_unregister_device(driver, i);

	mutex_lock(&tty_mutex);
	list_del(&driver->tty_drivers);
	mutex_unlock(&tty_mutex);

err_unreg_char:
	unregister_chrdev_region(dev, driver->num);
err:
	return error;
}

主要功能是将tty_driver挂在到对应的tty_driver链表中。

行8~20 申请设备号,没有主设备号的话就分配一个设备号,并分解成主次设备号分别存储;有主设备号直接申请。driver->num是前面的 ACM_TTY_MINORS,这个申请的设备号区间也是 ACM_TTY_MINORS ,表示支持的最大设备数。
行22~26 driver->flags 设置的是静态,在alloc_tty_driver 已经完成cdev的内存分配。tty_cdev_add是为tty设备注册cdev
行28~30 将tty_drivers添加到链表中,后面动态规划也会在链表中将tty_driver取出
行32~40 根据fllags来区分是否动态还是静态的生成设备节点
行41~42 proc文件有关的注册,flags添加标志位

 

2、注册一个tty设备


cdc_acm.c是动态动态生成设备节点
因为是动态的生成的,肯定有一个合适的时机,cdc_acm是一个usb设备,猜测是底层配置完成,后再配置上层驱动

acm_probe
	minor = acm_alloc_minor(acm);
	tty_port_init(&acm->port);
	acm->port.ops = &acm_port_ops;
	tty_port_register_device(&acm->port, acm_tty_driver, minor,&control_interface->dev);

从上面的代码可以看出来注册tty设备。

相关初始化处理流程

static int acm_alloc_minor(struct acm *acm)
{
	int minor;

	mutex_lock(&acm_table_lock);
	for (minor = 0; minor < ACM_TTY_MINORS; minor++) {
		if (!acm_table[minor]) {
			acm_table[minor] = acm;
			break;
		}
	}
	mutex_unlock(&acm_table_lock);

	return minor;
}
void tty_port_init(struct tty_port *port)
{
	memset(port, 0, sizeof(*port));
	tty_buffer_init(port);
	init_waitqueue_head(&port->open_wait);
	init_waitqueue_head(&port->close_wait);
	init_waitqueue_head(&port->delta_msr_wait);
	mutex_init(&port->mutex);
	mutex_init(&port->buf_mutex);
	spin_lock_init(&port->lock);
	port->close_delay = (50 * HZ) / 100;
	port->closing_wait = (3000 * HZ) / 100;
	kref_init(&port->kref);
}
void tty_buffer_init(struct tty_port *port)
{
	struct tty_bufhead *buf = &port->buf;

	spin_lock_init(&buf->lock);
	buf->head = NULL;
	buf->tail = NULL;
	buf->free = NULL;
	buf->memory_used = 0;
	INIT_WORK(&buf->work, flush_to_ldisc);
}

行1~15 在acm_table数组中找一个没有使用的成员存储acm,返回数组的下标,这个下标和次设备号是一样的。
行16~29 初始化port相关的成员变量
行30~40 初始化port的buff,和对这个buff的操作工作队列。 flush_to_ldisc这个函数主要是对接收到的数据从buff搬运到线路规划的buff中,具体的函数以后会分析

struct device *tty_port_register_device(struct tty_port *port,
		struct tty_driver *driver, unsigned index,
		struct device *device)
{
	tty_port_link_device(port, driver, index);
	return tty_register_device(driver, index, device);
}
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;
}
struct device *tty_register_device(struct tty_driver *driver, unsigned index,
				   struct device *device)
{
	return tty_register_device_attr(driver, index, device, NULL, NULL);
}

行8~14 把port放到对应tty_driver的port数组中对应的位置

struct device *tty_register_device_attr(struct tty_driver *driver,
				   unsigned index, struct device *device,
				   void *drvdata,
				   const struct attribute_group **attr_grp)
{
	char name[64];
	dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
	struct device *dev = NULL;
	int retval = -ENODEV;
	bool cdev = false;

	if (index >= driver->num) {
		printk(KERN_ERR "Attempt to register invalid tty line number "
		       " (%d).\n", index);
		return ERR_PTR(-EINVAL);
	}

	if (driver->type == TTY_DRIVER_TYPE_PTY)
		pty_line_name(driver, index, name);
	else
		tty_line_name(driver, index, name);

	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		retval = tty_cdev_add(driver, devt, index, 1);
		if (retval)
			goto error;
		cdev = true;
	}

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev) {
		retval = -ENOMEM;
		goto error;
	}

	dev->devt = devt;
	dev->class = tty_class;
	dev->parent = device;
	dev->release = tty_device_create_release;
	dev_set_name(dev, "%s", name);
	dev->groups = attr_grp;
	dev_set_drvdata(dev, drvdata);

	retval = device_register(dev);
	if (retval)
		goto error;

	return dev;

error:
	put_device(dev);
	if (cdev)
		cdev_del(&driver->cdevs[index]);
	return ERR_PTR(retval);
}

行7~7 生成这个tty设备的设备号
行18~21 设备设备的名字
行23~28 这个是为动态设备的cdev分配内存,和tty_driver_register中对应,动态注册的cdev在这里,静态的设备在tty_driver_register中注册。
行30~48 dev分配内存并注册

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
		unsigned int index, unsigned int count)
{
	/* init here, since reused cdevs cause crashes */
	cdev_init(&driver->cdevs[index], &tty_fops);
	driver->cdevs[index].owner = driver->owner;
	return cdev_add(&driver->cdevs[index], dev, count);
}

这里最重要是tty_fops。


总结


看过上面没的代码流程,我们可以看到我们注册了一个tty的字符设备。类似于我们在tty_init中我们看到的步骤

cdev_init(&tty_cdev, &tty_fops);
if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
    panic("Couldn't register /dev/tty driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

这样我们就可以在dev中看到相应的tty节点,这个节点的fops是tty_cdev_add中设置的tty_fops。这里还出现了两外两个ops:tty_driver的fops被赋值为acm_ops,acm->port.ops的 &acm_port_ops,这几个操作函数有什么联系后面的文章再介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值