设备号
此处仅仅介绍api,详细解析请参考设备号。
设备号的数据类型:dev_t
typedef unsigned int dev_t;
设备号是一个统称,分为主设备号和次设备号。
主设备号保存在高12位;
次设备号保存在低20位。设备号的功能
主设备号:一个设备驱动对应一个主设备号。应用程序通过设备文件中的主设备号在内核中找到对应的设备驱动。
次设备号:一个设备对应一个次设备号。当一个驱动程序管理多个设备时,驱动程序通过设备文件中的次设备号,找到需要操作的设备。设备号操作
/* 通过已知的主次设备号,合成设备号 */
dev_t dev = MKDEV(major, minor);
/* 通过已知的设备号,获取主设备号 */
major = MAJOR(dev);
/* 通过已知的设备号,获取次设备号 */
minor = MINOR(dev);
上述宏的实现:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
设备号申请方法:
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
name是设备名称,可通过cat /proc/devices查看
设备号释放的方法:
/**
* unregister_chrdev_region() - unregister a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count);
字符设备
首先了解字符设备对象:
struct cdev {
struct kobject kobj;
struct module *owner; //指向拥有该驱动程序的模块,一般为THIS_MODULE
const struct file_operations *ops; //设备文件操作方法集合的结构体的指针
struct list_head list;
dev_t dev; //设备号
unsigned int count; //驱动程序管理的外设数量
};
file_operations结构体:
struct file_operations {
struct module *owner;
... ...
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
... ...
};
还需要关注kobject结构体:
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype; /* 用来描述一种kobject的共同特性 */
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;
};
初始化cdev
在定义字符设备对象之后,需要将其初始化,并添加到内核:
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
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;
}
初始化cdev中的成员,需要关注的是cdev->ops = fops;
,其实就是将实现的file_operations赋值给cdev。
初始化kobj成员:
/**
* kref_init - initialize object.
* @kref: object in question.
*/
static inline void kref_init(struct kref *kref)
{
atomic_set(&kref->refcount, 1); /* 原子操作 */
}
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 - initialize a kobject structure
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
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 */
printk(KERN_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:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
EXPORT_SYMBOL(kobject_init);
kobject_init的第二个参数是ktype_cdev_default,它被赋值给了ktype,用来描述一类kobject(cdev使用的kobject)的共同特性。
static void cdev_default_release(struct kobject *kobj)
{
struct cdev *p = container_of(kobj, struct cdev, kobj);
struct kobject *parent = kobj->parent;
cdev_purge(p);
kobject_put(parent);
}
static struct kobj_type ktype_cdev_default = {
.release = cdev_default_release,
};
初始化完毕,将cdev添加到内核
static struct kobj_map *cdev_map;
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
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填充设备号,然后通过kobj_map函数将cdev添加到用于管理设备号和对应设备的kobj_map结构体(cdev_map)中。
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
// 1. 根据cdev的信息,对struct probe结构体初始化
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
// 2. 将载有cdev信息的probe结构体插入到kobj_map中,至此将cdev注册到内核
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
和cdev_add配套使用的函数是cdev_del函数:
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
初始化硬件资源
... ...
硬件操作接口实现
上面有提到file_operations结构体,该结构体为文件操作提供了接口。
一个驱动管理多个外设
一个主设备号对应一个驱动程序,同一个主设备号不同的每个次设备号对应一个外设,驱动程序如何精确找到外设呢?
需要使用设备号。那如何找到设备号呢?在file_operations结构描述的一系列文件操作的函数指针的参数中,有两个结构体需要特别注意。(inode和file结构体)
inode结构体
作用:用来描述一个文件的物理信息(大小,创建日期,修改日期,用户和组,权限等)
生命周期:文件被创建,内核即创建一个inode结构来描述该文件的物理信息;文件被删除,内核会删除掉对应的inode结构。
特点:linux允许多个文件共用一个inode结构(硬链接)
struct inode {
dev_t i_rdev; //存放设备号
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; //描述字符设备的结构
char *i_link;
};
... ...
};
从inode中获取主次设备号的方法:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
fs.h中源码实现:
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
static inline unsigned imajor(const struct inode *inode)
{
return MAJOR(inode->i_rdev);
}
可以看到,就是使用了设备号操作宏MINOR和MAJOR。
结论:得到inode对象,就可以得到主次设备号
file结构体
作用:描述一个文件被打开(open)以后的状态属性
生命周期:当一个文件被成功打开,内核会创建一个file结构;当文件被关闭,内核也会销毁对应的file对象。
struct file {
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/* needed for tty driver, and maybe others */
void *private_data;
... ...
};
inode和file之间的关系
struct inode *inode = file->f_inode;
struct inode *inode = file->f_path.dentry->d_inode;
找到次设备号
int minor = MINOR(inode->i_rdev);