前沿
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结构一样,不再说明*/
};