一步一步寫miscdevice的驅動模塊

(本文使用的平臺爲友善tiny210SDKv2)node


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

 

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

 

 

這時,咱們先不討論這幾個間的關係與驅別,對於新手來講,上手最重要!數組

 

首先咱們先看看混雜項:架構

 

在Linux驅動中把沒法歸類的五花八門的設備定義爲混雜設備(用miscdevice結構體表述)。miscdevice共享一個主設備號MISC_MAJOR(即10),但次設備號不一樣。 全部的miscdevice設備造成了一個鏈表,對設備訪問時內核根據次設備號查找對應的miscdevice設備,而後調用其file_operations結構中註冊的文件操做接口進行操做。 在內核中用struct miscdevice表示miscdevice設備,而後調用其file_operations結構中註冊的文件操做接口進行操做。miscdevice的API實如今drivers/char/misc.c中。 app

 

 

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

新建一個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"

 

 

名字有了,但樣子是怎樣的呢?如今就開始定義一個「樣子」!this

 

若是一個字符設備驅動要驅動多個設備,那麼它就不該該用spa

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");

 

 

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

相關文章
相關標籤/搜索