#define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crc32.h" #include "mtd/mtd-user.h" #include "mcast_image.h" #define min(x,y) ( (x)>(y)?(y):(x) ) #define WBUF_SIZE 2048 struct eraseblock { uint32_t flash_offset; unsigned char wbuf[WBUF_SIZE]; int wbuf_ofs; int nr_pkts; int *pkt_indices; uint32_t crc; }; int main(int argc, char **argv) { struct addrinfo *ai; struct addrinfo hints; struct addrinfo *runp; int ret; int sock; size_t len; int flfd; struct mtd_info_user meminfo; unsigned char *eb_buf, *decode_buf, **src_pkts; int nr_blocks = 0; int pkts_per_block; int block_nr = -1; uint32_t image_crc; int total_pkts = 0; loff_t mtdoffset = 0; int badcrcs = 0; int duplicates = 0; int file_mode = 0; struct fec_parms *fec; int i; struct eraseblock *eraseblocks = NULL; uint32_t start_seq; struct timeval start, now; unsigned long fec_time = 0, flash_time = 0, crc_time = 0; if (argc != 4) { fprintf(stderr, "usage: %s \n", (strrchr(argv[0], '/')?:argv[0]-1)+1); exit(1); } /* Open the device */ flfd = open(argv[3], O_RDWR); if (flfd >= 0) { /* Fill in MTD device capability structure */ if (ioctl(flfd, MEMGETINFO, &meminfo) != 0) { perror("MEMGETINFO"); close(flfd); flfd = -1; } else { printf("Receive to MTD device %s with erasesize %d\n", argv[3], meminfo.erasesize); } } if (flfd == -1) { /* Try again, as if it's a file */ flfd = open(argv[3], O_CREAT|O_TRUNC|O_RDWR, 0644); if (flfd < 0) { perror("open"); exit(1); } meminfo.erasesize = 131072; file_mode = 1; printf("Receive to file %s with (assumed) erasesize %d\n", argv[3], meminfo.erasesize); } pkts_per_block = (meminfo.erasesize + PKT_SIZE - 1) / PKT_SIZE; eb_buf = malloc(pkts_per_block * PKT_SIZE); decode_buf = malloc(pkts_per_block * PKT_SIZE); if (!eb_buf && !decode_buf) { fprintf(stderr, "No memory for eraseblock buffer\n"); exit(1); } src_pkts = malloc(sizeof(unsigned char *) * pkts_per_block); if (!src_pkts) { fprintf(stderr, "No memory for decode packet pointers\n"); exit(1); } memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; hints.ai_socktype = SOCK_DGRAM; ret = getaddrinfo(argv[1], argv[2], &hints, &ai); if (ret) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); exit(1); } runp = ai; for (runp = ai; runp; runp = runp->ai_next) { sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol); if (sock == -1) { perror("socket"); continue; } if (runp->ai_family == AF_INET && IN_MULTICAST( ntohl(((struct sockaddr_in *)runp->ai_addr)->sin_addr.s_addr))) { struct ip_mreq rq; rq.imr_multiaddr = ((struct sockaddr_in *)runp->ai_addr)->sin_addr; rq.imr_interface.s_addr = INADDR_ANY; if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &rq, sizeof(rq))) { perror("IP_ADD_MEMBERSHIP"); close(sock); continue; } } else if (runp->ai_family == AF_INET6 && ((struct sockaddr_in6 *)runp->ai_addr)->sin6_addr.s6_addr[0] == 0xff) { struct ipv6_mreq rq; rq.ipv6mr_multiaddr = ((struct sockaddr_in6 *)runp->ai_addr)->sin6_addr; rq.ipv6mr_interface = 0; if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &rq, sizeof(rq))) { perror("IPV6_ADD_MEMBERSHIP"); close(sock); continue; } } if (bind(sock, runp->ai_addr, runp->ai_addrlen)) { perror("bind"); close(sock); continue; } break; } if (!runp) exit(1); while (1) { struct image_pkt thispkt; len = read(sock, &thispkt, sizeof(thispkt)); if (len < 0) { perror("read socket"); break; } if (len < sizeof(thispkt)) { fprintf(stderr, "Wrong length %d bytes (expected %d)\n", len, sizeof(thispkt)); continue; } if (!eraseblocks) { image_crc = thispkt.hdr.totcrc; start_seq = ntohl(thispkt.hdr.pkt_sequence); if (meminfo.erasesize != ntohl(thispkt.hdr.blocksize)) { fprintf(stderr, "Erasesize mismatch (0x%x not 0x%x)\n", ntohl(thispkt.hdr.blocksize), meminfo.erasesize); exit(1); } nr_blocks = ntohl(thispkt.hdr.nr_blocks); fec = fec_new(pkts_per_block, ntohs(thispkt.hdr.nr_pkts)); eraseblocks = malloc(nr_blocks * sizeof(*eraseblocks)); if (!eraseblocks) { fprintf(stderr, "No memory for block map\n"); exit(1); } for (i = 0; i < nr_blocks; i++) { eraseblocks[i].pkt_indices = malloc(sizeof(int) * pkts_per_block); if (!eraseblocks[i].pkt_indices) { fprintf(stderr, "Failed to allocate packet indices\n"); exit(1); } eraseblocks[i].nr_pkts = 0; if (!file_mode) { if (mtdoffset >= meminfo.size) { fprintf(stderr, "Run out of space on flash\n"); exit(1); } #if 1 /* Deliberately use bad blocks... test write failures */ while (ioctl(flfd, MEMGETBADBLOCK, &mtdoffset) > 0) { printf("Skipping flash bad block at %08x\n", (uint32_t)mtdoffset); mtdoffset += meminfo.erasesize; } #endif } eraseblocks[i].flash_offset = mtdoffset; mtdoffset += meminfo.erasesize; eraseblocks[i].wbuf_ofs = 0; } gettimeofday(&start, NULL); } if (image_crc != thispkt.hdr.totcrc) { fprintf(stderr, "Image CRC changed from 0x%x to 0x%x. Aborting\n", ntohl(image_crc), ntohl(thispkt.hdr.totcrc)); exit(1); } block_nr = ntohl(thispkt.hdr.block_nr); if (block_nr >= nr_blocks) { fprintf(stderr, "Erroneous block_nr %d (> %d)\n", block_nr, nr_blocks); exit(1); } for (i=0; i= pkts_per_block) { /* We have a block which we didn't really need */ eraseblocks[block_nr].nr_pkts++; continue; } if (crc32(-1, thispkt.data, PKT_SIZE) != ntohl(thispkt.hdr.thiscrc)) { printf("Discard %08x pkt %d with bad CRC (%08x not %08x)\n", block_nr * meminfo.erasesize, ntohs(thispkt.hdr.pkt_nr), crc32(-1, thispkt.data, PKT_SIZE), ntohl(thispkt.hdr.thiscrc)); badcrcs++; continue; } pkt_again: eraseblocks[block_nr].pkt_indices[eraseblocks[block_nr].nr_pkts++] = ntohs(thispkt.hdr.pkt_nr); total_pkts++; if (!(total_pkts % 50) || total_pkts == pkts_per_block * nr_blocks) { uint32_t pkts_sent = ntohl(thispkt.hdr.pkt_sequence) - start_seq - 1; long time_msec; gettimeofday(&now, NULL); time_msec = ((now.tv_usec - start.tv_usec) / 1000) + (now.tv_sec - start.tv_sec) * 1000; printf("\rReceived %d/%d (%d%%) in %lds @%ldKiB/s, %d lost (%d%%), %d dups ", total_pkts, nr_blocks * pkts_per_block, total_pkts * 100 / nr_blocks / pkts_per_block, time_msec / 1000, total_pkts * PKT_SIZE / 1024 * 1000 / time_msec, pkts_sent - total_pkts - duplicates, (pkts_sent - total_pkts - duplicates) * 100 / pkts_sent, duplicates); } if (eraseblocks[block_nr].wbuf_ofs + PKT_SIZE < WBUF_SIZE) { /* New packet doesn't full the wbuf */ memcpy(eraseblocks[block_nr].wbuf + eraseblocks[block_nr].wbuf_ofs, thispkt.data, PKT_SIZE); eraseblocks[block_nr].wbuf_ofs += PKT_SIZE; } else { int fits = WBUF_SIZE - eraseblocks[block_nr].wbuf_ofs; ssize_t wrotelen; memcpy(eraseblocks[block_nr].wbuf + eraseblocks[block_nr].wbuf_ofs, thispkt.data, fits); wrotelen = pwrite(flfd, eraseblocks[block_nr].wbuf, WBUF_SIZE, eraseblocks[block_nr].flash_offset); if (wrotelen < WBUF_SIZE) { if (wrotelen < 0) perror("packet write"); else fprintf(stderr, "short write of packet wbuf\n"); if (!file_mode) { struct erase_info_user erase; /* FIXME: Perhaps we should store pkt crcs and try to recover data from the offending eraseblock */ erase.start = eraseblocks[block_nr].flash_offset; erase.start -= eraseblocks[block_nr].nr_pkts * PKT_SIZE / WBUF_SIZE * WBUF_SIZE; erase.length = meminfo.erasesize; if (ioctl(flfd, MEMERASE, &erase)) { perror("MEMERASE"); exit(1); } if (mtdoffset >= meminfo.size) { fprintf(stderr, "Run out of space on flash\n"); exit(1); } while (ioctl(flfd, MEMGETBADBLOCK, &mtdoffset) > 0) { printf("Skipping flash bad block at %08x\n", (uint32_t)mtdoffset); mtdoffset += meminfo.erasesize; } eraseblocks[block_nr].flash_offset = mtdoffset; total_pkts -= eraseblocks[block_nr].nr_pkts; eraseblocks[block_nr].nr_pkts = 0; goto pkt_again; } else /* Usually nothing we can do in file mode */ exit(1); } eraseblocks[block_nr].flash_offset += WBUF_SIZE; /* Copy the remainder into the wbuf */ memcpy(eraseblocks[block_nr].wbuf, &thispkt.data[fits], PKT_SIZE - fits); eraseblocks[block_nr].wbuf_ofs = PKT_SIZE - fits; } if (eraseblocks[block_nr].nr_pkts == pkts_per_block) { eraseblocks[block_nr].crc = ntohl(thispkt.hdr.block_crc); if (total_pkts == nr_blocks * pkts_per_block) break; } } close(sock); for (block_nr = 0; block_nr < nr_blocks; block_nr++) { ssize_t rwlen; eraseblocks[block_nr].flash_offset -= meminfo.erasesize; rwlen = pread(flfd, eb_buf, meminfo.erasesize, eraseblocks[block_nr].flash_offset); if (rwlen < 0) { perror("read"); /* Argh. Perhaps we could go back and try again, but if the flash is going to fail to read back what we write to it, and the whole point in this program is to write to it, what's the point? */ fprintf(stderr, "Packets we wrote to flash seem to be unreadable. Aborting\n"); exit(1); } memcpy(eb_buf + meminfo.erasesize, eraseblocks[block_nr].wbuf, eraseblocks[block_nr].wbuf_ofs); for (i=0; i < pkts_per_block; i++) src_pkts[i] = &eb_buf[i * PKT_SIZE]; gettimeofday(&start, NULL); if (fec_decode(fec, src_pkts, eraseblocks[block_nr].pkt_indices, PKT_SIZE)) { /* Eep. This cannot happen */ printf("The world is broken. fec_decode() returned error\n"); exit(1); } gettimeofday(&now, NULL); fec_time += (now.tv_usec - start.tv_usec) / 1000; fec_time += (now.tv_sec - start.tv_sec) * 1000; for (i=0; i < pkts_per_block; i++) memcpy(&decode_buf[i*PKT_SIZE], src_pkts[i], PKT_SIZE); /* Paranoia */ gettimeofday(&start, NULL); if (crc32(-1, decode_buf, meminfo.erasesize) != eraseblocks[block_nr].crc) { printf("CRC mismatch: want %08x got %08x\n", eraseblocks[block_nr].crc, crc32(-1, decode_buf, meminfo.erasesize)); } gettimeofday(&now, NULL); crc_time += (now.tv_usec - start.tv_usec) / 1000; crc_time += (now.tv_sec - start.tv_sec) * 1000; start = now; if (!file_mode) { struct erase_info_user erase; erase.start = eraseblocks[block_nr].flash_offset; erase.length = meminfo.erasesize; printf("Erasing block at %08x...", erase.start); if (ioctl(flfd, MEMERASE, &erase)) { perror("MEMERASE"); /* This block has dirty data on it. If the erase failed, we're screwed */ fprintf(stderr, "Erase to clean FEC data from flash failed. Aborting\n"); exit(1); } } write_again: rwlen = pwrite(flfd, decode_buf, meminfo.erasesize, eraseblocks[block_nr].flash_offset); if (rwlen < meminfo.erasesize) { if (rwlen < 0) { perror("decoded data write"); } else fprintf(stderr, "short write of decoded data\n"); if (!file_mode) { struct erase_info_user erase; erase.start = eraseblocks[block_nr].flash_offset; erase.length = meminfo.erasesize; printf("Erasing failed block at %08x\n", eraseblocks[block_nr].flash_offset); if (ioctl(flfd, MEMERASE, &erase)) { perror("MEMERASE"); exit(1); } if (mtdoffset >= meminfo.size) { fprintf(stderr, "Run out of space on flash\n"); exit(1); } while (ioctl(flfd, MEMGETBADBLOCK, &mtdoffset) > 0) { printf("Skipping flash bad block at %08x\n", (uint32_t)mtdoffset); mtdoffset += meminfo.erasesize; } printf("Will try again at %08lx...", (long)mtdoffset); eraseblocks[block_nr].flash_offset = mtdoffset; goto write_again; } else /* Usually nothing we can do in file mode */ exit(1); } gettimeofday(&now, NULL); flash_time += (now.tv_usec - start.tv_usec) / 1000; flash_time += (now.tv_sec - start.tv_sec) * 1000; printf("wrote image block %08x (%d pkts)\n", block_nr * meminfo.erasesize, eraseblocks[block_nr].nr_pkts); } close(flfd); printf("FEC time %ld.%03lds\n", fec_time / 1000, fec_time % 1000); printf("CRC time %ld.%03lds\n", crc_time / 1000, crc_time % 1000); printf("flash IO %ld.%03lds\n", flash_time / 1000, flash_time % 1000); return 0; }