文章是作为笔记,可能有的地方理解不对。
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,这几个操作函数有什么联系后面的文章再介绍
本文详细解析了USB CDC ACM设备驱动中如何注册tty设备,包括分配内存alloc_tty_driver、注册tty驱动tty_register_driver的过程,以及动态生成tty设备节点的步骤。通过分析cdc_acm.c驱动源码,介绍了tty_driver初始化、设备号申请、cdev注册等关键环节,展示了tty设备从无到有的完整流程。

2376

被折叠的 条评论
为什么被折叠?



