Linux操作系统的全称是GNU/Linux,它是由GNU工程和Linux内核两个部分共同组成的一个操作系统。该系统中所有组件的源代码都是自由的,可以有效保护学习成果,因而在嵌入式领域得到了广泛的应用。
FPGA是英文Field Programmable Gate Array的缩写,即现场可编程门阵列,该器件是作为专用集成电路ASIC (Application Specific Integrated Circuit)领域中的一种半定制电路而出现的,它的出现既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。在通信行业、传输网、医疗仪器、各种电子仪器、安防监控、电力系统、汽车电子以及消费类电子中都大面积使用。随着产品研发周期的逐步缩短,定制型产品的开发使FPGA在后面的应用面越来越广。例如在2G和3G通信,以及以后的4G通信和wimax等等通信类设备中,它与DSP、MPU一起将大量出现在其中。
S3C2410微处理器是一款由Samsung为手持设备设计的低功耗、高度集成的微处理器,采用272脚FBGA封装,内含一个ARM920T内核和一些片内外围设备。在时钟方面,该芯片集成了一个具有日历功能的RTC和具有PLL (MPLL和UPLL)的芯片时钟发生器。MPLL产生的主时钟能够使处理器工作频率最高达到203MHz。这个工作频率能够使处理器轻松运行于Windows CE,Linux等操作系统并进行较为复杂的信息处理。为此,本文以S3C2410上使用Altera公司的EP2S30F67214为例,系统地介绍了在Linux系统环境下的FPGA的驱动方法。
1 基本原理
Linux下的设备驱动程序通常是一个存在于应用程序和实际设备间的软件层。许多设备驱动都是与用户程序一起发行的,可以帮助配置和存取目标设备。
在Linux下驱动FPGA,其本质上就是字符设备的驱动,惯例上它们位于/dev目录。
1.1 主次编号
在内核中,dev_t类型(在中定义)用来持有设备编号。通常2.6内核版本限制在255个主编号和255个次编号。
建立一个字符驱动时,需要做的第一件事是获取一个或多个设备编号。其必要的函数是regis-ter_chrdev_region,设计时可在中声明:
int register_chrdev_region(dev_t first,unsigned int count,char*name);
如同大部分内核函数一样,如果分配成功,register_chrdev_region的返回值将是0。出错时,则返回一个负的错误码,但不能存取请求的区域。
1.2 重要数据结构
注册设备编号仅仅是驱动代码必须进行的诸多任务中的第一个。驱动操作包括三个重要的内核数据结构,称为file_operations、file和inode。其中,对于FPGA驱动来说,最值得关注的是文件操作(file_operations)。
file_operation结构是一个用字符驱动方式建立设备编号和设备操作的连接结构,定义在.是一个函数指针的集合。每个打开文件与它自身的函数集合相关,这些操作大部分可由系统调用,例如:open(),read ()等等。典型的file_operation结构可用FPGA设备列表所示,其代码如下:
第一个file_operations元素根本不是一个操作,它是一个指向拥有这个结构的模块指针,或用来在操作使用时阻止模块被卸载,它也是在中定义的宏;
llseek主要用于改变文件中的当前读/写位置,同时可将新位置作为(正的)返回值。其定义如下:
loff_t(*llseek) (struct file*,loff_t,int);
ioctl可为系统调用提供一个发出设备特定命令的方法。如果设备不提供ioctl方法,那么,对于任何未事先定义的请求,系统调用将返回一个错误。定义如下:
int(*ioctl) (struct inode*,struct file*,unsigned int,unsigned long):
1.3 设备注册
内核在内部将使用struct cdev类型结构来代表字符设备。在内核调用设备操作前,代码应当包含。而如果想将cdev结构嵌入设备特定的结构中,则应当初始化已经分配的结构,其使用的代码为:
void cdev_init(struct cdev*cdev,structfile_operations*fops);
1.4 open和release
open主要用于提供驱动初始化,在大部分驱动中,open应当检查设备特定的错误(例如设备没准备好,或者类似的硬件错误),但是,其第一步常常是确定打开哪个设备。open的原代码为:
int(*open) (struct inode*inode,structfile*flip);
1.5 读/写操作
读和写都是进行类似的任务,就是从设备到应用程序代码的数据拷贝。因此,它们的原代码比较相似:
ssize_t read(struct file*flip,char__user*buff,size_t count,loff_t*offp);
ssize_t write(struct file*filp,const char__user*buff,size_t count,loff_t*offp);
read的任务是从设备拷贝数据到用户空间(使用copy_to_user),而write方法则是从用户空间拷贝数据到设备(使用copy_from_user)。
图1所示是用read参数表示一个典型读的实现过程。
2 硬件电路
通常在大容量存储项目中,S3C2410处理器一般作为主CPU,可对EP2S30F67214进行扩展,以使系统具有拍摄、存储、下载、I/O口扩展的功能。由于FPGA的高速处理能力和易扩展性,ARM与FPGA的结合使用,将在嵌入式系统领域占据主导地位。
本项目中的ARM主要读取FPGA的数据,然后进行数据处理并送给上位机。其ARM处理器与FPGA的连接关系如图2所示,其主要连接有32位宽数据线、27位宽地址线以及读、写、中断和片选控制线等。
在S3C2410中,nGPCS4的物理地址为0x2000000—0x28000000,共计128MB的静态物理空间。中断方式为下降沿有效。
3 编程实现
3.1 设备驱动初始化
初始化模块在内核启动时主要负责初始化FPGA工作。其实现由module_init () 和module_exit ()两部分组成。其代码如下:
3.2 异步中断通知
在应用程序中,可用如下代码获得中断响应:
signal (SIGIO,test_handler);/*test_handler为函数名字*/
fcntl(fa,F_SETOWN,getpid ());
oflags=fcntl(fa,F_GETFL);/*fd为打开设备返回值*/
fcntl (fd,F_SETFL,oflags∣FASYNC);/*fd为打开设备返回值*/
应当注意的是,不是所有的设备都支持异步通知。应用程序常常假定异步能力只对socket和tty可用。
3.3 地址映射
在Linux设备驱动程序开发过程中,由于驱动程序操作的都是设备的虚拟地址,因此,要使驱动程序对虚拟地址的操作反映到正确的设备上,还需要通过内存管理单元MMU来将设备的虚拟地址映射到正确的物理地址上去,从而保证驱动程序对设备的虚拟地址的操作,也就是要对其相应的物理地址进行操作。使用内存映射的好处是处理大文件时,其速度明显快于标准文件I/O,这样无论读和写,都少了一次用户空间与内核空间之间的复制。在用户空间对FPGA设备的访问可通过内存映射来实现。FPGA可以看作是硬件连接在S3C2410微处理器的片选信号nGPCS4上的一段物理地址的寻址。因此,必须先把物理地址映射到虚拟地址空间,然后才能对该段地址进行读/写。通常用户可用如下代码关联FPGA的地址:
fpga_base=ioremap(FPGA_PHY_START,FPGA_PHY_SIZE);
4 结束语
本文系统的介绍了ARM基于Linux平台下的FPGA的驱动开发方法,并通过开发用户程序,实现了数据的处理和传输,从而实现了FPGA在嵌入式领域的广泛应用。