I2c内核源码一

前沿

i2c作为bmc管理机架硬件最常用、最简单的硬件通道;通过学习i2c内核源码,深入了解字符设备完整的驱动过程,增强对内核代码的理解

主要内容

字符设备

* 内核中每一个字符设备驱动程序都由一个`struct char_device_struct`描述,包含主设备号、起始次设备号、次设备号个数等信息
* 内核使用`chrdevs`指针数组管理所有字符设备驱动程序

源码学习

学习内核模块一般从module_init(i2c_dev_init)开始

/*
 * 
 */
static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
	/* 根据要求,进行i2c设备号范围申请; 89<<8, 1<<20 */
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;
	/* 创建一个设备类,并将class注册到/sys/class中 */
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

register_chrdev_region

/**
 * 在已知起始设备号的情况下去申请一组连续的设备号
 * @from: the first in the desired range of device numbers; must include
 *        the major number.
 * @count: the number of consecutive device numbers required
 * @name: the name of the device or driver.
 *
 * Return value is zero on success, a negative error code on failure.
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
	struct char_device_struct *cd;
	dev_t to = from + count;
	dev_t n, next;

	for (n = from; n < to; n = next) {/*每次申请256个设备号 */
		next = MKDEV(MAJOR(n)+1, 0);/*先得到下一个设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中*/
		if (next > to)/* 判断在from基础上再追加count个设备,是否已经溢出到下一个主设备号 */
			next = to;
    /* 如果没有溢出,那么整个for语句就只执行一次__register_chrdev_region
     * 否则会讲溢出的设备号范围划分为几个小范围,分别调用
     */
		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
			       next - n, name);
		if (IS_ERR(cd))
			goto fail;
	}
	return 0;
fail:	/*当有任何一次分配失败时,释放所有已经申请的设备号 */
	to = n;
	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	}
	return PTR_ERR(cd);
}

__register_chrdev_region


/*
 * Register a single major with a specified minor range.
 *
 * If major == 0 this function will dynamically allocate an unused major.
 * If major > 0 this function will attempt to reserve the range of minors
 * with given major.
 *
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	struct char_device_struct *cd, *curr, *prev = NULL;
	int ret = -EBUSY;
	int i;

	if (major >= CHRDEV_MAJOR_MAX) {
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		return ERR_PTR(-EINVAL);
	}

	if (minorct > MINORMASK + 1 - baseminor) {
		pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n",
			name, baseminor, baseminor + minorct - 1, 0, MINORMASK);
		return ERR_PTR(-EINVAL);
	}

	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

	if (major == 0) {
    /* 动态分配设备号 */
		ret = find_dynamic_major();/*分配一个主设备号!*/
		if (ret < 0) {
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;
	}
	/*将major对256取余数,得到char_device_struct在chrdevs中索引 */
	i = major_to_index(major);
  /*
   *退出循环:
   (1)chrdevs[i]为空
   (2)chrdevs[i]的主设备号大于major
   (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor
   */
	for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) {
		if (curr->major < major)
			continue;

		if (curr->major > major)
			break; /* (2)chrdevs[i]的主设备号大于major*/

		if (curr->baseminor + curr->minorct <= baseminor)
			continue;

		if (curr->baseminor >= baseminor + minorct)
			break; /*(3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor */

		goto out;
	}

	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;/* 申请设备号个数 */
	strlcpy(cd->name, name, sizeof(cd->name));
	/****************以上为第一部分,处理char_device_struct变量的分配和初始化************/
	/****************将char_device_struct变量注册到内核,即chrdevs结构体数组*****************/
	if (!prev) {
		cd->next = curr;
		chrdevs[i] = cd;
	} else {
		cd->next = prev->next;
		prev->next = cd;
	}

	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

附录

/* 代表次设备号连续的一组设备,使用相同的file_operation */
static struct char_device_struct {
  /* 指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠 */
	struct char_device_struct *next;
	unsigned int major; //主设备号
	unsigned int baseminor;//起始次设备号
	int minorct;					//设备编号范围大小
	char name[64];				//设备驱动的名称
	struct cdev *cdev;		/* will die 指向字符设备驱动程序描述符的指针*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
struct class {
  const char              *name;/* class的名称,会在“/sys/class/”目录下体现*/
  struct module           *owner;
  /*该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件*/
  struct class_attribute          *class_attrs;
  /*该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件*/
  struct device_attribute         *dev_attrs;
  /*类似dev_attrs,只不过是二进制类型attribute*/
  struct bin_attribute            *dev_bin_attrs;
  /*该class下的设备在/sys/dev/下的目录,现在一般有char和block两个,如果dev_kobj为NULL,则默认选择char*/
  struct kobject                  *dev_kobj;
  /*该class下有设备发生变化时,会调用class的uevent回调函数*/
  int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode);
	/*用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针*/
	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);
 
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
 	const struct kobj_ns_type_operations *ns_type;
  const void *(*namespace)(struct device *dev);
  const struct dev_pm_ops *pm;
  struct subsys_private *p;/*和“Linux设备模型(6)_Bus”中struct bus结构一样,不再说明*/
};