5.2 input设备的注册
input是一个虚拟的设备,在Linux系统中,键盘、鼠标、触摸屏 和 游戏杆 都要由input设备统一管理。
input设备是个字符设备,如何注册设备驱动,要 从input设备的初始化函数input_init开始。
5.2.1 主从设备号
Linux系统通过设备号来区分不同的设备。设备号由两个部分组成: 主设备号 和 从设备号
下面摘录了系统定义的一些主设备号 (include/linux/major.h)
/** This file has definitions for major device numbers. For the device number assignments, see Documentation/devices.txt. **/
#define UNNAMED_MAJOR 0
#define MEM_MAJOR 1
#define RAMDISK_MAJOR 1
#define FLOPPY_MAJOR 2
#define PTY_MASTER_MAJOR 2
#define IDE0_MAJOR 3
#define HD_MAJOR IDE0_MAJOR
#define PTY_SLAVE_MAJOR3
#define TTY_MAJOR 4
#define TTYAUX_MAJOR 5
#define LP_MAJOR 6
#define VCS_MAJOR7
#define LOOP_MAJOR 7
#define SCSI_DISK0_MAJOR8
#define SCSI_TAPE_MAJOR 9
#define MD_MAJOR 9
#define MISC_MAJOR 10
#define SCSI_CDROM_MAJOR11
#define MUX_MAJOR 11/* PA-RISC only */
#define XT_DISK_MAJOR13
#define INPUT_MAJOR13
#define SOUND_MAJOR 14
#define CDU31A_CDROM_MAJOR15
#define JOYSTICK_MAJOR 15
#define GOLDSTAR_CDROM_MAJOR16
字符设备input是设备的一个聚合层,众多的驱动和设备被input封装,经过这个封装之后,键盘和鼠标等设备各行其是,分别由不同的驱动所控制。而且不仅仅input是一个封装层,在input之下系统还提供了几个层次的封装。
5.2.2 把input设备注册到系统
input_init函数的作用是把input设备注册到系统,【在drivers/input/Input.c】:
static int __init input_init( void )
{
int err;
/* input要注册input类,
err = class_register( &input_class);
if (err) {
printk(KERN_ERR, "input: unable to register input_dev class \n" );
return err;
}
/* 在proc目录下创建input相关的文件 */
err = input_proc_init();
if(err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
}
input_class 类在drivers/input/Input.c:
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
在/proc/bus/ 下面建立input目录,下面有两个文件
devices 和
handles 两个文件,分别记录input设备的 设备信息 、
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;
proc_bus_input_dir = proc_mkdir("bus/input", NULL);
if (!proc_bus_input_dir)
return -ENOMEM;
entry = proc_create("devices", 0, proc_bus_input_dir,
&input_devices_fileops);
if (!entry)
goto fail1;
entry = proc_create("handlers", 0, proc_bus_input_dir,
&input_handlers_fileops);
if (!entry)
goto fail2;
return 0;
fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}
/proc/bus/input/devices /proc/bus/input/handlers 显示如下
***@***PC:input$ cat handlers
N: Number=0 Name=rfkill
N: Number=1 Name=kbd
N: Number=2 Name=sysrq (filter)
N: Number=3 Name=mousedev Minor=32
N: Number=4 Name=evdev Minor=64
N: Number=5 Name=joydev Minor=0
N: Number=6 Name=leds
***@***PC:input$ cat devices
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
......
N: Name="HDA Intel PCH Front Mic"
P: Phys=ALSA
S: Sysfs=/devices/pci0000:00/0000:00:1b.0/sound/card1/input6
U: Uniq=
H: Handlers=event6
B: PROP=0
B: EV=21
B: SW=10
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="HDA Intel PCH Rear Mic"
P: Phys=ALSA
S: Sysfs=/devices/pci0000:00/0000:00:1b.0/sound/card1/input7
U: Uniq=
H: Handlers=event7
B: PROP=0
B: EV=21
B: SW=10
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="HDA Intel PCH Line"
P: Phys=ALSA
S: Sysfs=/devices/pci0000:00/0000:00:1b.0/sound/card1/input8
U: Uniq=
H: Handlers=event8
B: PROP=0
B: EV=21
B: SW=2000
input_handlers_fileops的定义如下:
static const struct file_operations input_handlers_fileops = {
.owner = THIS_MODULE,
.open = input_proc_handlers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
input_init函数最终调用register_chrdev 函数来注册 input 驱动,代码如下:
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, const char *name, const struct file_operations *fops)
{
cd = __register_chrdev_region(major, 0, 256, name);
if
cdev = cdev_alloc();
cdev->owner = fops->owner;
cdev->ops = fops;
/* 设置字符设备 kobj结构的名字 */
kobject_set_name( & cdev->kobj, "%s", name);
for ( s = strchr(kobject_name( &cdev->kobj), '/') ; s ; s = strchr(s, '/') ) *s = '!';
err = cdev_add(cdev, MKDEV( cd->major, 0) , 256);
}
register_chrdev 函数实际执行两个登记,一个登记设备的区间,另一个登记是注册一个字符设备。首先分析设备区间的登记
5.2.3 设备区间的登记
区间 是 主设备号 和 从设备号 共同占用的一段空间,register_chrdev函数要登记0~256的从设备号区间,这个区间之前不能被占用。登记区间通过__register_chrdev_region函数实现,它的代码如下:
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc (sizeof(struct char_device_struct), CFP_KERNEL;
if ( cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock( &chrdevs_lock);
/* 主设备号为0 ,说明这个设备没有指定设备号,需要分配一个
主设备号存在,则不进行分配操作 */
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
__register_chrdev_region 函数第一部分首先创建一个char_device_struct 结构,在输入的主设备号为 0 的情况下,要为字符设备分配一个主设备号。
分配主设备号的算法是从高到低 遍历数组 chrdevs,发现某个主设备号为空,则分配给字符设备。chrdev是全局变量,是255个元素的指针数组,对应设备的主设备号。如果输入的主设备号大于255,取余数。这个结构数组保存了所有的主设备号和从设备号
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
/* 根据主设备号计算索引 ,实际是主设备号除以255的余数*/
i = major_to_index(major);
/* 找一个未占用的区间*/
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
__register_chrdev_region第二部分从数组chrdevs找到未占用的区间。
首先通过主设备号索引获得结构char_device_struct, 然后遍历char_device_struct结构的单项列表,依次比较从设备号,找到一个合适的区间。最后将创建的字符设备结构cd链接到 单项链表,完成字符设备区间的登记。
}
chrdevs 的定义( fs/Char_dev ) :
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];
5.2.4 注册字符设备
/**
* cdev_add() - add a char device to the system
* 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)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
cdev_add 函数把复合设备号【主设备号、从设备号计算而来】 和设备区间注册到系统,这是通过调用
kobj_map 实现。
kobj_map 和前一节学习的 kobj_lookup 是同一组函数,目的通过系统的指针数组 和 链表管理字符设备,kobj_map 函数的代码如下:
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)
{
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(sizeof(struct probe) * n, 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;
}
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;
}
5.2.5 打开input设备
根据2章文件打开过程的分析 和本章字符舍不得额分析, 内核中打开设备最终调用了设备驱动open函数。
上一节input_init 函数注册了input设备的open函数,即 input_open_file :
另写
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;
err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5];
if (handler)
new_fops = fops_get(handler->fops);
mutex_unlock(&input_mutex);
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
return err;
}
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
input_open_file 函数是最重要的部分是 input_handler的应用。 input_table是个数组。包含8个input_handler指针。input设备分装了8个不同的handler。,每个对应一个次设备号。 设备打开时通过次设备号获得注册的input_handler,然后调用input_handler提供的 open函数。
/*
* input_mutex protects access to both input_dev_list and input_handler_list.
* This also causes input_[un]register_device and input_[un]register_handler
* be mutually exclusive which simplifies locking in drivers implementing
* input handlers.
*/
static DEFINE_MUTEX(input_mutex);
static struct input_handler *input_table[8];
--可以看到内核定义的时候input_table[]数组就包含了 8 个指针
handler = input_table[iminor(inode) >> 5];
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
//--Kdev.h--/include/linux
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#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))
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
struct inode {
/* RCU path lookup touches following: */
umode_t i_mode;
uid_t i_uid;
gid_t i_gid;
const struct inode_operations *i_op;
struct super_block *i_sb;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned int i_flags;
struct mutex i_mutex;
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
dev_t i_rdev;
unsigned int i_blkbits;
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
blkcnt_t i_blocks;
unsigned short i_bytes;
struct rw_semaphore i_alloc_sem;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
......
};
--dev_t i_rdev,表示设备文件对应的设备号。
struct list_head i_devices ------ 该成员使设备文件连接到对应的cdev结构,从而对应到自己的驱动程序。
struct cdev *i_cdev ------ 该成员也指向cdev设备
--除了从 dev_t 得到主设备号和次设备号外,这里还可以使用imajor()和iminor()函数从i_rdev中得到主设备号 和 次设备号。
imajor()函数在内部调用MAJOR宏,如下代码所示。
static inline unsigned imajor(const struct inode *inode) { return MAJOR(inode->i_rdev); /*从inode->i_rdev中提取主设备号*/ }
同样,iminor()函数在内部调用MINOR宏,如下代码所示。
static inline unsigned iminor(const struct inode *inode) { return MINOR(inode->i_rdev); ; /*从inode->i_rdev中提取次设备号*/ }
啊啊