Keyword1983 朋友在8月2日,在RTEMS 4.9.5:QEMU MINI2440 BSP 中的网络驱动开发(上)回复:
“您好 我有問題想請問 您的int rtems_dm9000_attach 帶了兩個參數 一個是struct rtems_bsdnet_ifconfig *config, 另一個是void *chip 不過我去看元代碼中其它的network driver的attach函數,其第一個參數都一樣沒錯,但是第二個參數有的是自定的chip configuration結構 有的卻只是個int attach的值.
這會造成一個問題, AP要初始化網卡,都是借由設定ifconfig結構達成,其中ifconfig第二個參數就是設定為你要使用的網卡的attach function point, 當呼叫rtems_bsdnet_initial()來初始化網路,接著會執行rtems_bsdnet_attach(ipf), 她會call剛剛記錄下來的attach function point 並且直接給予第二個參數值為1(ifp->attach(ifp, 1)),這樣一來不就表示 我們網卡所撰寫的attach的第二個參數只能為int型態,而不能使用自定的chip configuration結構? 那rtems source中的那些網路driver該如何使用呢??”
说实话,起初并没有准确理解这个问题。后来,仔细想了一下,这个问题倒有些意思。虽然是RTEMS网络特有的问题,但我觉得作为软件架构和数据结构抽象分析,具有一定的代表性。
RTEMS 网络控制的驱动链接函数原型如下所示:
int rtems_xxxx_attach(struct rtems_bsdnet_ifconfig *config, void *chip );
第一个参数 config 存放rtems网络协议栈对以太网控制器的配置。
第二个参数 chip 存放以太网控制器驱动初始化所需要的特殊数据。
我们来看看RTEMS怎么调用这个函数的。RTEMS的网络配置,通过用户代码初始化数据结构:
static struct rtems_bsdnet_ifconfig netdriver_config = {
RTEMS_BSP_NETWORK_DRIVER_NAME, /* name */
RTEMS_BSP_NETWORK_DRIVER_ATTACH,/* attach function */
#ifdef RTEMS_USE_LOOPBACK
&loopback_config, /* link to next interface */
#else
NULL, /* No more interfaces */
#endif
#if 0
NULL, /* BOOTP supplies IP address */
NULL, /* BOOTP supplies IP net mask */
#else
"10.0.0.9", /* IP address */
"255.255.255.0", /* IP net mask */
#endif /* !RTEMS_USE_BOOTP */
ethernet_address, /* Ethernet hardware address */
0, /* ignore broadcast */
0, /* mtu */
0, /* rbuf_count */
0, /* xbuf_count */
0x300, /* port */
9, /* irq */
0, /* bpar */
NULL /* driver control pointer */
};
/*
* Network configuration
*/
struct rtems_bsdnet_config rtems_bsdnet_config = {
&netdriver_config,
#if (defined (RTEMS_USE_BOOTP))
rtems_bsdnet_do_bootp,
#else
NULL,
#endif
8, /* Default network task priority */
256 * 1024, /* Default mbuf capacity */
256 * 1024, /* Default mbuf cluster capacity */
"leo", /* Host name */
"leo", /* Domain name */
"10.0.0.4", /* Gateway */
"10.0.0.4", /* Log host */
{"10.0.0.4" }, /* Name server(s) */
{"10.0.0.4" }, /* NTP server(s) */
0, /* efficiency */
0, /* udp TX buffer */
0, /* udp RX buffer */
0, /* tcp TX buffer */
0, /* tcp RX buffer */
};
这个是在用户代码中配置RTEMS网络的初始化数据。
cpukit/libnetworking/rtems下的RTEMS_glue.c中的
int rtems_bsdnet_initialize_network(void);函数完成对网络的初始化。核心代码970行:
/*
* Attach interfaces
*/
for (ifp = rtems_bsdnet_config.ifconfig ; ifp ; ifp = ifp->next) {
rtems_bsdnet_attach (ifp);
}
这里的rtems_bsdnet_config是用户定义的。ifconfig就是用户定义的netdriver_config。链接代码时,直接将ifp传递到函数rtems_bsdnet_attach中。
/*
* Attach a network interface.
*/
void rtems_bsdnet_attach(struct rtems_bsdnet_ifconfig *ifp)
{
if (ifp) {
rtems_bsdnet_semaphore_obtain ();
(ifp->attach)(ifp, 1);
rtems_bsdnet_semaphore_release ();
}
}
在rtems_bsdnet_attach中,很明显,调用以太网驱动具体链接函数rtems_xxxx_attach时,第二个参数始终为1。原本这个东西并不难理,也不该有问题。但现在问题来了,rtems 网络协议栈对以太网控制器的配置应该是比较好抽象的,但是chip这个参数就比较麻烦了,这个参数属于和硬件底层高度相关的。我们在 struct rtems_bsdnet_ifconfig里也不方便安插与硬件高度相关的特殊数据。如果安插了,其结果只能是,代码可移植性变差,也变得难以理解。
RTEMS的相关资料缺乏,我们从源代码中找线索:
我们看c/src/libchip/network/sonic.c中的函数:
int rtems_sonic_driver_attach (
struct rtems_bsdnet_ifconfig *config,
sonic_configuration_t *chip
);
可以看到 1593 行 C 代码:
sc->sonic = (void *) chip->base_address;
sc->vector = chip->vector;
sc->dcr_value = chip->dcr_value;
sc->dc2_value = chip->dc2_value;
sc->write_register = chip->write_register;
sc->read_register = chip->read_register;
看红字标出的几个成员变量,应用层的话,是非常难以理解的。只有对应具体的驱动,才能明白具体的意思。所以从我个人的角度说,chip 这个变量是非常不适合作为高层调用传递的参数。所以在rtems_glue.c中,传递时用1表示挂载驱动,用0表示卸载驱动。只能是把chip这个参数作为整数。才能解决高层的封装问题,那么sonic.c这样的驱动中的attach函数里的第二个chip不能用整数传递的。
c/src/libchip/network/文件下有许多网络驱动。关于第二个参数chip也是五花8门,有的是正儿八经的与硬件高度相关的数据结构。如greth.c, smc91111.c;有的就是整数,如cs8900.c。
又怎么解决呢?怎么把它们统一起来呢?
在这里我提供一个简单的思路,算是抛砖引玉吧:
1.不可能改rtems_glue.c中的代码,使得第二个参数传递与硬件相关配置数据;(与rtems发行版本相抵触)
2.不可能在用户应用程序中对底层硬件结构的数据进行配置;(违反可移植性,代码难以理解)
3.不可能改变rtems中已经写好的c/src/libchip/network/下的驱动。(与rtems发行版本相抵触)
基于这三个思路,自己撰写驱动时,可以:
这些硬件特殊的数据和具体的bsp相关,个人认为在rtems的架构下应属于bsp相关的数据。仍然属于网络驱动部分。参考 c/src/lib/libbsp/arm/csb337/network/network.c,应该为每一个以太网控制器建立一个数据结构。在csb337中是,staic struct at91rm9200_emac_softc_t softc;
如果有两个控制器,那么就定义两个 static struct at91rm9200_emac_softc_t softc[2]; softc是驱动内部的数据。高层无权访问,也无需访问。怎么判断是哪个网络控制器呢?其实也很简单,
if ((unitnumber = rtems_bsdnet_parse_driver_name (config, &unitname)) < 0)
return 0;
/*
* Is driver free?
*/
if (unitnumber != 0) {
printf ("Bad AT91RM9200 EMAC unit number.\n");
return 0;
}
其实unitnumber就是控制器的编号,自己在做BSP的时候,不妨把第一个网络控制器称为eth0,第二个网络控制器称为eth1,以此类推。RTEMS_BSP_NETWORK_DRIVER_NAME, 定义的时候,在bsp.h中直接就写:
#define RTEMS_BSP_NETWORK_DRIVER_NAME "eth0"
#define RTEMS_BSP_NETWORK_DRIVER_ATTACH rtems_xxxx_attach
如果有第二个驱动,可以写:
#define RTEMS_BSP_NETWORK_DRIVER_2_NAME "eth1"
第二个网卡用这个名字就可以了,连接函数还是一样的。这样,就可以通过这个参数判断是哪个网络控制器,进而对类似的内部数据结构,如softc进行初始化。这个过程是由驱动自身完成的。
对于sonic.c这种*chip有特殊数据结构的函数,比如说,我在mycpu的bsp里使用它,我依旧写一个network.c的驱动,但是使用sonic.c驱动。
int rtems_mycpu_attach(struct rtems_bsdnet_ifconfig *config, void *chip)
{
sonic_configuration_t sonic_chip;
...
if (chip == 1)//连接驱动
{
...//获得当前驱动的名称,得到是哪一个芯片驱动,存储在unitnumber中
switch(unitnumber)
{
case 0:
...//第一个芯片,初始化sonic_chip数据结构
return rtems_sonic_driver_chip(config, &sonic_chip);
break;
...
}
}
...
}
这个就是写一个自己的attach函数,把这些特殊的数据封装起来,既符合rtems的封装思路,又最大的利用了既有的成果。
欢迎拍砖!