二、tftp实现与说明(二)

  上一讲中了解了tftp的原理和代码的具体实现之后,这一讲具体对uboot客户端环境下的tftp升级进行说明。与之前自己实现的tftp客户端不同的是,uboot中还没有实现创建udp的接口,只是通过将传输信息必要的信息放入一定的字符数组位置中,然后将该字符数组放入网卡的发送缓冲区中,由网卡实现数据解析和发送。
  接下来就uboot的tftp客户端实现和uboot怎么发送传输和升级进度进行详细的说明。

uboot的tftp客户端实现

int cmd_tftp(int argc, char *argv[])
{
    ...
tftp:
    if (net_tftp(ac, buf, byte_count, fname)) //调用net_tftp接口
        return ERR_OK;
    else
        return ERR_FILE;
    ...
}

int net_tftp(int put, unsigned long dest_address, unsigned int bytecount, char *file)
{
    img.addr = dest_address;
    img.count = bytecount;
    printf("tftp %s %s:%s \n", (put ? "put" : "get"),
           inet_ntoa(&bootvars.server), file);
    return ((tftp(file, (put ? upload_file : download_file)))? 1 : 0);      //调用tftp接口
}

//uboot中具体实现tftp客户端的接口
int tftp(const char *name, int (*func) (unsigned char *, int, int, int))
{
    static unsigned short iport = 2000;
    unsigned short oport, len, block = 0, prevblock = 0, txblk = 0;
    int bcounter = 0, retry = 0, timeout, result;
    struct tftp_t *tr;
    struct nbuf *pkt;
    int packetsize = TFTP_DEFAULTSIZE_PACKET;                   //和前一章说明的一样,这里的数据包限定完整长度为TFTP_DEFAULTSIZE_PACKET,即512字节
    unsigned long server = bootvars.server;                 //全局变量,这里表示的是uboot环境变量中设置的服务器IP
    unsigned long data;
    short put = (func == upload_file);                      //这里的put变量用于后续区分执行是上传文件还是下载文件
    await_reply(AWAIT_QDRAIN, 0, 0);
    tp.opcode = put ? htons(TFTP_WRQ) : htons(TFTP_RRQ);            //前面讲过,TFTP_WRQ和TFTP_RRQ分别表示的是tftp协议中开始处的请求上传及请求下载选项
    len = 1 + sizeof (tp.ip) + sizeof (tp.udp) +                //发送必要信息:发送的总数据长度
        sizeof (tp.opcode) + sprintf((char *) tp.u.rrq, "%s%coctet", name, 0);

    //!!!
    if (0 == udp_tx(server, ++iport, TFTP_PORT, len, &tp))          //!!!发现这里通过udp_tx进行信息发送,必要的发送信息包括:服务器地址,发送端口号,TFTP接收端端口号(69)以及发送信息;
        return (0);

    //在发送完上传请求或者下载请求后,进入接收服务器返回的信息并处理阶段
    while (1)
    {
        timeout = rfc951_sleep(block ? TFTP_REXMT : TIMEOUT, retry);
        data = (unsigned long) iport;
        result = await_reply(AWAIT_TFTP, &data, timeout);
        if (ESC == result)
            return 0;

    //超时,进入重发机制
        if (0 == result)
        {
            if (!block && (MAX_TFTP_RETRIES > retry++))
            {
                if (0 == udp_tx(server, ++iport, TFTP_PORT, len, &tp))
                    return (0);
                continue;
            }
            if (put)
                goto retry;
            if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT))
            {
                udp_tx(server, iport, oport, TFTP_MIN_PACKET, &tp); /* re-send ack */
                continue;
            }
            break;
        }

    //接收并解析数据
        pkt = (struct nbuf *) data;
        tr = (struct tftp_t *) &pkt->data[ETHER_HEAD_SZ];
        if (ntohs(TFTP_ERROR) == tr->opcode)                    //服务器端返回错误
        {
            printf("Err %d (%s)\n", ntohs(tr->u.err.errcode), tr->u.err.errmsg);
            break;
        }

        //download , ack
    //下载文件处理部分:服务器端返回ACK包,表示服务器端已经接收到请求,可以继续下一步的执行
        if (tr->opcode == ntohs(TFTP_OACK))
        {
            char *e, *p = tr->u.oack.data;
            if (prevblock)
                continue;       /* skip */
            len = ntohs(tr->udp.len) - UDPHDR_SZ - 2;               //获取接收到的tftp整个包的长度,不大于1432字节(注意这里是整个包长度,不是数据域长度)
            if (len > TFTP_MAX_PACKET)
                goto noak;
            e = p + len;
            while (*p != '\0' && p < e)
            {
        //判断tftp包中的数据域长度是否符合<=512字节的标准
                if (!strcasecmp("blksize", p))
                {
                    p += 8;
                    if ((packetsize =
                         strtol(p, &p, 10)) < TFTP_DEFAULTSIZE_PACKET)
                        goto noak;
                    while (p < e && *p)
                        p++;
                    if (p < e)
                        p++;
                }
                else                    
                {
                  noak:
                    tp.opcode = htons(TFTP_ERROR);
                    tp.u.err.errcode = 8;
                    len =
                        sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) +
                        sizeof (tp.u.err.errcode) +
                        sprintf((char *) tp.u.err.errmsg, "RFC1782 error") + 1;
                    udp_tx(server, iport, ntohs(tr->udp.src), len, &tp);
                    return 0;
                }
            }
            if (p > e)
                goto noak;
            block = tp.u.ack.block = 0;
        }                       // request, response
        else                    //data
        if (tr->opcode == htons(TFTP_DATA))                 //返回的是DATA数据选项
        {
            len = ntohs(tr->udp.len) - UDPHDR_SZ - 4;
            if (len > packetsize)                       //数据域字节正确性判断
                continue;
            block = ntohs(tp.u.ack.block = tr->u.data.block);
        }
        else /* upload ack */ if (tr->opcode == ntohs(TFTP_ACK))        //文件上传处理(暂不详细说明)
        {
            static int prev_sent;
            int upload_count;
            block = ntohs(tr->u.ack.block);
            if (block == txblk)
                retry = 0;
            else
            {
                printf("blk# dis-continuous txblk=%d, ack-blk=%d\n", txblk,
                       block);
                len = prev_sent;
                if (retry++ < 16)
                {
                    img.addr -= prev_sent;
                    goto retry;
                }
                else
                {
                    printf("max retry fail\n");
                    return 0;
                }
            }
            if (0 >= img.count)
                return 1;
            txblk++;
            tp.u.data.block = htons(txblk);
            upload_count = (img.count >
                  TFTP_DEFAULTSIZE_PACKET) ? TFTP_DEFAULTSIZE_PACKET :
                img.count;
            img.count -= upload_count;
            tp.opcode = htons(TFTP_DATA);
            prev_sent = len = upload_count + sizeof (tp.ip) + sizeof (tp.udp) +
                sizeof (tp.opcode) + sizeof (tp.u.data.block);
            memcpy((char *) tp.u.data.download, (char *) img.addr, upload_count);
            oport = ntohs(tr->udp.src);
          retry:
            udp_tx(server, iport, oport, len, &tp);
            upload_file(0, 0, upload_count, 0);
            continue;
        }
        if ((block || bcounter) && (block != prevblock + 1))                //块序号准确性判断(为防止丢包,发送的块序号会累加。前一章中有对此说明)
        {           /* Block order should be continuous */
            tp.u.ack.block = htons(block = prevblock);
        }
        tp.opcode = htons(TFTP_ACK);                            //回应答包(一发一答)
        oport = ntohs(tr->udp.src);
        udp_tx(server, iport, oport, TFTP_MIN_PACKET, &tp);             //发送数据
        if ((unsigned short) (block - prevblock) != 1)
            continue;
        prevblock = block;
        retry = 0;
        result = func(tr->u.data.download, ++bcounter, len, len < packetsize);      //根据传入的函数指针参数,执行上传或下载数据处理
        if (len < packetsize) {
#if defined(CONFIG_CMD_FLASH) && defined(CONFIG_FLASH_CMD_PROGRAM_AFTER_DOWNLOAD_FILE)
            downloaded = 1;
#endif
            return (1);
        }
    }
    return (0);
} 

  可以看出,uboot和前面内核环境下的tftp客户端处理并没有太大区别,要有的话也只是发送方式不同,一个是建udp一个是存发送缓冲区,但说到底uboot的发送方式也是内核环境下的底层实现。
  既然发现了uboot的发送,那么就顺便看下udp_tx接口的具体内容,从下面的代码可以发现,udp的发送还是需要ip头和udp头的。

//tftp中的传输的结构体,但udp_tx的接口不一定传的就是这个结构体,但其中的ip头,udp头和选项是必要的
struct tftpreq_t
{
    struct iphdr ip;
    struct udphdr udp;
    unsigned short opcode;
    union
    {
        char rrq[512];
        struct
        {
            unsigned short block;
        } ack;
        struct
        {
            unsigned short errcode;
            char errmsg[512 - 2];
        } err;
        struct
        {
            unsigned short block;
            char download[TFTP_MAX_PACKET];
        } data;
    } u;
} __attribute__ ((packed));


//udp_tx发送接口
//tftp中的调用示例:udp_tx(server, ++iport, TFTP_PORT, len, &tp)
int udp_tx(unsigned long dip, unsigned int sp, unsigned int dp, int len, const void *buf)
{

    struct udphdr *pudp;
    //separate from original udp_transmit()
    pudp = (struct udphdr *) ((char *) buf + IPHDR_SZ); //填充udp头前,预留一段位置给IP头
    pudp->chksum =  0; //no checksum
    pudp->dest = htons(dp); //填端口号
    pudp->src = htons(sp); //填服务器端tftp的端口号(69)
    pudp->len = htons(len - IPHDR_SZ); //填除ip头外的包长度
    return ip_tx(dip, IP_UDP, len, buf); //进入下一阶段的字符填充
}


//ip发送接口
int ip_tx(unsigned long destip, char prot, int len, const void *buf)
{
    struct iphdr *ip;
//  struct udphdr *udp;
    struct arprequest arpreq;
    int arpentry, retry, timeout, result;


 //进入ip头的填充
    ip = (struct iphdr *) buf;
//  udp = (struct udphdr *)((long)buf + sizeof(struct iphdr));
    ip->verhdrlen = 0x45;
    ip->service = 0;
    ip->len = htons(len); //填充长度
    ip->ident = 0;
    ip->frags = 0;
    ip->ttl = 64;
//  ip->protocol = IP_UDP; 
    ip->protocol = prot; //参数中传下来的IP_UDP值是17
    ip->chksum = 0;
    ip->src.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; //填源地址
    ip->dest.s_addr = destip; //填目的地址
    ip->chksum = ~ipchksum((unsigned short *) buf, IPHDR_SZ);
/*
    udp->src = htons(srcsock);
    udp->dest = htons(destsock);
    udp->len = htons(len - sizeof(struct iphdr));
    udp->chksum = 0;
*/   //填充发送的数据
    if (destip == IP_BROADCAST)
        eth_tx(broadcast, IP, len, buf);
    else
    {
        if (((destip & netmask) !=
             (arptable[ARP_CLIENT].ipaddr.s_addr & netmask)) &&
            arptable[ARP_GATEWAY].ipaddr.s_addr)
            destip = arptable[ARP_GATEWAY].ipaddr.s_addr;
        for (arpentry = 0; arpentry < MAX_ARP; arpentry++)
            if (arptable[arpentry].ipaddr.s_addr == destip)
                break;
        if (arpentry == MAX_ARP)
        {
            arptable[--arpentry].ipaddr.s_addr = destip;
            memset(arptable[arpentry].node, 0, 6);
        }
        if ( !memcmp(arptable[arpentry].node, zeros, 6))
        {
            arpreq.hwtype = htons(1);
            arpreq.protocol = htons(IP);
            arpreq.hwlen = ETHER_ADDR_SZ;
            arpreq.protolen = 4;
            arpreq.opcode = htons(ARP_REQUEST);
            memcpy(arpreq.shwaddr, arptable[ARP_CLIENT].node, ETHER_ADDR_SZ);
            memcpy(arpreq.sipaddr,
                   &arptable[ARP_CLIENT].ipaddr, sizeof (in_addr));
            memset(arpreq.thwaddr, 0, ETHER_ADDR_SZ);
            memcpy(arpreq.tipaddr, &destip, sizeof (in_addr));
            for (retry = 1; retry <= MAX_ARP_RETRIES; retry++)
            {
                eth_tx(broadcast, ARP, sizeof (arpreq), &arpreq);
                timeout = rfc951_sleep(TIMEOUT, retry);
                if (ESC == (result = await_reply(AWAIT_ARP, &arpentry, timeout)))
                    return 0;
                else if (1 == result)
                    goto xmit;
            }
            return (0);
        }
      xmit:
        eth_tx(arptable[arpentry].node, IP, len, buf); //将填充好的数据存入buf中,然后当做参数传给eth_tx接口进行发送
    }
    return (1);
}

void eth_tx(const char *d, unsigned int t, unsigned int s, const void *p)
{
    ...
    result = dev->hard_start_xmit(pkt, dev);        //由设备驱动执行hard_start_xmit接口进行发送
    ...
}

uboot端返回传输和升级进度

  结合上面介绍的udp_tx接口,就可以很容易仿造出uboot环境下的网络传输数据的方法了。接下来就通过实现一个从uboot端向服务端传输当前boot下擦除文件和烧写文件进度的功能来实质性地了解uboot的网络传输。
  首先,从上面对udp_tx的接口分析发现在uboot中执行网络传输的流程其实只是将填有ip头和udp头的数据放入一个buffer中,然后再将buffer传入eth_tx,最后给设备驱动接口hard_start_xmit执行发送。但由于之前tftp的结构体tftpreq_t 中有很多我们普通传输不需要关心的内容,所以我们也可以在了解tftpreq_t 中哪些成员必要,哪些成员可以省略的前提下自己创建一个结构体并通过udp_tx传输。
  观察之前能成功发送的tftp结构体:

struct tftpreq_t
{
    struct iphdr ip;                //ip头,组包过程中必要成员内容
    struct udphdr udp;              //udp头,组包过程中必要成员内容
    unsigned short opcode;          //包类型,在tftp中使用,可忽略  
    union
    {
        char rrq[512];              //包数据,可忽略自己创建
        struct
        {
            unsigned short block;
        } ack;                  //ack包内容,可忽略
        struct
        {
            unsigned short errcode;
            char errmsg[512 - 2];
        } err;                  //err包内容,可忽略
        struct
        {
            unsigned short block;
            char download[TFTP_MAX_PACKET];
        } data;                 //data包内容,可忽略
    } u;
} __attribute__ ((packed));

  由此可得,上面的结构体中仅ip头和udp是必要的,他们用于填充网络传输中必要的ip地址和端口号等。所以如果需要在uboot中自己创建一个发送擦除和烧写进度的结构体,就可以这样实现。

struct upgrade_schedule_t
{
    struct iphdr ip; //ip头
    struct udphdr udp; //udp头
    char erase_schedule; //指示擦除进度
    char write_schedule; //指示烧写进度
} __attribute__ ((packed));

  有了发送的结构体后,接下来的发送就可以变得很简单了,具体程序如下代码所示:

int send_schedule_to_server(int eraseSchedule, int writeSchedule)
{
    unsigned long server = bootvars.server; //服务端ip地址
    unsigned short iport = 2100; //发送端口号
    struct upgrade_schedule_t schedule; //自己建的发送结构体
    memset(&schedule, 0, sizeof(schedule));
    int ret = 0;
    int txLen = 1 + sizeof(schedule.ip) + sizeof(schedule.udp) + 2; //发送长度,这里1个字节暂不知为什么会加 + ip头长度 + udp头长度 + 1个表示擦除的字节 + 1个表示烧写的字节

    schedule.erase_schedule = eraseSchedule; //赋值擦除进度
    schedule.write_schedule = writeSchedule; //赋值烧写进度
    ret = udp_tx(server, ++iport, TFTP_RETURN_PORT, txLen, &schedule); //调用udp_tx接口发送

    return ret;
}

  至此,服务端也创建一个服务端,对应uboot的发送接口,这里服务端对应接收的端口号是TFTP_RETURN_PORT端口。这样uboot在调用该send_schedule_to_server接口时服务端就能正确接收到了。

你可能感兴趣的:(设备固件升级)