1.注册字符设备驱动新接口1
1.1、新接口与老接口
(1)老接口:register_chrdev
(2)新接口:register_chrdev_region/alloc_chrdev_region + cdev
(3)为什么需要新接口。因为以前是没有次设备号的需求的,随着之后的发展,又有了此设备号,因为主设备号只能有255个,这样能注册的设备数量就很少,但是设备的驱动有很多,比如一块开发板上可就就有好几个LED驱动,但是他们之前的区别可能就是寄存器不同而已。所以内核又引入了此设备号,将注册设备号分为了两部。第一步,申请设备号。第二部注册设备号。同时,还兼容了老的接口(register_chrdev)。
兼容:register_chrdev
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
查看register_chrdev在内核里的定义,有以下几个关键点:
(1)inline。
(2)参数列表:major,name,fops。
(3)return __register_chrdev(major, 0, 256, name, fops)。
首先,关键字inline。inline代表函数是一个内联函数,其作用是对于那些比较精简的函数,减少开销,函数调用是用到栈的开销的,内敛函数即可以有宏函数的减少开销的优点,还可以检查参数匹配。
第二,参数列表。major是主设备,那么是驱动的名字,fops是驱动的file_operations。
第三,return __register_chrdev(major, 0, 256, name, fops)。可以看到,register_chrdev 的实质还是调用的__register_chrdev(major, 0, 256, name, fops);其中的0是设备号。可见,使用register_chrdev注册设备号是不可以不可以设置次设备号的,都是采用的0设备号。__register_chrdev会调用__register_chrdev_region,cdev_alloc,cdev_init,cdev_add。
1.2、新接口
新接口:register_chrdev_region/alloc_chrdev_region + cdev。新接口将注册设备驱动分为了两部。第一步,申请设备号。第二部注册设备号。
申请设备号有两种方式,register_chrdev_region/alloc_chrdev_region。
(1) register_chrdev_region。
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) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
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可以申请一段连续的设备号,传入参数有dev_t from, unsigned count, const char *name。from是给的起始的设备号,dev包括主设备和次设备号。count是要申请的驱动设备数,name是值驱动的名字。同样新的接口其实也是调用__register_chrdev_region。
申请设备号后还需要两步操作,cdev_init(),和cdev_add(),如:
cdev_init(&adxl34x_cdev, &adxl34x_fops);
ret = cdev_add(&adxl34x_cdev, adxl34x_devid, 1);
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default); //计数器
cdev->ops = fops;
}
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;
}
cdev结构体里面有个重要的元素,ops和dev。cdev_init是将设置cdev中的ops,cdev_add是设置cdev中的dev。这样ops和dev就绑定在一起了,完成了注册设备驱动。
再卸载驱动时:
unregister_chrdev_region(dev_t from, unsigned count);//注销设备,解除绑定,归还设备号
cdev_del(&hello_cdev); //删除一个字符设备。
(2) alloc_chrdev_region。
与register_chrdev_region的区别是,register_chrdev_region知道主设备号,alloc_chrdev_region不知道,需要系统分配。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
可以看出利用alloc_chrdev_region分配设备号时,会调用__register_chrdev_region自动分配(第一个参数是0),第二个参数是此设备号的开始,第三个参数是要分配的数量。这一个步同样是在申请设备号。后面注册,注销都是和register_chrdev_region一样。cdev_init,cdev_add,unregister_chrdev_region,cdev_del。
2.自动创建设备文件
1.相关函数
class_create、device_create和device_destroy,class_destroy。
adxl34x_class = class_create(THIS_MODULE, "adxl345");
device_create(adxl34x_class, NULL, MKDEV(adxl34x_major, 0), NULL, "adxl345");
device_destroy(adxl34x_class, MKDEV(adxl34x_major, 0));
class_destroy(adxl34x_class);