1. 程式人生 > >字元裝置 和 input 裝置--input裝置的註冊

字元裝置 和 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:

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中提取次裝置號*/  
} 

啊啊