Explorar o código

flash update: handle data from a memory buffer; add header

Handle flash data from a memory buffer.

Add a header to each flash chunk; tools/wrapflash.pl creates a bundle.
H. Peter Anvin %!s(int64=3) %!d(string=hai) anos
pai
achega
2e7c547f79
Modificáronse 6 ficheiros con 396 adicións e 143 borrados
  1. 1 1
      rv32/checksum.h
  2. 1 1
      rv32/io.h
  3. 3 1
      rv32/irq.h
  4. 283 136
      rv32/spiflash.c
  5. 30 4
      rv32/spiflash.h
  6. 78 0
      tools/wrapflash.pl

+ 1 - 1
rv32/checksum.h

@@ -1,4 +1,4 @@
 #ifndef CHECKSUM_H
 #define CHECKSUM_H
-#define SDRAM_SUM 0xf0181ce9
+#define SDRAM_SUM 0xf0001ce9
 #endif

+ 1 - 1
rv32/io.h

@@ -38,7 +38,7 @@ enum leds {
 
 static __always_inline no_return reset(unsigned int type)
 {
-    p_maskirq(~0, 0);		/* Block all interrupts */
+    disable_irqs();
     for (;;)
 	SYS_RESET = type;
 }

+ 3 - 1
rv32/irq.h

@@ -3,6 +3,7 @@
 
 #include "compiler.h"
 #include "picorv32.h"
+#include "iodevs.h"		/* For _IRQ constants */
 
 typedef void (*irqhandler_t)(unsigned int vector, size_t pc);
 extern irqhandler_t __irq_handler_table[];
@@ -17,9 +18,10 @@ extern irqhandler_t __irq_handler_table[];
 
 typedef unsigned int irqmask_t;
 
+/* Disable IRQs except fatal system errors (to facilitate debugging) */
 static inline irqmask_t disable_irqs(void)
 {
-    return p_maskirq(~0U, 0);
+    return p_maskirq(~((1 << EBREAK_IRQ)|(1 << BUSERR_IRQ)), 0);
 }
 static inline irqmask_t restore_irqs(irqmask_t mask)
 {

+ 283 - 136
rv32/spiflash.c

@@ -9,40 +9,78 @@ struct spz_stream {
     const struct spiflash *flash;
     int (*read_data)(spz_stream *); /* Routine to get more data */
     int (*end_data)(z_stream *);    /* Termination routine for zlib */
-    int err;			    /* Error code to return */
-    bool eoi;			    /* Reached end of input */
     uint8_t *optr;		    /* Output data pointer into obuf */
     /* Note: available output data ends at zs->next_out */
     uint8_t *ibuf;		    /* Input buffer if compressed */
     uint8_t *obuf;		    /* Output buffer */
     uint8_t *vbuf;		    /* Readback/verify buffer */
+    const struct spiflash_header *header;
+    uint32_t crc32;		    /* Input data CRC32 */
+    unsigned int input_left;	    /* Input data unread */
+    int err;			    /* Error code to return */
+    bool eoi;			    /* Reached end of input */
+    bool free_header;		    /* header is malloc()'d */
 };
 
+static void *spz_malloc(spz_stream *spz, size_t bytes)
+{
+    void *p = malloc(bytes);
+    if (!p && !spz->err) {
+	spz->err = Z_MEM_ERROR;
+    }
+    return p;
+}
+
 static int spiflash_read_data(spz_stream *spz)
 {
     int rv;
+    int (*read_data)(void *, void *, unsigned int);
+    unsigned int read_block_size;
 
-    if (spz->eoi)
+    if (spz->eoi || spz->err)
 	return 0;
 
-    spz->zs.next_in  = NULL;
-    spz->zs.avail_in = 0;
-
-    rv = spz->flash->ops->read_data(spz->flash->cookie,
-				    spz->ibuf, SPIFLASH_BLOCK_SIZE);
-    if (rv != SPIFLASH_BLOCK_SIZE)
+    read_data = spz->flash->ops->read_data;
+    if (!spz->input_left || !read_data) {
 	spz->eoi = true;
-
-    if (rv < 0) {
-	if (!spz->err)
-	    spz->err = rv;
 	return 0;
     }
 
+    read_block_size = min(spz->input_left, SPIFLASH_BLOCK_SIZE);
+
+    if (!spz->ibuf) {
+	spz->ibuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+	if (!spz->ibuf) {
+	    spz->eoi = true;
+	    return 0;
+	}
+    }
+
     spz->zs.next_in  = spz->ibuf;
-    spz->zs.avail_in = rv;
+    spz->zs.avail_in = 0;
+
+    rv = read_data(spz->flash->cookie, spz->ibuf, read_block_size);
+    if (spz->err) {
+	rv = 0;
+    } else if (rv < 0) {
+	spz->err = rv;
+	rv = 0;
+    }
+    if (rv != (int)read_block_size)
+	spz->eoi = true;
+
+    if (rv) {
+	spz->crc32 = crc32(spz->crc32, spz->ibuf, rv);
+	spz->input_left -= rv;
+	if (!spz->input_left) {
+	    if (spz->crc32 != spz->header->crc32) {
+		spz->err = Z_STREAM_ERROR;
+		rv = 0;
+	    }
+	}
+    }
 
-    return rv;
+    return spz->zs.avail_in = rv;
 }
 
 static int read_data_raw(spz_stream *spz)
@@ -50,17 +88,16 @@ static int read_data_raw(spz_stream *spz)
     int rlen;
 
     if (spz->eoi)
-	return Z_STREAM_END;
+	return 0;
 
     rlen = spiflash_read_data(spz);
-    if (rlen < 0)
-	return rlen;		/* Error! */
-
-    spz->optr         = spz->ibuf;
-    spz->zs.next_out  = spz->ibuf + rlen;
-    spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen;
+    if (rlen) {
+	spz->optr         = spz->ibuf;
+	spz->zs.next_out  = spz->ibuf + rlen;
+	spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen;
+    }
 
-    return spz->eoi ? Z_STREAM_END : Z_OK;
+    return rlen;
 }
 
 static int read_data_inflate(spz_stream *spz)
@@ -82,90 +119,155 @@ static int read_data_inflate(spz_stream *spz)
 	if (rv == Z_OK || (rv == Z_BUF_ERROR && !spz->eoi))
 	    continue;
 
-	if (rv != Z_STREAM_END) {
-	    if (!spz->err)
-		spz->err = rv >= 0 ? Z_STREAM_ERROR : rv;
-	}
+	spz->eoi = true;
+	if (rv != Z_STREAM_END && !spz->err)
+	    spz->err = rv;
 	break;
     }
 
-    if (rv != Z_OK) {
-	/* Z_STREAM_END or error */
-	inflateEnd(&spz->zs);
-    }
-
-    return rv;
-}
-
-static void *spz_malloc(spz_stream *spz, size_t bytes)
-{
-    void *p = malloc(bytes);
-    if (!p && !spz->err) {
-	spz->err = Z_MEM_ERROR;
-    }
-    return p;
+    return spz->zs.next_out - spz->optr;
 }
 
-int spiflash_data_init(spz_stream *spz, const struct spiflash *flash)
+/*
+ * spz needs to be initialized to zero except the flash, zs.next_in,
+ * and zs.avail_in fields.
+ *
+ * Returns Z_STREAM_END on end of data.
+ */
+static int spiflash_data_init(spz_stream *spz)
 {
-    int rlen;
     int rv = Z_OK;
     uint8_t *rdbuf = NULL;
+    int rlen;
+    int (*read_data)(void *cookie, void *buf, unsigned int bufsize);
+    uint32_t header_crc;
 
-    memset(spz, 0, sizeof *spz);
-    spz->flash = flash;
+    spz->vbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+    if (!spz->vbuf)
+	goto err;
 
-    spz->ibuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
-    if (!spz->ibuf)
+    rlen = spz->zs.avail_in;
+    spz->header = (void *)spz->zs.next_in;
+
+    read_data = spz->flash->ops->read_data;
+    spz->eoi = !read_data;
+    if (!rlen && read_data) {
+	struct spiflash_header *header;
+	spz->header = header = spz_malloc(spz, sizeof *spz->header);
+	spz->free_header = true;
+	spz->input_left = UINT_MAX; /* Unknown at this point */
+	rlen = read_data(spz->flash->cookie, header, sizeof *header);
+    }
+    if (spz->err)
+	goto err;
+    if (rlen < (int)sizeof *spz->header) {
+	spz->err = rlen ? Z_STREAM_ERROR : Z_STREAM_END;
 	goto err;
+    }
 
-    spz->vbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
-    if (!spz->vbuf)
+    rlen -= sizeof *spz->header;
+    spz->zs.next_in += sizeof *spz->header;
+
+    /*
+     * Check header magic and CRC
+     */
+    if (spz->header->magic != SPIFLASH_MAGIC) {
+	spz->err = Z_STREAM_ERROR;
+	goto err;
+    }
+    header_crc = crc32(0, NULL, 0);
+    header_crc = crc32(header_crc, (const void *)spz->header,
+		       sizeof spz->header - 4);
+    if (header_crc != spz->header->header_crc32 ||
+	spz->header->zlen > spz->header->dlen ||
+	(spz->eoi && (int)spz->header->zlen > rlen)) {
+	spz->err = Z_STREAM_ERROR;
+	goto err;
+    }
+    if (!spz->header->dlen) {
+	/* End of data */
+	spz->err = Z_STREAM_END;
 	goto err;
+    }
 
-    rlen = spiflash_read_data(spz);
+    if (rlen > (int)spz->header->zlen)
+	rlen = spz->header->zlen;
+    spz->zs.avail_in = rlen;
+
+    spz->crc32 = crc32(0, NULL, 0);
+    if (rlen) {
+	/* Received data in input buffer already */
+	spz->crc32 = crc32(spz->crc32, spz->zs.next_in, spz->zs.avail_in);
+    }
 
-    if (rlen >= 14 && !memcmp(spz->ibuf, "\37\213\10", 3)) {
-	/* It is a gzip file */
-	spz->zs.next_in   = spz->ibuf;
-	spz->zs.avail_in  = rlen;
-	spz->read_data    = read_data_inflate;
+    spz->input_left = spz->header->zlen - rlen;
+    if (!spz->input_left) {
+	if (spz->crc32 != spz->header->crc32) {
+	    spz->err = Z_STREAM_ERROR;
+	    goto err;
+	}
+    }
 
+    if (spz->header->zlen == spz->header->dlen) {
+	/* Assume it is a raw binary; input buffer is output buffer */
+	spz->read_data    = read_data_raw;
+	spz->optr         = spz->zs.next_in;
+	spz->zs.next_out  = spz->zs.next_in + spz->zs.avail_in;
+    } else {
+	/* Compressed data? */
 	spz->obuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
 	if (!spz->obuf)
 	    goto err;
 
-	rv = inflateInit2(&spz->zs, 16 + 15);	/* gzip, max window size */
-	if (rv != Z_OK) {
-	    spz->err = rv;
+	if (rlen >= 14 && !memcmp(spz->zs.next_in, "\37\213\10", 3)) {
+	    /* It is a gzip file */
+	    spz->read_data = read_data_inflate;
+	    /* gzip, max window size */
+	    rv = inflateInit2(&spz->zs, 16 + 15);
+	    if (rv != Z_OK && rv != Z_STREAM_END) {
+		spz->err = rv;
+		goto err;
+	    }
+	    spz->eoi = rv == Z_STREAM_END;
+	    spz->end_data = inflateEnd;
+	} else {
+	    /* Unknown compression format */
+	    spz->err = Z_STREAM_ERROR;
 	    goto err;
 	}
-	spz->end_data = inflateEnd;
-    } else {
-	/* Assume it is a raw binary; input buffer is output buffer */
-	spz->optr         = spz->ibuf;
-	spz->zs.next_out  = spz->ibuf + rlen;
-	spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen;
-	rv = spz->eoi ? Z_STREAM_END : Z_OK;
     }
 
 err:
-    return spz->err ? spz->err : rv;
+    return spz->err;
  }
 
-static void spiflash_data_cleanup(spz_stream *spz)
+static int spiflash_data_cleanup(spz_stream *spz)
 {
+    int err = 0;
+
     if (!spz)
-	return;
+	return 0;
+
+    err = spz->err;
+
+    if (spz->flash->ops->close_data) {
+	int rv = spz->flash->ops->close_data(&spz->zs, spz->flash->cookie);
+	if (!err)
+	    err = rv;
+    }
 
     if (spz->end_data)
 	spz->end_data(&spz->zs);
+    if (spz->free_header)
+	free((void *)spz->header);
     if (spz->vbuf)
 	free(spz->vbuf);
     if (spz->obuf)
 	free(spz->obuf);
     if (spz->ibuf)
 	free(spz->ibuf);
+
+    return err;
 }
 
 /*
@@ -406,103 +508,144 @@ static int spiflash_check_block(spz_stream *spz, uint32_t addr,
     return 0;
 }
 
-int spiflash_flash_file(const struct spiflash *flash, uint32_t addr)
+int spiflash_flash_files(const struct spiflash *flash, void *buf, size_t buflen)
 {
     spz_stream _spz;
     spz_stream * const spz = &_spz; /* For consistency in notation */
-    int rv;
-    bool eof;
+    int  err;
     enum flashmem_status fs;
+    uint8_t *padbuf = NULL;
 
-    rv = spiflash_data_init(spz, flash);
-    eof = rv != Z_OK;
-
-    while (!eof && !spz->err) {
-	unsigned int bytes = spz->zs.next_out - spz->optr;
-	unsigned int padding;
+    err = 0;
+    while (!err) {
+	int rv;
+	bool eof;
+	uint32_t addr;
 
-	if (bytes < SPIFLASH_BLOCK_SIZE) {
-	    int rv;
+	memset(spz, 0, sizeof *spz);
+	spz->zs.avail_in = buflen;
+	spz->zs.next_in  = buf;
+	spz->flash = flash;
 
-	    rv = spz->read_data(spz);
-	    eof = rv != Z_OK || spz->err;
-	    bytes = spz->zs.next_out - spz->optr;
-	    if (!bytes)
-		break;
+	if (spiflash_data_init(spz))
+	    goto err;
 
-	    padding = -bytes & (SPIFLASH_BLOCK_SIZE-1);
-	    if (padding > spz->zs.avail_out) {
-		/* This should never happen */
-		padding = spz->zs.avail_out;
-	    }
-	    if (padding) {
-		memset(spz->zs.next_out, 0xff, padding);
-		spz->zs.avail_out -= padding;
-		bytes += padding;
+	if (!spz->ibuf) {
+	    /* No ibuf allocated, feeding from raw data buffer */
+	    unsigned int bufskip = (spz->zs.next_out - (uint8_t *)buf + 3) & ~3;
+	    if (bufskip >= buflen) {
+		buflen = 0;
+		buf = NULL;
+	    } else {
+		buflen -= bufskip;
+		buf = spz->zs.next_out;
 	    }
+	} else {
+	    /* Buffer exhausted, additional data read */
+	    buflen = 0;
+	    buf = NULL;
 	}
 
-	uint32_t erase_mask;
-	uint32_t prog_mask[SPIFLASH_BLOCK_SIZE >> (SPIFLASH_PAGE_SHIFT+5)];
+	eof = false;
+	addr = spz->header->address;
 
-	rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
-	if (rv)
-	    break;
+	while (!eof && !spz->err) {
+	    unsigned int bytes = spz->zs.next_out - spz->optr;
+	    unsigned int padding;
 
-	if (erase_mask) {
-	    rv = spiflash_erase(spz->flash, addr, erase_mask);
-	    if (rv)
-		break;
-	    rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
-	    if (spz->err)
-		break;
-	    if (erase_mask) {
-		spz->err = SPIFLASH_ERR_ERASE_FAILED;
-		break;
+	    if (bytes < SPIFLASH_BLOCK_SIZE) {
+		int rv;
+
+		rv = spz->read_data(spz);
+		if (spz->err || rv < 0)
+		    goto err;
+		eof = !rv;
+		bytes = spz->zs.next_out - spz->optr;
+		if (!bytes)
+		    break;
+
+		padding = -bytes & (SPIFLASH_BLOCK_SIZE-1);
+		if (padding) {
+		    if (!padbuf) {
+			padbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+			if (!padbuf)
+			    goto err;
+		    }
+		    memcpy(padbuf, spz->optr, bytes);
+		    memset(padbuf+bytes, 0xff, padding);
+		    spz->optr = padbuf;
+		    eof = true;
+		}
 	    }
-	}
 
-	unsigned int page;
+	    uint32_t erase_mask;
+	    uint32_t prog_mask[SPIFLASH_BLOCK_SIZE >> (SPIFLASH_PAGE_SHIFT+5)];
 
-	for (page = 0; page < (SPIFLASH_BLOCK_SIZE >> SPIFLASH_PAGE_SHIFT);
-	     page++) {
-	    uint32_t page_offs = page << SPIFLASH_PAGE_SHIFT;
+	    rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
+	    if (rv)
+		goto err;
 
-	    if (prog_mask[page >> 5] & (UINT32_C(1) << (page & 31))) {
-		rv = spiflash_program(spz->flash, addr + page_offs,
-				      spz->optr + page_offs,
-				      SPIFLASH_PAGE_SIZE);
+	    if (erase_mask) {
+		rv = spiflash_erase(spz->flash, addr, erase_mask);
 		if (rv) {
 		    spz->err = rv;
-		    break;
+		    goto err;
 		}
 
-		/* Read back data and verify */
-		rv = spiflash_read(spz->flash, addr+page_offs,
-				   spz->vbuf + page_offs,
-				   SPIFLASH_PAGE_SIZE);
-		if (rv) {
-		    spz->err = rv;
-		    break;
+		rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
+		if (spz->err)
+		    goto err;
+		if (erase_mask) {
+		    rv = SPIFLASH_ERR_ERASE_FAILED;
+		    goto err;
 		}
+	    }
 
-		if (memcmp(spz->optr + page_offs, spz->vbuf + page_offs,
-			   SPIFLASH_PAGE_SIZE)) {
+	    unsigned int page;
+
+	    for (page = 0; page < (SPIFLASH_BLOCK_SIZE >> SPIFLASH_PAGE_SHIFT);
+		 page++) {
+		uint32_t page_offs = page << SPIFLASH_PAGE_SHIFT;
+
+		if (prog_mask[page >> 5] & (UINT32_C(1) << (page & 31))) {
+		    rv = spiflash_program(spz->flash, addr + page_offs,
+					  spz->optr + page_offs,
+					  SPIFLASH_PAGE_SIZE);
+		    if (rv) {
+			spz->err = rv;
+			goto err;
+		    }
+
+		    /* Read back data and verify */
+		    rv = spiflash_read(spz->flash, addr+page_offs,
+				       spz->vbuf + page_offs,
+				       SPIFLASH_PAGE_SIZE);
+		    if (rv) {
+			spz->err = rv;
+			goto err;
+		    }
+
+		    if (memcmp(spz->optr + page_offs, spz->vbuf + page_offs,
+			       SPIFLASH_PAGE_SIZE)) {
 			spz->err = SPIFLASH_ERR_PROGRAM_FAILED;
-			break;
+			goto err;
+		    }
 		}
 	    }
+
+	    spz->optr += SPIFLASH_BLOCK_SIZE;
+	    addr      += SPIFLASH_BLOCK_SIZE;
 	}
 
-	spz->optr += SPIFLASH_BLOCK_SIZE;
-	addr      += SPIFLASH_BLOCK_SIZE;
+    err:
+	err = spiflash_data_cleanup(spz);
     }
 
-    if (spz->flash->ops->close_data)
-	spz->flash->ops->close_data(spz->flash->cookie);
-
-    spiflash_data_cleanup(spz);
-    return spz->err;
+    if (padbuf) {
+	free(padbuf);
+	padbuf = NULL;
+    }
+    return err == Z_STREAM_END ? 0 : err;
 }
 
 /*
@@ -529,3 +672,7 @@ int spiflash_read_vdid(const struct spiflash *flash, void *vdid)
 				sizeof read_vdid,
 				vdid, SPIFLASH_VDID_LEN, flash->param->tshsl);
 }
+
+/*
+ * Flash data from memory buffer(s)
+ */

+ 30 - 4
rv32/spiflash.h

@@ -5,6 +5,23 @@
 #include <stddef.h>
 #include <string.h>
 
+/*
+ * Firmware blob data header. A dlen of 0 means no futher blobs.
+ * dlen == zlen means raw binary data, not compressed.
+ */
+#define SPIFLASH_MAGIC		0xe301e7eb
+#define SPIFLASH_HEADER_CRC32	0x99d8ef20
+
+struct spiflash_header {
+    uint32_t magic;		/* Magic number */
+    uint32_t zlen;		/* Compressed data length */
+    uint32_t dlen;		/* Uncompressed data length */
+    uint32_t crc32;		/* CRC32 of (raw) data block */
+    uint32_t address;		/* Target address in flash (block aligned) */
+    uint32_t resv[2];		/* Set to zero for now */
+    uint32_t header_crc32;	/* CRC32 of above fields */
+};
+
 /*
  * A page is the maximum amount that can be programmed in one operation.
  * A sector is the minimum amount that can be erased in one operation.
@@ -54,6 +71,16 @@ struct spiflash_ops {
      * aligned to a malloc() alignment boundary; it will preserve
      * alignment boundaries if and only if short read returns only byte
      * counts in multiple of those alignment boundaries.
+     *
+     * If there is a memory buffer available containing full or
+     * partial input data on entry, pass it to spiflash_flash_file();
+     * If this memory buffer contains all available input, this
+     * function can be NULL.
+     *
+     * A partial memory buffer must contain the full stream header.
+     *
+     * A full memory buffer must either be expanded to a multiple
+     * of SPIFLASH_BLOCK_SIZE or safely allow the flash code to do so.
      */
     int (*read_data)(void *cookie, void *buf, unsigned int bufsize);
 
@@ -62,7 +89,7 @@ struct spiflash_ops {
      * or an error happened.) May be NULL. A nonzero value will be passed
      * to the top-level routine as an error.
      */
-    int (*close_data)(void *cookie);
+    int (*close_data)(const z_stream *zs, void *cookie);
 
     /*
      * Perform a SPI write operation. The SPI write operation consists of:
@@ -195,9 +222,8 @@ struct spiflash {
  * functions, any of the negative error values defined in zlib.h,
  * or one of the above error codes.
  */
-int spiflash_flash_file(const struct spiflash *flash, uint32_t addr);
-int spiflash_read(const struct spiflash *flash,
-		  uint32_t addr, void *buffer, size_t len);
+int spiflash_flash_file(const struct spiflash *flash,
+			void *data, size_t datalen);
 
 /*
  * Read identifying data from SPI flash.

+ 78 - 0
tools/wrapflash.pl

@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+use strict;
+use integer;
+use Digest::CRC;
+
+my $MAX_DATA_LEN = 16 << 20;
+my $SPIFLASH_MAGIC = 0xe201e7eb;
+
+sub getint($) {
+    my($s) = @_;
+    return ($s =~ /^0/) ? oct $s : $s+0;
+}
+sub filelen($) {
+    my($f) = @_;
+    my @s = stat($f);
+
+    return $s[7];
+}
+sub makeheader($$$$) {
+    my($zlen,$dlen,$dcrc,$address) = @_;
+
+    my $hdr = pack("V*", $SPIFLASH_MAGIC, $zlen, $dlen, $dcrc, $address,
+		   0, 0);
+    $hdr .= pack("V", Digest::CRC::crc32($hdr));
+    return $hdr;
+}
+
+my $outfile = shift @ARGV;
+
+if (!defined($outfile)) {
+    die "Usage: $0 outfile [infile addr]...\n";
+}
+
+open(my $out, '>', $outfile)
+    or die "$0: $outfile: $!\n";
+binmode($outfile);
+
+my $err;
+while (1) {
+    my $infile = shift @ARGV;
+    my $inaddr = getint(shift @ARGV);
+
+    last if (!defined($infile));
+
+    my $in;
+    if (!open($in, '<', $infile)) {
+	$err = "$infile: $!";
+	last;
+    }
+    binmode($in);
+
+    my $data;
+    my $zlen = read($in, $data, $MAX_DATA_LEN);
+    close($in);
+
+    my $dlen = $zlen;
+    if (substr($in, 0, 3) == "\37\213\10") {
+	# gzip file
+	$dlen = unpack("V", substr($data, -4));
+    }
+    my $dcrc = Digest::CRC::crc32($data);
+    print $out makeheader($zlen, $dlen, $dcrc, $inaddr);
+    print $out $data;
+    if ($zlen & 3) {
+	# pad to dword boundary
+	print $out "\0" x ((4-$zlen) & 3);
+    }
+}
+
+if (defined($err)) {
+    close($out);
+    unlink($outfile);
+    die "$0: $err\n";
+}
+
+print $out makeheader(0, 0, Digest::CRC::crc32(''), 0);
+close($out);