首先講下字符設備控制技術 :
大部分驅動程序除了需要提供讀寫設備的能力外,還需要具備控制設備的能力。比如: 改變波特率。
在用戶空間,使用ioctl系統調用來控制設備,原型如下:
int ioctl(int fd,unsigned long cmd,...)
fd: 要控制的設備文件描述符
cmd: 發送給設備的控制命令
…: 第3個參數是可選的參數,存在與否是依賴於控制命令(第 2 個參數 )。
當應用程序使用ioctl系統調用時,驅動程序將由如下函數來響應:
2.6.36 之前的內核:long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)
2.6.36 之后的內核:long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
參數cmd: 通過應用函數ioctl傳遞下來的命令
命令從其實質而言就是一個整數, 但為了讓這個整數具備更好的可讀性,我們通常會把這個整數分為幾個段:類型(8位),序號,參數傳送方向,參數長度。
Type(類型/幻數):表明這是屬於哪個設備的命令。
Number( ):序號 ,用來區分同一設備的不同命令
Direction:參數傳送的方向,可能的值是 _IOC_NONE(沒有數據傳輸), _IOC_READ, _IOC_WRITE(向設備寫入參數)
Size: 參數長度
Linux系統提供了下面的宏來幫助定義命令:
_IO(type,nr):不帶參數的命令
_IOR(type,nr,datatype):從設備中讀參數的命令
_IOW(type,nr,datatype):向設備寫入參數的命令
例:
#define MEM_MAGIC ‘m’ //定義幻數
#define MEM_SET _IOW(MEM_MAGIC, 0, int)
unlocked_ioctl函數的實現通常是根據命令執行的一個switch語句。但是,當命令號不能匹配任何一個設備所支持的命令時,返回-EINVAL.
編程模型:
Switch cmd
Case 命令A://執行A對應的操作
Case 命令B://執行B對應的操作
Default:return -EINVAL
LED驅動程序是以內核模塊的形式存在,首先搭好框架:
#include <linux/module.h> #include <linux/init.h> static int led_init(void) { return 0; } static void led_exit(void) { } MODULE_LICENSE("GPL"); module_init(led_init); module_exit(led_exit);
接着定義設備結構體和設備號:
設備都會用內核中的一種結構來描述。我們的字符設備在內核中使用structcdev來描述。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //設備操作集
struct list_head list;
dev_t dev; //設備號
unsigned int count; //設備數
};
struct cdev cdev; dev_t devnum;
接着定義文件操作結構體:這里就實現文件打開和IO控制:
在Linux系統中,每一個打開的文件,在內核中都會關聯一個struct file,它由內核在打開文件時創建, 在文件關閉后釋放。
static struct file_operations led_fops = { .open = led_open, .ioctl = led_ioctl, };
接下來初始化設備,分配主設備號,注冊設備:
這里說一下靜態申請和動態申請:
靜態申請:開發者自己選擇一個數字作為主設備號,然后通過函數register_chrdev_region向內核申請使用。缺點:如果申請使用的設備號已經被內核中的其他驅動使用了,則申請失敗。
動態分配:使用alloc_chrdev_region由內核分配一個可用的主設備號。優點:因為內核知道哪些號已經被使用了,所以不會導致分配到已經被使用的號。
向led_init(void)里面添加這些代碼:
static int led_init(void) { cdev_init(&cdev, &led_fops); //初始化設備 alloc_chrdev_region(&devnum, 0, 1, "myled"); //動態分配主設備號,這里的0是我們寫的,如果設備在注冊時候會檢查,如果我們寫的不行,會重新分配 cdev_add(&cdev, devnum, 1); //注冊設備 return 0; }
然后再led_exit(void)這個函數里添加設備注銷的代碼,不論使用何種方法分配設備號,都應該在驅動退出時,使用unregister_chrdev_region函數釋放這些設備號。
static void led_exit(void) { cdev_del(&cdev); //刪除這個設備 unregister_chrdev_region(devnum, 1); //注銷這個設備的設備號 }
接下來我們實現文件操做結構體里面的兩個接口函數,就是這兩個函數
當我們在應用程序里訪問這兩個open和ioctl時,內核就會通過這個文件操做結構體訪問led_open和led_ioctl這兩個函數。如果不知道這個函數的結構,可以去Linux下找file_operations這個關鍵詞
這些都是指向寫的函數(比如led_open、led_ioctl這些函數)的指針,復制過來加上名字、填寫參數(比如這樣:int led_open(struct inode *node, struct file *filp)),就可以了。
每一個存在於文件系統里面的文件都會關聯一個inode 結構,該結構主要用來記錄文件物理上的信息。因此, 它和代表打開文件的file結構是不同的。一個文件沒有被打開時不會關聯file結構,但是卻會關聯一個inode 結構。
首先我們要定義出硬件led的寄存器
#define GPBCON 0x56000010 #define GPBDAT 0x56000014 unsigned int *led_config; unsigned int *led_data; int led_open(struct inode *node, struct file *filp) { led_config = ioremap(GPBCON,4); //在linux系統下操作硬件,不能直接使用物理地址,必須轉化成虛擬地址,需要ioremap函數 writel(0x400,led_config); //對一個寄存器賦值,用writel這個函數,l表示寫進的是一個32位的值,這里是選擇mini2440上的LED1 led_data = ioremap(GPBDAT,4); return 0; } int led_ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd) { case LED_ON: writel(0x00,led_data); //這里是點亮LED1 return 0; case LED_OFF: writel(0x7ff,led_data); //這里是熄滅LED1 return 0; default: return -EINVAL; } }
上面代碼里的LED_ON和LED_OFF是操作者的指令(cmd),需要再次定義一個led.h的文件
#define LED_MAGIC 'L' #define LED_ON _IO(LED_MAGIC,0) #define LED_OFF _IO(LED_MAGIC,1)
接着加入需要的頭文件:
#include <linux/cdev.h> #include <linux/fs.h> #include <linux/io.h> #include "led.h"
編寫Makefile文件
obj-m := led.o KDIR := /root/myhome/linux-2.6.32.2 //內核文件的目錄 all : make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean : rm -f *.o *.ko *.order *.symvers
接着編寫led_app應用程序
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include "led.h" int main(int argc, char *argv[]) //帶參數的函數 { int fd; int cmd; if(argc < 2) { printf("please enter the second para!\n"); return 0; } cmd = atoi(argv[1]); fd = open("/dev/myled",O_RDWR); if(cmd == 1) { ioctl(fd,LED_ON); } else { ioctl(fd,LED_OFF); } return 0; }
編譯這個led_app的時候,一定要選取靜態編譯,不然下載開發板后運行會找不到動態鏈接庫
arm-linux-gcc -static led_app.c -o led_app
把編譯好的led.ko和led_app這兩個文件通過nfs傳到開發板后,顯示這樣
現狀安裝驅動insmod led.ko
然后再cat /proc/devices主設備號
主設備號是動態分配的253
這個時候再mknod /dev/myled c 253 0創建設備文件,這個文件就是led的設備文件
mknod 創建設備文件指令
/dev/myled 這是目錄
c 代表字符設備文件
253 主設備號
0 次設備號
創建好了之后就./led_app 0,這個時候會發現燈滅了
再執行./led_app 1,燈又亮了
正如代碼里那樣,點亮第一個燈。