一步一步寫miscdevice的驅動模組
(本文使用的平臺為友善tiny210SDKv2)
對於linux的驅動程式來說,主要分為三種:miscdevice、platform_device、platform_driver 。
這三個結構體關係:
(基類)
kobject --------------------/ \ \/ \ \device cdev 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]");
好了,程式已經打好出來了(黃色程式碼),我們把它整理好,試下編譯一下試試效果(晚點補上效果)。