linux驅動開發(一)


1:驅動開發環境

要進行linux驅動開發我們首先要有linux內核的源碼樹,並且這個linux內核的源碼樹要和開發板中的內核源碼樹要一直;

比如說我們開發板中用的是linux kernel內核版本為2.6.35.7,在我們ubuntu虛擬機上必須要有同樣版本的源碼樹,

我們再編譯好驅動的的時候,使用modinfo XXX命令會打印出一個版本號,這個版本號是與使用的源碼樹版本有關,如果開發板中源碼樹中版本與

modinfo的版本信息不一致使無法安裝驅動的;

我們開發板必須設置好nfs掛載;這些在根文件系統一章有詳細的介紹;

2:開發驅動常用的幾個命令

lsmod :list moduel 把我們機器上所有的驅動打印出來,

insmod:安裝驅動

rmmod:刪除驅動

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");            // 描述模塊的別名信息

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM