字元裝置 和 input 裝置--input裝置的註冊
5.2input裝置的註冊
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:
在/proc/bus/ 下面建立input目錄,下面有兩個檔案 devices 和 handles 兩個檔案,分別記錄input裝置的 裝置資訊 、struct class input_class = { .name = "input", .devnode = input_devnode, };
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中提取次裝置號*/ }
啊啊