zoukankan      html  css  js  c++  java
  • 在U-boot中添加以太网驱动 分类: arm-linux-Ubuntu HI3531 2013-12-24 09:21 784人阅读 评论(0) 收藏

    当定义CONFIG_CMD_NET和CONFIG_CMD_PING,编译之后执行ping命令,告警没有找到以太网。

    因此,需要打开U-boot的网络功能, u-boot-sunxi-sunxi中没有找到明显的网络驱动代码,或许有通用的驱动,但可以获得资料的途径有限,再说我是个初学者,平时工作属于自动控制类,网络方面很菜,因此想通过修改一个网络驱动,进行一次初步学习,想到就开工...

    边做边写,恐怕会比较乱。

    开发环境:   1、笔记本RHEL5,安装编译器arm-none-eabi-版本4.7.2; 编辑器Vim;minicom2.1

                         2、台式机XPsp3,安装SourceInsight3.5

    基本思路:  1、找到u-boot内网络代码运行的轨迹,初始化、数据交换的驱动接口等等

                         2、实现一个驱动,加入到这个运行轨迹中,设备初始化,数据读写等等

    准备工作:   1、找到芯片资料,这个比较坑,只能找到RTL8021CP的PDF,至于CPU芯片资料,

                                ×,那简直不能称为资料,看看三星处理器资料,为啥别人能做大,不是没有原因的。

                         2、Cubieboard原理图一份,这个好弄,人手一份呀

                         3、网线准备了两根,一个常用上网的,一根交叉线。路由器一只。

                                在没有研究清楚硬件连接之前,这样准备应该比较充足了。

                         4、下个新版本编译器,找了一个arm-none-eabi-的编译器,版本4.7.2,估计是目前最高版本。

                                下载:http://www.codesourcery.com/sgpp/lite/arm

                                选择arm处理器,linux版本,点进去之后需要用邮箱注册,下载地址会发到邮箱。

                         5、因为没有CPU资料,需要寻找一个A10的网络驱动代码,在支持Cubieboard的内核中找到了。

                               下载:http://linux-sunxi.org/Cubieboard/Cubieboard_Linux-3.9

                                这个驱动是linux下的,需要修改一下或参考其操作硬件的过程,以便在u-boot内运行。

    一、环境建立,初步编译

            考虑到这次需要参考其他开发板或CPU的网络驱动,因此用SourceInsight3.5建立一个u-boot-sunxi-sunxi的工程,建立关联关系,方便代码查阅。建立方法很简单,不懂的可以网上搜索一下。建立工程时不要删除其他代码,全部使用。

            linux下编译环境设置,将下载的编译器解压到 /usr/local/arm/目录,解压之后目录是arm-2012.09

            设置环境变量,在RHEL5系统内,在/ect/profile的末尾加上一句:

            export   PATH=$PATH:/usr/local/arm/arm-2012.09/bin

            保存之后,执行:source /ect/profile 或者logout,或者重启机器。

            回到bash,输入arm再按Tab键,可以看到编译工具链列出来了,执行

            arm-none-eabi-gcc   -v,可以看到版本是4.7.2

            其他系统编译器安装设置可以在网上搜索,有很多文章会提到。

            进入u-boot-sunxi-sunxi目录,修改Makefile内容,找到 CROSS_COMPILE ?=  这一行

            改为:CROSS_COMPILE ?= arm-none-eabi-

            进入 u-boot-sunxi-sunxi/arch/arm,修改config.mk内容,也是这一句 CROSS_COMPILE ?= 

            改为:CROSS_COMPILE ?= arm-none-eabi-

           回到u-boot-sunxi-sunxi目录,执行:

           make  distclean                     //清除之前编译的内容

           make  cubieboard_config     //设置板子

           make  -j4                                //编译,-j4表示多线程编译,可以使用-j2,如果是虚拟机可以不用这个参数,

                                                           //如果电脑配置比较好,可以使用 -j8

          等待一会,不出意外,编译完成,写入SD卡,到板子上运行,可以看到串口输出信息。

    二、U-boot网络模块分析

          1、网络初始化

           之前曾经分析过主循环,在主循环main_loop()调用之前就是初始化。

           在文件u-boot-sunxi-sunxiarcharmliboard.c, 函数board_init_r()内:

           ...........

            #if defined(CONFIG_CMD_NET)
            puts("Net:   ");
            eth_initialize(gd->bd);
            #if defined(CONFIG_RESET_PHY_R)
            debug("Reset Ethernet PHY ");
            reset_phy();
            #endif
            #endif

            ...........

           这一段代码可以看出,要执行网络初始化,需要定义CONFIG_CMD_NET

           因此在u-boot-sunxi-sunxiincludeconfigssunxi-common.h 末尾加一句:

           #define  CONFIG_CMD_NET

          再来看看函数eth_initialize() 的内容,在文件 u-boot-sunxi-sunxi eteth.c内:

           .................

           if (board_eth_init != __def_eth_init) {
                 if (board_eth_init(bis) < 0)
                 printf("Board Net Initialization Failed ");
           } else if (cpu_eth_init != __def_eth_init) {
                 if (cpu_eth_init(bis) < 0)
                        printf("CPU Net Initialization Failed ");
           } else
                 printf("Net Initialization Skipped ");

           .................

           如果只定义CONFIG_CMD_NET,在上电时就会打印 Net Initialization Skipped,执行了最后一个else的内容,因此,需要完成网络初始化,需要实现函数board_eth_init()或者cpu_eth_init()

           看代码,这两个函数谁等于__def_eth_init就执行谁,在看看__def_eth_init是啥,也在eth.c这个文件内

           static int   __def_eth_init(bd_t *bis)
           {
                  return -1;
           }
           int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));
           int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

           可见,实现__def_eth_init的“alias  ”,bash命令里面有个alias,如果你用过就明白这是什么意思了,实现它,就可以进行初始化啦。

           想知道weak, alias是什么意思,请围观这位大侠的博客 http://blog.chinaunix.net/uid-20272712-id-1969771.html

           以太网模块在A10内,A10的手册称之为WEMAC模块,因此,我们需要实现cpu_eth_init函数,表明eathnet模块在CPU内部。

           在文件u-boot-sunxi-sunxiarcharmcpuarmv7sunxioard.c内增加函数cpu_eth_init,内容如下:

       

          #ifdef   CONFIG_SUN4I_WEMAC

           int cpu_eth_init(bd_t *bis)
           {
                  return   sun4i_wemac_initialize(bis);
           }
           #endif

          2、如何实现网络模块

          网络模块已经成熟了,我们并不需要增加很多代码,只需要实现对硬件的操作就可以了。

          基本的操作大约就几个:初始化、打开、关闭、接收、发送、挂起

          分析u-boot-sunxi-sunxi et目录内的代码,发现网络的数据结构定义在u-boot-sunxi-sunxiinclude et.h内

          struct eth_device {
               char name[16];                                                                              //网络名
               unsigned char enetaddr[6];                                                         //以太网地址
               int iobase;                                                                                       //io基址?
               int state;                                                                                          //设备状态

              

               int  (*init) (struct eth_device *, bd_t *);                                       //网络设备初始化
               int  (*send) (struct eth_device *, void *packet, int length);     //发送数据
               int  (*recv) (struct eth_device *);                                                  //接收数据
               void (*halt) (struct eth_device *);                                                 //挂起
               #ifdef CONFIG_MCAST_TFTP
               int (*mcast) (struct eth_device *, u32 ip, u8 set);
               #endif
               int  (*write_hwaddr) (struct eth_device *);                                 //写硬件地址?这个是啥?
               struct eth_device *next;
               int index;
               void *priv;
          };

           struct eth_device结构体定义了网络设备的基本操作,从面向对象的角度来说,只要在驱动代码内将struct eth_device的init,send,recv,halt,write_hwaddr这几种方法实现,就可以实现网络的操作了,至于数据收回来之后在上层的解析方式那就不是驱动关心的了。任务变得简单了,实现这几种操作便可

          再看struct eth_device结构体下面这几个函数:

          extern int eth_initialize(bd_t *bis); /* Initialize network subsystem */
          extern int eth_register(struct eth_device* dev);/* Register network device */
          extern int eth_unregister(struct eth_device *dev);/* Remove network device */
          extern void eth_try_another(int first_restart); /* Change the device */
          extern void eth_set_current(void);  /* set nterface to ethcur var */

          都在u-boot-sunxi-sunxi eteth.c内实现,分析一下eth_register这个函数(其他的就不多说了),如下,

          int  eth_register( struct eth_device  *dev )
          {
                 struct eth_device *d;
                 static int index;

                 assert( strlen(dev->name)  <  sizeof(dev->name) );

                 if (!eth_devices) {  

                 //注册之前eth_devices应该初始化为NULL,在当前文件的函数int  eth_initialize( bd_t *bis ) 中,

                 //开始几句代码就这样初始化了,但是这个函数eth_initialize将调用我们自己写的cpu_eth_init(),

                 //并未调用eth_register,因此可以预见,自己写的函数cpu_eth_init将要调用eth_register对网络进行注册,

                 //实际工作还是要自己实现
                       eth_current = eth_devices = dev;
                       eth_current_changed();
                  } else {
                       for (d = eth_devices; d->next != eth_devices; d = d->next)
                             ;
                       d->next = dev;
                  }

                  dev->state  =  ETH_STATE_INIT;     //网络设备状态为初始化
                  dev->next    =  eth_devices;               //链表的下一个指向自己,为啥呢?
                  dev->index  =  index++;                       //设备个数增加了

                  return 0;
           }

    三、实现网络驱动

          这些内容都将实现在sun4i_wemac.c和sun4i_wemac.h内。这两个文件都在u-boot-sunxi-sunxidriver et目录下。      

         1、数据结构

          经过上面分析,可以了解到,假设自己定义一个网络设备的数据结构,那么这个结构大致如下,下面直接写一个,实际代码还要推敲推敲:

          struct   sun4i_wemac_dev {
     

                 void   *wemac_base;    // A10内部wemac模块的基地址

                 void   *gpio_base;          //wemac模块使用的gpio的基地址,去看看原理图,实际使用A10的PA口

               

                 //接收发送buffer管理,使用队列

                 unsigned int  rx_head;
                 unsigned int  rx_tail;
                 unsigned int  tx_head;
                 unsigned int  tx_tail;

                 void   *rx_buffer;
                 void   *tx_buffer;

                 struct   eth_device   netdev;      //这就是上面的以太网设备,这个一定要有
                 unsigned short     phy_addr;    //PHY地址,就是板子上RTL8201CP的地址
           };

           有的人又问啦,为什么要自己定义一个数据结构呢?net.h里面不是已经有一个struct eth_device了吗?仔细想一下,struct eth_device是u-boot定义的数据结构,里面的每一个成员可能在u-boot的其他网络模块代码中被使用修改,我们并不完全知道所有代码对struct eth_device的操作,因此需要自己定义一个结构,提供这个接口就可以了。用一句话总结就是:struct eth_device是定义给u-boot的网络模块使用的,用户得定义自己的设备,以此隔离了驱动代码与u-boot代码。

           2、实现初始化

           就是上面提到的cpu_eth_init()内调用的sun4i_wemac_initialize()函数,只写一个思路,具体代码还需要推敲: 

           int   sun4i_wemac_initialize(  bd_t  *bis  )

           {

                struct   sun4i_wemac_dev  *wemac_dev;    //自己定义的结构

                struct   eth_device   *netdev;                            //u-boot已经定义的结构

               wemac_dev  =  malloc( sizeof ( struct sun4i_wemac_dev  ) );   //分配内存
               if ( wemac_dev  ) {
                      printf("Error: Failed to allocate memory for WEMAC ");
                      return -1;
               }
               memset(wemac_dev , 0, sizeof( struct    sun4i_wemac_dev  ));            

               netdev = &wemac_dev ->netdev;

              /*****************************/

              初始化发送、接收管理队列

              若还需要其他功能,可以增加到数据结构struct   sun4i_wemac_dev中,并在此初始化,

             写文章时,代码还没写,留待后续补全

              /*****************************/

              wemac_dev ->wemac_base =  (void *)EMAC_BASE;

              wemac_dev ->gpio_base      =  (void *)PA_BASE;

              wemac_dev ->phy_addr        = WEMAC_PHY;

              

              //以下就是要实现的网络设备结构体内的“方法”,就是驱动代码中主要的几个函数,

              //可以参考从内核拷贝过来的驱动是如何实现的:

               netdev->init                      = sun4i_wemac_init;      //注意这个初始化函数跟当前函数是不同的,这个函数

                                                                                                      //主要初始化wemac模块和RTL8201芯片
               netdev->halt                     = sun4i_wemac_halt;
               netdev->send                  = sun4i_wemac_send;
               netdev->recv                    = sun4i_wemac_recv;
               netdev->write_hwaddr   = sun4i_wemac_write_hwaddr;

               .....................其他初始化...........

               eth_register( netdev );    // 最后将网络设备注册,这样u-boot就能使用驱动程序啦

                                                           // 注册之后,eth.c内的全局变量eth_current 指向wemac_dev ->netdev,

                                                           //这是在u-boot任何代码使用eth_current,

                                                           // 都表示使用的是现在初始化的模块

                return 0;

           }
          

           3、其他模块的实现

           看看eth.c内怎么实现接收和发送接口的,就可以知道驱动代码应该怎么写了

           int eth_send(void *packet, int length)
           {
                 if ( !eth_current )                         //eth_current是全局指针,指向驱动初始化的结构体wemac_dev ->netdev
                      return -1;

                return eth_current->send(eth_current, packet, length);  //注册之后,实际调用就是sun4i_wemac_send;
           }

           int eth_rx(void)
           {
                if (!eth_current)
                       return -1;

                 return eth_current->recv(eth_current);  //注册之后,实际调用就是sun4i_wemac_recv;

           }

           因此,可以知道,驱动代码实现的接收发送形式如下:

          static  int   sun4i_wemac_send( struct  eth_device  *dev,  void  *packet,   int  length )

          {

                 struct   sun4i_wemac_dev  wemac_dev  =  to_wemac(dev);            

                 //功能如何实现,可以参考内核代码的驱动。

          } 

         
          static  int   sun4i_wemac_recv( struct  eth_device   *dev )

          {

                  struct   sun4i_wemac_dev  wemac_dev  =  to_wemac(dev); 

                  //功能如何实现,可以参考内核代码的驱动。

          }

          由于我们自己定义的结构体是struct   sun4i_wemac_dev,而传过来的参数是全局指针eth_current,

          根据上面的分析,eth_current指向struct   sun4i_wemac_dev结构内的struct   eth_device   netdev,

          因此要获得指向struct   sun4i_wemac_dev的指针需要使用linux内核常用的手段,“容器”的概念就出来了,

          看代码定义: 

          struct   sun4i_wemac_dev  wemac_dev  =  container_of(  dev ,  struct   sun4i_wemac_dev ,  netdev);

          就可以获得注册之前申请的结构体的首地址,如何办到的呢?

          来看看这个在linux内核中常用的宏:

          #define   container_of( ptr, type, member )       ( {   
                                                                                               const typeof( ((type *)0)->member ) *__mptr  =  (ptr); 
                                                                                               (type *)( (char *)__mptr - offsetof(type,member) ); }  )

         #define   offsetof( TYPE, MEMBER )     ( (size_t)  & ((TYPE *)0)->MEMBER )

          这是一个很神奇的宏,可以把它用于自己以后的C代码开发中,如果使用其他编译器,这里面的关键字要改一下。

          意思就是,dev指向的内存地址,减掉它在struct   sun4i_wemac_dev中的偏移量,就获得了初始化时定义的

    struct   sun4i_wemac_dev 指针

          偏移量的获取很简单 (( struct   sun4i_wemac_dev *) 0)->netdev  就是偏移量;我们知道使用 -> 操作时,获得指向当前结构体的某一个成员的地址, 而这个成员的地址与结构体首地址的差值,就是它在结构体内的偏移量,如果把结构体的首地址设置为0,结果妙不可言。

          由于很多函数都要用到这个宏,因此可以再写成下面这样:

          #define  to_wemac(_nd)    container_of( _nd,  struct   sun4i_wemac_dev, netdev)

          上面的定义就可以这样:  struct   sun4i_wemac_dev  wemac_dev  =  to_wemac(dev);    

          

           另外3个个函数,实现方式也是一样,先去分析eth.c内调用,一并都分析一下吧:

           int eth_init(bd_t *bis)
           {
                  struct eth_device *old_current, *dev;

                  if (!eth_current) {                   //必须注册成功之后,否则这里判断失败,将打印找不到ethernet
                          puts("No ethernet found. ");
                          return -1;
                  }

                 /* Sync environment with network devices */
                 dev = eth_devices;
                 do {
                        uchar env_enetaddr[6];

                        if ( eth_getenv_enetaddr_by_index( "eth",   dev->index,  env_enetaddr))                               

                                 memcpy(dev->enetaddr, env_enetaddr, 6);  

                                 //这里就是获取IP地址了,从哪里获取呢?这是u-boot解决的问题啦                                        

                                 //熟悉情况的人这时会想起一个文件,就是u-boot移植时说的参数配置文件

                                 //uEnv.txt或boot.scr,怎么获得,需要进一步追查代码流程,这不是网络驱动关心的。

                        dev = dev->next;
                 } while ( dev  !=  eth_devices );          // CB上只有一个以太网设备,因此循环一次就会结束

                old_current = eth_current;
                do {
                           debug("Trying %s ", eth_current->name);

                           if ( eth_current->init( eth_current, bis )  >=  0 ) { //实际调用的就是函数sun4i_wemac_init ,

                                                                                                                //对WEMAC模块和PHY芯片进行初始化
                                  eth_current->state = ETH_STATE_ACTIVE;    //初始化成功,处于活动状态标志

                                  return 0;    //成功就返回啦
                          }
                          debug("FAIL ");

                          eth_try_another(0);  //不成功,试试另一个,不用分析,这个函数可定会尝试改变全局指针eth_current
                 } while (old_current != eth_current);

                 return -1;
          }

          还有一个疑问,那又是什么地方调用int eth_init(bd_t *bis)这个函数呢?借助强大的Source Insight,很快就能找到调用它的地方啦,搜索工程,出来一大片,只需关心文件net.c的调用即可,其他调用是不会被编译进来的,以下两个函数调用了这个初始化,从名字就能看出其大概功能了

          int  NetLoop( enum   proto_t protocol );  //这就是读取网络数据的主循环

          void  NetStartAgain( void );                        //这个是net重新开始,当然要初始化

          再来看看ping命令干了啥,在文件u-boot-sunxi-sunxicommoncmd_net.c

          #if defined(CONFIG_CMD_PING)
          int do_ping (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
          {
                if (argc < 2)
                     return -1;

                NetPingIP = string_to_ip(argv[1]);
                if (NetPingIP == 0)
                       return CMD_RET_USAGE;

                if (NetLoop(PING) < 0) {    //这里调用了NetLoop函数,再调用eth_init,再调用自己写的sun4i_wemac_init 
                       printf("ping failed; host %s is not alive ", argv[1]);
                       return 1;
                }

                printf("host %s is alive ", argv[1]);

                return 0;
            }

            U_BOOT_CMD(
                                         ping, 2, 1, do_ping,
                                        "send ICMP ECHO_REQUEST to network host",
                                        "pingAddress"
            );
            #endif

            至于NetLoop内如何实现数据打包,如何解析,有兴趣的可以继续深入。

          void eth_halt(void)
          {
                  if (!eth_current)
                  return;

                  eth_current->halt(eth_current);  //实际就是调用sun4i_wemac_halt,网络设备挂起

                  eth_current->state = ETH_STATE_PASSIVE; //标志变化
          }

         int eth_write_hwaddr(struct eth_device *dev, const char *base_name,  int eth_number)
         {
                  unsigned char env_enetaddr[6];
                  int ret = 0;

                  eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr);

                  if (memcmp(env_enetaddr, "", 6)) {
                          if (memcmp(dev->enetaddr, "", 6) &&  memcmp(dev->enetaddr, env_enetaddr, 6)) {
                                    printf(" Warning: %s MAC addresses don't match: ", dev->name);
                                    printf("Address in SROM is         %pM ", dev->enetaddr);
                                    printf("Address in environment is  %pM ", env_enetaddr);
                          }

                          memcpy(dev->enetaddr, env_enetaddr, 6);
                  } else if (is_valid_ether_addr(dev->enetaddr)) {
                          eth_setenv_enetaddr_by_index(base_name, eth_number,  dev->enetaddr);
                           printf(" Warning: %s using MAC address from net device ", dev->name);
                  }

                  if (dev->write_hwaddr &&  !eth_mac_skip(eth_number)) {
                          if (!is_valid_ether_addr(dev->enetaddr))
                                   return -1;

                         ret = dev->write_hwaddr(dev);//实际就是调用sun4i_wemac_write_hwaddr,将IP地址写入硬件??
                  }

                 return ret;
          }

          这三个函数形式如下:

          int     sun4i_wemac_init ( struct eth_device *, bd_t * );
          void  sun4i_wemac_halt( struct eth_device * );
          int     sun4i_wemac_write_hwaddr( struct eth_device * );             

          具体内容如何实现,在没有详细CPU手册的情况下,参考内核驱动代码是最好的选择,如果熟悉内核驱动编程就更好了,那是下一个目标。

    四、驱动完成之后的初步测试

          测试ping命令

          测试tftp命令-----这个命令还没有,下一次学习的目标。

          代码还没写,留待后续补全

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    微信二维码 场景二维码 用于推送事件,关注等 注册用户 ,经过测试
    简单的 Helper 封装 -- CookieHelper
    简单的 Helper 封装 -- SecurityHelper 安全助手:封装加密算法(MD5、SHA、HMAC、DES、RSA)
    Java反射机制
    Windows Azure Web Site (13) Azure Web Site备份
    Windows Azure Virtual Machine (1) IaaS用户手册
    Windows Azure Web Site (1) 用户手册
    Windows Azure Web Site (12) Azure Web Site配置文件
    Windows Azure Web Site (11) 使用源代码管理器管理Azure Web Site
    Windows Azure Web Site (10) Web Site测试环境
  • 原文地址:https://www.cnblogs.com/mao0504/p/4706689.html
Copyright © 2011-2022 走看看