linux驱动学习(2)——访问硬件设备

(一)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: 映射区的特性, 可以是:
    1. MAP_SHARED:写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
    2. MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件
  • fd:由open返回的文件描述符, 代表要映射的文件。
  • offset:以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。

解除映射:

int munmap(void *start,size_t length)

功能:

   取消参数start所指向的映射内存,参数length表示欲取消的内存大小。

返回值: 

  解除成功返回0,否则返回-1,错误原因存于errno中。

虚拟内存区域:

  虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。一个进程的内存映象由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。

一个进程的内存区域可以通过查看 /proc/pid/maps
每一行的域为:start_end perm offset major:minor inode

    • Start: 该区域起始虚拟地址
    • End: 该区域结束虚拟地址
    • Perm: 读、写和执行权限;表示对这个区域,允许进程做什么。这个域的最后一个字符要么是p表示私有的,要么是s表示共享的。
    • Offset: 被映射部分在文件中的起始地址
    • Major、minor:主次设备号
    • Inode:索引结点

Linux内核使用结构vm_area_struct(<linux/mm_types.h>)来描述虚拟内存区域,其中几个主要成员如下:

    • 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标志内存区域不能被换出

mmap设备操作:(重要)

映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址时,它实际上是在访问设备。
mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表
int (*mmap) (struct file *, struct vm_area_struct *)
mmap如何完成页表的建立?

    1. 使用remap_pfn_range一次建立所有页表;
    2. 使用nopage VMA方法每次建立一个页表;

构造页表的工作可由remap_pfn_range函数完成,原型如下:

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)

    • vma: 虚拟内存区域指针
    • virt_addr: 虚拟地址的起始值
    • pfn: 要映射的物理地址所在的物理页帧号,可将物理地址
    • >>PAGE_SHIFT得到。
    • size:要映射的区域的大小。
    • prot: VMA的保护属性。
 1 int memdev_mmap(struct file*filp, struct vm_area_struct *vma)
 2 {
 3     Vma->vm_flags |= VM_IO;
 4     Vma->vm_flags |= VM_RESERVED;
 5     if (remap_pfn_range(vma, vma->vm_start,virt_to_phys(dev-     >data)>> PAGE_SHIFT,size,vma->vm_page_prot))
 6     {
 7     return -EAGAIN;
 8     }
 9     return 0;
10 }
View Code

(二)寄存器与内存

寄存器与内存的区别在哪里呢?

  • 寄存器和 RAM 的主要不同在于寄存器操作有副作用(side effect 或 边际效果):
  • 读取某个地址时可能导致该地址内容发生变化,比如很多设备的中断状态寄存器只要一读取,便自动清零。

内存与I/O:

在X86处理器中存在I/O空间的概念,I/O 空间是相对内存空间而言的,他们是彼此独立的地址空间,在32位的x86系统中,I/O空间大小为64K,内存空间大小为4G。

    • X86  支持内存空间、IO空间
    • ARM  只支持内存空间
    • MIPS 只支持内存空间
    • PowerPC 只支持内存空间
    • IO端口: 当一个寄存器或内存位于IO空间时,称其为IO端口。
    • IO内存: 当一个寄存器或内存位于内存空间时,称其为IO内存。

操作I/O端口: 对I/O端口的操作需按如下步骤完成:

    1. 申请 
    2. 访问
    3. 释放

申请I/O端口:

内核提供了一套函数来允许驱动申请它需要的I/O端口,其中核心的函数是: struct resource *request_region(unsigned long first,unsigned long n, const char *name)
这个函数告诉内核,你要使用从 first 开始的n个端口,name参数是设备的名字。如果申请成功,返回非 NULL,申请失败,返回 NULL。

系统中端口的分配情况记录在/proc/ioports 中(展示)。如果不能分配需要的端口,可以来这里查看谁在使用。

访问I/O端口:

I/O端口可分为8-位, 16-位, 和 32-位端口。Linux内核头文件(体系依赖的头文件 <asm/io.h>) 定义了下列内联函数来访问 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-位 端口。

释放I/O端口:

当用完一组 I/O 端口(通常在驱动卸载时),应使用如下函数把它们返还给系统:
void release_region(unsigned long start, unsigned long n)

操作I/O内存:

对I/O内存的操作需按如下步骤完成:

    1. 申请
    2. 映射
    3. 访问
    4. 释放

申请I/O内存:

内核提供了一套函数来允许驱动申请它需要的I/O内存,其中核心的函数是:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name)
这个函数申请一个从start 开始,长度为len 字节的内存区。如果成功,返回非NULL;否则返回NULL,所有已经在使用的I/O内存在/proc/iomem 中列出。

映射I/O内存:

在访问I/O内存之前, 必须进行物理地址到虚拟地址的映射,ioremap 函数具有此功能:
void *ioremap(unsigned long phys_addr, unsigned long size)

访问I/O内存:

访问 I/O 内存的正确方法是通过一系列内核提供的函数:
从 I/O 内存读, 使用下列之一:

    1. unsigned ioread8(void *addr)
    2. unsigned ioread16(void *addr)
    3. unsigned ioread32(void *addr)

写I/O 内存, 使用下列之一:

    1. void iowrite8(u8 value, void *addr)
    2. void iowrite16(u16 value, void *addr)
    3. void iowrite32(u32 value, void *addr)

老版本的 I/O 内存访问函数:
从 I/O 内存读, 使用下列之一:

    1. unsigned readb(address)
    2. unsigned readw(address)
    3. unsigned readl(address)

写I/O 内存, 使用下列之一:

    1. unsigned writeb(unsigned value, address)
    2. unsigned writew(unsigned value, address)
    3. unsigned writel(unsigned value, address)

释放I/O内存:

(三)混杂设备驱动

定义:

在Linux系统中,存在一类字符设备,它们共享一个主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice)。所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查找到相应的miscdevice设备。

设备描述:

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;
};

设备注册:

Linux内核使用misc_register函数来注册一个混杂设备驱动。
int misc_register(struct miscdevice * misc)

(四)LED驱动程序设计

上拉/下拉电阻:

上拉是将不确定的信号通过一个电阻与电源相连,固定在高电平。下拉是将不确定的信号通过一个电阻与地相连,固定在低电平。上拉是对器件注入电流,下拉是输出电流。当一个接有上拉电阻的I/O端口设为输入状态时,它的常态为高电平,可用于检测低电平的输入。

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/WenLee/p/12114983.html
今日推荐