Commit 53a5c424bf8655b7b4e2c305a441963259a26a81

Authored by David Updegraff
Committed by Ben Warren
1 parent 5d110f0aa6

multicast tftp: RFC2090

Implemented IETF RFC2090, Multicast TFTP.  Initial implementation
on Realtek RTL8139 and Freescale TSEC.

Signed-off-by: David Updegraff <dave@cray.com>
Signed-off-by: Ben Warren <bwarren@qstreams.com>

Showing 7 changed files with 360 additions and 7 deletions Side-by-side Diff

... ... @@ -1066,6 +1066,16 @@
1066 1066 Defines a default value for theIP address of a TFTP
1067 1067 server to contact when using the "tftboot" command.
1068 1068  
  1069 +- Multicast TFTP Mode:
  1070 + CONFIG_MCAST_TFTP
  1071 +
  1072 + Defines whether you want to support multicast TFTP as per
  1073 + rfc-2090; for example to work with atftp. Lets lots of targets
  1074 + tftp down the same boot image concurrently. Note: the ethernet
  1075 + driver in use must provide a function: mcast() to join/leave a
  1076 + multicast group.
  1077 +
  1078 + CONFIG_BOOTP_RANDOM_DELAY
1069 1079 - BOOTP Recovery Mode:
1070 1080 CONFIG_BOOTP_RANDOM_DELAY
1071 1081  
... ... @@ -193,6 +193,10 @@
193 193 static int rtl_transmit(struct eth_device *dev, volatile void *packet, int length);
194 194 static int rtl_poll(struct eth_device *dev);
195 195 static void rtl_disable(struct eth_device *dev);
  196 +#ifdef CONFIG_MCAST_TFTP/* This driver already accepts all b/mcast */
  197 +static int rtl_bcast_addr (struct eth_device *dev, u8 bcast_mac, u8 set)
  198 + { return (0); }
  199 +#endif
196 200  
197 201 static struct pci_device_id supported[] = {
198 202 {PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8139},
... ... @@ -228,6 +232,9 @@
228 232 dev->halt = rtl_disable;
229 233 dev->send = rtl_transmit;
230 234 dev->recv = rtl_poll;
  235 +#ifdef CONFIG_MCAST_TFTP
  236 + dev->mcast = rtl_bcast_addr;
  237 +#endif
231 238  
232 239 eth_register (dev);
233 240  
... ... @@ -129,6 +129,9 @@
129 129 unsigned char reg, unsigned short value);
130 130 static int tsec_miiphy_read(char *devname, unsigned char addr,
131 131 unsigned char reg, unsigned short *value);
  132 +#ifdef CONFIG_MCAST_TFTP
  133 +static int tsec_mcast_addr (struct eth_device *dev, u8 mcast_mac, u8 set);
  134 +#endif
132 135  
133 136 /* Initialize device structure. Returns success if PHY
134 137 * initialization succeeded (i.e. if it recognizes the PHY)
... ... @@ -167,6 +170,9 @@
167 170 dev->halt = tsec_halt;
168 171 dev->send = tsec_send;
169 172 dev->recv = tsec_recv;
  173 +#ifdef CONFIG_MCAST_TFTP
  174 + dev->mcast = tsec_mcast_addr;
  175 +#endif
170 176  
171 177 /* Tell u-boot to get the addr from the env */
172 178 for (i = 0; i < 6; i++)
... ... @@ -1538,6 +1544,48 @@
1538 1544 }
1539 1545  
1540 1546 #endif
  1547 +
  1548 +#ifdef CONFIG_MCAST_TFTP
  1549 +
  1550 +/* CREDITS: linux gianfar driver, slightly adjusted... thanx. */
  1551 +
  1552 +/* Set the appropriate hash bit for the given addr */
  1553 +
  1554 +/* The algorithm works like so:
  1555 + * 1) Take the Destination Address (ie the multicast address), and
  1556 + * do a CRC on it (little endian), and reverse the bits of the
  1557 + * result.
  1558 + * 2) Use the 8 most significant bits as a hash into a 256-entry
  1559 + * table. The table is controlled through 8 32-bit registers:
  1560 + * gaddr0-7. gaddr0's MSB is entry 0, and gaddr7's LSB is
  1561 + * gaddr7. This means that the 3 most significant bits in the
  1562 + * hash index which gaddr register to use, and the 5 other bits
  1563 + * indicate which bit (assuming an IBM numbering scheme, which
  1564 + * for PowerPC (tm) is usually the case) in the tregister holds
  1565 + * the entry. */
  1566 +static int
  1567 +tsec_mcast_addr (struct eth_device *dev, u8 mcast_mac, u8 set)
  1568 +{
  1569 + struct tsec_private *priv = privlist[1];
  1570 + volatile tsec_t *regs = priv->regs;
  1571 + volatile u32 *reg_array, value;
  1572 + u8 result, whichbit, whichreg;
  1573 +
  1574 + result = (u8)((ether_crc(MAC_ADDR_LEN,mcast_mac) >> 24) & 0xff);
  1575 + whichbit = result & 0x1f; /* the 5 LSB = which bit to set */
  1576 + whichreg = result >> 5; /* the 3 MSB = which reg to set it in */
  1577 + value = (1 << (31-whichbit));
  1578 +
  1579 + reg_array = &(regs->hash.gaddr0);
  1580 +
  1581 + if (set) {
  1582 + reg_array[whichreg] |= value;
  1583 + } else {
  1584 + reg_array[whichreg] &= ~value;
  1585 + }
  1586 + return 0;
  1587 +}
  1588 +#endif /* Multicast TFTP ? */
1541 1589  
1542 1590 #endif /* CONFIG_TSEC_ENET */
... ... @@ -99,10 +99,12 @@
99 99 int state;
100 100  
101 101 int (*init) (struct eth_device*, bd_t*);
102   - int (*send) (struct eth_device*, volatile void* pachet, int length);
  102 + int (*send) (struct eth_device*, volatile void* packet, int length);
103 103 int (*recv) (struct eth_device*);
104 104 void (*halt) (struct eth_device*);
105   -
  105 +#ifdef CONFIG_MCAST_TFTP
  106 + int (*mcast) (struct eth_device*, u32 ip, u8 set);
  107 +#endif
106 108 struct eth_device *next;
107 109 void *priv;
108 110 };
... ... @@ -123,6 +125,11 @@
123 125 extern int eth_rx(void); /* Check for received packets */
124 126 extern void eth_halt(void); /* stop SCC */
125 127 extern char *eth_get_name(void); /* get name of current device */
  128 +
  129 +#ifdef CONFIG_MCAST_TFTP
  130 +int eth_mcast_join( IPaddr_t mcast_addr, u8 join);
  131 +u32 ether_crc (size_t len, unsigned char const *p);
  132 +#endif
126 133  
127 134  
128 135 /**********************************************************************/
... ... @@ -353,6 +353,51 @@
353 353  
354 354 memcpy(dev->enetaddr, enetaddr, 6);
355 355 }
  356 +#ifdef CONFIG_MCAST_TFTP
  357 +/* Multicast.
  358 + * mcast_addr: multicast ipaddr from which multicast Mac is made
  359 + * join: 1=join, 0=leave.
  360 + */
  361 +int eth_mcast_join( IPaddr_t mcast_ip, u8 join)
  362 +{
  363 + u8 mcast_mac[6];
  364 + if (!eth_current || !eth_current->mcast)
  365 + return -1;
  366 + mcast_mac[5] = htonl(mcast_ip) & 0xff;
  367 + mcast_mac[4] = (htonl(mcast_ip)>>8) & 0xff;
  368 + mcast_mac[3] = (htonl(mcast_ip)>>16) & 0x7f;
  369 + mcast_mac[2] = 0x5e;
  370 + mcast_mac[1] = 0x0;
  371 + mcast_mac[0] = 0x1;
  372 + return eth_current->mcast(eth_current, mcast_mac, join);
  373 +}
  374 +
  375 +/* the 'way' for ethernet-CRC-32. Spliced in from Linux lib/crc32.c
  376 + * and this is the ethernet-crc method needed for TSEC -- and perhaps
  377 + * some other adapter -- hash tables
  378 + */
  379 +#define CRCPOLY_LE 0xedb88320
  380 +u32 ether_crc (size_t len, unsigned char const *p)
  381 +{
  382 + int i;
  383 + u32 crc;
  384 + crc = ~0;
  385 + while (len--) {
  386 + crc ^= *p++;
  387 + for (i = 0; i < 8; i++)
  388 + crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
  389 + }
  390 + /* an reverse the bits, cuz of way they arrive -- last-first */
  391 + crc = (crc >> 16) | (crc << 16);
  392 + crc = (crc >> 8 & 0x00ff00ff) | (crc << 8 & 0xff00ff00);
  393 + crc = (crc >> 4 & 0x0f0f0f0f) | (crc << 4 & 0xf0f0f0f0);
  394 + crc = (crc >> 2 & 0x33333333) | (crc << 2 & 0xcccccccc);
  395 + crc = (crc >> 1 & 0x55555555) | (crc << 1 & 0xaaaaaaaa);
  396 + return crc;
  397 +}
  398 +
  399 +#endif
  400 +
356 401  
357 402 int eth_init(bd_t *bis)
358 403 {
... ... @@ -118,6 +118,10 @@
118 118 char NetOurRootPath[64]={0,}; /* Our bootpath */
119 119 ushort NetBootFileSize=0; /* Our bootfile size in blocks */
120 120  
  121 +#ifdef CONFIG_MCAST_TFTP /* Multicast TFTP */
  122 +IPaddr_t Mcast_addr;
  123 +#endif
  124 +
121 125 /** END OF BOOTP EXTENTIONS **/
122 126  
123 127 ulong NetBootFileXferSize; /* The actual transferred size of the bootfile (in bytes) */
... ... @@ -1386,6 +1390,9 @@
1386 1390 }
1387 1391 tmp = NetReadIP(&ip->ip_dst);
1388 1392 if (NetOurIP && tmp != NetOurIP && tmp != 0xFFFFFFFF) {
  1393 +#ifdef CONFIG_MCAST_TFTP
  1394 + if (Mcast_addr != tmp)
  1395 +#endif
1389 1396 return;
1390 1397 }
1391 1398 /*
... ... @@ -1491,6 +1498,7 @@
1491 1498 }
1492 1499 }
1493 1500 #endif
  1501 +
1494 1502  
1495 1503 #ifdef CONFIG_NETCONSOLE
1496 1504 nc_input_packet((uchar *)ip +IP_HDR_SIZE,
... ... @@ -61,10 +61,43 @@
61 61 extern flash_info_t flash_info[];
62 62 #endif
63 63  
  64 +/* 512 is poor choice for ethernet, MTU is typically 1500.
  65 + * Minus eth.hdrs thats 1468. Can get 2x better throughput with
  66 + * almost-MTU block sizes. At least try... fall back to 512 if need be.
  67 + */
  68 +#define TFTP_MTU_BLOCKSIZE 1468
  69 +static unsigned short TftpBlkSize=TFTP_BLOCK_SIZE;
  70 +static unsigned short TftpBlkSizeOption=TFTP_MTU_BLOCKSIZE;
  71 +
  72 +#ifdef CONFIG_MCAST_TFTP
  73 +#include <malloc.h>
  74 +#define MTFTP_BITMAPSIZE 0x1000
  75 +static unsigned *Bitmap;
  76 +static int PrevBitmapHole,Mapsize=MTFTP_BITMAPSIZE;
  77 +static uchar ProhibitMcast=0, MasterClient=0;
  78 +static uchar Multicast=0;
  79 +extern IPaddr_t Mcast_addr;
  80 +static int Mcast_port;
  81 +static ulong TftpEndingBlock; /* can get 'last' block before done..*/
  82 +
  83 +static void parse_multicast_oack(char *pkt,int len);
  84 +
  85 +static void
  86 +mcast_cleanup(void)
  87 +{
  88 + if (Mcast_addr) eth_mcast_join(Mcast_addr, 0);
  89 + if (Bitmap) free(Bitmap);
  90 + Bitmap=NULL;
  91 + Mcast_addr = Multicast = Mcast_port = 0;
  92 + TftpEndingBlock = -1;
  93 +}
  94 +
  95 +#endif /* CONFIG_MCAST_TFTP */
  96 +
64 97 static __inline__ void
65 98 store_block (unsigned block, uchar * src, unsigned len)
66 99 {
67   - ulong offset = block * TFTP_BLOCK_SIZE + TftpBlockWrapOffset;
  100 + ulong offset = block * TftpBlkSize + TftpBlockWrapOffset;
68 101 ulong newsize = offset + len;
69 102 #ifdef CFG_DIRECT_FLASH_TFTP
70 103 int i, rc = 0;
... ... @@ -90,6 +123,10 @@
90 123 {
91 124 (void)memcpy((void *)(load_addr + offset), src, len);
92 125 }
  126 +#ifdef CONFIG_MCAST_TFTP
  127 + if (Multicast)
  128 + ext2_set_bit(block, Bitmap);
  129 +#endif
93 130  
94 131 if (NetBootFileXferSize < newsize)
95 132 NetBootFileXferSize = newsize;
... ... @@ -108,6 +145,13 @@
108 145 int len = 0;
109 146 volatile ushort *s;
110 147  
  148 +#ifdef CONFIG_MCAST_TFTP
  149 + /* Multicast TFTP.. non-MasterClients do not ACK data. */
  150 + if (Multicast
  151 + && (TftpState == STATE_DATA)
  152 + && (MasterClient == 0))
  153 + return;
  154 +#endif
111 155 /*
112 156 * We will always be sending some sort of packet, so
113 157 * cobble together the packet headers now.
114 158  
115 159  
... ... @@ -132,11 +176,30 @@
132 176 printf("send option \"timeout %s\"\n", (char *)pkt);
133 177 #endif
134 178 pkt += strlen((char *)pkt) + 1;
  179 + /* try for more effic. blk size */
  180 + pkt += sprintf((char *)pkt,"blksize%c%d%c",
  181 + 0,htons(TftpBlkSizeOption),0);
  182 +#ifdef CONFIG_MCAST_TFTP
  183 + /* Check all preconditions before even trying the option */
  184 + if (!ProhibitMcast
  185 + && (Bitmap=malloc(Mapsize))
  186 + && eth_get_dev()->mcast) {
  187 + free(Bitmap);
  188 + Bitmap=NULL;
  189 + pkt += sprintf((char *)pkt,"multicast%c%c",0,0);
  190 + }
  191 +#endif /* CONFIG_MCAST_TFTP */
135 192 len = pkt - xp;
136 193 break;
137 194  
138   - case STATE_DATA:
139 195 case STATE_OACK:
  196 +#ifdef CONFIG_MCAST_TFTP
  197 + /* My turn! Start at where I need blocks I missed.*/
  198 + if (Multicast)
  199 + TftpBlock=ext2_find_next_zero_bit(Bitmap,(Mapsize*8),0);
  200 + /*..falling..*/
  201 +#endif
  202 + case STATE_DATA:
140 203 xp = pkt;
141 204 s = (ushort *)pkt;
142 205 *s++ = htons(TFTP_ACK);
143 206  
... ... @@ -177,8 +240,13 @@
177 240 {
178 241 ushort proto;
179 242 ushort *s;
  243 + int i;
180 244  
181 245 if (dest != TftpOurPort) {
  246 +#ifdef CONFIG_MCAST_TFTP
  247 + if (Multicast
  248 + && (!Mcast_port || (dest != Mcast_port)))
  249 +#endif
182 250 return;
183 251 }
184 252 if (TftpState != STATE_RRQ && src != TftpServerPort) {
... ... @@ -208,6 +276,24 @@
208 276 #endif
209 277 TftpState = STATE_OACK;
210 278 TftpServerPort = src;
  279 + /* Check for 'blksize' option */
  280 + for (i=0;i<len-8;i++) {
  281 + if (strcmp ((char*)pkt+i,"blksize") == 0) {
  282 + TftpBlkSize = (unsigned short)
  283 + simple_strtoul((char*)pkt+i+8,NULL,10);
  284 +#ifdef ET_DEBUG
  285 + printf ("Blocksize ack: %s, %d\n",
  286 + (char*)pkt+i+8,TftpBlkSize);
  287 +#endif
  288 + break;
  289 + }
  290 + }
  291 +#ifdef CONFIG_MCAST_TFTP
  292 + parse_multicast_oack((char *)pkt,len-1);
  293 + if ((Multicast) && (!MasterClient))
  294 + TftpState = STATE_DATA; /* passive.. */
  295 + else
  296 +#endif
211 297 TftpSend (); /* Send ACK */
212 298 break;
213 299 case TFTP_DATA:
... ... @@ -224,7 +310,7 @@
224 310 */
225 311 if (TftpBlock == 0) {
226 312 TftpBlockWrap++;
227   - TftpBlockWrapOffset += TFTP_BLOCK_SIZE * TFTP_SEQUENCE_SIZE;
  313 + TftpBlockWrapOffset += TftpBlkSize * TFTP_SEQUENCE_SIZE;
228 314 printf ("\n\t %lu MB received\n\t ", TftpBlockWrapOffset>>20);
229 315 } else {
230 316 if (((TftpBlock - 1) % 10) == 0) {
... ... @@ -248,6 +334,11 @@
248 334 TftpBlockWrap = 0;
249 335 TftpBlockWrapOffset = 0;
250 336  
  337 +#ifdef CONFIG_MCAST_TFTP
  338 + if (Multicast) { /* start!=1 common if mcast */
  339 + TftpLastBlock = TftpBlock - 1;
  340 + } else
  341 +#endif
251 342 if (TftpBlock != 1) { /* Assertion */
252 343 printf ("\nTFTP error: "
253 344 "First block is not block 1 (%ld)\n"
254 345  
... ... @@ -274,9 +365,44 @@
274 365 * Acknoledge the block just received, which will prompt
275 366 * the server for the next one.
276 367 */
  368 +#ifdef CONFIG_MCAST_TFTP
  369 + /* if I am the MasterClient, actively calculate what my next
  370 + * needed block is; else I'm passive; not ACKING
  371 + */
  372 + if (Multicast) {
  373 + if (len < TftpBlkSize) {
  374 + TftpEndingBlock = TftpBlock;
  375 + } else if (MasterClient) {
  376 + TftpBlock = PrevBitmapHole =
  377 + ext2_find_next_zero_bit(
  378 + Bitmap,
  379 + (Mapsize*8),
  380 + PrevBitmapHole);
  381 + if (TftpBlock > ((Mapsize*8) - 1)) {
  382 + printf ("tftpfile too big\n");
  383 + /* try to double it and retry */
  384 + Mapsize<<=1;
  385 + mcast_cleanup();
  386 + NetStartAgain ();
  387 + return;
  388 + }
  389 + TftpLastBlock = TftpBlock;
  390 + }
  391 + }
  392 +#endif
277 393 TftpSend ();
278 394  
279   - if (len < TFTP_BLOCK_SIZE) {
  395 +#ifdef CONFIG_MCAST_TFTP
  396 + if (Multicast) {
  397 + if (MasterClient && (TftpBlock >= TftpEndingBlock)) {
  398 + puts ("\nMulticast tftp done\n");
  399 + mcast_cleanup();
  400 + NetState = NETLOOP_SUCCESS;
  401 + }
  402 + }
  403 + else
  404 +#endif
  405 + if (len < TftpBlkSize) {
280 406 /*
281 407 * We received the whole thing. Try to
282 408 * run it.
... ... @@ -290,6 +416,9 @@
290 416 printf ("\nTFTP error: '%s' (%d)\n",
291 417 pkt + 2, ntohs(*(ushort *)pkt));
292 418 puts ("Starting again\n\n");
  419 +#ifdef CONFIG_MCAST_TFTP
  420 + mcast_cleanup();
  421 +#endif
293 422 NetStartAgain ();
294 423 break;
295 424 }
... ... @@ -301,6 +430,9 @@
301 430 {
302 431 if (++TftpTimeoutCount > TIMEOUT_COUNT) {
303 432 puts ("\nRetry count exceeded; starting again\n");
  433 +#ifdef CONFIG_MCAST_TFTP
  434 + mcast_cleanup();
  435 +#endif
304 436 NetStartAgain ();
305 437 } else {
306 438 puts ("T ");
... ... @@ -370,6 +502,7 @@
370 502 TftpState = STATE_RRQ;
371 503 /* Use a pseudo-random port unless a specific port is set */
372 504 TftpOurPort = 1024 + (get_timer(0) % 3072);
  505 +
373 506 #ifdef CONFIG_TFTP_PORT
374 507 if ((ep = getenv("tftpdstp")) != NULL) {
375 508 TftpServerPort = simple_strtol(ep, NULL, 10);
376 509  
... ... @@ -382,9 +515,104 @@
382 515  
383 516 /* zero out server ether in case the server ip has changed */
384 517 memset(NetServerEther, 0, 6);
  518 + /* Revert TftpBlkSize to dflt */
  519 + TftpBlkSize = TFTP_BLOCK_SIZE;
  520 +#ifdef CONFIG_MCAST_TFTP
  521 + mcast_cleanup();
  522 +#endif
385 523  
386 524 TftpSend ();
387 525 }
388 526  
389   -#endif
  527 +#ifdef CONFIG_MCAST_TFTP
  528 +/* Credits: atftp project.
  529 + */
  530 +
  531 +/* pick up BcastAddr, Port, and whether I am [now] the master-client. *
  532 + * Frame:
  533 + * +-------+-----------+---+-------~~-------+---+
  534 + * | opc | multicast | 0 | addr, port, mc | 0 |
  535 + * +-------+-----------+---+-------~~-------+---+
  536 + * The multicast addr/port becomes what I listen to, and if 'mc' is '1' then
  537 + * I am the new master-client so must send ACKs to DataBlocks. If I am not
  538 + * master-client, I'm a passive client, gathering what DataBlocks I may and
  539 + * making note of which ones I got in my bitmask.
  540 + * In theory, I never go from master->passive..
  541 + * .. this comes in with pkt already pointing just past opc
  542 + */
  543 +static void parse_multicast_oack(char *pkt, int len)
  544 +{
  545 + int i;
  546 + IPaddr_t addr;
  547 + char *mc_adr, *port, *mc;
  548 +
  549 + mc_adr=port=mc=NULL;
  550 + /* march along looking for 'multicast\0', which has to start at least
  551 + * 14 bytes back from the end.
  552 + */
  553 + for (i=0;i<len-14;i++)
  554 + if (strcmp (pkt+i,"multicast") == 0)
  555 + break;
  556 + if (i >= (len-14)) /* non-Multicast OACK, ign. */
  557 + return;
  558 +
  559 + i+=10; /* strlen multicast */
  560 + mc_adr = pkt+i;
  561 + for (;i<len;i++) {
  562 + if (*(pkt+i) == ',') {
  563 + *(pkt+i) = '\0';
  564 + if (port) {
  565 + mc = pkt+i+1;
  566 + break;
  567 + } else {
  568 + port = pkt+i+1;
  569 + }
  570 + }
  571 + }
  572 + if (!port || !mc_adr || !mc ) return;
  573 + if (Multicast && MasterClient) {
  574 + printf ("I got a OACK as master Client, WRONG!\n");
  575 + return;
  576 + }
  577 + /* ..I now accept packets destined for this MCAST addr, port */
  578 + if (!Multicast) {
  579 + if (Bitmap) {
  580 + printf ("Internal failure! no mcast.\n");
  581 + free(Bitmap);
  582 + Bitmap=NULL;
  583 + ProhibitMcast=1;
  584 + return ;
  585 + }
  586 + /* I malloc instead of pre-declare; so that if the file ends
  587 + * up being too big for this bitmap I can retry
  588 + */
  589 + if (!(Bitmap = malloc (Mapsize))) {
  590 + printf ("No Bitmap, no multicast. Sorry.\n");
  591 + ProhibitMcast=1;
  592 + return;
  593 + }
  594 + memset (Bitmap,0,Mapsize);
  595 + PrevBitmapHole = 0;
  596 + Multicast = 1;
  597 + }
  598 + addr = string_to_ip(mc_adr);
  599 + if (Mcast_addr != addr) {
  600 + if (Mcast_addr)
  601 + eth_mcast_join(Mcast_addr, 0);
  602 + if (eth_mcast_join(Mcast_addr=addr, 1)) {
  603 + printf ("Fail to set mcast, revert to TFTP\n");
  604 + ProhibitMcast=1;
  605 + mcast_cleanup();
  606 + NetStartAgain();
  607 + }
  608 + }
  609 + MasterClient = (unsigned char)simple_strtoul((char *)mc,NULL,10);
  610 + Mcast_port = (unsigned short)simple_strtoul(port,NULL,10);
  611 + printf ("Multicast: %s:%d [%d]\n", mc_adr, Mcast_port, MasterClient);
  612 + return;
  613 +}
  614 +
  615 +#endif /* Multicast TFTP */
  616 +
  617 +#endif /* CFG_CMD_NET */