網(wǎng)站首頁輪播圖怎么換百度開放平臺登錄
目錄
驅動模塊的加載和卸載
?驅動程序Makefile編寫
?字符設備注冊與注銷
字符設備驅動模板
應用程序對驅動讀寫操作
iounmap函數(shù)
LED寄存器物理地址映射到虛擬地址
應用程序代碼編寫
Linux驅動的兩種運行方式:
1、將驅動編譯進Linux內核中,也就是zImage,當內核啟動的說話就會自動運行驅動程序;
2、將驅動編譯成模塊(Linux下模塊擴展名為.ko),在Linux內核啟動以后要用insmod命令加載驅動模塊,用rmmod命令卸載驅動模塊
驅動模塊的加載和卸載
模塊的加載和卸載的注冊函數(shù)如下:
module_init(xxx_init); //注冊模塊加載函數(shù)
module_exit(xxx_exit); //注冊模塊卸載函數(shù)
?編譯驅動的時候需要用到linux內核源碼,因此要解壓縮linux內核源碼,然后再編譯,得到zImage和.dtb。需要使用編譯后的zImage和dtb啟動系統(tǒng)。
?驅動程序Makefile編寫
KERNELDIR := /home/zzs/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)obj-m := chrdevbase.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
驅動編譯完成以后擴展名為.ko,兩種命令可以加載驅動模塊:insmod和modprobe
?在啟動Linux內核后,輸入如下命令加載驅動:
insmod drv.ko
modprobe命令相比于insmod命令,區(qū)別在于modprobe可以解決依賴關系的問題,insmod命令如果要驅動依賴于first.ko模塊的模塊,就有一個先加載后加載的問題,而modprobe就比較智能一些,會對模塊進行依賴關系的分析,然后就所有的依賴模塊都加載到內核中。
?我們將編譯生成的.ko模塊拷貝到rootfs中,通過nfs傳輸?shù)介_發(fā)板中進行使用。
?使用modprobe命令出現(xiàn)下面的錯誤:
原因是沒有加載modules.dep,通過depmod命令進行加載,如果無法使用depmod,要通過busybox重新配置。
顯示當前存在的模塊命令如下:
lsmod
驅動模塊的卸載使用rmmod命令即可,因為另一種卸載驅動模塊的命令modprobe -r的使用前提是所卸載模塊的依賴模塊已經(jīng)沒有被其他模塊使用,否則就不能使用該命令卸載驅動模塊:
rmmod drv.ko
使用printk打印日志信息驗證模塊的加載和卸載:
?
?字符設備注冊與注銷
字符設備的注冊函數(shù)
static inline register chrdev(unsigned int major,const char *name,const struct file_operations *fops)
?字符設備的注銷函數(shù)
static inline void unregister_chrdev(unsigned int major,const char *name)
major:主設備號,Linux下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分
name:設備名字
fops:結構體file_operations類型指針,指向設備的操作函數(shù)集合變量
設備號由主設備號和次設備號組成,主設備號表示一個具體的驅動,次設備號表示使用這個驅動的各個設備。
設備號的類型為dev_t,是一個定義為u32的數(shù)據(jù)類型,也就是unsigned int,其中,高12位是主設備號,低20位是次設備號,所有主設備號的范圍是0~4095,選擇主設備號時不要超出這個范圍。
查看當前系統(tǒng)所有已經(jīng)使用了的設備號:
cat /proc/devices
一般字符設備的注冊在驅動模塊的入口函數(shù)xxx_init中進行,字符設備的注銷在驅動模塊的出口函數(shù)xxx_exit中進行。
字符設備驅動模板
/*打開設備*/
static int chrtest_open(struct inode *inode,struct file *filp)
{/*用戶實現(xiàn)具體功能*/return 0;
}/*從設備讀取*/
static ssize_t chrtest_read(struct file *filp,char __user *buf,size_t cnt,loft_t *offt)
{/*用戶實現(xiàn)具體功能*/return 0;
}/*向設備寫數(shù)據(jù)*/
static ssize_t chrtest_write(struct file *filp,const char __user *buf,size_t cnt,loft_t *offt)
{/*用戶實現(xiàn)具體功能*/return 0;
}/*關閉/釋放設備*/
static int chrtest_release(struct inode *inode,struct file *filp)
{/*用戶實現(xiàn)具體功能*/return 0;
}static struct file_operations test_fops={.owner = THIS_MODULE,.open = chrtest_open,.read = chrtest_read,.write = chrtest_write,.release = chrtest_release,
} ;/*驅動入口函數(shù)*/
static int __init xxx_init(void)
{/*入口函數(shù)具體內容*/int retvalue = 0;/*注冊字符設備驅動*/retvalue = register_chrdev(200,"chrtest",&test_fops);if(retvalue<0){/*字符設備注冊失敗,自行處理*/}return 0;
}/*驅動出口函數(shù)*/
static void __exit xxx_exit(void)
{/*注銷字符設備驅動*/unregister_chrdev(200,"chrtest");
}/*將上面兩個函數(shù)指定為驅動的入口和出口函數(shù)*/
module_init(xxx_init);
module_exit(xxx_exit);/*添加LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZHANGZHONGSHENG");
應用程序對驅動讀寫操作
?驅動給應用傳遞數(shù)據(jù)的時候用到copy_to_user函數(shù)(該函數(shù)用來完成內核空間的數(shù)據(jù)到用戶空間的復制),函數(shù)原型如下:
static inline long copy_to_user(void *to,const void *from,unsigned long n);
參數(shù)to表示目的,參數(shù)from表示源,參數(shù)n表示要復制的數(shù)據(jù)長度,如果復制成功,返回值為0,如果復制失敗返回負數(shù)。
?因為用戶空間的內存不能直接訪問內核空間內存,所以使用copy_from_user函數(shù)來實現(xiàn),函數(shù)原型如下:
static inline long copy_from_user(void *to,const void *from,unsigned long n);
字符串轉換為整型數(shù)據(jù):
首先添加<stdlib>庫,然后使用atoi函數(shù)。
MMU
全稱是Memory Manage Unit,內存管理單元,其主要功能如下:
1、完成虛擬空間到物理空間的映射;
2、內存保護,設置存儲器的訪問權限,設置虛擬存儲空間的緩沖特性
裸機的時候可以直接對0x01010101這個物理地址進行操作,但是linux不行,因為linux會使能mmu
在Linux里面操作的都是虛擬地址,所以需要得到0x01010101這個物理地址的虛擬地址。
ioremap函數(shù)
如果我們沒有使能MMU,可以直接向寄存器地址讀寫數(shù)據(jù),但是我們現(xiàn)在啟動Linux內核后,會自動使能MMU,此時我們需要將這個寄存器的物理地址轉換為虛擬地址,涉及到兩個函數(shù),如下:
ioremap函數(shù)用于獲取指定物理地址空間對應的虛擬地址空間,定義在arch/arm/include/asm/io.h中,本質是一個宏
第一個參數(shù)是物理地址起始大小,第二個參數(shù)是要轉換的字節(jié)數(shù)量,例如0x01010101開始的10個地址進行轉換:
va = ioremap(0x01010101,10)
返回值是轉換的起始地
iounmap函數(shù)
卸載驅動時使用iounmap函數(shù)釋放掉ioremap函數(shù)所做的映射
iounmap(va);
寄存器物理地址
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
LED寄存器物理地址映射到虛擬地址
(1)地址映射后的虛擬地址指針
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
(2)物理地址映射成虛擬地址供Linux使用(在驅動入口函數(shù)中)
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
(3)取消地址映射(在驅動出口函數(shù)中)
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
(4)對虛擬地址進行初始化配置
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /*先清除以前的配置bit26,27*/
val |= 3 << 26; /*bit26,27置1*/
writel(val,IMX6U_CCM_CCGR1);writel(0x5,SW_MUX_GPIO1_IO03); //設置復用
writel(0X10B0,SW_PAD_GPIO1_IO03); //設置電氣屬性val = readl(GPIO1_GDIR);
val |= 1 << 3; //bit3置1,設置為輸出
writel(val,GPIO1_GDIR); val = readl(GPIO1_DR);
val &= ~(1 << 3); //bit3清零,打開LED
writel(val,GPIO1_DR);
應用程序代碼編寫
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/**argc:應用程序參數(shù)個數(shù)*argv:具體的參數(shù)內容,字符串形式*./ledAPP <filename> <0:1> 0表示關燈 1表示開燈*./ledAPP /dev/led 0 關燈*./ledAPP /dev/led 1 開燈 */#define LEDOFF 0
#define LEDON 1int main(int argc,char *argv[])
{int fd,retvalue; //fd是文件描述符char *filename; //filename是設備名稱unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename,O_RDWR); //O_RDWR是用讀寫模式if(fd < 0){printf("file %s open failed!\r\n",filename);return -1;}databuf[0] = atoi(argv[2]); //將字符數(shù)據(jù)轉換為數(shù)字retvalue = write(fd,databuf,sizeof(databuf)); //對設備進行寫操作if(retvalue < 0){printf("LED control Failed!\r\n");close(fd);return -1;}close(fd);return 0;
}
創(chuàng)建設備結點命令:
mknod /dev/led c 200 0
/dev/led是設備名稱,是傳遞給應用程序的第二個參數(shù),c表示字符設備,200是主設備號,0表示次設備號