在開發驅動程式的時候,有時希望快速地實現其業務功能,以把精力放在檔案介面功能的測試上來。這樣,對於常見的一些繁瑣而又不能省略的步驟(如裝置號的申請、字元裝置的註冊、裝置節點檔案的建立等),就希望能儘量地簡化。這時,就可以借用Linux核心提供的misc(雜項)來實現。
這裡仍然使用“嵌入式Linux中的LED驅動控制”一文中的例子,實現三個LED的驅動,但改成使用misc來實現,其驅動程式程式碼如下,檔名為led.c。
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/miscdevice.h> //以下定義匯流排及暫存器的實體地址 #define AHB4_PERIPH_BASE (0x50000000) #define RCC_BASE (AHB4_PERIPH_BASE + 0x0000) #define RCC_MP_GPIOENA (RCC_BASE + 0xA28) #define GPIOA_BASE (AHB4_PERIPH_BASE + 0x2000) #define GPIOA_MODER (GPIOA_BASE + 0x0000) #define GPIOA_OTYPER (GPIOA_BASE + 0x0004) #define GPIOA_OSPEEDR (GPIOA_BASE + 0x0008) #define GPIOA_PUPDR (GPIOA_BASE + 0x000C) #define GPIOA_IDR (GPIOA_BASE + 0x0010) #define GPIOA_ODR (GPIOA_BASE + 0x0014) #define GPIOA_BSRR (GPIOA_BASE + 0x0018) #define GPIOG_BASE (AHB4_PERIPH_BASE + 0x8000) #define GPIOG_MODER (GPIOG_BASE + 0x0000) #define GPIOG_OTYPER (GPIOG_BASE + 0x0004) #define GPIOG_OSPEEDR (GPIOG_BASE + 0x0008) #define GPIOG_PUPDR (GPIOG_BASE + 0x000C) #define GPIOG_ODR (GPIOG_BASE + 0x0014) #define GPIOG_BSRR (GPIOG_BASE + 0x0018) #define GPIOB_BASE (AHB4_PERIPH_BASE + 0x3000) #define GPIOB_MODER (GPIOB_BASE + 0x0000) #define GPIOB_OTYPER (GPIOB_BASE + 0x0004) #define GPIOB_OSPEEDR (GPIOB_BASE + 0x0008) #define GPIOB_PUPDR (GPIOB_BASE + 0x000C) #define GPIOB_ODR (GPIOB_BASE + 0x0014) #define GPIOB_BSRR (GPIOB_BASE + 0x0018) //以下定義時鐘控制暫存器名稱 static void __iomem *RCC_MP_AHB4ENSETR; static void __iomem *GPIO_MODER_PA; static void __iomem *GPIO_OTYPER_PA; static void __iomem *GPIO_OSPEEDR_PA; static void __iomem *GPIO_PUPDR_PA; static void __iomem *GPIO_ODR_PA; static void __iomem *GPIO_BSRR_PA; static void __iomem *GPIO_MODER_PB; static void __iomem *GPIO_OTYPER_PB; static void __iomem *GPIO_OSPEEDR_PB; static void __iomem *GPIO_PUPDR_PB; static void __iomem *GPIO_ODR_PB; static void __iomem *GPIO_BSRR_PB; static void __iomem *GPIO_MODER_PG; static void __iomem *GPIO_OTYPER_PG; static void __iomem *GPIO_OSPEEDR_PG; static void __iomem *GPIO_PUPDR_PG; static void __iomem *GPIO_ODR_PG; static void __iomem *GPIO_BSRR_PG; //實現open函式,為file_oprations結構體成員函式 static int led_open(struct inode *inode, struct file *filp) { unsigned int tmp; //以下使能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0); iowrite32(tmp, RCC_MP_AHB4ENSETR); return 0; } //實現write函式,為file_oprations結構體成員函式 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { unsigned char value; unsigned long n; n = copy_from_user(&value, buf, cnt); //從應用空間獲取值 switch(value) //根據應用空間的值判斷具體操作 { case 0: //全部點亮三個LED iowrite32(0x20000000, GPIO_BSRR_PA); iowrite32(0x200000, GPIO_BSRR_PB); iowrite32(0x40000, GPIO_BSRR_PG); break; case 1: //點亮紅色LED iowrite32(0x20000000, GPIO_BSRR_PA); break; case 2: //點亮綠色LED iowrite32(0x40000, GPIO_BSRR_PG); break; case 3: //點亮藍色LED iowrite32(0x200000, GPIO_BSRR_PB); break; case 4: //熄滅紅色LED iowrite32(0x2000, GPIO_BSRR_PA); break; case 5: //熄滅綠色LED iowrite32(0x04, GPIO_BSRR_PB); break; case 6: //熄滅藍色LED iowrite32(0x20, GPIO_BSRR_PG); break; case 7: //全部熄滅三個LED iowrite32(0x2000, GPIO_BSRR_PA); iowrite32(0x20, GPIO_BSRR_PB); iowrite32(0x04, GPIO_BSRR_PG); break; default: //全部熄滅 iowrite32(0x2000, GPIO_BSRR_PA); iowrite32(0x20, GPIO_BSRR_PB); iowrite32(0x04, GPIO_BSRR_PG); break; } return 0; } //實現release函式,為file_oprations結構體函式 static int led_release(struct inode *inode, struct file *filp) { unsigned int tmp; //以下禁能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp &= ~0x43; iowrite32(tmp, RCC_MP_AHB4ENSETR); return 0; } //定義一個file_oprations型別的結構體,名為led_dev_fops,包含上述申明的成員函式 static struct file_operations led_dev_fops = { .owner = THIS_MODULE, .open = led_open, //指定open函式成員 .write = led_write, //指定write函式成員 .release = led_release, //指定release函式成員 }; //定義一個miscdevice型別的結構體,名為led static struct miscdevice led = { .minor = 0, //定義次裝置號 .name = "led", //定義裝置節點名稱 .fops = &led_dev_fops, //指定file_oprations介面 }; //初始化函式,此處為驅動模組的入口函式 static int __init led_init(void) { unsigned int tmp; //以下實現各個暫存器的地址對映 RCC_MP_AHB4ENSETR = ioremap(RCC_MP_GPIOENA, 4); GPIO_MODER_PA = ioremap(GPIOA_MODER, 4); GPIO_OTYPER_PA = ioremap(GPIOA_OTYPER, 4); GPIO_OSPEEDR_PA = ioremap(GPIOA_OSPEEDR, 4); GPIO_PUPDR_PA = ioremap(GPIOA_PUPDR, 4); GPIO_ODR_PA = ioremap(GPIOA_ODR, 4); GPIO_BSRR_PA = ioremap(GPIOA_BSRR, 4); GPIO_MODER_PB = ioremap(GPIOB_MODER, 4); GPIO_OTYPER_PB = ioremap(GPIOB_OTYPER, 4); GPIO_OSPEEDR_PB = ioremap(GPIOB_OSPEEDR, 4); GPIO_PUPDR_PB = ioremap(GPIOB_PUPDR, 4); GPIO_ODR_PB = ioremap(GPIOB_ODR, 4); GPIO_BSRR_PB = ioremap(GPIOB_BSRR, 4); GPIO_MODER_PG = ioremap(GPIOG_MODER, 4); GPIO_OTYPER_PG = ioremap(GPIOG_OTYPER, 4); GPIO_OSPEEDR_PG = ioremap(GPIOG_OSPEEDR, 4); GPIO_PUPDR_PG = ioremap(GPIOG_PUPDR, 4); GPIO_ODR_PG = ioremap(GPIOG_ODR, 4); GPIO_BSRR_PG = ioremap(GPIOG_BSRR, 4); //以下使能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0); iowrite32(tmp, RCC_MP_AHB4ENSETR); //以下把GPIOA、GPIOB、GPIOG埠配置為輸出、上位模式 tmp = ioread32(GPIO_MODER_PA); tmp &= ~(0x3 << 26); tmp |= (0x1 << 26); iowrite32(tmp, GPIO_MODER_PA); tmp = ioread32(GPIO_MODER_PB); tmp &= ~(0x3 << 10); tmp |= (0x1 << 10); iowrite32(tmp, GPIO_MODER_PB); tmp = ioread32(GPIO_MODER_PG); tmp &= ~(0x3 << 4); tmp |= (0x1 << 4); iowrite32(tmp, GPIO_MODER_PG); tmp = ioread32(GPIO_PUPDR_PA); tmp &= ~(0x3 << 26); tmp |= (0x1 << 26); iowrite32(tmp, GPIO_PUPDR_PA); tmp = ioread32(GPIO_PUPDR_PB); tmp &= ~(0x3 << 10); tmp |= (0x1 << 10); iowrite32(tmp, GPIO_PUPDR_PB); tmp = ioread32(GPIO_PUPDR_PG); tmp &= ~(0x3 << 4); tmp |= (0x1 << 4); iowrite32(tmp, GPIO_PUPDR_PG); //以下設定GPIOA、GPIOB、GPIOG埠初始值 iowrite32(0x2000, GPIO_BSRR_PA); iowrite32(0x20, GPIO_BSRR_PB); iowrite32(0x04, GPIO_BSRR_PG); //以下禁能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp &= ~0x43; iowrite32(tmp, RCC_MP_AHB4ENSETR); //註冊misc裝置 tmp = misc_register(&led); return tmp; } //退出函式,此處為驅動模組的出口函式 static void __exit led_exit(void) { //以下實現各個暫存器的解除對映 iounmap(RCC_MP_AHB4ENSETR); iounmap(GPIO_MODER_PA); iounmap(GPIO_OTYPER_PA); iounmap(GPIO_OSPEEDR_PA); iounmap(GPIO_PUPDR_PA); iounmap(GPIO_ODR_PA); iounmap(GPIO_BSRR_PA); iounmap(GPIO_MODER_PB); iounmap(GPIO_OTYPER_PB); iounmap(GPIO_OSPEEDR_PB); iounmap(GPIO_PUPDR_PB); iounmap(GPIO_ODR_PB); iounmap(GPIO_BSRR_PB); iounmap(GPIO_MODER_PG); iounmap(GPIO_OTYPER_PG); iounmap(GPIO_OSPEEDR_PG); iounmap(GPIO_PUPDR_PG); iounmap(GPIO_ODR_PG); iounmap(GPIO_BSRR_PG); //銷燬misc裝置 misc_deregister(&led); } module_init(led_init); //模組入口申明 module_exit(led_exit); //模組出口申明 MODULE_LICENSE("GPL"); //GPL協議申明
配套的Makefile檔案內容如下。
KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := led.o all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
Makefile檔案中的第一行指定了開發板的核心原始碼位置,若放到了其他路徑可自行修改。另外,該內容在複製貼上後,需要修改命令兩行(倒數第1、3行)前面的空餘部分,修改為Tab鍵開頭。
以下是測試用的應用程式程式碼,檔名為app.c。
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd; unsigned char val = 0; fd = open("/dev/led", O_RDWR); //開啟裝置節點 if( fd < 0 ) printf("can`t open\n"); if( argc != 3 ) //命令引數不對時提示 { printf("Usage :\n"); printf("%s <all|red|green|blue> <on|off>\n", argv[0]); return 0; } if(strcmp(argv[1], "all") == 0) { if(strcmp(argv[2], "on") == 0) val = 0; //值為0時全部點亮 else val = 7; //值為7時全部熄滅 } else if(strcmp(argv[1], "red") == 0) { if(strcmp(argv[2], "on") == 0) val = 1; //值為1時紅色點亮 else val = 4; //值為4時紅色熄滅 } else if(strcmp(argv[1], "green") == 0) { if(strcmp(argv[2], "on") == 0) val = 2; //值為2時綠色點亮 else val = 6; //值為6時綠色熄滅 } else if(strcmp(argv[1], "blue") == 0) { if(strcmp(argv[2], "on") == 0) val = 3; //值為3時藍色點亮 else val = 5; //值為5時藍色熄滅 } write(fd, &val, 1); //把值寫入裝置節點 close(fd); //關閉裝置節點 return 0; }
執行make命令編譯驅動程式,成功後會生成名為led.ko的驅動模組檔案。再對應用程式進行交叉編譯,執行“arm-linux-gnueabihf-gcc app.c -o app”即可。實驗的果與“嵌入式Linux中的LED驅動控制”一文中的完全一樣,這裡就不給出了。
把上面的驅動程式碼和“嵌入式Linux中的LED驅動控制”一文中的程式碼進行比較可以看出,本例的程式碼省略了動態申請裝置號、定義字元結構體cdev、建立類、建立裝置節點檔案等部分,只關注了具體的控制過程。為何這些內容可以被精簡?其時並沒有減,而是在Linux核心中,本來就有一個名為misc的裝置(雜項裝置),其主裝置號固定為10(可透過cat /proc/devices檢視)。本例程式碼其實就是“借用”了這個裝置名和主裝置號,所以就不用再申請和註冊了。misc裝置有一個自己的結構體名為struct miscdevice,在它內部可透過minor成員指定次裝置號(主裝置號已固定為10),透過name成員指定裝置節點檔案的名稱(裝置名稱已固定為misc),透過fops成員指定file_oprations結構體。一般只需要指定這三個成員變數就可以了,檔案操作的介面仍然透過file_oprations結構體來指定。在驅動模組的入口函式中,在進行暫存器對映和配置之後,呼叫了misc_register函式,對前面定義的miscdevice結構體(misc裝置)進行註冊。在出口函式中,在解除暫存器對映之後,呼叫了misc_deregister函式,對註冊的裝置進行銷燬。
本例程式碼中使用的應用程式和“嵌入式Linux中的LED驅動控制”一文中的完全一樣,因為驅動的裝置節點檔案沒變,對於驅動操作的介面(file_oprations)也沒變,所以可以延用該應用程式。
可見,透過misc雜項裝置的應用,可讓驅動開發者專注於具體功能的實現,而忽略一些形式化的東西。當然,在開發測試完成後,最好還是回到通用的方法上來,這樣比較規範和保險。