摘要 在开发数据流设备的驱动程序时,采用中断驱动的I/O方式结合缓冲区的使用,可以将数据的接收和系统调用read隔离开来,提高设备在系统中的运行效率。本文在讨论uClinux下中断处理程序和底半部分的开发的基础上,以一种电信E1线路和以太网互联设备上数据流设备为例,讲述中断驱动的I/O方式的驱动程序开发。主要过程是在中断期间填充数据到缓冲块,并用链表将缓冲块串接起来;在系统调用read期间从缓冲块取走数据,再将缓冲块放到自由链表中备用。涉及驱动程序中常用的阻塞式I/O和自旋锁等技术应用。通过使用上述多种技术开发的数据流设备驱动程序,确保系统稳定高效的动作。关键词 uClinux 中断驱动 I/O方式 引 言 在32位微处理器逐渐成为嵌入式系统主流的同时,嵌入式应用也变得越来越复杂。许多嵌入式系统都不得不借助于专用的操作系统来支撑自己的应用。uClinux作为类UNIx操作系统,继承了Linux的各种优秀的品质,成为首选的嵌入式系统的操作系统。 为自己的设备在操作系统下添加驱动程序,是嵌入式设计必不可少的部分。针对不同的设备类型,选择合适的驱动程序的模式,同样也是十分重要的。通常的设备驱动采用直接I/O的方式,如存储器、看门狗等;而对于象网络这样的数据流设备的驱动,则应该用到中断机制。 本文以uClinux为背景,以一种数据流设备为目标,介绍中断驱动的I/O设备驱动的开发。 1 应用背景1.1 硬件描述 本文介绍的驱动程序是应用在一种电信E1线路和以太网互联设备上的。它是旁路接收E1数据并将其发送到以太网的某一台服务器上,在服务器上对E1的话路和信令时隙分析。 该设备中的处理器是采用三星公司出品的网络型ARM处理器S3C4510B。E1线路接口采用Dallas半导体公司的专用El接口单元(LIU)芯片DS2148,它完成波形整理、时钟恢复和HDB3解码。DS2148将整理后的E1数据流送给一片Altera公司的Cyclone系列的FPGA(EPLC3T144C8),它将串行的E1数据流存入到FIFO,再通过ARM的32位外部总线将数据传送给ARM。ARM将数据打包通过以太网发送到服务器上。图l所示是本系统的硬件框图。本文主要介绍接在ARM的外部总线上的FPGA,在uClinux下的驱动程序中断机制的设计。1.2硬件连接 S3C4510B处理器和FPGA的连接电路如图2所示。
1.3 FPGA内FIFO的结构 在FPGA内部设置了两个FIFO。为了防止ARM和FPGA操作的冲突,ARM和FPGA对两个FIFO操作采用乒乓方式,这样ARM和FPGA就可以同时操作不同的FIFO,而不需要等待。FIFO的大小是4096位,能容纳一个E1复帧的数据量。当FPGA将一个FIFO填满后,会用中断的方式通知ARM来读FIFO,同时FPGA会置内部的F1FO状态寄存器。FIFO)状态寄存器命名为fpga_imf,是一个32位的寄存器,用其中某几位置“l”,表示对应的FIFO需要读取。 2 软件设计 中断驱动的I/O是指,输人数据在中断期间被填充到缓冲区内,并由读取该设备的进程取走缓冲区内的数据;输出缓冲区由写设备的进程填充,并在中断期间取走数据。数据缓冲可以将数据的发送和接收与write及read系统调用分离开来,提高系统的整体性能。下面是uCllnux下的中断程序的设计。2.1 uClinux下的中断程序 在uClinux系统中,通过调用下面这个函数向系统申请一个中断通道(或中断请求IRQ),并在处理完以后释放掉它。 mt reqLIest_irq(unsigned int irq,void(*handler)(int,vold*, struct pt_regs*),unsigned 10ng flags,const chat*device, vold*dev_id); void free_irq(unstgned int lrq,VOid*dev_id); 其中,irq是中断号。在本系统中它对应于S3C4510B的21个中断源。这里用的是中断源O。handler指向要安装的中断处理函数的指针。flags是一个与中断管理有关的各种选项的字节掩码。device传递给request_irq的字符串,在/proc/interrupts中用于显示中断的拥有者。dev_id指针用于共享的中断信号线。函数的返回值为O时表示成功,或者返回一个负的错误码。函数返回一EBUJSY通知另一个设备驱动程序已经使用了要申请的中断信号线。下面是FPGA的设备中断申请函数。这个函数是在驱动中的fpga_open函数中被调用的。 int fpga_open(struct inode*inocle,stuct_file*file){ int result; result=request_irq(FPGA_IRQ,δfpga_isr,SA_INTER-RUPT,″fpga″,NULL); if(resuIt!=O){ printk(KERN_INFO”Can not register FPGA ISR!\n”);}else{ printk(KERN_INFO″FPGA ISR Register successfully!\n”); }} 在申请了中断通道后,系统会响应外部中断0,而进入中断处理程序。中断处理程序的第一步是要先清除S3C4510B的中断悬挂寄存器的外部中断O位。这是为了让FPGA可以产生新的中断。在uClinux系统中是调用下面的宏来实现的。 #deflne CLEAR_PEND_INT(n) IntPend=(1<<(n)) 中断处理程序功能就是将有关中断接收的信息反馈给设备,并根据要服务的中断的不同含义相应地对数据进行读写。所以FPGA的中断处理的主要任务是,读取FPGA中FIFO状态寄存器的值,获取需要读取的FIFO的信息并安排接收数据。在程序中用到了系统提供的inl函数。 unmgned mt status status=inl(FPGA_IMF); 中断处理程序的执行应尽可能的短,而从FPGA中接收数据,一次必须读完一个FIFO及128字。这是一个需要较长时间的外部I/O操作,所以把这个操作放到中断处理的底半部(bottom-haIf)来完成。下面介绍中断处理的底半部的设计。2.2 BH机制 底半部处理程序和上半部最大的不同就在于,在执行BH时所有的中断都是打开的,所以说它是在“更安全”时间内运行。2.4版本的uClinux内核有三种机制来实现底半部的处理:软中断、tasklet和BH。在这里选用了较为简单的BH机制。 BH机制实际上是一个任务队列,中断处理程序将要处理的任务插到特定的任务队列中等待内核执行。内核维护着多个任务队列,但驱动程序只能用前三种: ①tq_scheduler队列。当调度器被运行时,该队列就会被处理。因为此时调度器在被调度出的进程的上下文中运行,所以该队列中的任务几乎可以做任何事。它们不会在中断时运行。 ②tq_timer队列。该队列由定时器队列处理程序(timertick)运行,因为该处理程序是在中断时问运行的。该队列中的所有任务就也是在中断时间内运行的。 ③tu_lmmediate队列。立即队列在系统调用返回时或调度器运行时尽快得到处理的(不管两种情况谁先发生了)。该队列是在中断时间内得到处理的。 队列元素由下面的结构来描述: structtq_struct structq_struct*mext /*激活的BH的链接表*/ unsigned 1ong sync; /*必须初始化为零*/ void(*outine)(vold*); /*调用的函数*/ void*data; /*传递给函数的参数*/ }; 上面的数据结构中最重要的字段是rotltine和data。将要延迟的任务插入队列,必须先设置好结构的这些字段,并把next和sync两个字段清零。结构中的sync标志位用于避免同一任务被插人多次,这会破坏next指针。一旦任务被排人队列,该数据结构就被认为是内核“拥有”了,不能再被修改。 在FPGA的驱动中,定义了一个任务队列元素用于完成底半部分: struct tq_struct el_task; unsigned int el_line; el_line数组用来保存传递给任务的参数。在打开FPGA时要对任务队列结构赋值: el_task.routine=fpga_bh; e1 task.data=&e1_line: 上面的fpga_bh是底半部分处理函数void fpga_bh(unsigned int*line)的函数名,el_line是传递给fpga_bh函数的实参。 与任务队列有关的还有下面的函数: void queue_task(struct tq_struet*task,task_queue*List); 正如该函数的名字,本函数用于将任务排进队列中。它关闭了中断,避免了竞争,因此可以被模块中任一函数调用。FPGA的任务被插入到tq_immediate队列中,所以,list被赋值为&tq_immediate。 当某段代码需要调度运行下半部处理时,只要调用mark_bh即可: void mark_bh(int nr); 这里,nr是激活的BH的类型。这个数是在头文件
结语 连续数据流设备在uClinux下的驱动,通常会用到中断机制。本文讨论的中断驱动的I/O式为这种应用提供了一种实用的方法。文中所涉及的链表、阻塞型I/O、自旋锁等技术在驱动程序的开发中也经常得到使用。




