Nios II设备管理分析

来源:互联网 发布:淘宝网商城中老年裙裤 编辑:程序博客网 时间:2024/04/30 09:27
 

Nios II设备分析
       Altera公司是世界上可编程芯片系统(SOPC)解决方案领先者之一,Nios II是Altera公司推出的最新32位嵌入式软核处理器,具有很大的灵活性,Nios II开发包中含有一套通用外设和接口库,用户可以方便的进行系统集成。我们也需要将拥有自主知识产权的IP集成到Nios II处理器系统,通过SOPC Builder软件的用户逻辑接口向导可以方便的将IP集成到Nios II处理器系统中,同时提供设备驱动程序,本文对Nios II设备体系做一个分析,供开发人员在编写设备驱动程序时参考。

Nios设备体系总体说明
      Nios II与以往版本Nios的设备体系有了较大的改进,Nios II不再使用SDK方式提供设备驱动,而是采用了硬件抽象库(hardware abstraction layer system library, HAL),应用程序不再直接去控制硬件设备,而是调用HAL API去驱动。HAL API包含了ANSI C 标准库,应用开发人员可以用熟悉的C库函数存取(控制)设备和文件,HAL使Nios II中的设备象UNIX系统设备一样具有相对一致的开发接口,同时也提供了UNIX风格的库函数,其使用方式和习惯类似于我们熟悉的UNIX (LINUX)系统,设备使用更加一致。这种体系使应用开发人员和设备驱动开发人员可以分工合作,提高开发效率,同时降低应用开发难度。下面是基于HAL 系统逻辑层次图:
 

Nios II 设备与硬件系统定义的相关性
       Nios II系统开发是一个用户可定制的高度灵活的过程,在SOPC Builder中用户定义处理器系统和外设生成相关的.ptf文件,Nios II IDE 根据此ptf文件在创建项目时为项目生成系统库,其中system.h完整的定义硬件系统参数, alt_sys_init.c完成外设系统资源的分配和设备环境的初始化,这两个文件是我们分析的入口,如图:

 
Nios II项目逻辑结构示意图如下:

 

外设系统资源的分配和设备环境的初始化
        Nios II系统中设备驱动程序、实时操作系统(ucos II)和应用程序最终被编译为同一个二进制文件,下载到目标系统FLASH中使用。所以设备资源都以全局变量的形式定义,相同种类的设备加入设备双向链表中,通过HAL API使用设备。下面以lan91c111网络外设为例说明外设系统资源的分配和设备环境的初始化。

1. 全局设备链表的定义

在系统库alt_lwip_dev.c中通过宏ALT_LLIST_HEAD(alt_lwip_device_list)定义了alt_lwip_device_list的设备双向链表。

在alt_llist.h中宏ALT_LLIST_HEAD相关定义如下:

typedef struct alt_llist_s alt_llist;

struct alt_llist_s {

alt_llist* next; /* Pointer to the next element in the list. */

alt_llist* previous; /* Pointer to the previous element in the list. */

};

#define ALT_LLIST_HEAD(head) alt_llist head = {&head, &head}

从定义中可知初始定义是一个指向自身的双向链表结构。

2. 全局外设系统资源分配

l Nios II IDE生成的应用项目system.h中Lan91c111外设相关部分如下:

/*

* lan91c111_0 configuration

*

*/

#define LAN91C111_0_NAME "/dev/lan91c111_0"

#define LAN91C111_0_TYPE "altera_avalon_lan91c111"

#define LAN91C111_0_BASE 0x01200000

#define LAN91C111_0_IRQ 1

#define LAN91C111_0_IS_ETHERNET_MAC 1

#define LAN91C111_0_LAN91C111_REGISTERS_OFFSET 0x0300

#define LAN91C111_0_LAN91C111_DATA_BUS_WIDTH 32

l 分配外设系统资源:

在alt_sys_init.c中分配外设系统资源

/*

* Allocate the device storage

*

*/

ALTERA_AVALON_CFI_FLASH_INSTANCE( CFI_FLASH_0, cfi_flash_0 );

ALTERA_AVALON_JTAG_UART_INSTANCE( JTAG_UART_0, jtag_uart_0 );

ALTERA_AVALON_LAN91C111_INSTANCE( LAN91C111_0, lan91c111_0 );

从以上代码中可以知Nios II以相对一致的宏定义了外设系统资源。

其中ALTERA_AVALON_LAN91C111_INSTANCE宏在altera_avalon_lan_91c111.h定义:

#define ALTERA_AVALON_LAN91C111_INSTANCE(name, dev) /

alt_avalon_lan91c111_if dev = /

{/

{/

ALT_LLIST_ENTRY,/

{/

0,/

name##_NAME,/

alt_avalon_lan91c111_init, /

alt_avalon_lan91c111_rx,/

},/

},/

name##_BASE + name##_LAN91C111_REGISTERS_OFFSET,/

name##_IRQ,/

name##_LAN91C111_DATA_BUS_WIDTH,/

0,/

0/

};

语法上理解这个宏有两个关键点,首先定义了类型为alt_avalon_lan91c111_if的结构变量lan91c111_0(dev替换的结果),其次name##在编译预处理时被替换为LAN91C111_0,例:name##_IRQ编译后为LAN91C111_0_IRQ,这个宏在 system.h中被定义,进一步替换为1。

逻辑功能上理解这个宏,还需要明确alt_avalon_lan91c111_if类型的定义,在altera_avalon_lan_91c111.h中alt_avalon_lan91c111_if结构定义如下:

typedef struct

{

alt_lwip_dev_list lwip_dev_list;

int base_addr;

int irq;

int bus_width;

sys_sem_t semaphore;

alt_u8 tx_packet_no; /* Number of packet allocated for Tx */

}alt_avalon_lan91c111_if;

上述结构定义中,base_addr、irq、bus_width在system.h中定义,sys_sem_t为设备使用信号量,将在设备初始化时被赋值,tx_packet_no在使用中分配。Alt_lwip_dev_list结构在alt_lwip_de.h中相关定义如下:

struct alt_lwip_dev

{

/* The netif pointer MUST be the first element in the structure */

struct netif* netif;

const char* name;

err_t (*init_routine)(struct netif*);

void (*rx_routine)();

};

typedef struct

{

alt_llist llist; /* for internal use */

alt_lwip_dev dev;

}alt_lwip_dev_list;

从以上代码可知,alt_lwip_list中llist是设备链表,在宏中被赋为内容为NULL指针的结构。alt_lwip_dev_list中dev 结构中除netif被赋0外其他域都被设置,netif将在设备初始化时被设置,其中网络设备lan91c111的初始化函数和读设备函数被设置, alt_avalon_lan91c111_init函数和alt_avalon_lan91c111_rx函数在网络设备驱动程序中定义,如果我们要开发其他网络芯片的驱动程序HAL就需要提供类似的函数。

1. 设备环境初始化

通过上一节的分析,我们清楚的看到,设备管理链表被定义为全局结构变量,设备资源在宏定义中被分配为全局变量,这些资源都是静态的被定义和分配。设备环境初始化动态的组织这些资源数据,在alt_sys_init.c中alt_sys_ini()函数在main()函数前被执行,程序相关部分如下:

/*

* Initialise the devices

*/

void alt_sys_init( void )

{

ALTERA_AVALON_CFI_FLASH_INIT( CFI_FLASH_0, cfi_flash_0 );

ALTERA_AVALON_JTAG_UART_INIT( JTAG_UART_0, jtag_uart_0 ); ALTERA_AVALON_LAN91C111_INIT( LAN91C111_0, lan91c111_0 );

}

alt_sys_init函数中相关设备环境初始化函数被调用,ALTERA_AVALON_LAN91C111_INIT是一个宏被定义在altera_avalon_lan91c111.h中:

#define ALTERA_AVALON_LAN91C111_INIT(name, dev) /

if (name##_IRQ == ALT_IRQ_NOT_CONNECTED) /

{ /

ALT_LINK_ERROR ("Error: Interrupt not connected for " #dev ". " /

"The ALTERA Avalon lan91c111 driver requires that an "/

"interrupt is connected. Please select an IRQ for " /

"this device in SOPC builder."); /

} /

else if (name##_LAN91C111_DATA_BUS_WIDTH != 32) /

{ /

ALT_LINK_ERROR ("Error: Invalid configuration for " #dev ". " /

"The ALTERA Avalon lan91c111 driver currently only " /

"supports the configuration of MAC/PHY on the " /

"development board. Please select this option " /

"in SOPC builder."); /

} /

else /

{ /

alt_lwip_dev_reg(dev); /

}

以上代码清楚的说明alt_lwip_dev_reg(dev)函数被执行,其参数宏替换后应该为 lan91c111_0,而 lan91c111_0在第2节叙述中可知被定义为alt_avalon_lan91c111_if的全局变量。

函数alt_lwip_dev_reg 在alt_lwip_dev.h中被定义为以下宏:

#define alt_lwip_dev_reg(dev) alt_llist_insert(&alt_lwip_device_list, &dev.lwip_dev_list.llist)

函数调用被转换成alt_llist_insert(&alt_lwip_device_list,& lan91c111_0.lwip_dev_list.llist);前一个参数是我们在第1节中叙述的全局链表结构,后一个参数是第2节中叙述的设备链表地址。通过上述分析,我们知道设备环境初始化被具体为设备双向环形链表结构的增加,在实际应用开发中我们也可以通过类似的手法管理设备。下面我们具体的分析alt_llist_insert()函数。

Alt_llist_insert()函数在alt_llist.h中定义:

static ALT_INLINE void ALT_ALWAYS_INLINE alt_llist_insert(alt_llist* list,

alt_llist* entry)

{

entry->previous = list;

entry->next = list->next;

 

list->next->previous = entry;

list->next = entry;

}

网络协议栈和网络设备初始化
       Nios II IDE环境中集成了ucosII和lwip,lwip必须在ucos的支持下使用。在Nios II IDE中我们可以选择Simple Socket Server项目模板来创建网络支持的项目,项目主文件main函数一般都会有以下程序行:

/*

* Our very first call in an lwIP example design is to start up lwIP TCP/IP

* stack. We specify a MicroC/OS-II thread priority for the tcp_ip thread,

* as well as a call-back routine, init_done_func(), which is called once

* the stack is alive and well.

*/

lwip_stack_init(LWIP_TCPIP_TASK_PRIORITY, init_done_func, 0);

……

/*

* As with all MicroC/OS-II designs, once the initial thread(s) and

* associated RTOS resources are declared, we start the RTOS. That's it!

*/

OSStart();

第一行语句初始化网络协议栈和网络设备初始化,OSStart()启动UCOS多任务实时系统。

在上一篇叙述中我们分析了网络设备分配了系统资源并加入到网络设备链表中,但网络接口芯片和相关接口电路并未被初始化,现在我们来逐步分析Nios II的网络协议栈是如何建立并初始化底层设备的,我们分析的侧重点不是lwip,而是准备写自己的网络HAL,所以我们分析的重点是网络HAL和lwip 结合的部分。

Lwip_stack_init函数在alt_lwip_dev.c中定义,其第二个参数为回调初始化函数,Lwip_stack_init定义如下:

void lwip_stack_init(int thread_prio, void (* initfunc)(void *), void *arg)

{

sys_init();

#ifdef STATS

stats_init();

#endif /* STATS */

mem_init();

memp_init();

pbuf_init();

netif_init();

tcpip_init(thread_prio, initfunc, arg);

return;

}

其中tcpip_init()函数中在启动tcpip任务时回调initfunc函数即init_done_func函数,init_done_func 函数在用户主程序中(一般和main函数在同一文件中)。在init_done_func函数中初始化以太网设备:

/*

* At this point LWIP has been initialized, but the Ethernet interface has

* not; the initialise_lwip_devices() call does so, adding in MicroC-OS/II

* threads for low-level Ethernet MAC interface and TCP protocol timer.

*/

if (!lwip_devices_init(ETHER_PRIO))

die_with_error("[tcpip_init_done] Fatal: Can't add ethernet interface!");

 

在lwip_devices_init(ETHER_PRIO)函数中,分配netif空间,并调用

dev_list_ptr->dev.netif =netif_add(dev_list_ptr->dev.netif,

&ipaddr, &netmask, &gw,

(void*)dev_list_ptr, dev_list_ptr->dev.init_routine,

tcpip_input);

}

在设备环境初始化时被设置的dev.init_routine被调用,设置网络接口电路和芯片寄存器完成网络设备初始化