1:驅動開發環境html
要進行linux驅動開發咱們首先要有linux內核的源碼樹,而且這個linux內核的源碼樹要和開發板中的內核源碼樹要一直;node
好比說咱們開發板中用的是linux kernel內核版本爲2.6.35.7,在咱們ubuntu虛擬機上必需要有一樣版本的源碼樹,linux
咱們再編譯好驅動的的時候,使用modinfo XXX命令會打印出一個版本號,這個版本號是與使用的源碼樹版本有關,若是開發板中源碼樹中版本與shell
modinfo的版本信息不一導致沒法安裝驅動的;編程
咱們開發板必須設置好nfs掛載;這些在根文件系統一章有詳細的介紹;ubuntu
2:開發驅動經常使用的幾個命令api
lsmod :list moduel 把咱們機器上全部的驅動打印出來,數組
insmod:安裝驅動cookie
rmmod:刪除驅動app
modinfo:打印驅動信息
3:寫linux驅動文件和裸機程序有很大的不一樣,雖然都是操做硬件設備,可是因爲寫裸機程序的時候是咱們直接寫代碼操做硬件設備,這隻有一個層次;
而咱們寫驅動程序首先要讓linux內核經過必定的接口對接,而且要在linux內核註冊,應用程序還要經過內核跟應用程序的接口相關api來對接;
4:驅動的編譯模式是固定的,之後編譯驅動的就是就按照這個模式來套便可,下面咱們來分下一下驅動的編譯規則:
#ubuntu的內核源碼樹,若是要編譯在ubuntu中安裝的模塊就打開這2個 #KERN_VER = $(shell uname -r) #KERN_DIR = /lib/modules/$(KERN_VER)/build # 開發板的linux內核的源碼樹目錄 KERN_DIR = /root/driver/kernel obj-m += module_test.o all: make -C $(KERN_DIR) M=`pwd` modules cp: cp *.ko /root/porting_x210/rootfs/rootfs/driver_test .PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean
make -C $(KERN_DIR) M=`PWD` modules
這句話代碼的做用就是到 KERN_DIR這個文件夾中 make modules
把當前目錄賦值給M,M做爲參數傳到主目錄的Makefile中,其實是主目錄的makefile中有目標modules,下面有必定的規則來編譯驅動;
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
咱們在ubuntu中編譯內核的時候用這兩句代碼,由於在ubuntu中爲咱們保留了一份linux內核的源碼樹,咱們編譯的時候直接調用那個源碼樹的主Makefile以及一些頭文件、內核函數等;
瞭解規則之後,咱們設置好KERN_DIR、obj-m這兩個變量之後直接make就能夠了;
通過編譯會獲得下面一些文件:
下面咱們可使用
lsmod命令來看一下咱們ubuntu機器現有的一些驅動
能夠看到有不少的驅動,
下面咱們使用
insmod XXX命令來安裝驅動,在使用lsmod命令看一下實驗現象
能夠看到咱們剛纔安裝的驅動放在了第一個位置;
使用modinfo來打印一下驅動信息
modinfo xxx.ko
這裏注意vermagic 這個的1.8.0-41是你用的linux內核源碼樹的版本號,只有這個編譯的版本號與運行的linux內核版本一致的時候,驅動程序纔會被安裝
注意license:GPL linux內核開元項目的許可證通常都是GPL這裏儘可能設置爲GPL,不然有些狀況下會出現錯誤;
下面使用
rmmod xxx刪除驅動;
-------------------------------------------------------------------------------------
5:下面咱們分析一下驅動。C文件
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit // 模塊安裝函數
static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init helloworld init\n"); //printk("<7>" "chrdev_init helloworld init\n"); //printk("<7> chrdev_init helloworld init\n");
return 0; } // 模塊下載函數
static void __exit chrdev_exit(void) { printk(KERN_INFO "chrdev_exit helloworld exit\n"); } module_init(chrdev_init); module_exit(chrdev_exit); // MODULE_xxx這種宏做用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的做者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
module_init宏的做用就是把insmod與module_init(XXX)中的XXX綁定起來,insmod命令其實是執行的chrdev_init這個函數;
咱們寫的這個chrdev_init函數只有一條打印信息;
由於內核的printk函數是設置打印級別的,咱們能夠是用dmesg命令來查看打印信息
如上面圖,可見咱們insmod的時候打印了 chrdev_init helloworld init這條信息,這裏正是chrdev_init 這個函數中打印的信息;
由於這裏用到了不少內核函數、宏等,因此要包含這些函數、宏的頭文件
咱們能夠創建內核的man手冊來查詢這些函數;
http://blog.sina.com.cn/s/blog_6642cd020101gtin.html
下載的版本爲linux-2.6.35.7創建內核man手冊
如今就能夠man printk來查找linux內核函數了,可是這裏注意到,好像仍是沒有相關的頭文件包含
能夠參考,經常使用的內核函數的頭文件包含,
http://blog.csdn.net/guowenyan001/article/details/43342301
最後辦法就是把內核文件在SI中創建鏈接來查找;
下面來看一下__init
#define __init __section(.init.text) __cold notrace
用來定義段屬性的,把Chrdev_init函數定義爲.init.text段,這個段在啓動內核之後會內核會自動釋放掉,以節省內存空間;
採用2.6.35.7源碼樹編譯在開發板上運行;
KERN_DIR = /usr/bhc/kernel/linux-2.6.35.7/
把KERN_DIR目錄變量更改成咱們安裝的內核源碼樹目錄便可
make
注意:必定要把源碼樹目錄中主Makefile中ARCH、cross_compile變量的值更改了;
開發板使用在zImage 也要是用這個內核來編譯的zImage
在開發板中一樣使用
lsmod
insmod
rmmod
----------------------------------------------------------------------------------------------------------
6:應用層是如何調用驅動的
在應用層進行應用編程的時候咱們對底層設備的操做包括:open close write read 等操做;
如fd = open("/dev/mouse", O_RDWR);
這些操做都是經過內核給定的api接口來實現的,這些接口是經過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 *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 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 **); };
這個結構體中包含了對硬件的全部操做;
結構體中大多都是函數指針,這些函數指針指向真正的設備的操做函數;
寫好硬件真正的讀寫函數之後,在創建一個struct file_operations類型的結構體,把相應的操做對應真正的讀寫函數初始化之後,
咱們還須要把這個結構體向內核初始化,告訴內核,讓內核知道咱們創建了一個驅動,
咱們用register_chrdev這個函數來向內核註冊:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
這個函數來註銷驅動
註冊的時候須要一個註冊號major、name、以及創建好的struct file_operations結構體;
內核中有一個結構體數組,這個數組中一共255個元素,咱們寫好的驅動註冊的時候須要一個major主設備號,這個主設備號對應數組的下標,註冊的時候設備命令以及file_operations的指針就放入了這個結構體數組對應的major下標的那個元素中;
註冊之後,內核知道有這個設備了,應用程序才能夠經過內核來調用api來操做這個設備;
應用是如何調用驅動呢?
應用調用驅動是經過驅動設備文件來調用驅動的,咱們首先要用mknod /dev/xxx c 主設備號 次設備號 命令來建立驅動設備文件,
這樣的話應用程序就能夠通/dev/xxx這個設備驅動文件,獲取對應的主設備號,內核在經過這個主設備號找到設備名稱和file_operations這個結構體;
能夠看一下上面這個圖:
應用程序:經過/dev/xxx設備文件來找到這個主設備號,在經過系統api(open、close、read、write),執行對設備的操做;
而在內核中linux內核經過主設備號找到file_operation這個結構圖,在經過這個結構體找到真正的操做設備的函數;
因此咱們寫驅動程序在應用層作的事情就是:
使用mknod命令來創建設備驅動文件;
找到設備文件,調用api操做設備便可;
在linux內核中要作的事情有:
寫好真正的設備操做函數,創建file_operation結構體,用register_chrdev函數來向內核註冊;
下面咱們對每個步驟作詳細的操做分析:
真正操做硬件設備的函數(以led爲例)
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#define MYMAJOR 200
#define MYNAME "LED_DEVICE"
//int (*open) (struct inode *, struct file *); //open函數的格式是上面的格式:
static int led_dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev_open open\n"); } //release函數的原型是:int (*release) (struct inode *, struct file *);
static int led_dev_close(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev_close close\n"); } static const struct file_operations led_dev_fops{ .opne = led_dev_open, .release = led_dev_close, } static int __init leddev_init(void) { int ret = -1; printk(KERN_INFO "leddev_init"); ret = register_chrdev(MYMAJOR, MYNAME, &led_dev_fops); if(ret) { printk(KERN_ERR "led devices rigister failed"); retunt -EINVAL; } printk(KERN_INFO "led regist sucess"); return 0; } static int __exit leddev_exit(void) { printfk(KERN_INFO "led device exit"); unregister_chrdev(MYMAJOR, NAME); }
module_init(leddev_init);
module_exit(leddev_exit);
// MODULE_xxx這種宏做用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("bh
c"); // 描述模塊的做者
MODULE_DESCRIPTION("led test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
這樣咱們就能夠去編譯之後在開發板中使用insmod rmmod等命令來安裝刪除驅動了;
在這裏補充一問題,安裝好驅動之後,主設備號能夠在/proc/devices文件中查看,可是因爲不一樣的設備主設備號佔用的不同,有時候須要系統來自動分配
主設備號,這個如何實現呢:
咱們能夠在register_chrdev函數的major變量傳參0進去,由於這個函數的返回值爲主設備號,因此咱們定義一個全局變量來接受這個值便可
static int mymajor;
//註冊的時候
mymajor = register_chrdev(0, MYNAME, &ded_dev_fops);
//釋放的時候
unregister_chrdev(mymajor, MYNAME);
這樣便可;
-----------------------------------------------------------------------------------------------------------------
下面介紹api的write read函數:
read函數的函數原型是下面:
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
write函數的函數原型:
static ssize_t ab3550_bank_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
write函數的函數原型是這樣的
這裏注意下:應用層面的內存buf與內核中的buf數據不能直接交換的,不能用memcpy函數複製;
要使用
copy_form_user
copy_to_user兩個函數;
static inline long copy_to_user(void __user *to, const void *from, unsigned long n);
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
兩個函數的原型爲上:
app程序
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
#define FILE "/dev/led_device"
char ubuf[100]; int main(void) { int fd = -1; int ret = -1; //打開led設備
fd = open(FILE, O_RDWR); if(fd < 0) { printf("/dev/led_device open failed\n"); return -1; } printf("/dev/led_device open success\n"); //寫led設備
ret = write(fd, "led_blink_bbb", 13); if(ret < 0) { printf("write error\n"); } printf("write success...\n"); //讀led設備
ret = read(fd, ubuf, 100); printf("read is %s\n", ubuf); //關閉led設備
close(fd); }
驅動程序相關代碼
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> #include <asm/uaccess.h>
#define MYMAJOR 200
#define MYNAME "LED_DEVICE"
static char kbuf[100]; static int mymojor; static int led_dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev open\n"); return 0; } static int led_dev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev close\n"); return 0; } ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int ret = -1; ret = copy_to_user(buf, kbuf, sizeof(kbuf)); if(ret) { printk(KERN_ERR "kernel led read error\n"); } printk(KERN_INFO "led device read success\n"); } static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int ret = -1; ret = copy_from_user(kbuf, user_buf, count); if(ret) { printk(KERN_ERR "kernel led write error\n"); return -EINVAL; } printk(KERN_INFO "led device write success\n"); return 0; } static const struct file_operations led_dev_fops = { .open = led_dev_open, .write = led_dev_write, .read = led_dev_read, .release = led_dev_release, .owner = THIS_MODULE, }; // 模塊安裝函數
static int __init leddev_init(void) { printk(KERN_INFO "led_device init\n"); //printk("<7>" "chrdev_init helloworld init\n"); //printk("<7> chrdev_init helloworld init\n"); //在這裏進行註冊驅動,由於安裝驅動實際上執行的就是這個函數;
mymojor = register_chrdev(0, MYNAME, &led_dev_fops); if(!mymojor) { printk(KERN_ERR "led_device failed\n"); return -EINVAL; } printk(KERN_INFO "leddev_dev regist success\n"); return 0; } // 模塊下載函數
static void __exit leddev_exit(void) { printk(KERN_INFO "leddev_dev exit\n"); //註銷led設備驅動
unregister_chrdev(mymojor, MYNAME); printk(KERN_INFO "leddev_dev unregist success\n"); } module_init(leddev_init); module_exit(leddev_exit); // MODULE_xxx這種宏做用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("bhc"); // 描述模塊的做者
MODULE_DESCRIPTION("led test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
編譯之後在開發板上測試。。。。。
-----------------------------------------------------------------------------------------------------
下面咱們的低層驅動開始真正的操做硬件了:
在操做硬件的時候,咱們會用到硬件的香瓜寄存器,由於咱們以前在邏輯程序中使用的物理地址來直接寫的,而咱們在開發板上移植好內核之後
咱們的內核程序就是運行在虛擬地址上了,因此咱們要看一下咱們的linux內核中虛擬地址跟物理地址是如何映射的,三星在移植的內核的時候,把硬件相關
的寄存器,創建了三個映射表文件,讓咱們來查找這些物理地址對應的虛擬地址:
首先來看一下靜態虛擬地址映射:
分別爲
arch/arm/plat-samsung/plat/map-base.h
看一下這個文件中的內容:
#define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */
#define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
#define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */
#define S3C_VA_OTG S3C_ADDR(0x00E00000) /* OTG */
#define S3C_VA_OTGSFR S3C_ADDR(0x00F00000) /* OTG PHY */
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
由於cpu的相關模塊的寄存器地址都是分塊的,如終端相關寄存器,被分配在一塊兒,如mem內存相關寄存器,map-base.h中把各個模塊先關的虛擬基地址羅列了出來;
arch/arm/plat-s5p/include/plat/map-s5p.h
#define S5P_VA_CHIPID S3C_ADDR(0x00700000)
#define S5P_VA_GPIO S3C_ADDR(0x00500000)
#define S5P_VA_SYSTIMER S3C_ADDR(0x01200000)
#define S5P_VA_SROMC S3C_ADDR(0x01100000)
#define S5P_VA_AUDSS S3C_ADDR(0X01600000)
#define S5P_VA_UART0 (S3C_VA_UART + 0x0)
#define S5P_VA_UART1 (S3C_VA_UART + 0x400)
#define S5P_VA_UART2 (S3C_VA_UART + 0x800)
#define S5P_VA_UART3 (S3C_VA_UART + 0xC00)
#define S3C_UART_OFFSET (0x400)
#define VA_VIC(x) (S3C_VA_IRQ + ((x) * 0x10000))
#define VA_VIC0 VA_VIC(0)
#define VA_VIC1 VA_VIC(1)
#define VA_VIC2 VA_VIC(2)
#define VA_VIC3 VA_VIC(3)
這個文件中三星工程師把要用獲得的模塊的基地址又細化了,如GPIO UART0-3 SROM VIC中斷,如咱們要添加新的模塊的話,能夠在這個文件中,定義相關模塊寄存器
的基地址;
arch/arm/mach-s5pv210/include/mach/regs-gpio.h
arch/arm/mach-s5pv210/include/mach/gpio-bank.h
能夠看到在gpio-bank.h文件中,定義了每一個寄存器的虛擬地址;咱們在操做相關寄存器的時候直接用這裏定義好的宏就能夠;
如何遇到一個新的開發板如何找虛擬內存映射表呢,通常都是在arch/arm/plat或者 arch/arm/mach 等目錄,通常是文件名都是map-文件;
下面開始咱們真正的應用程序經過驅動來控制led硬件;
驅動模塊
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> #include <asm/uaccess.h> #include <plat/map-base.h> #include <plat/map-s5p.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/string.h>
#define MYMAJOR 200
#define MYNAME "LED_DEVICE"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
static char kbuf[100]; static int mymojor; static int led_dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev open\n"); return 0; } static int led_dev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev close\n"); return 0; } ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int ret = -1; ret = copy_to_user(buf, kbuf, sizeof(kbuf)); if(ret) { printk(KERN_ERR "kernel led read error\n"); } printk(KERN_INFO "led device read success\n"); } static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int ret = -1; //首先把kbuf清零
memset(kbuf, 0, sizeof(kbuf)); ret = copy_from_user(kbuf, user_buf, count); if(ret) { printk(KERN_ERR "kernel led write error\n"); return -EINVAL; } printk(KERN_INFO "led device write success\n"); if (kbuf[0] == '1') { rGPJ0CON = 0x11111111; rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); } if (kbuf[0] == '0') { rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); } return 0; } static const struct file_operations led_dev_fops = { .open = led_dev_open, .write = led_dev_write, .read = led_dev_read, .release = led_dev_release, .owner = THIS_MODULE, }; // 模塊安裝函數
static int __init leddev_init(void) { printk(KERN_INFO "led_device init\n"); //printk("<7>" "chrdev_init helloworld init\n"); //printk("<7> chrdev_init helloworld init\n"); //在這裏進行註冊驅動,由於安裝驅動實際上執行的就是這個函數;
mymojor = register_chrdev(0, MYNAME, &led_dev_fops); if(!mymojor) { printk(KERN_ERR "led_device failed\n"); return -EINVAL; } printk(KERN_INFO "leddev_dev regist success\n"); return 0; } // 模塊下載函數
static void __exit leddev_exit(void) { printk(KERN_INFO "leddev_dev exit\n"); //註銷led設備驅動
unregister_chrdev(mymojor, MYNAME); printk(KERN_INFO "leddev_dev unregist success\n"); } module_init(leddev_init); module_exit(leddev_exit); // MODULE_xxx這種宏做用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("bhc"); // 描述模塊的做者
MODULE_DESCRIPTION("led test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
app文件
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h>
#define FILE "/dev/led_device"
char ubuf[100]; int main(void) { int fd = -1; int ret = -1; //打開led設備
fd = open(FILE, O_RDWR); if(fd < 0) { printf("/dev/led_device open failed\n"); return -1; } printf("please input on | off | quit.\n"); while (1) { memset(ubuf, 0, sizeof(ubuf)); scanf("%s", ubuf); if (!strcmp(ubuf, "on")) { write(fd , "1", 1); } if (!strcmp(ubuf, "off")) { write(fd, "0", 1); } if (!strcmp(ubuf, "quit")) { break; } } //讀led設備 //ret = read(fd, ubuf, 100); //printf("read is %s\n", ubuf); //關閉led設備
close(fd); }
使用動態虛擬地址
動態虛擬地址:當咱們要使用這個寄存器的物理地址的時候,不用事先創建好的頁表,而是給物理地址動態的分配一個虛擬地址,操做的時候直接使用這個動態分配的虛擬地址
操做物理地址便可,使用完之後取消映射便可;
使用動態虛擬地址映射首先:
1:創建映射
使用request_mem_region向內核申請虛擬地址空間;
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
request_mem_region其實是一個宏,真正調用的是 __request_region這個函數;
request_mem_region宏須要三個參數:start:啓示的物理地址,n長度,name
申請成功則返回0;
ioremap
#define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)
也是一個宏,調用的是內核函數__arm_ioremap
這個宏須要兩個參數起始物理地址以及 長度;
2:使用完之後咱們首先要消除映射
取消映射iounmap宏
#define iounmap(cookie) __iounmap(cookie)
接受一個參數,起始物理地址;
而後在消除分配的虛擬地址
使用release_mem_region
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))
這個宏只須要兩個參數便可一個是起始物理地址,一個長度;
下面看具體代碼:咱們只修改驅動代碼,應用層代碼不進行修改了。。。
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> #include <asm/uaccess.h> #include <plat/map-base.h> #include <plat/map-s5p.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/ioport.h> #include <linux/string.h> #include <asm/io.h>
#define MYMAJOR 200
#define MYNAME "LED_DEVICE"
#define GPJ0_PA_base 0xE0200240
#define GPJ0CON_PA_OFFSET 0x0 unsigned int *pGPJ0CON; unsigned int *pGPJ0DAT; static char kbuf[100]; static int mymojor; static int led_dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev open\n"); return 0; } static int led_dev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "led_dev close\n"); return 0; } ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int ret = -1; ret = copy_to_user(buf, kbuf, sizeof(kbuf)); if(ret) { printk(KERN_ERR "kernel led read error\n"); } printk(KERN_INFO "led device read success\n"); } static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int ret = -1; //首先把kbuf清零
memset(kbuf, 0, sizeof(kbuf)); ret = copy_from_user(kbuf, user_buf, count); if(ret) { printk(KERN_ERR "kernel led write error\n"); return -EINVAL; } printk(KERN_INFO "led device write success\n"); if (kbuf[0] == '1') { *pGPJ0CON = 0x11111111; *(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5)); } if (kbuf[0] == '0') { *(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5)); } return 0; } static const struct file_operations led_dev_fops = { .open = led_dev_open, .write = led_dev_write, .read = led_dev_read, .release = led_dev_release, .owner = THIS_MODULE, }; // 模塊安裝函數
static int __init leddev_init(void) { printk(KERN_INFO "led_device init\n"); //printk("<7>" "chrdev_init helloworld init\n"); //printk("<7> chrdev_init helloworld init\n"); //在這裏進行註冊驅動,由於安裝驅動實際上執行的就是這個函數;
mymojor = register_chrdev(0, MYNAME, &led_dev_fops); if(!mymojor) { printk(KERN_ERR "led_device failed\n"); return -EINVAL; } printk(KERN_INFO "leddev_dev regist success\n"); if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) { return -EINVAL; } pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4); return 0; } // 模塊下載函數
static void __exit leddev_exit(void) { printk(KERN_INFO "leddev_dev exit\n"); //註銷led設備驅動
unregister_chrdev(mymojor, MYNAME); iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET); release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8); printk(KERN_INFO "leddev_dev unregist success\n"); } module_init(leddev_init); module_exit(leddev_exit); // MODULE_xxx這種宏做用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("bhc"); // 描述模塊的做者
MODULE_DESCRIPTION("led test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息