RTEMS 4.9.5:QEMU MINI2440 BSP 中的网络驱动开发(上)

(原创文章,转载注明出处,谢谢)

这些天花了些时间把 RTEMS 4.9.5 关于 QEMU mini2440 bsp 弄了一下,弄得不好,把我遇到的问题说出来和大家分享一下。便于大家在相关问题上的继续研究。也请您不吝赐教,指出我移植中的问题。

 

开发环境的建立请参考:

QEMU MINI2440 的 Linux FC 下网络配置

http://blog.csdn.net/coolbacon/archive/2011/03/16/6252938.aspx

 

关于RTEMS MINI2440的QEMU仿真从UBOOT加载问题的研究

http://blog.csdn.net/coolbacon/archive/2011/03/20/6262776.aspx

 

RTEMS 4.9.5 在 QEMU MINI2440 上的移植发布啦……

http://blog.csdn.net/coolbacon/archive/2011/03/22/6269551.aspx

s3c2440的移植相对来讲,更加简单。因为s3c2410和s3c2440非常接近。可以借用gp32、smdk2410、s3c2410的文件修改生成 mini2440 的bsp。这里可以参考rickleaf写的移植记录:

http://blog.csdn.net/rickleaf/archive/2011/03/16/6254361.aspx

也可以参考我关于ARM移植的这一块的博文,关于at91sam9260的移植部分,大同小异。

这块就不细说了,想练手的朋友可以试试,如您遇到问题,欢迎找我讨论。


移植好了以后, 就开始做 dm9000 的驱动。

首先在c/src/lib/libbsp/arm/mini2440/下建立一个network/文件夹,在该文件夹下建立一个network.c的空文件。关于dm9000的驱动就写在这个文件里。然后修改c/src/lib/libbsp/arm/mini2440/Makefile.am文件。修改这个文件的目的,主要是:

在configure配置代码时,如果指定了--enable-networking 参数,即将网络代码编译进bsp;如果指定了--enable-networking ,也需要将我们编写的驱动编译进入bsp中。

 

我把Makefile.am文件内容贴在这里,修改的部分用红字标识出来。

##
## $Id: Makefile.am,v 1.5.2.1 2008/09/29 01:47:32 ralf Exp $
##

ACLOCAL_AMFLAGS = -I ../../../../aclocal

include $(top_srcdir)/../../../../automake/compile.am
include $(top_srcdir)/../../bsp.am

dist_project_lib_DATA = bsp_specs

include_HEADERS = include/bsp.h
include_HEADERS += smc/smc.h
include_HEADERS += ../../shared/include/tm27.h

nodist_include_HEADERS = include/bspopts.h
DISTCLEANFILES = include/bspopts.h
nodist_include_HEADERS += ../../shared/include/coverhd.h
noinst_PROGRAMS =

EXTRA_DIST = start/start.S
start.$(OBJEXT): start/start.S
    $(CPPASCOMPILE) -o $@ -c $<
project_lib_DATA = start.$(OBJEXT)

dist_project_lib_DATA += startup/linkcmds

startup_SOURCES = ../../shared/bsplibc.c ../../shared/bsppost.c /
    startup/bspstart.c startup/bspclean.c startup/memmap.c /
    ../../shared/bootcard.c ../../shared/sbrk.c /
    ../../shared/bsppredriverhook.c ../../shared/gnatinstallhandler.c
console_SOURCES = console/uart.c ../../shared/console.c
abort_SOURCES = ../shared/abort/abort.c
smc_SOURCES = smc/smc.c smc/smc.h

if HAS_NETWORKING
network_CPPFLAGS = -D__INSIDE_RTEMS_BSD_TCPIP_STACK__
noinst_PROGRAMS += network.rel
network_rel_SOURCES = network/network.c
network_rel_CPPFLAGS = $(AM_CPPFLAGS) $(network_CPPFLAGS)
network_rel_LDFLAGS = $(RTEMS_RELLDFLAGS)
endif


noinst_LIBRARIES = libbsp.a
libbsp_a_SOURCES = $(startup_SOURCES) $(console_SOURCES) $(abort_SOURCES) /
    $(smc_SOURCES)

libbsp_a_LIBADD = ../../../libcpu/@RTEMS_CPU@/shared/arm920.rel /
    ../../../libcpu/@RTEMS_CPU@/@RTEMS_CPU_MODEL@/clock.rel /
    ../../../libcpu/@RTEMS_CPU@/@RTEMS_CPU_MODEL@/timer.rel /
    ../../../libcpu/@RTEMS_CPU@/@RTEMS_CPU_MODEL@/irq.rel

if HAS_NETWORKING
libbsp_a_LIBADD += network.rel
endif


include $(srcdir)/preinstall.am
include $(top_srcdir)/../../../../automake/local.am

以上修改的地方不多,但是位置不能错。

确认我们要准备以下资源:

1.手里有dm9000、s3c2440的datasheet;

2.找一份linux的源代码,从中找出dm9000的驱动代码;

3.手里有networking.pdf,RTEMS官方关于网络方面的编程指导手册。

 

这里我们就开始编写DM9000的驱动了。RTEMS的网络驱动不同于其他驱动,其他驱动全部是通过RTEMS的IO管理器管理的,而网络驱动是交由BSD的协议栈管理的。这是RTEMS系统中唯一一个特别的驱动。BSD的在RTEMS下的整个系统模型是:




这个图中,虚线方框内基本上就是我们驱动需要完成的目标。这个图可以这样理解:

  1. 整个网络协议栈内部有三个任务。每添加一个网络接口,就相当于添加 2 个任务。任务数量公式是:1 + 2 * n, n为网络接口数量。一个是网卡接收监视任务、一个是网卡发送监视任务、另外一个是网络协议栈运转任务。
  2. 用户的任务通过socket接口将数据输入到网络代码中,这里注意两个问题:a.用户任务调用socket,socket通过用户任务获取执行时间,将数据输入到网络协议栈中;b.网络协议栈需要对数据做一些处理,比如说tcp的分包,Icmp的处理,arp的处理等等,这些都是用户透明的,他们看不到,这些的的确确需要CPU的运行时间才能完成,所以网络协议栈的运行时间从网络协议栈运转任务获得。
  3. 当网络协议栈把一个个数据包处理好后,放到合适的队列里,交由驱动处理。驱动的发送任务需要从队列里取出数据,并输送到实际的硬件上。这一步千差万别,和硬件高度相关。 所以从设计的角度说,这部分代码不易被标准化,且需要执行时间,那么干脆分配个发送任务让你干脆干这个事情。:) ,由驱动完成。
  4. 与第三条同理,接收任务接收到的包就是一个一个的,没必要对包做更进一步的处理,直接交由网络协议栈这边处理就好。但从实际硬件上获取数据这一过程并不简单,也是与硬件高度相关,故而也弄一个接收任务专门负责此事。
  5. 网络接收、发送任务不能无休止的运行,肯定是有需要再运行,怎么判断需要不需要?肯定是由网络这边的中断来呼叫了。这个比较好理解。

我看到networking.pdf 手册上问了一系列的问题,问得非常好。如果回答完全,在理解以上架构下写一个驱动是没有任何问题的。这里我翻译如下:

 

  1. 该设备是否使用~DMA~传送数据包到设备和内存,或者处理器必须复制数据包到设备和内存?
  2. 如果设备使用~DMA,是否能从多个分布在独立缓冲区内的数据帧形成一个单一的输出数据包?
  3. 如果设备使用~DMA,是否能够连续传出多个数据包,还是每个传出数据包都需要由驱动干预?
  4. 是否设备自动将短帧填充到最低的~64~个字节,或由驱动提供填充?
  5. 当检测到碰撞时设备是否会自动重新重传?
  6. 如果设备使用~DMA,它可以缓冲多个数据包到内存,或是接收任务必须在每一个包到达时重新启动传输?
  7. 数据包过短,过长,或收到的~CRC~错误怎么处理?设备自动接收或是驱动程序必须介入?
  8. 如何设置设备的以太网地址?如何对设备编程以接受或拒绝广播和多播数据包?
  9. 设备产生什么样的中断?它为每一个到达的数据包产生中断,或仅为没有错误的接收数据包产生中断?它是否为
    每个发送数据包产生中断,或只是在发送队列是空的时候产生中断?
    在发送时检测到错误会发生什么?

驱动由以下函数组成:

  • 驱动链接函数:int rtems_dm9000_attach (struct rtems_bsdnet_ifconfig *config, void *chip  /* only one ethernet, so o chip number */);
  • 驱动程序初始化函数:void dm9000_init(void *arg);
  • 驱动程序启动:void dm9000_start(struct ifnet *ifp);
  • 驱动程序停止:void dm9000_stop (dm9000_softc_t *sc);
  • 驱动程序发送监视任务:void dm9000_txDaemon (void *arg);
  • 驱动程序接收监视任务:void dm9000_rxDaemon(void *arg);
  • 驱动程序IO控制函数:static int dm9000_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data);
  • 驱动程序中断相关函数。

 

其他函数都是辅助这些主要函数的,:)。

撰写驱动时,要理解一个重要的数据结构:struct ifnet。ifnet 结构中的几个域是由驱动提供维护的:

  • ifp-> if_softc:指向设备相关的数据。在设备相关的数据结构内第一项数据结构必须是一个 arpcom 结构;
  • ifp->if_name:该设备的名称。网络协议栈使用此字符串和设备号用于设备名称查找。该设备名称应获得了在配置结构的名称条目;
  • ifp->if_unit:设备编号。网络协议栈使用这个号码和设备名用于设备名称查找。例如,如果ifp->if_name是scc,并且ifp->if_unit是'1',完整的设备的名称将是SCC1。该单位的数量应从配置结构中的name获取;
  • ifp->if_mtu:该设备的最大传输单元。对于以太网设备这个值应该总是为~1500;
  • ifp->if_flags:设备标志。以太网设备应设置标志为IFF_BROADCAST | IFF_SIMPLEX,表明该设备可以传输包到多个目的地,不在同一时间接收和发送;
  • ifp->if_snd.ifq_maxlen:等待发送到该驱动程序里的数据包队列的最大长度。这通常是设置为ifqmaxlen;
  • ifp->if_init:该驱动程序初始化函数的地址;
  • ifp->if_start:该驱动程序启动函数的地址;
  • ifp->if_ioctl:该驱动程序ioctl~函数的地址;
  • ifp->if_output:输出的函数的地址。以太网设备应设置该值为ether_output。

DM9000的自定义数据结构是:


typedef struct
{
    /*
     * Connection to networking code
     * This entry *must* be the first in the sonic_softc structure.
     */
    struct arpcom                    arpcom;
    /*
     * Interrupt vector
     */
    rtems_vector_number             vector;
    /*
     *  Indicates configuration
     */
    int                             acceptBroadcast;
    /*
     * Task waiting for interrupts
     */
    rtems_id                        rxDaemonTid;
    rtems_id                        txDaemonTid;


    uint32_t                      (*outmbuf)(struct mbuf *);

    void                            (*outblk)(volatile void *, int);
    void                            (*inblk)(void *, int);
    void                            (*rx_status)(uint16_t *, uint16_t *);

} dm9000_softc_t;

 

rtems_dm9000_attach 函数:

我的 rtems_dm9000_attach函数比较简单,基本上都没啥了,都是些必要的操作。我简单的做了一些注释:

 

int rtems_dm9000_attach (
    struct rtems_bsdnet_ifconfig *config,
    void *chip  /* only one ethernet, so no chip number */
    )
{
    struct ifnet *ifp;
    int mtu;
    int unitnumber;
    char *unitname;

    /*
     * Parse driver name 分析驱动的名称
     */
    if ((unitnumber = rtems_bsdnet_parse_driver_name (config, &unitname)) < 0)
        return 0;

    /*
     * Is driver free? 判断驱动是否已经被链接过了
     */
    if (unitnumber != 0) {
        printf ("Bad DM9000 unit number./n");
        return 0;
    }

    ifp = &softc.arpcom.ac_if;
    if (ifp->if_softc != NULL) {
        printf ("Driver already in use./n");
        return 0;
    }

    /*
     *  zero out the control structure 初始化数据结构 softc
     */

    memset( &softc, 0, sizeof(softc) );


    /* get the MAC address from the chip */

    /*获取设备的MAC地址,这个MAC地址呢获取的方法我觉得各个系统是千差万别的,

       我这里偷懒,就随便弄了一下

       这个MAC地址这样配置后,实际的效果是:00:00:01:23:45:00

    */
    softc.arpcom.ac_enaddr[0] = 0x00;
    softc.arpcom.ac_enaddr[1] = 0x00;
    softc.arpcom.ac_enaddr[2] = 0x01;
    softc.arpcom.ac_enaddr[3] = 0x23;
    softc.arpcom.ac_enaddr[4] = 0x45;
    softc.arpcom.ac_enaddr[5] = 0x00;


    if (config->mtu) {
        mtu = config->mtu;
    } else {
        mtu = ETHERMTU;
    }

    softc.acceptBroadcast = !config->ignore_broadcast;

    /*
     * Set up network interface values
     */
    ifp->if_softc = &softc;
    ifp->if_unit = unitnumber;
    ifp->if_name = unitname;
    ifp->if_mtu = mtu;
    ifp->if_init = dm9000_init;
    ifp->if_ioctl = dm9000_ioctl;
    ifp->if_start = dm9000_start;
    ifp->if_output = ether_output;
    ifp->if_flags = IFF_BROADCAST;
    if (ifp->if_snd.ifq_maxlen == 0) {
        ifp->if_snd.ifq_maxlen = ifqmaxlen;
    }

    /*
     * Attach the interface
     */
    if_attach (ifp);
    ether_ifattach (ifp);
    return 1;
}


dm9000_init 函数:

 

 

void dm9000_init(void *arg)
{
    dm9000_softc_t     *sc = arg;
    struct ifnet *ifp = &sc->arpcom.ac_if;

    if (sc->txDaemonTid == 0) {/*需要判断一下驱动是不是已经启动了,不然会造成比较严重的错误*/
        /*初始化DM9000的硬件,硬件的初始化我不赘述,这里也不贴它们的代码了,

          大家可以查看dm9000的相关文档和资料,参考linux的驱动 去理解,都是死东西,没啥好说的。 */
        dm9000_init_hw(sc);

        /*      Start driver tasks */
        sc->rxDaemonTid = rtems_bsdnet_newproc("ENrx",
                                               4096,
                                               dm9000_rxDaemon,
                                               sc);
        sc->txDaemonTid = rtems_bsdnet_newproc("ENtx",
                                               4096,
                                               dm9000_txDaemon,
                                               sc);

 

        /*创建并 启动用于轮询方式的网络任务 ,需要定义宏 CONFIG_NET_POLL_CONTROLLER */
#ifdef CONFIG_NET_POLL_CONTROLLER
        gName = rtems_build_name('D', 'E', 'A', 'M');
        rtems_task_create(gName, 1, RTEMS_MINIMUM_STACK_SIZE * 2,
                            RTEMS_DEFAULT_MODES,
                            RTEMS_DEFAULT_ATTRIBUTES,
                            &gID);
        rtems_task_start(gID, dm9000_poll_task, 1);
#endif
    } /* if txDaemonTid */

     /*安装网络的中断*/
#if !defined(CONFIG_NET_POLL_CONTROLLER)
    /* install the interrupt handler */
    BSP_install_rtems_irq_handler(&dm9000_isr_data);
#endif

    /* EMAC doesn't support promiscuous, so ignore requests

        混杂模式,即DM9000像一个监听器,监视所有网络上的数据包,不管其是不是发送给自己的。没打算支持这个功能。 :)

    */
    if (ifp->if_flags & IFF_PROMISC) {
        printf ("Warning - DM9000 Ethernet driver"
                " doesn't support Promiscuous Mode!/n");
    }

    /*

     * Tell the world that we're running.
     */
    ifp->if_flags |= IFF_RUNNING;
}

 

 

dm9000_start/dm9000_stop函数:

 

void dm9000_start(struct ifnet *ifp)
{

    /*开始函数这样写几乎是定式,没啥好说的*/
    dm9000_softc_t *sc = ifp->if_softc;

    rtems_event_send(sc->txDaemonTid, START_TRANSMIT_EVENT);
    ifp->if_flags |= IFF_OACTIVE;
}


void dm9000_stop (dm9000_softc_t *sc)
{
    struct ifnet *ifp = &sc->arpcom.ac_if;

    ifp->if_flags &= ~IFF_RUNNING;

    /*
     * Stop the transmitter and receiver.

     *这里我只停了接收,没有停发送。其实不然:

 

     要解释一下:

 

整个驱动中,我采用的策略是:发送使用查询,接收使用中断。由于发送是协议栈控制的,实际上

ifp->if_flags &= ~IFF_RUNNING;已经将发送停止了。接收是由DM9000控制的,所以要通知DM9000停止接收数据。

当然DM9000可以关闭发送,已经没有那个必要了。

     */
    DM9000_iow(DM9000_RCR, 0x00);   /* Disable RX */
}

 


(原创文章,转载注明出处,谢谢)

你可能感兴趣的:(数据结构,网络,struct,vector,网络协议,任务)