目錄:
1、驅動與應用程序的設計思想
2、字符設備驅動編寫步驟與規范
3、操作寄存器地址:readl / writel
4、實例:實現LED燈閃爍
1、驅動與應用程序的設計思想
用戶態:應用程序 玩策略: 怎么去做 1, 一閃一閃 2,10s閃一次,也可以1s閃一次 3,一直亮 4,跑馬燈 控制權是在應用程序(程序員) -------------------------------------- 內核態:驅動 玩機制: 能做什么 led:亮 和 滅
2、字符設備驅動編寫步驟與規范
1)步驟
1,實現模塊加載和卸載入口函數 module_init(chr_dev_init); module_exit(chr_dev_exit); 2,在模塊加載入口函數中 a,申請主設備號 (內核中用於區分和管理不同字符設備) register_chrdev(dev_major, "chr_dev_test", &my_fops); b,創建設備節點文件 (為用戶提供一個可操作到文件接口--open()) struct class *class_create(THIS_MODULE, "chr_cls"); struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2"); c, 硬件的初始化 1,地址的映射 gpx2conf = ioremap(GPX2_CON, GPX2_SIZE); 2,中斷到申請 3,實現硬件的寄存器的初始化 // 需要配置gpio功能為輸出 *gpx2conf &= ~(0xf<<28); *gpx2conf |= (0x1<<28); e,實現file_operations const struct file_operations my_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, };
2)規范
1,面向對象編程思想 用一個結構體來表示一個對象 //設計一個類型,描述一個設備的信息 struct led_desc{ unsigned int dev_major; //設備號 struct class *cls; struct device *dev; //創建設備文件 void *reg_virt_base; }; struct led_desc *led_dev;//表示一個全局的設備對象 // 0(在init中第一步做), 實例化全局的設備對象--分配空間 // GFP_KERNEL 如果當前內存不夠用到時候,該函數會一直阻塞(休眠) // #include <linux/slab.h> led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL) { printk(KERN_ERR "malloc error\n"); return -ENOMEM; } led_dev->dev_major = 250; 2,做出錯處理 在某個位置出錯了,要將之前申請到資源進行釋放 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0) { printk(KERN_ERR "register_chrdev error\n"); ret = -ENODEV; goto err_0; } err_0: kfree(led_dev); return ret;
引用:我們知道內核有些函數是返回指針的,如Kmalloc分配內存,如果分配不到內核就會返回NULL指針,這樣我們可以通過判斷是否是NULL指針來判斷Kmalloc執行成功與否。但是有些函數返回錯誤時,我們不僅需要知道函數錯了,還需要知道錯在哪里了,也就是說我們要或得錯誤碼。在用戶空間編程的時候,因為每個線程都有一個error變量,我們可以通過訪問這個變量來得到錯誤碼。但是在內核就沒有這個變量,所以不能這樣或得函數執行的錯誤碼。內核開發者就根據內核的地址空間特點用了一種新的方法來或得錯誤碼,那就是PTR_ERR,ERR_PTR,IS_ERR這三個宏
對錯誤處理的詳解見:解讀PTR_ERR,ERR_PTR,IS_ERR
3、操作寄存器地址的方式:readl / writel
1)傳統的方式
volatile unsigned long *gpxcon;
*gpxcon &= ~(0xf<<28);
2)內核提供的方式
readl/writel(); u32 readl(const volatile void __iomem *addr) //從地址中讀取地址空間的值 //都是讀取/寫入32位數據,即4字節
void writel(unsigned long value , const volatile void __iomem *addr) // 將value的值寫入到addr地址 例子1: // gpio的輸出功能的配置 u32 value = readl(led_dev->reg_virt_base); value &= ~(0xf<<28); value |= (0x1<<28); writel(value, led_dev->reg_virt_bas); 例子2: *gpx2dat |= (1<<7); 替換成: writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
實際上傳統方法和內核提供的方式並無差別,甚至內核的方式還有點繁雜,但是為了標准化與可移植性,推薦使用內核提供的方法來實現相應功能。
4、實例:實現led燈閃爍
1)led_drv.c

1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/fs.h> 4 #include <linux/device.h> 5 #include <asm/uaccess.h> 6 #include <asm/io.h> 7 #include <linux/slab.h> 8 9 //設計一個類型,描述一個設備的信息 10 struct led_desc{ 11 unsigned int dev_major; //設備號 12 struct class *cls; //創建設備文件的類 13 struct device *dev; //創建設備文件 14 void *reg_virt_base; //寄存器地址的基准值, void *型,地址偏移時+4 15 }; 16 17 //物理地址 18 #define GPX2CON 0x11000C40 19 #define GPX2_SIZE 8 20 21 22 struct led_desc *led_dev; //聲明全局的設備對象 23 24 static int kernal_val = 555; 25 26 // 用戶空間向內核空間傳數據 27 ssize_t led_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos) 28 { 29 30 int ret; 31 int value; //從用戶空間獲取到的數據存在value中 32 ret = copy_from_user(&value, buf, count); 33 if(ret > 0) 34 { 35 printk("copy_from_user"); 36 return -EFAULT; 37 } 38 39 if(value) //根據用戶傳遞的值決定燈的亮滅 40 { 41 writel((readl(led_dev->reg_virt_base+4) | (0x1<<7)), led_dev->reg_virt_base+4); 42 43 }else 44 { 45 writel((readl(led_dev->reg_virt_base+4) & ~(0x1<<7)), led_dev->reg_virt_base+4); 46 } 47 48 return 0; 49 } 50 51 // read(fd, buf, size) 用戶空間獲取內核空間的數據 52 ssize_t led_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fops) 53 { 54 printk("---------read %s-------------\n",__FUNCTION__); 55 56 int ret; 57 ret = copy_to_user(buf, &kernal_val, count); 58 if(ret > 0) 59 { 60 printk("copy_to_user"); 61 return -EFAULT; 62 } 63 64 return 0; 65 } 66 67 int led_drv_open (struct inode *inode, struct file *filp) 68 { 69 printk("---------open %s-------------\n",__FUNCTION__); 70 71 return 0; 72 } 73 74 int led_drv_close (struct inode *inode, struct file *filp) 75 { 76 printk("---------close %s-------------\n",__FUNCTION__); 77 78 return 0; 79 } 80 81 82 83 const struct file_operations my_fops = { 84 .open = led_drv_open, 85 .read = led_drv_read, 86 .write = led_drv_write, 87 .release = led_drv_close, 88 }; 89 90 static int __init led_dev_init(void) 91 { 92 93 int ret; 94 95 //1、實例化全局的設備對象 -- 分配空間 96 //GFP_KERNEL 若當前內存不夠用,該函數會一直阻塞 97 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); 98 if(led_dev == NULL) 99 { 100 printk(KERN_ERR "malloc error\n"); 101 return -ENOMEM; 102 } 103 104 //裝載一般都是申請設備號資源 105 //2、動態申請設備號 106 led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); 107 if(led_dev->dev_major < 0) 108 { 109 printk(KERN_ERR "register_led_dev error\n"); 110 ret = -ENODEV; 111 goto err_0; 112 //return -ENODEV; //如果直接返回的話,kmalloc分配的空間不會釋放,造成內存泄漏 113 } 114 115 //3、自動創建設備節點 116 led_dev->cls = class_create(THIS_MODULE, "led_cls"); 117 118 if(IS_ERR(led_dev->cls)) 119 { 120 printk(KERN_ERR"class create error\n"); 121 ret = PTR_ERR(led_dev->cls); //將指針出錯的具體原因轉換成一個出錯碼 122 goto err_1; //此步出錯不僅要釋放kmalloc空間,還要注銷注冊成功的設備號 123 } 124 125 led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major,0),NULL,"led%d",0); //后兩參數:->led0 126 127 if(IS_ERR(led_dev->dev)) 128 { 129 printk(KERN_ERR"device create error\n"); 130 ret = PTR_ERR(led_dev->dev); //將指針出錯的具體原因轉換成一個出錯碼 131 goto err_2; //此步出錯不僅要釋放kmalloc空間,還要注銷注冊成功的設備號 132 } 133 134 //4、硬件初始化 135 //對地址進行映射 136 led_dev->reg_virt_base = ioremap(GPX2CON, GPX2_SIZE); //映射兩個寄存器 137 if(led_dev->reg_virt_base == NULL) 138 { 139 printk(KERN_ERR"ioremap error\n"); 140 ret = -ENOMEM; 141 goto err_3; 142 } 143 144 //GPIO輸出功能的配置,使用標准寫法 145 u32 value = readl(led_dev->reg_virt_base); 146 value &= ~(0xf << 28); 147 value |= (0x1 << 28); 148 writel(value, led_dev->reg_virt_base); 149 150 return 0; 151 152 //出錯處理 153 err_3: 154 device_destroy(led_dev->cls, MKDEV(led_dev->dev_major,0)); 155 156 err_2: 157 class_destroy(led_dev->cls); 158 159 err_1: 160 unregister_chrdev(led_dev->dev_major, "led_dev_test"); 161 //err1放在err0前面,標簽會順序執行,err_1 -> err_0,進而釋放kmalloc的資源 162 163 err_0: 164 kfree(led_dev); 165 return ret; //釋放完空間后,再返回錯誤碼 166 167 } 168 169 static void __exit led_dev_exit(void) 170 { 171 //卸載一般都是釋放資源 172 iounmap(gpx2conf); 173 174 //銷毀節點和類 175 device_destroy(led_dev->cls,MKDEV(led_dev->dev_major,0)); 176 class_destroy(led_dev->cls); 177 //釋放設備號 178 unregister_chrdev(led_dev->dev_major, "led_dev_test"); 179 kfree(led_dev); 180 } 181 182 module_init(led_dev_init); 183 module_exit(led_dev_exit); 184 MODULE_LICENSE("GPL");
2) led_test.c

1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <fcntl.h> 8 9 10 int main(int argc, char * argv [ ]) 11 { 12 //調用驅動 13 int fd; 14 int value = 0; //buf 15 16 fd = open("/dev/led0", O_RDWR); //設備文件名在驅動中創建設備文件時指定 17 if(fd < 0) 18 { 19 perror("open"); 20 exit(1); 21 } 22 23 read(fd, &value, 4); //應用程序從內核獲取4字節數據存放在value中 24 printf("__USER__: value = %d\n", value); //打印獲取的值 25 sleep(1); //sleep1秒,防止串口打印信息不完整 26 27 //應用程序控制燈 28 while(1) 29 { 30 value = 0; 31 write(fd, &value, 4); 32 sleep(1); 33 34 value = 1; 35 write(fd, &value, 4); 36 sleep(1); 37 } 38 39 40 close(fd); 41 42 return 0; 43 }
3) Makefile

1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系統路徑 2 3 APP_NAME = chr_test 4 MODULE_NAME = chr_drv1 5 6 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi- 7 CC = $(CROSS_COMPILE)gcc 8 9 ifeq ($(KERNELRELEASE),) 10 11 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412 #編譯過的內核源碼的路徑 12 CUR_DIR = $(shell pwd) #當前路徑 13 14 all: 15 make -C $(KERNEL_DIR) M=$(CUR_DIR) modules #把當前路徑編成modules 16 $(CC) $(APP_NAME).c -o $(APP_NAME) 17 @#make -C 進入到內核路徑 18 @#M 指定當前路徑(模塊位置) 19 20 clean: 21 make -C $(KERNEL_DIR) M=$(CUR_DIR) clean 22 23 install: 24 sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把當前的所有.ko文件考到根文件系統的drv_module目錄 25 26 else 27 28 obj-m += MODULE_NAME #指定內核要把哪個文件編譯成ko 29 30 endif
實驗結果: