1. 程式人生 > >字符設備驅動另一種寫法—mmap方法操作LED

字符設備驅動另一種寫法—mmap方法操作LED

一個 控制寄存器 abs 提交數據 函數參數 功能 控制 讀取 調用方法

最近在看韋老師的視頻,講解了很多種字符設備的驅動寫法。經過自己的研究之後,我發現還有另外一種寫法,直接在應用層操作,省去了內核中的地址映射部分,使得用戶可以在應用層直接操作LED。

mmap方法是把設備物理地址直接映射到用戶空間的一種系統調用方法,他使得用戶可以在應用層直接操作硬件設備,而不必在驅動裏使用ioremap做地址映射。這在一定程度上實現了傳說中的“零拷貝”技術。即,設備的數據不用經過內核的轉存,再向應用層提交數據。由於設備物理地址直接映射到了用戶空間,所以就相當於省去了內核的中間媒介,用戶空間直接去操作硬件設備。
技術分享圖片
總結一下,mmap方法的用處是把設備(文件)內容直接映射到進程虛擬空間,通過對這個虛擬地址的讀寫修改,實現對設備(文件)的讀寫和修改,從而不必使用read、write等系統調用即可實現對設備的操作。

mmap的內核態函數為:
int (*mmap)(struct file *filp,struct vm_area_struct *vma)
結構體struct vm_area_struct *vma是我們在使用mmap系統調用的時候內核幫我們找到的虛擬地址區間,它的主要成員是:
vma->vm_start: 映射後的用戶態虛擬地址起始地址;
vma->vm_end: 映射後的用戶態虛擬地址結束地址;
vma->vm_pgoff: 物理地址所在的頁幀號,它的值由用戶空間傳進來的物理地址右移PAGE_SHIFT位得到,PAGE_SHIFT值為12,那麽它右移12位就得到物理地址的頁幀號(一頁大小為4KB)。

下面是我寫的內核驅動程序,在TQ2440開發板上運行:

#include linux/kernel.h

#include linux/init.h

#include linux/module.h

#include linux/fs.h

#include linux/cdev.h

#include linux/mm.h

#include linux/device.h

#define DEV_NAME mmapled

struct mmapled

{

dev_t devno;

struct cdev mcdev;

struct class *mmap_class;

};

struct mmapled *mpt;

int mmapled_open(struct inode *inode, struct file *filp)

{

printk(In kernel open,major =%d,minor = %d\n,MAJOR(mpt->devno),MINOR(mpt->devno));

return 0;

}

int mmapled_close(struct inode *inode,struct file *filp)

{

return 0;

}

/* mmap系統調用函數 */

int mmapled_mmap(struct file *filp,structvm_area_struct *vma)

{

int ret;

vma->vm_flags |= VM_RESERVED;

vma->vm_flags |= VM_IO;

vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

/* vma->vm_pgoff為用戶層off, PAGE_SHIFT,即物理地址的頁幀號,映射大小必為PAGE_SIZE整數倍 */

ret =remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end -vma->vm_start,vma->vm_page_prot);

if(ret)

{

printk(remap_pfn_range err!\n);

return -EAGAIN;

}

printk(In %s,pgoff = %lx, start= %lx,end = %lx\n,__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);

return 0;

}

/* 文件操作結構體 */

struct file_operations mmapled_fops =

{

.owner =THIS_MODULE,

.open =mmapled_open,

.release = mmapled_close,

.mmap = mmapled_mmap,

};

/* 驅動程序入口函數 */

int mmapled_init(void)

{

int ret;

mpt = kzalloc(sizeof(struct mmapled),GFP_KERNEL);

if(!mpt)

{

printk(kzalloc mpt err!\n);

return -ENOMEM;

}

/* 動態分配主設備號,起始次設備號為0 */

ret = alloc_chrdev_region(mpt->devno,0,1,DEV_NAME);

if(ret)

{

printk(register chrdev err!\n);

kfree(mpt);

return ret;

}

/* 創建類,用於自動創建設備節點 */

mpt->mmap_class=class_create(THIS_MODULE, DEV_NAME);

if(IS_ERR(mpt->mmap_class))

{

printk(KERN_ALERTabsmem_class create failed.\n);

kfree(mpt);

unregister_chrdev_region(mpt->devno,1);

return -1;

}

cdev_init(mpt-mcdev, mmapled_fops);

mpt->mcdev.owner=THIS_MODULE;

cdev_add(mpt->mcdev,mpt->devno,1);

/* 創建設備節點mmapled0 */

device_create(mpt->mmap_class,NULL,mpt->devno,NULL,mmapled%d,0);

return 0;

}

/* 驅動程序出口函數 */

void mmapled_exit(void)

{

device_destroy(mpt->mmap_class,mpt->devno);

cdev_del(mpt->mcdev);

class_destroy(mpt->mmap_class);

unregister_chrdev_region(mpt->devno,1);

kfree(mpt);

}

module_init(mmapled_init);

module_exit(mmapled_exit);

MODULE_LICENSE(GPL);


用戶態的mmap函數接口為:
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
函數參數的意義如下:
addr:指定映射的起始地址,即進程虛擬空間的虛擬地址,人為的指定;通常設置為NULL,讓內核幫我們去指定;
len: 要映射的區間大小;
prot: 映射區的保護方式,可以取以下值:
PROC_EXEC:映射區可被執行;
PROC_READ:映射區可被讀取;
PROC_WRITE:映射區可寫;
PROC_NONE:映射區不能存取。
flags是映射區的特性,可以取以下值:
MAP_SHARED:寫入映射區的數據會復制回文件,且允許其他映射該文件的進程共享;
MAP_PRIVATE:對映射區的寫入會產生一個映射區的復制(COPY_ON_WRITE),對此映射區的修改不會寫入源文件;
fd:由open函數返回的文件描述符;
offset:文件開始處的偏移量,必須是分頁大小的整數倍。
函數返回值:映射得到的用戶虛擬地址;

下面是我寫的用戶態的程序,供大家參考(在TQ2440開發板上運行,如果是其他開發板,可以參考原理圖做一些修改):

/* 函數功能:實現4個LED燈的同時亮滅,間隔為1秒 */

#include stdio.h

#include string.h

#include fcntl.h

#include sys/mman.h

#define DEV_NAME /dev/mmapled0

int main()

{

int fd,k;

void *start, *reg=NULL;

fd = open(DEV_NAME,O_RDWR);

if(fd&0)

{

printf(Open device err!\n);

return -1;

}

/*參數解釋:

* NULL:映射到的內核虛擬地址,設置為NULL由內核決定

* 1024*4:映射大小,最小一頁,必為頁大小的整數倍

* 映射區的權限

* 對映射區的修改改變映射區的內容

* fd:open返回的文件描述符

* 物理地址,一個頁的起始物理地址,它PAGE_SHIFT之後傳給驅動的vma結構體的vm_pgoff

*/

/*0x56000000是LED等所在的GPIO口的BANK起始物理地址*/

/*start是得到的虛擬地址*/

start =mmap(NULL,1024*4,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x56000000);

if(start == NULL)

{

printf(mmap err!\n);

return -1;

}

reg = start + 0x10; // GPBCON,控制寄存器

*(unsigned*)reg = 0xfffc03ff; // [17:10]清零

*(unsigned*)reg |= 0x00015400; // [17:10]=01010101,輸出功能

reg = start + 0x14; // GPBDAT

/* 量滅k次,實現對LED的操作 */

k=25;

while(k--)

{

*(unsigned*)reg & = ~(0x1e0); // [8:5], set 0,led on

sleep(1);

*(unsigned*)reg |= 0x1e0; // [8:5], set 1,led off

sleep(1);

printf(k= %d\n,k);

}

/* 取消映射 */

munmap(start,1024*4);

close(fd);

return 0;

}

完。

稍微修改代碼即可在jz2440上運行,需要代碼的學員請留下郵箱,我們發給您。

字符設備驅動另一種寫法—mmap方法操作LED