一、mmap设备操作
1>mmap系统调用
void* mmap(void* addr, size_t len, int prot, int flags, int fd,off_t offset)
内存映射函数mmap,负责把文件内容映射到进程的虚拟内存空间,通过这段内存的读取和修改,来实
现对文件的读取和修改,而不需要再调用read,write等操作。
参数:
addr:指定映射的起始地址(一般不指定,设为NULL由系统指定)
length:映射到内存的文件长度。
prot:映射区的保护方式,可以是:
PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROT_WRITE:映射区可被写入。
flags:映射区的特性,可以是:
MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。
MAP_PRIVATE:对映射区的写入擦做会产生一个映射区的复制(copy-on-write),对此区域
所做的修改不会写回原文件。
fd:由open返回的文件描述符,代表要映射的文件。
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
2>解除映射
int munmap(void* start, size_t length)
功能:
取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
返回值:
解除成功返回0,否则返回-1,错误原因存于errno中。
3>mmap系统调用的使用
/****************mmap.c*******************/
#include
#include
#include
#include
#include
#include
#include
#include
#define MEMDEV_SIZE 4096
int main(void)
{
int fd, ret;
char buf[MEMDEV_SIZE];
char *pmdev;
if (-1==(fd=open ("/dev/memdev0", O_RDWR))) {
printf("open dev0 error\n");
_exit(EXIT_FAILURE);
}
printf("\nTest for mmap(): ...\n");
//sleep(30);
pmdev = (char*)mmap(NULL, MEMDEV_SIZE, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
printf("mmap() return: pmdev=%p\n", pmdev);
sleep(20);
printf("begin to use pmdev ...\n");
bzero(pmdev, MEMDEV_SIZE);
strcpy(buf, "Test for mmap() -- write by write()");
printf("[use write()] write to mdev: %s\n", buf);
ret = write(fd, buf, strlen(buf));
printf("[use pmdev] content in mdev: %s\n", pmdev);
bzero(buf, MEMDEV_SIZE);
strcpy(buf, "write through pmdev, read by read()");
printf("[use pmdev] store to mdev(offset=%d): %s\n", ret, buf);
strcat(pmdev, buf);
ret = read(fd, buf, strlen(buf));
buf[ret] = 0;
printf("[use read()] read from mdev: %s\n", buf);
//sleep(30);
_exit(EXIT_SUCCESS);
}
4>虚拟内存区域
1、虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。
一个进程的内存映像由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。一
个进程的内存区域可以通过查看/proc/pid/maps.
每一行的域为:
start end perm offset major:minor inode
起始虚拟地址 结束虚拟地址 读写执行权限 被映射部分在文件中的起始地址 主次设备号 索引结点
2、Linux内核使用结构vm_area_struct()来描述虚拟内存区域,其中主要成
员如下:unsigned long vm_start:虚拟内存区域起始地址 unsigned long vm_end :虚拟内存区域
结束地址 unsigned long vm_flags:该区域的标记。如VM_IO和VM_RESERVED。VM_IO将该VMA标记为内
存映射的IO区域,VM_IO会阻止系统将将该区域包含在进程的存放转存(core dump)中,
VM_RESERVED标志内存区域不能被换出。
5>mmap设备操作
映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址
时,它实际上是在访问设备。mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调
用。在此之前,内核已经完成了很多工作,mmap设备方法所需要做的就是建立虚拟地址到物理地址的
页表。int(*mmap)(struct file*,struct vm_area_struct*).
mmap建立页表有两种方法:
1、使用remap_pfn_range一次建立所有页表;
2、使用nopage VMA方法每次建立一个页表。
构造页表的函数原型:
int remap_pfn_range(struct vm_area_struct* vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t prot)
参数:
vma:虚拟内存区域指针
vir_addr:虚拟地址的起始值
pfn:要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到。
size:要映射的区域大小
prot:VMA的保护属性。
实现实例:
int memdev_mmap(struct file* filp,struct vm_area_struct* vma)
{
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED ;
if(remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT,size,vma->vm_page_prot))
return -EAGAIN;
return 0;
}
二、硬件访问
1>寄存器和内存
寄存器和RAM的主要不同在于寄存器操作有副作用(side effect或边际效果):读取某个地址时
可能导致该地址内容发生变化,比如很多设备的中断状态寄存器只要一读取,便自动清零。
2>内存与I/O
在X86处理器中存在I/O空间的概念,I/O空间是相对内存空间而言的,他们是彼此独立的地址空
间,在32位的X86系统中,I/O空间大小为64K,内存空间大小为4G。X86支持内存空间、I/O空间,
ARM、MIPS、PowerPC只支持内存空间。
3>IO端口和IO内存
IO端口:当一个寄存器或内存位于IO空间时,称其为IO端口。
IO内存:当一个寄存器或内存位于内存空间时,称其为IO内存。
4>操作I/O端口
对I/O端口的操作需按如下步骤完成:1.申请、2.访问、3.释放。
1、内核提供了一套函数来驱动申请它需要的I/O端口,其中核心函数是:
struct resource* request_region(unsigned long first,unsigned long n,const char* name)
这个函数告诉内核,你要使用从first开始的n个端口,name参数是设备的名字,如果申请成功,返回
是非NULL,申请失败,返回NULL。系统中端口的分配情况记录在/proc/ioports中展示。如果不能分
配需要的端口,可以来这里查看谁在使用。
2、I/O端口可分为8位,16位,32位端口。Linux内核头文件(体系依赖的头文件
)定义了下列内联函数来访问I/O端口:
unsigned inb(unsigned port) 读字节端口(8位宽)。
void outb(unsigned char byte,unsigned port) 写字节端口(8位)。
unsigned inw(unsigned port)
void outw(unsigned short word,unsigned port) 存取16-位端口。
unsigned inl(unsigned port)
void outl(unsigned longword,unsigned port) 存取32-位端口。
3、当用完一组I/O端口(通常在驱动卸载时),应使用如下函数把他们返还系统:
void release_region(unsigned long start, unsigned long n)
5>操作I/O内存
1、申请:内核提供了一套函数来允许驱动申请它需要的I/O内存,其中核心的函数是:
struct resource* request_mem_region(unsigned long start,unsigned long len , char* name)
这个函数申请一个从start开始,长度为len字节的内存区。如果成功,返回非NULL;否则返回NULL,
所有已经在使用的I/O内存在/proc/iomem中列出。
2、映射:在访问I/O内存之前,必须进行物理地址到虚拟地址的映射,ioremap函数具有此功能:
void* ioremap(unsigned long phys_addr,unsigned long size)
3、访问:访问I/O内存的正确方法是通过一系列内核提供的函数:
读I/O内存读,使用下列之一:
unsigned ioread8(void* addr)
unsigned ioread16(void* addr)
unsigned ioread32(void* addr)
写I/O内存,使用下列之一:
void iowrite8(u8 value, void* addr)
void iowrite16(u16 value, void* addr)
void iowrite32(u32 value, void* addr)
4:释放:I/O内存不在需要使用时应当释放,步骤如下:
1、void iounmap(void* addr):解除映射
2、void release_mem_region(unsigned long start,unsigned long len)
三、混杂设备驱动
1>定义:在Linux系统中,存在一类字符设备,它们共享一个主设备号(10),但次设备号不同,我
们称这类设备为混杂设备(miscdevice)。所有的混杂设备形成一个链表,对设备访问时内核根据次
设备号查找到相应的miscdevice设备。
2>设备描述
Linux内核使用struct miscdevice来描述一个混杂设备。
struct miscdevice{
int minor;/*次设备号*/
const char* name;/*设备名*/
const struct file_operations* fops;/*文件操作*/
struct list_head list;
struct device* parent;
struct device* this_device;
};
3>设备注册
Linux内核使用misc_register函数来注册一个混杂设备驱动。
int misc_register(struct miscdevice* misc)
4>设备资源
设备资源使用struct resource来描述
struct resource{
resource_size_t start; //资源的起始物理地址
resource_size_t end; //资源的结束物理地址
const char* name;//资源的名称
unsigned long flags;//资源的类型,比如MEM。IO,IRQ类型
struct resource *parent,*sibling,*child;//资源链表指针
}
四、LED驱动程序设计
1>上拉/下拉电阻
上拉是将不确定的信号通过一个电阻与电源相连,固定在高电平。下拉是将不确定的信号通过一
个电阻与地相连,固定在低电平。上拉是对器件注入电流,下拉是输出电流。当一个接有上拉电阻的
I/O端口设为输入状态时,它的常态为高电平,可用于检测低电平的输入。
-
#ifndef __LED_H__
-
#define __LED_H__
-
-
#include <linux/ioctl.h>
-
-
#define DEVICE_NAME "up6410_led"
-
-
/*幻数*/
-
#define led_IOC_MAGIC '1'
-
-
/*led命令*/
-
-
#define LED_IOCGETDAT _IOR(led_IOC_MAGIC,1,int)
-
#define LED_IOCSETDAT _IOW(led_IOC_MAGIC,2,int)
-
-
#define LED_IOC_MAXNR 2
-
-
#endif
-
#include <linux/miscdevice.h>
-
#include <linux/kernel.h>
-
#include <linux/module.h>
-
#include <linux/init.h>
-
#include <linux/fs.h>
-
#include <linux/types.h>
-
#include <linux/errno.h>
-
#include <linux/ioctl.h>
-
#include <linux/cdev.h>
-
#include <linux/device.h>
-
#include <liux/ioport.h>
-
#include <linux/io.h>
-
#include <linuxe/uaccess.h>
-
-
#include "led.h"
-
-
unsigned long GPIOM_VA_BASE;
-
-
#define GPIOM_CON_VA GPIO_VA_BASE
-
#define GPIOM_DAT_VA GPIO_VA_BASE + 0x4
-
#define GPIOM_PUD_VA GPIO_VA_BASE + 0x8
-
-
#define GPIO_VA_BASE 0x7f008820
-
-
-
struct resource ok6410_led_resource = {
-
.name = "led io_mem",
-
.strat = GPIO_PA_BASE,
-
.end = GPIO_PA_BASE + 0xc,
-
.flags = IORESOURCE_MEM,
-
};
-
static void ok6410_led_pin_setup(void)
-
{
-
unsigned long start = ok6410_led_resource.start;
-
unsigned long size = ok6410_led_resource.end - start;
-
unsigned long tmp;
-
-
/*申请IO内存*/
-
request_mem_region(start, size,ok6410_led_resource,name);
-
-
/*映射IO内存*/
-
GPIOM_VA_BASE = (unsigned long)ioremap(start, size);
-
-
printk("<1>[GPIOM_VA_BASE = 0x%lx]\n",GPIO_VA_BASE);
-
-
/*访问*/
-
tmp = readl(GPIOM_CON_VA);
-
tmp = (tmp & ~(0xffffU))|(0x1111U);//设置IO端口为输出功能
-
writel(tmp,GPIO_CON_VA);
-
-
tmp = readl(GPIOM_DAT_VA);
-
tmp |= 0xf;
-
writel(tmp, GPIOM_DAT_VA);
-
}
-
static unsigned long ok6410_led_getdat()
-
{
-
return (readl(GPIOM_DAT_VA)&0xF);
-
}
-
-
static void ok6410_led_setdat(int dat)
-
{
-
unsigned long tmp;
-
-
tmp = readl(GPIOM_DAT_VA);
-
tmp = (tmp & ~0xF) | (dat&0xF);
-
writel(tmp, GPIOM_DAT_VA);
-
}
-
static long led_ioctl(struct file* filp,unsigned long cmd,unsigned long arg)
-
{
-
int ioarg, ret;
-
-
if(_IOC_TYPE(cmd) != LED_IOC_MAGIC)
-
return -EINVAL;
-
if(_IOC_NR(cmd) > LED_IOC_MAXNR)
-
return -EINVAL;
-
-
switch(cmd){
-
case LED_IOCGETDAT:
-
ioarg = ok6410_led_getdat();
-
ret = put_user(ioarg,(int*)arg);
-
break;
-
case LED_IOCSETDAT:
-
ret = get_user(ioarg,(int*)arg);
-
ok6410_led_setdat(ioarg);
-
break;
-
default:
-
return -EINVAL;
-
-
}
-
-
return ret;
-
}
-
-
static void ok6410_led_pin_release(void)
-
{
-
/*解除映射*/
-
iounmap((void*)GPIOM_VA_BASE);
-
release_mem_region(ok6410_led_resource.start,ok6410_led_resource.end - ok6410_led_resource.start);
-
-
}
-
-
staitc struct file_operations dev_fops = {
-
.owner = THIS_MODULE,
-
.unlocked_ioctl = led_ioctl,
-
}
-
-
struct miscdevice misc = {
-
.minor = MISC_DYNAMIC_MINOR,
-
.name = DEVICE_NAME,
-
.fops = &dev_fops,
-
}
-
-
static int __init dev_init(void)
-
{
-
int ret;
-
-
ok_6410_led_pin_setup();//设置引脚功能
-
-
ret = misc_register(&misc);//混杂设备注册
-
-
return ret;
-
-
}
-
-
static void __exit dec_exit(void)
-
{
-
ok6410_led_pin_release();//释放引脚
-
misc_deregister(&misc);//注销设备
-
}
-
-
module_init(dev_init);
-
module_exit(dev_exit);
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("XIAHAIJUN");