1. 程式人生 > >一步一步寫miscdevice的驅動模組

一步一步寫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("[email protected]");

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