您好,  [请登录] [QQ登录]  [支付宝登录[免费注册]

商品分类

分享到: 百度搜藏 搜狐微博 新浪微博 腾讯微博 QQ收藏 人人网 Facebook Twitter

嵌入式Linux下IC卡接口筹划与驱动开辟

发布日期:2011-05-10

    择要 在开辟数据流配置的驱动步调时,采取克制驱动的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的典范。这个数是在头文件<linux/interupth>中定义的一个标记常数。每个下半部BH相应的处理惩罚处罚函数由拥有它的那个驱动步调提供。

    完成任务行列步队元素设置后,克制处理惩罚处罚函数中就可以启用BH机制。在读得fpga_imf的值后将其赋给el_line,然后调用queue_task将任务插入到tq_immediate行列步队中,再调用mark_bh(IMMEDIATE_BH),启动底半部分处理惩罚处罚。到此,克制处理惩罚处罚步调就可以退出了。

   2.3底半部分处理惩罚处罚步调温和冲区

    uClinux利用体系退出克制处理惩罚处罚步调后,会立即将tq_immediate行列步队中任务投入运行,此中也有fpga_bh函数。在进入fpga_bh同时,体系会将el_line的地点作为实参转达给形参line。也便是将FIFO状态寄存器(fpga_imf)的值间接传给了底半部处理惩罚处罚步调。底半部分步调中会查抄这个值的每一位,据此决定须要读的FIFO。

    从FIFO中读上来的数据都是存放在内核的缓冲区中的。由于每一个FIFO的容量是一个E1的复帧,以是内核的缓冲也因此E1复帧的大小为一个缓冲块。缓冲块用链表勾结起来。缓冲单位的数据布局如下:
    struct buf_struct{
    struct list_head list;   /*链表头*/
    unsigned int buf_size;   /*数据块的大小*/
    unsigned int*buLhead;    /*缓冲块的指针*/
    unsigned int*buL_curl     /*缓冲块当前指针*/
};
    buf_size阐发白数据块的大小。这是一个以“字”为单位的数值。缓冲块在内核堆区开辟,buf_head指向实际的缓冲块的首地点,而buf_cur指向缓冲块中正在利用的单位。为了利用链表机制,驱动必须包括头文件<linux/list.h>。此中定义了list_head典范布局:
    struct list_head{
    struct list_head*next.*prev;

    为了访问缓冲块链表,还要创建一个链表头,在驱动 中定义全局变量: 
    struct list_head read_list;

    链表头必须是一个独立的list_head布局。在利用之前,必须用INIT_LIST_HEAD宏来初始化链表头:
    INIT_LIST_HEAD(&readlist); I
    Linux体系提供了链表的利用函数,在头文件<linux/list.h>中: 
    list_add(struet list_head*new,struct list_head*head);     /*在链表头后插入一个新项*/ 
    list_add_tail(stuot list_head*new,struet list_head*head); /*在链表尾部添加一个新项*/ 
    list_del(struet_list_head*entry);                           /*将给定项从链表中删除*/ 
    list_empty(struct list_head*head)                            /*刚强链表是否为空*/ 
    list_entry(struct list_head。ptr,type_of_struet,field_ name); /*访问包括链表头的布局*/ 
 
   此中list_entry的作用是一个1ist_head布局指针映射回一个指向包括它的大布局的指针。ptr是指向structlist_head布局的指针,type_of_struct是包括ptr的布局典范,field_name是布局中链表字段的名字。如可以用这个宏将指向数据缓冲块的链表指针(readl)映射为缓冲块布局指针(buf): 
    struet buf_strcut*buf=list_entry(real,struct buf_struct,list); 

    底半部分处理惩罚处罚步调中,内核缓冲块是动态分派的。由于驱动步调是内核的一部分,以是在内核堆区开辟缓冲区就要用专用的函数,在头文件<linux/malloc.h>定义了如下函数:
    void*kmalloc(size t size,int flags);/*在内核堆中分派size大小的空问*/
    void kfree(void*obi/*开释kmalloc分派的空间*/

    kmalloc函数的第1个参数是size(大小),第2个参数是优先级。最常用的优先级是GFP_KERNEL,它的意思是该内存分派是由运行在内核态的进程调用的。偶然偶尔kmalloc是在进程上下文之外调用的,比如在克制处理惩罚处罚、任务行列步队处理惩罚处罚和内核定时器处理惩罚处罚时孕育产生。这些环境下,current进程就不该该进入就寝状态,这时应该就利用优先级GFP_ATOMIC。

    不要过于频繁地用kmalloc在内核堆中分派空间,由于在分派空间时大概有克制到来,如许是不沉寂的。在驱动中创建另一个链表用于采取利用过的缓冲块。在驱动中用free_1ist作为采取缓冲块的链表头:
    struct list_head free_list;

    如许就存在两个链表:一个是装载着数据的链表,一个是已经利用过的缓冲块的链表(称为自由链表)。那么只要自由链表中另有表项,在须要缓冲块时就可以直接从自由链表中取出一个利用,而不消kmalloc再去分派。

   2.4 壅闭型I/O和自旋锁的利用

    在驱动步调中,read的变乱是将内核缓冲区中拷贝到用户空间。在举行这种利用时有两种环境是应该过细的:
    ①当read时发明读链表是空,也便是还没有数据可读。

    这种环境下,可以让read立即返回一EAGAIN,告知用户进程没有读到数据;另一个步调便是实现壅闭型I/O,在没有数据可读时让用户进程进入就寝状态并等待数据。

    有几种处理惩罚处罚和唤醒的要领,都要处理惩罚处罚同一个底子的数据典范——等待行列步队(walt_queue_head_t),便是由正在等待某变乱孕育产生的进程构成的一个行列步队。利用之前必须声明和初始化,在驱动步调中是如下声明的:
    wait_queue_head_t read_Jqueue;
    init_waitqueue_head(&read_queue);

    可以调用如下函数之一让进程进入就寝状态:
    void wait_evet(wait_queue_head_ queue,int condition);
    int wait_evem_interruptible(Walt_queue_hean_t queue,int condition);

    这两个函数把等待变乱和测试变乱是否孕育产生归并起来。调用之后,进程会不绝就寝到C布尔表达式condition为真时为止。在驱动中的read函数中,刚强读链表为空,就调用它进入就寝:
    while(1ist_efnpty(&read_list)){
    If(filp一>f_flags δO_NoNBLOCK)/*要是设置成非壅闭I/o*/
    return—EAGAIN;
    if(wait_evert_interruptible(read_queue,!list_empty(δread_list))) return—ERESTARTSYS;

}

    映射上面的函数,要唤醒进程可以调用下面的函数:
    wake_up(wait_queue_gead_t*queue);
    wake_up_jnterruptlbk(wait_queue_head_t*queue);

    驱动步调应该在数据到来后及时唤醒进程,也便是从FIFO读取数据后,在退出底半部处理惩罚处罚步调前实行:
    wake_up_mterIuptible(&read_queue);

    要指出的是被唤醒并不包管等待的变乱孕育产生了,以是从就寝态返回后,应该循环测试condition。

    ②当read利用正在访问某一个链表时,底半步调也要访问同一个链表。如许是比较伤害的,应该克制。

    为了克制这种环境的孕育产生,这里利用自旋锁。在read利用访问链表前得到锁,访问结束时解锁。底半部要访问链表时先要查抄自旋锁是否已上锁,要是有,则等待到锁可用。

    自旋锁利用典范spinlock_t来形貌。自旋锁被声明和初始化为不加锁状态要领如下:
    spinlock_t1ist_10ck=SPIN_LoCK_UNLOCKED;

    处理惩罚处罚自旋锁的函数如下:
    spill_1ock_bh(Spllalock-t*1ock);
    spin_unloek_bh(splnlock_t*lock);

    这里利用得到自旋锁并且克制底半部实行的函数,就可以完全包管底半部步调不会在read利用访问链表时来访问链表。步调中如下实现:
    spln_lock_bh(&list_lock);
    list_del(readl); /*将利用后的缓冲块从读链表中删除*/
    list_add_tail(readI,&free_list);/*将利用后的缓冲块插入自由链表中*/
    spin_unlock_bh(&list_lock);

   2.5克制驱动的I/O

    至此,可以完备地形貌ARM与FPGA之间数据活动的进程:当FPGA的一个FIFO满后,向ARM发出克制,ARM进入克制处理惩罚处罚步调后,读取FPGA中的FlFO状态寄存器(fpga_imf)的值,然后把一个任务插到立即行列步队(tq_imrnediate)中,启动底半部分(BH),同时将FIFO)状态寄存器的值转达给底半部分处理惩罚处罚步调(fpga_bh),完成这些变乱撤退出克制处理惩罚处罚步调。进入底半部分处理惩罚处罚步调后,根据FIFO状态寄存器的值确定要处理惩罚处罚的F1F0。从FIFO中将数据读出存人到内核缓冲块中,这个缓冲块大概是从自由行列步队(free_list)中取出来的一个。要是自由行列步队中是空的,就新分派一个缓冲块。接下来将填好的缓冲块加到读行列步队(read-list)中,并唤醒就寝的进程,如许底半部分的变乱也完成了。当用户进程对FPGA配置举行读利用时,驱动中的read函数查抄读链表。要是读链表为空,则进入就寝并等待数据到来。有数据后将从读行列步队中取出的缓冲块的数据拷贝到用户空间,然后将利用过的缓冲块插到自由行列步队中,等待以后再次利用。内核缓冲区的利用进程如图3所示。图3上半部分是在底半部分步调中,下半部分是在read函数中。

   结语
    连续数据流配置在uClinux下的驱动,通常会用到克制机制。本文讨论的克制驱动的I/O式为这种应用提供了一种实用的要领。文中所涉及的链表、壅闭型I/O、自旋锁等技能在驱动步调的开辟中也通常得到利用。