ARM通过HPI读写DSP数据空间,须按以下三步顺序执行:首先,对HPIC寄存器初始化,主要针对HPI16模式最低位HWOB位设置,决定数据传输格式是按高半字在前(设置为0),还是低半字在前(设置为1),该位对于HPI32模式无效,可不设置;然后,对HPIA寄存器初始化,设置访问单元的地址;最后通过读写数据寄存器(HPIDA、HPIDF)实现数据读写操作,其中读写HPIDA寄存器是完成连续地址单元读写操作,读写HPIDF寄存器是完成固定地址单元读写操作。注意,在ARM读写的过程中,如果DSP的nHRDY控制线一直为高,表示HPI数据总线未准备好,ARM的读写操作必须等待;当nHRDY为低后,ARM才继续向下执行指令。
2 Linux驱动设计
Linux虽然是一种整体式操作系统,但允许在运行时动态加载或删除功能模块。这个特点方便了驱动功能模块的开发。Linux系统支持两种模块调用方式:一种是静态编译,直接编译进内核,在系统启动时就运行;另外一种是动态加载,在内核运行时,用insmod/rmmod实现模块的加载和删除功能。在嵌入式系统开发中,一般采用动态加载方式,避免了系统频繁重启。当最终发布产品时,可以把模块直接编译进内核。这种处理方式比较简单,且效率高。
Linux系统中,内存地址主要涉及以下几个概念:物理地址、内核虚拟地址(包括内核逻辑地址)和进程虚拟地址。在内核层,当内核要访问某内存空间时,用的是内核虚拟地址,再由MMU(存储器管理单元)将内核虚拟地址转换为物理地址。采用虚拟内存技术,每个进程都有互不干涉的虚拟空间。三者直接映射的关系如图2所示,其中内核函数zap_page_range完成去掉物理地址与进程虚拟地址映射关系的功能。
2.1 驱动结构
在Linux中,设备也是作为文件来访问的。VFS(虚拟文件系统)为各种不同的文件系统提供了统一的访问接口,通过这些接口,应用程序可以直接使用open、read和IOctl等系统调用对设备进行访问和控制。
本例中,把HPI作为一个外围设备,其驱动主要实现对设备的打开、关闭、内存映射、视频数据缓冲区管理和物理内存切换等功能。根据原理图,可以确定HPI四个寄存器对应的物理地址,在驱动初始化过程中,调用ioremap_uncache函数把物理地址映射为内核虚拟地址,在驱动层通过内核虚拟地址访问HPI的4个寄存器。
存储器映射I/O把HPI驱动分配的数据空间直接映射到应用程序的虚拟地址空间,应用程序直接访问该空间,避免了用read/write系统调用导致的视频数据二次拷贝。在内核里,由驱动分配一定的缓存,当应用程序不能及时处理DSP发送过来的视频数据,可以缓存这些数据;当应用程序处理完一帧图像时,采用Linux的物理内存切换技术,把下一帧数据所在的物理地址重映射到应用程序的同一虚拟地址,这样,应用程序不用频繁调用mmap函数映射内存。
2.2 存储器映射I/O
一般情况下,当应用程序用read/write读写设备数据时,该设备的驱动先将设备数据从设备上采样到内核缓冲区,再从内核缓冲区拷贝到应用程序缓冲区,数据经过了两次拷贝。当数据量比较小时,如一些控制命令或状态信息,对系统性能几乎没有影响。但是,如果一次传输的数据量比较大,比如视频显卡上的实时视频图像,两次拷贝将大大影响系统的数据处理效率。这时,可采用存储器映射I/O技术,在内核层存储器映射I/O由函数remap_page_range完成。
由remap_page_range函数的原型可以知道,该函数的意义在于通过将特定物理地址映射到进程虚拟地址,进程可以访问特定的物理地址,而这在普通情况下是不可能的。在本例中,当进程调用mmap函数进行存储映射时,内核会调用驱动注册的hpi_mmap函数,传入的参数之一包括进程虚拟地址。在hpi_mmap函数里,调用remap_page_range完成从缓冲区物理地址到进程虚拟地址的映射。hpi_mmap函数实现如下: