(本文使用的平臺爲友善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");
好了,程序已經打好出來了(黃色代碼),咱們把它整理好,試下編譯一下試試效果(晚點補上效果)。