Linux驱动开发BL5372项目BUG(一)--rtc_device_register函数注册失败

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134388432

目录

    • 一、问题描述
    • 二、结论
    • 三、Debug过程
    • 四、解决方案
    • 题外话

一、问题描述

在使用linux的RTC框架的时候,使用rtc_device_register()函数时出现内核错误,报错如下:kobject: ‘null’ (0000000007394d5b): is not initialized, yet kobject_get() is being called. 以及 bl5372:failed to add char device 253:0

Linux驱动开发BL5372项目BUG(一)--rtc_device_register函数注册失败_第1张图片
Linux驱动开发BL5372项目BUG(一)--rtc_device_register函数注册失败_第2张图片 Linux驱动开发BL5372项目BUG(一)--rtc_device_register函数注册失败_第3张图片

先贴上调用该函数前后部分的代码:

static int bl5372_read_time(struct device * device_, struct rtc_time * time_struct)
{
	...
}

static int bl5372_set_time(struct device * device_, struct rtc_time * time_struct)
{
	...
}
...
...
char* device_name = "BL5372";
const struct rtc_class_ops rtc_ioctl_ops = 
{
    .read_time = bl5372_read_time,
    .set_time = bl5372_set_time,    
};
struct device parent_device = {
    .init_name = "parent_device",
};
...
...
int bl5372_module_init(void)   /* modprobe .ko的时候执行*/
{
	...
    bl5372_data.rtc_device_ = rtc_device_register(device_name, parent_device, &rtc_ioctl_ops, THIS_MODULE);       
}

二、结论

先说结论,出现这个错误的原因是因为在使用rtc_device_register()时传入的第二个参数dev没有注册,也就是这个RTC设备的父设备没有注册导致的

三、Debug过程

在分析了rtc_device_register()函数所做的事情后(参考我的另外一篇博客),我想当然的认为该函数传入的第二个参数就是指定要创建的rtc设备的父设备,而且这个父设备在使用Linux的RTC框架的用处并不多,唯一有用的就是可以在自己定义的RTC底层操作集函数中来获取这个父设备的私有数据,并以此来传递一些信息(如何传递一些数据呢,我看了看内核中自带的一些rtc驱动,通过设置设备结构体的私有数据来实现,在这篇博客中记录了一下)。所以我以为随便定义一个struct device类型的变量作为该参数传入就可以了,但是事实并非如此。

首先看一下内核报错的信息,根据call trace来看,程序是在执行到rtc_device_register()下的cdev_device_add()时出错了。

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
先看一下cdev_device_add()函数的源码,该函数位于fs/char_dev.c下:

int cdev_device_add(struct cdev *cdev, struct device *dev)	/* 传进来的是rtc->dev */
{
	int rc = 0;

	if (dev->devt) {
		cdev_set_parent(cdev, &dev->kobj);			/* 这个是给cdev设备设置父节点的,就是把cdev的kobj.parent = dev->kobj了 */

		rc = cdev_add(cdev, dev->devt, 1);			/* 这里面会调用kobject_get(cdev->kobj.parent),但是没问题,这个kobj已经被initialize了 */
		if (rc)
			return rc;
	}

	rc = device_add(dev);							/* 根据calltrace的提示,是这里出问题了 */
	if (rc)
		cdev_del(cdev);

	return rc;
}

其中涉及到一个比较重要的变量,就是dev->kobj,此为struct device下的一个变量,其类型为struct kobject,定义于include/linux/kobject.h

include/linux/device.h:
struct device {
	...
	struct kobject kobj;
	...
};

include/linux/kobject.h:
struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	struct kobj_type	*ktype;
	struct kernfs_node	*sd; /* sysfs directory entry */
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
};

其中的变量state_initialized用来指示该kobject是否已经初始化,也是后面理解和解决BUG的关键。而至于struct kobject是什么东西?是最终boss,是linux内核的基石之一,是linux下设备模型的大一统。这里linux使用了面向对象的设计思想来设计设备模型,也就是说,struct kobject是设备模型的基类,其余像是struct device什么的都是公有“继承”(以struct device为例,就是结构体中包含了一个struct kobject类型的变量)了这个基类。那么每个继承了struct kobject的子类都能访问struct kobject的成员变量,也会有自己的一些成员变量。如果想要继续了解的话,可以看下这几篇文章和专栏,这是我看到的写的挺好的:

  • Linux设备管理(一)_kobject, kset,ktype分析
  • 深入分析kobject与sysfs的关系
  • linux内核部件分析(五)——设备驱动模型的基石kobject

言归正传,继续来解决BUG。虽然后面知道问题出在devcie_add()里了(call_trace里也提到了),但是解决问题的时候还是把这几个函数看了一遍,出现BUG的cdev_device_add()函数里面只调用了三个函数:

  • cdev_set_parent()
  • cdev_add()
  • device_add()

先来看看cdev_set_parent()函数吗,该函数位于fs/char_dev.c中:

void cdev_set_parent(struct cdev *p, struct kobject *kobj)
{
	WARN_ON(!kobj->state_initialized);
	p->kobj.parent = kobj;
}

很简单,就是判断是否传入的kobj(rtc->dev->kobj)是否已经初始化,如果没有的话就报出警告。随后令p->kobj的父kobj指向传入的kobj,最终的效果就是:rtc->cdev->kobj.paret = rtc->dev->kobj

那么报错是不是从这里的WARN_ON发起的呢?好像并没有对传入的rtc->dev->kobjstate_initialized进行过初始化啊?实则不然,在rtc_device_register()函数中调用过一个函数叫rtc_allocate_device()函数,之前写笔记的时候只是写了一下这个函数对定时器的初始化,但是其实设备的初始化也是在这个设备中进行的,再回头看一下这个函数:

drivers/rtc/class.c:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
					const struct rtc_class_ops *ops,
					struct module *owner)
{
	...
	rtc = rtc_allocate_device(); /* 这里面调用了device_initialize(&rtc->dev)-->kobject_init(&(rtc->dev)->kobj, &device_ktype);*/
	...
}

drivers/rtc/class.c:
static struct rtc_device *rtc_allocate_device(void)
{
	struct rtc_device *rtc;
	rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
	if (!rtc)
		return NULL;
	...
	device_initialize(&rtc->dev);
	...
	return rtc;
}

从来没见过device_initialize()这个函数,接下来看一下这个device_initialize()函数做了什么事情,该函数位于drivers/base/core.c中:

void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset;
	kobject_init(&dev->kobj, &device_ktype);
	INIT_LIST_HEAD(&dev->dma_pools);
	mutex_init(&dev->mutex);
	lockdep_set_novalidate_class(&dev->mutex);
	spin_lock_init(&dev->devres_lock);
	INIT_LIST_HEAD(&dev->devres_head);
	device_pm_init(dev);
	set_dev_node(dev, -1);
#ifdef CONFIG_GENERIC_MSI_IRQ
	INIT_LIST_HEAD(&dev->msi_list);
#endif
	INIT_LIST_HEAD(&dev->links.consumers);
	INIT_LIST_HEAD(&dev->links.suppliers);
	dev->links.status = DL_DEV_NO_DRIVER;
}

其他函数暂时不考虑,重点看一下kobject_init()函数,该函数位于lib/kobject.c中:

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
	char *err_str;

	if (!kobj) {
		err_str = "invalid kobject pointer!";
		goto error;
	}
	if (!ktype) {
		err_str = "must have a ktype to be initialized properly!\n";
		goto error;
	}
	if (kobj->state_initialized) {
		/* do not error out as sometimes we can recover */
		pr_err("kobject (%p): tried to init an initialized object, something is seriously wrong.\n",
		       kobj);
		dump_stack();
	}

	kobject_init_internal(kobj);
	kobj->ktype = ktype;
	return;

error:
	pr_err("kobject (%p): %s\n", kobj, err_str);
	dump_stack();
}
...
...
static void kobject_init_internal(struct kobject *kobj)
{
	if (!kobj)
		return;
	kref_init(&kobj->kref);
	INIT_LIST_HEAD(&kobj->entry);
	kobj->state_in_sysfs = 0;
	kobj->state_add_uevent_sent = 0;
	kobj->state_remove_uevent_sent = 0;
	kobj->state_initialized = 1;						/* 初始化 */
}

可以看到,最终在kobject_init_internal()函数中完成了对rtc->dev->kobj->state_initialized做了初始化。所以在cdev_set_parent()函数中的WARN_ON是不起作用的,所以错误并不是在这个函数中。接着来看一下cdev_add()函数,该函数同样位于fs/char_dev.c中:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

让我们看一下其中调用的kobject_get()函数,该函数位于lib/kobject.c:

struct kobject *kobject_get(struct kobject *kobj)
{
	if (kobj) {
		if (!kobj->state_initialized)
			WARN(1, KERN_WARNING
				"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
			     kobject_name(kobj), kobj);
		kref_get(&kobj->kref);
	}
	return kobj;
}

等等,感觉什么地方有点眼熟,看一眼内核崩溃时报的错:kobject: ‘null’ (0000000007394d5b): is not initialized, yet kobject_get() is being called. 与WARN中的内容是不是一模一样?算是找到了问题所在,就是在某处调用kobject_get()函数时,传入的kobject->state_initialized没有被初始化导致的,也就是没有对该kobjcet调用kobject_init()函数。

但是问题是不是出现在这里?是不是出现在cdev_add()函数中?其实并不是,在cdev_add()函数中调用kobject_get()时传入的是p->kobj.parent,也就是rtc->dev->kobject。根据前面梳理的内容,已经调用了kobject_init()函数对rtc->dev->kobject进行了初始化,所以问题不是出在这里。

那么问题只能是出在最后一个函数,也就是device_add()函数,该函数位于drivers/base/core.c中,由于该函数内容较多,所以只放上部分源码:

int device_add(struct device *dev)
{
	...
	dev = get_device(dev);
	if (!dev)
		goto done;

	if (!dev->p) {
		error = device_private_init(dev);
		if (error)
			goto done;
	}
	...
	parent = get_device(dev->parent);				/* 应该是这里出问题了,因为本来dev就是没注册的 */
	kobj = get_device_parent(dev, parent);
	...
done:
	...
}
EXPORT_SYMBOL_GPL(device_add);

问题就出在get_device(dev->parent)中,该函数定义如下:

struct device *get_device(struct device *dev)
{
	return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
}

可以看到,该函数最终调用了刚才讲的kobject_get()函数,而在这次调用kobject_get()函数时,传入的是rtc->dev->parent.kobjcet。在分析rtc_device_register()函数时就提到,刚函数中会将rtc->dev->parent设为调用rtc_device_register()时传入的第二个参数,也就是一个struct device类型的变量。现在就很明了了,在开头我放出的我的驱动程序的代码中,我只是随便传入了一个我自己定义的struct device类型的变量,肯定是失败的。

四、解决方案

其实正点原子教程里面在讲字符设备驱动开发时使用到的device_create()函数,最终也调用了device_initialize()函数,这里我把device_create()函数的层层调用过程贴上来,这些函数全部位于drivers/base/core.c中,但是我没有细究其他细节:

drivers/base/core.c:
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}
...
...
struct device *device_create_vargs(struct class *class, struct device *parent,
				   dev_t devt, void *drvdata, const char *fmt,
				   va_list args)
{
	return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
					  fmt, args);
}
...
...
device_create_groups_vargs(struct class *class, struct device *parent,
			   dev_t devt, void *drvdata,
			   const struct attribute_group **groups,
			   const char *fmt, va_list args)
{
	struct device *dev = NULL;
	int retval = -ENODEV;

	if (class == NULL || IS_ERR(class))
		goto error;

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

	device_initialize(dev);						/* 在这里有调用 */
	dev->devt = devt;
	dev->class = class;
	dev->parent = parent;
	dev->groups = groups;
	dev->release = device_create_release;
	dev_set_drvdata(dev, drvdata);

	retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
	if (retval)
		goto error;

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

	return dev;

error:
	put_device(dev);
	return ERR_PTR(retval);
}

所以我只要传入一个使用device_create()注册的设备,应该就能解决该问题。于是我修改了一下我的驱动程序:

static int bl5372_prepare_register_rtc(bl5372_driver_data* bl5372)
{

    bl5372->parent_devid = -1;
    bl5372->parent_class = NULL;
    bl5372->parent_device = NULL;
    alloc_chrdev_region(&bl5372->parent_devid, 0, 1, RTC_PARENT_NAME); /* 由系统来申请设备号 */
    if(bl5372->parent_devid < 0)
    {
        printk("alloc parent-devid error!\r\n");
        return 0;
    }
    
    bl5372->parent_class = class_create(THIS_MODULE, RTC_PARENT_NAME);  /* 创建类,最终会加载为/sys/class/xxx(class-name)*/
    if(!bl5372->parent_class)
    {
        printk("create parent-class error!\r\n");
        return 0;
    }

    bl5372->parent_device = device_create(bl5372->parent_class, NULL, bl5372->parent_devid, NULL, RTC_PARENT_NAME); /* 创建设备,最终会加载在:1./dev/xxx(device-name) 2./dev/class/xxx(class-name)/xxx(device-name)*/
    if(!bl5372->parent_device)
    {
        printk("create parent-device error!\r\n");
        return 0;
    }

    dev_set_drvdata(bl5372->parent_device, (void *)&bl5372_data);  
}

int bl5372_module_init(void)  
{
	...
    ret = bl5372_prepare_register_rtc(&bl5372_data); /* 这个函数给注册rtc设备做初始化,因为必须要注册设备 */
    if (!ret)
    {
        printk("Init bl5372 parent device fail\n");
        goto prepare_rtc_error;
    }
	...
    bl5372_data.rtc_device_ = rtc_device_register(device_name, bl5372_data.parent_device, &rtc_ioctl_ops, THIS_MODULE);      
    ...
}

最终解决该BUG,可以正常注册rtc设备。

但其实有简单的办法,让我们再回头看一下get_device()函数:

struct device *get_device(struct device *dev)
{
	return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
}

如果传入的dev为NULL的话,就不会调用kobject_get()函数,而是直接返回NULL,所以最简单的方法就是调用rtc_device_register()时传入的第二个参数为NULL就可以了。为什么没有想到这个呢?因为在看Linux下自带的其他rtc驱动程序在调用rtc_device_register()时都传入了一个struct device类型的变量,所以我想当然的认为这个是必须的。

题外话

插句题外话。我想结合我这一个月的经验说一下我的观点。首先说一下我的情况:我是看完正点原子的linux驱动开发这个教程之后就开始上手实际的项目中的驱动开发的,有些问题通过正点教程中的公式化方法可以解决,但是有些问题难以解决。所以我就尝试着去深入理解项目中需要用到的linux内核知识,并开始尝试阅读源码来解决bug。但是我发现这样效率很低,因为linux下这么多框架与子系统并不是完全独立的,往往是一环嵌套一环。

所以我认为应该先学习linux内核的机制,了解linux下的框架与子系统到底是如何设计又是如何构成这个庞大的工程的,才能在后面驱动开发的时候得心应手,看起源码来也有一个上层的理论指导。而我就是跳过了这一步,以为学了正点的linux驱动开发就可以上手驱动开发了,现在发现真的不行。所以我认为应该至少看完Robert Love所著的《Linux内核设计与实现》之后再进行开发更好一些。

而且像我这么分析问题,没有一个对linux内核的一个整体的认知,解决问题时的逻辑也是比较混乱的,最终复盘梳理起来逻辑还算清晰,但实际上在解决问题的过程中效率非常低。

你可能感兴趣的:(linux,驱动开发,bug)