一步一步寫miscdevice的驅動模塊


(本文使用的平台為友善tiny210SDKv2)


對於linux的驅動程序來說,主要分為三種:miscdevice、platform_device、platform_driver 。

 

這三個結構體關系:
(基類)
kobject --------------------
/     \                    \
/       \                     \
device     cdev                   driver
/     \ (設備驅動操作方法)           \
/       \                              \
miscdevice         platform_device       platform_driver
(設備驅動操作方法)    (設備的資源)          (設備驅動)  

 

 

這時,我們先不討論這幾個間的關系與驅別,對於新手來說,上手最重要!

 

首先我們先看看混雜項:

 

在Linux驅動中把無法歸類的五花八門的設備定義為混雜設備(用miscdevice結構體表述)。miscdevice共享一個主設備號MISC_MAJOR(即10),但次設備號不同。 所有的miscdevice設備形成了一個鏈表,對設備訪問時內核根據次設備號查找對應的miscdevice設備,然后調用其file_operations結構中注冊的文件操作接口進行操作。 在內核中用struct miscdevice表示miscdevice設備,然后調用其file_operations結構中注冊的文件操作接口進行操作。miscdevice的API實現在drivers/char/misc.c中。 

 

 

第二,我們再看看混雜項設備驅動的程序組織架構:

新建一個first_led.c,先可能用到的頭文件都引用上吧!


 

#include <linux/kernel.h>
#include <linux/module.h>//驅動模塊必需要加的個頭文件
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>.

//對應着相應機器平台的頭文件
#include <mach/gpio.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>


//給自己設備驅動定義一個名字

#define DEVICE_NAME "First_led"

 

 

名字有了,但樣子是怎樣的呢?現在就開始定義一個“樣子”!

 

如果一個字符設備驅動要驅動多個設備,那么它就不應該用

misc設備來實現。

 

通常情況下,一個字符設備都不得不在初始化的過程中進行下面的步驟: 

通過alloc_chrdev_region()分配主次設備號。

使用cdev_init()和cdev_add()來以一個字符設備注冊自己。

 

而一個misc驅動,則可以只用一個調用misc_register()

來完成這所有的步驟。(所以miscdevice是一種特殊的chrdev字符設備驅動)

所有的miscdevice設備形成一個鏈表,對設備訪問時,內核根據次設備號查找

對應的miscdevice設備,然后調用其file_operations中注冊的文件操作方法進行操作。

 在Linux內核中,使用struct miscdevice來表示miscdevice。這個結構體的定義為:

 

struct miscdevice  

{

int minor;

const char *name;

const struct file_operations *fops;

struct list_head list;

struct device *parent;

struct device *this_device;

const char *nodename;

mode_t mode;

};

minor是這個混雜設備的次設備號,若由系統自動配置,則可以設置為

MISC_DYNANIC_MINOR,name是設備名 

為了容易理解,我們先打大概的“樣子”做好。只做minor、name、fops;

定義一個myfirst_led_dev設備:

 

 

static struct miscdevice myfirst_led_dev = {
	.minor			=	 MISC_DYNAMIC_MINOR,
	.name			=	 DEVICE_NAME,
	.fops				=	 &myfirst_led_dev_fops,
};


 

 

Minor  name   都已經定義好了。那么接下來實現一下myfirst_led_dev_fops方法。

內核中關於file_operations的結構體如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

long (*fallocate)(struct file *file, int mode, loff_t offset,

  loff_t len);

};

對於LED的操作,只需要簡單實現io操作就可以了,所以只實現

 

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

該函數是在linux2.6.5以后才出現在設備的操作方法中的。

函數參數為文件節點、命令、參數

 

 

static struct file_operations myfirst_led_dev_fops = {
	.owner			= THIS_MODULE,
	.unlocked_ioctl	= myfirst_led_ioctl,
};

 

 

到了這里,我們就考慮一下LED的物理層面是怎樣的實現了,通過開發板的引腳我們可以知道,四個LED是分別接到了GPJ2的0~3號管腳上。因此,我們定義一個數組來引用這幾個管腳(當然不能像祼機那樣對IO的物理地址進行操作了,是需要經過內核的內存映射得來的IO內存操作!而內核把ARM的IO管腳地址按一個線性地址進行了編排)

 

 

static int led_gpios[] = {
	S5PV210_GPJ2(0),
	S5PV210_GPJ2(1),
	S5PV210_GPJ2(2),
	S5PV210_GPJ2(3),
};
#define LED_NUM	ARRAY_SIZE(led_gpios)//判斷led_gpio有多少個

 

 

S5PV210_GPJ2(*)的定義如下

#define S5PV210_GPJ2(_nr)  (S5PV210_GPIO_J2_START + (_nr))

enum s5p_gpio_number {

S5PV210_GPIO_A0_START = 0,

...................................

S5PV210_GPIO_J2_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_J1),

.....................................

}

#define S5PV210_GPIO_NEXT(__gpio) \

((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)

 

注:##是粘貼運算,具體用法請自行找度娘或谷哥

 

給用戶空間的接口操作:

static long myfirst_led_ioctl(struct file *filp, unsigned int cmd,
		unsigned long arg)
{
	switch(cmd) {
		case 0:
		case 1:
			if (arg > LED_NUM) {
				return -EINVAL;//判讀用戶的參數是否有誤
			}

			gpio_set_value(led_gpios[arg], !cmd);//用戶選定的LED並設置值
			//printk(DEVICE_NAME": %d %d\n", arg, cmd);
			break;
		default:
			return -EINVAL;
	}
	return 0;
}

 

對於gpio_set_value(unsigned int gpio, int value),內核有以下定義:

static inline void gpio_set_value(unsigned int gpio, int value)

{

__gpio_set_value(gpio, value);

}

void __gpio_set_value(unsigned gpio, int value)

{

struct gpio_chip *chip;

 

chip = gpio_to_chip(gpio);

WARN_ON(chip->can_sleep);

trace_gpio_value(gpio, 0, value);

chip->set(chip, gpio - chip->base, value);

}//到這里我們就不再分析下去了 ,無非就是判定是哪一個芯片

 

程序寫到這里,對於用戶空間來說,已經有了完整的操作方法接口,但對於內核模塊來說,還缺少驅動模塊的進入與退出。以下接着寫驅動模塊的初始化(即進入)和退出。

 

static int __init myfirst_led_dev_init(void) {;}

static void __exit myfirst_led_dev_exit(void) {;}

函數如上。雙下划線表示模塊在內核啟動和關閉時自動運行和退出

 

對於驅動模塊的初始化函數,要寫些什么呢?我們這樣考慮:

對於用戶空間接口來說,我們的實現函數只是給出了IO的值設置的,但是ARM的IO管腳使用還是需要配置方向、上拉下拉.....才能正常使用的,並且所有的硬件資源,都是受內核所支配的,驅動程序必需向內核申請硬件資源才能對硬件進行操作。另外還需要對設備進行注冊,內核才知道你這個設備是什么東東,用到哪些東西。這些操作,我們安排在init里實現!

static int __init myfirst_led_dev_init(void) 
{
	int ret;
	int i;

	for (i = 0; i < LED_NUM; i++) 
  {
		ret = gpio_request(led_gpios[i], "LED");//申請IO引腳
		if (ret) {
				printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
					led_gpios[i], ret);
				return ret;
				}
		s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
		gpio_set_value(led_gpios[i], 1);
	}
	ret = misc_register(&myfirst_led_dev);
	printk(DEVICE_NAME"\tinitialized\n");
	return ret;
}


 

pio_request(unsigned gpio, const char *label)  

gpio則為你要申請的哪一個管腳,label為其名字 。

 

int s3c_gpio_cfgpin(unsigned int pin, unsigned int config);

對芯片進行判斷,並設置引腳的方向。

 

ret = misc_register(&myfirst_led_dev);.

該函數中、內核會自動為你的設備創建一個設備節點

對設備進行注冊

到這里,設備的初始化與注冊已經完成!

當用戶不再需要該驅動資源時,我們必需在驅動模塊中,對占用內核的資源進行主動的釋放!

因此在驅動模塊退出時,完成這些工作!

 

 

static void __exit myfirst_led_dev_exit(void) {
	int i;

	for (i = 0; i < LED_NUM; i++) {
		gpio_free(led_gpios[i]);
	}

	misc_deregister(&myfirst_led_dev);
}

 

 

gpio_free(led_gpios[i]);

釋放IO資源

misc_deregister(&myfirst_led_dev);

注銷設備

還需要模塊的初始化與退出函數聲明

 

module_init(myfirst_led_dev_init);

module_exit(myfirst_led_dev_init);

最后,為了保持內核驅動模塊的風格,我們還要加上相應的許可跟作者

MODULE_LICENSE("GPL");

MODULE_AUTHOR("jjvip136@163.com");

 

 

好了,程序已經打好出來了(黃色代碼),我們把它整理好,試下編譯一下試試效果(晚點補上效果)。

 













 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM