字符设备驱动基础篇5——驱动如何操控硬件(动静态映射操作LED)
以下內容源于朱有鵬嵌入式課程的學習,,如有侵權,請告知刪除。
參考資料:http://www.cnblogs.com/biaohc/p/6575074.html
這里的映射,是指物理地址和虛擬地址的對應關系。
這里的動靜態,是指映射的建立是寫死的一直存在的,還是根據需要隨時建立、銷毀的。
一、驅動如何操控硬件
1、操作系統下操作硬件,和裸機操作硬件的相同和不同點
(1)硬件不變
- 硬件物理原理不變;
- 硬件操作接口(寄存器)不變;
- 硬件操作代碼不變
(2)改變點
- 寄存器地址不同。原來是直接用物理地址,現在需要用(該物理地址相對應的)虛擬地址。寄存器的物理地址是CPU設計時決定的,從datasheet中查找到的。
- 編程方法不同。裸機中習慣直接用函數指針操作寄存器地址,而kernel中習慣用封裝好的io讀寫函數來操作寄存器,以實現最大程度可移植性。
2、內核的虛擬地址映射方法
(1)為什么需要虛擬地址映射?https://www.zhihu.com/question/20696698
(2)內核中有兩套虛擬地址映射方法:動態和靜態
靜態映射方法的特點
- 內核移植時以代碼的形式硬編碼,如果要更改必須改源代碼后重新編譯內核;
- 在內核啟動時建立靜態映射表,到內核關機時銷毀,中間一直有效;
- 對于移植好的內核,你用不用它都在那里;
動態映射方法的特點:
- 驅動程序根據需要隨時動態的建立映射、使用、銷毀映射;
- 映射是短期臨時的
(3)如何選擇虛擬地址映射方法
- 2種映射并不排他,可以同時使用;
- 靜態映射類似于C語言中全局變量,動態方式類似于C語言中malloc堆內存;
- 靜態映射的好處是執行效率高,壞處是始終占用虛擬地址空間;
- 動態映射的好處是按需使用虛擬地址空間,壞處是每次使用前后都需要代碼去建立映射、銷毀映射。
二、靜態映射操作LED理論
1、關于靜態映射
- 不同版本內核中靜態映射表所在的目錄位置、文件名可能不同;
- 不同SoC的靜態映射表所在的目錄位置、文件名可能不同;
- 所謂映射表其實就是頭文件中的宏定義。
2、三星版本內核中的靜態映射表
(1)主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h
- CPU在安排寄存器地址時不是隨意亂序分布的,而是按照模塊去區分的。
- 每一個模塊內部的很多個寄存器的地址是連續的。所以內核在定義寄存器地址時都是先找到某個模塊的基地址,然后再用基地址+偏移量來尋找具體的一個寄存器。
- map-s5p.h中定義的就是要用到的幾個模塊(GPIO、UART等)的寄存器基地址。
- map-s5p.h中定義的是模塊的寄存器基地址的虛擬地址。里面定義的都是虛擬地址。
(2)虛擬地址基地址定義在:arch/arm/plat-samsung/include/plat/map-base.h
- #define S3C_ADDR_BASE ??(0xFD000000) ?// 三星移植時確定的靜態映射表的基地址,表中的所有虛擬地址都是以這個地址+偏移量來指定的
(3)GPIO相關的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
- 表中是GPIO的各個端口的虛擬基地址的定義
(4)GPIO的具體寄存器定義位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h
三、靜態映射操作LED實踐
1、參考裸機中的操作方法添加LED操作代碼
(1)虛擬地址的宏定義,注意這個地址是虛擬地址,而且是這個開發板對應的移植后的內核。
(2)在init和exit函數中分別點亮和熄滅LED;
2、實踐測試
(1)insmod和rmmod時觀察LED亮滅變化;
(2)打印出寄存器的值和靜態映射表中的分析相對比;
3、將點亮熄滅代碼移動到open和close函數中去
4、添加驅動中的寫函數
(1)先定義好應用和驅動之間的控制接口,這個是由自己來定義的。譬如定義為:應用向驅動寫"on"則驅動讓LED亮,應用向驅動寫"off",驅動就讓LED滅。
(2)應用和驅動的接口定義做的盡量簡單,把業務邏輯放在應用層,而底層只是進行操作。譬如用1個字目來表示。譬如定義為:應用寫"1"表示燈亮,寫"0"表示讓燈滅。
if (kbuf[0] == '1'){rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));}else if (kbuf[0] == '0'){rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));}/*// 真正的驅動中,數據從應用層復制到驅動中后,我們就要根據這個數據// 去寫硬件完成硬件的操作。所以這下面就應該是操作硬件的代碼if (!strcmp(kbuf, "on")){rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));}else if (!strcmp(kbuf, "off")){rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));} */5、寫應用來測試寫函數
6、驅動和應用中來添加讀功能
#include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit #include <linux/fs.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h #include <linux/string.h>#define MYMAJOR 200 #define MYNAME "testchar"#define GPJ0CON S5PV210_GPJ0CON//這里已經是虛擬地址了,在內核中已經定義好S5PV210_GPJ0CON,它指向了寄存器的實際地址。要包含頭文件//所謂的靜態映射就體現在這里。//這個是在內核中寫好定死的,如果要修改,就要把內核代碼相關部分修改,然后編譯 #define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *)GPJ0CON) #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)int mymajor;char kbuf[100]; // 內核空間的bufstatic int test_chrdev_open(struct inode *inode, struct file *file) {// 這個函數中真正應該放置的是打開這個設備的硬件操作代碼部分printk(KERN_INFO "test_chrdev_open\n");rGPJ0CON = 0x11111111;rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮return 0; }static int test_chrdev_release(struct inode *inode, struct file *file) {printk(KERN_INFO "test_chrdev_release\n");rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));return 0; }ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) {int ret = -1;printk(KERN_INFO "test_chrdev_read\n");ret = copy_to_user(ubuf, kbuf, count);if (ret){printk(KERN_ERR "copy_to_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_to_user success..\n");return 0; }// 寫函數的本質就是將應用層傳遞過來的數據先復制到內核中,然后將之以正確的方式寫入硬件完成操作。 static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos) {int ret = -1;printk(KERN_INFO "test_chrdev_write\n");// 使用該函數將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中//memcpy(kbuf, ubuf); // 不行,因為2個不在一個地址空間中memset(kbuf, 0, sizeof(kbuf));ret = copy_from_user(kbuf, ubuf, count);if (ret){printk(KERN_ERR "copy_from_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_from_user success..\n");if (kbuf[0] == '1'){rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));}else if (kbuf[0] == '0'){rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));}return 0; }// 自定義一個file_operations結構體變量,并且去填充 static const struct file_operations test_fops = {.owner = THIS_MODULE, // 慣例,直接寫即可.open = test_chrdev_open, // 將來應用open打開這個設備時實際調用的.release = test_chrdev_release, // 就是這個.open對應的函數.write = test_chrdev_write,.read = test_chrdev_read, };// 模塊安裝函數 static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏調用的函數中去注冊字符設備驅動// major傳0進去表示要讓內核幫我們自動分配一個合適的空白的沒被使用的主設備號// 內核如果成功分配就會返回分配的主設備好;如果分配失敗會返回負數mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);return 0; }// 模塊卸載載函數 static void __exit chrdev_exit(void) {printk(KERN_INFO "chrdev_exit helloworld exit\n"); // 在module_exit宏調用的函數中去注銷字符設備驅動unregister_chrdev(mymajor, MYNAME);// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); }module_init(chrdev_init); module_exit(chrdev_exit);// MODULE_xxx這種宏作用是用來添加模塊描述信息 MODULE_LICENSE("GPL"); // 描述模塊的許可證 MODULE_AUTHOR("aston"); // 描述模塊的作者 MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息 MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
三、動態映射操作LED
1、如何建立動態映射
(1)request_mem_region
- 向內核申請(報告)需要映射的內存資源。見代碼。
(2)ioremap
- 真正用來實現映射,傳給它一個物理地址,它映射返回一個虛擬地址。
2、如何銷毀動態映射?(一定要銷毀)
(1)iounmap
(2)release_mem_region
(3)映射建立時,是要先申請再映射;然后使用;使用完要解除映射時要先解除映射再釋放申請。
3、代碼實踐
(1)實踐1:2個寄存器分開獨立映射;
(2)實踐2:2個寄存器在一起映射;(可以只映射其中的一個寄存器(一般是作為基地址的),然后用指針的算術操作來操作其他寄存器)
#include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit #include <linux/fs.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h #include <linux/string.h> #include <linux/io.h> #include <linux/ioport.h>#define MYNAME "testchar"#define GPJ0CON_PA 0xe0200240 //物理地址 #define GPJ0DAT_PA 0xe0200244unsigned int *pGPJ0CON; unsigned int *pGPJ0DAT;int mymajor;char kbuf[100]; // 內核空間的buf//省略部分代碼// 模塊安裝函數 static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏調用的函數中去注冊字符設備驅動// major傳0進去表示要讓內核幫我們自動分配一個合適的空白的沒被使用的主設備號// 內核如果成功分配就會返回分配的主設備好;如果分配失敗會返回負數mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);// 使用動態映射的方式來操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))//參數含義:起始地址(或者說寄存器的原始地址),寄存器的長度(一般32bit,因此4字節),這段空間的名字return -EINVAL;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))return -EINVAL;pGPJ0CON = ioremap(GPJ0CON_PA, 4);//參數含義和上一樣,返回一個指針pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);*pGPJ0CON = 0x11111111;*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮return 0; }// 模塊卸載函數 static void __exit chrdev_exit(void) {printk(KERN_INFO "chrdev_exit helloworld exit\n");//想要熄燈,必要要在解除映射的前面*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); // 解除映射iounmap(pGPJ0CON);iounmap(pGPJ0DAT);release_mem_region(GPJ0CON_PA, 4);release_mem_region(GPJ0DAT_PA, 4);// 在module_exit宏調用的函數中去注銷字符設備驅動unregister_chrdev(mymajor, MYNAME);}module_init(chrdev_init); module_exit(chrdev_exit);// MODULE_xxx這種宏作用是用來添加模塊描述信息 MODULE_LICENSE("GPL"); // 描述模塊的許可證 MODULE_AUTHOR("aston"); // 描述模塊的作者 MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息 MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息總結
以上是生活随笔為你收集整理的字符设备驱动基础篇5——驱动如何操控硬件(动静态映射操作LED)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 带你彻底弄明白!java简历模板下载
- 下一篇: 苹果iPhone一键解锁破解流程(新机篇