firmware-utils: add new firmware generation tool for the TP-LINK TL-WR941ND device
authorGabor Juhos <juhosg@openwrt.org>
Thu, 26 Feb 2009 16:59:15 +0000 (16:59 +0000)
committerGabor Juhos <juhosg@openwrt.org>
Thu, 26 Feb 2009 16:59:15 +0000 (16:59 +0000)
SVN-Revision: 14672

tools/firmware-utils/Makefile
tools/firmware-utils/src/md5.c [new file with mode: 0644]
tools/firmware-utils/src/md5.h [new file with mode: 0644]
tools/firmware-utils/src/mktplinkfw.c [new file with mode: 0644]

index 5261a2bb62c7a0c52c43c9c9ede2e281aa5e9b3a..d789dcb34f8e548f830d03ab5ec782cf993b6e97 100644 (file)
@@ -39,6 +39,7 @@ define Host/Compile
        $(call cc,add_header)
        $(call cc,makeamitbin)
        $(call cc2,mkplanexfw sha1)
+       $(call cc2,mktplinkfw md5)
 endef
 
 define Host/Install
diff --git a/tools/firmware-utils/src/md5.c b/tools/firmware-utils/src/md5.c
new file mode 100644 (file)
index 0000000..2039760
--- /dev/null
@@ -0,0 +1,307 @@
+
+
+/*
+ ***********************************************************************
+ ** md5.c -- the source code for MD5 routines                         **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 1/91 SRD,AJ,BSK,JT Reference C ver., 7/10 constant corr. **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#include <string.h>
+#include "md5.h"
+
+/*
+ ***********************************************************************
+ **  Message-digest routines:                                         **
+ **  To form the message digest for a message M                       **
+ **    (1) Initialize a context buffer mdContext using MD5_Init       **
+ **    (2) Call MD5_Update on mdContext and M                         **
+ **    (3) Call MD5_Final on mdContext                                **
+ **  The message digest is now in mdContext->digest[0...15]           **
+ ***********************************************************************
+ */
+
+/* forward declaration */
+static void Transform ();
+
+static unsigned char PADDING[64] = {
+  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+  {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) \
+  {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) \
+  {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) \
+  {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+
+#ifdef __STDC__
+#define UL(x)  x##U
+#else
+#define UL(x)  x
+#endif
+
+/* The routine MD5_Init initializes the message-digest context
+   mdContext. All fields are set to zero.
+ */
+void MD5_Init (mdContext)
+MD5_CTX *mdContext;
+{
+  mdContext->i[0] = mdContext->i[1] = (UINT4)0;
+
+  /* Load magic initialization constants.
+   */
+  mdContext->buf[0] = (UINT4)0x67452301;
+  mdContext->buf[1] = (UINT4)0xefcdab89;
+  mdContext->buf[2] = (UINT4)0x98badcfe;
+  mdContext->buf[3] = (UINT4)0x10325476;
+}
+
+/* The routine MD5Update updates the message-digest context to
+   account for the presence of each of the characters inBuf[0..inLen-1]
+   in the message whose digest is being computed.
+ */
+void MD5_Update (mdContext, inBuf, inLen)
+MD5_CTX *mdContext;
+unsigned char *inBuf;
+unsigned int inLen;
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* update number of bits */
+  if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0])
+    mdContext->i[1]++;
+  mdContext->i[0] += ((UINT4)inLen << 3);
+  mdContext->i[1] += ((UINT4)inLen >> 29);
+
+  while (inLen--) {
+    /* add new character to buffer, increment mdi */
+    mdContext->in[mdi++] = *inBuf++;
+
+    /* transform if necessary */
+    if (mdi == 0x40) {
+      for (i = 0, ii = 0; i < 16; i++, ii += 4)
+        in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+                (((UINT4)mdContext->in[ii+2]) << 16) |
+                (((UINT4)mdContext->in[ii+1]) << 8) |
+                ((UINT4)mdContext->in[ii]);
+      Transform (mdContext->buf, in);
+      mdi = 0;
+    }
+  }
+}
+
+/* The routine MD5Final terminates the message-digest computation and
+   ends with the desired message digest in mdContext->digest[0...15].
+ */
+void MD5_Final (hash, mdContext)
+unsigned char hash[];
+MD5_CTX *mdContext;
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+  unsigned int padLen;
+
+  /* save number of bits */
+  in[14] = mdContext->i[0];
+  in[15] = mdContext->i[1];
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* pad out to 56 mod 64 */
+  padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+  MD5_Update (mdContext, PADDING, padLen);
+
+  /* append length in bits and transform */
+  for (i = 0, ii = 0; i < 14; i++, ii += 4)
+    in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+            (((UINT4)mdContext->in[ii+2]) << 16) |
+            (((UINT4)mdContext->in[ii+1]) << 8) |
+            ((UINT4)mdContext->in[ii]);
+  Transform (mdContext->buf, in);
+
+  /* store buffer in digest */
+  for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+    mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
+    mdContext->digest[ii+1] =
+      (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
+    mdContext->digest[ii+2] =
+      (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+    mdContext->digest[ii+3] =
+      (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+  }
+  memcpy(hash, mdContext->digest, 16);
+}
+
+/* Basic MD5 step. Transforms buf based on in.
+ */
+static void Transform (buf, in)
+UINT4 *buf;
+UINT4 *in;
+{
+  UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+  /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+  FF ( a, b, c, d, in[ 0], S11, UL(3614090360)); /* 1 */
+  FF ( d, a, b, c, in[ 1], S12, UL(3905402710)); /* 2 */
+  FF ( c, d, a, b, in[ 2], S13, UL( 606105819)); /* 3 */
+  FF ( b, c, d, a, in[ 3], S14, UL(3250441966)); /* 4 */
+  FF ( a, b, c, d, in[ 4], S11, UL(4118548399)); /* 5 */
+  FF ( d, a, b, c, in[ 5], S12, UL(1200080426)); /* 6 */
+  FF ( c, d, a, b, in[ 6], S13, UL(2821735955)); /* 7 */
+  FF ( b, c, d, a, in[ 7], S14, UL(4249261313)); /* 8 */
+  FF ( a, b, c, d, in[ 8], S11, UL(1770035416)); /* 9 */
+  FF ( d, a, b, c, in[ 9], S12, UL(2336552879)); /* 10 */
+  FF ( c, d, a, b, in[10], S13, UL(4294925233)); /* 11 */
+  FF ( b, c, d, a, in[11], S14, UL(2304563134)); /* 12 */
+  FF ( a, b, c, d, in[12], S11, UL(1804603682)); /* 13 */
+  FF ( d, a, b, c, in[13], S12, UL(4254626195)); /* 14 */
+  FF ( c, d, a, b, in[14], S13, UL(2792965006)); /* 15 */
+  FF ( b, c, d, a, in[15], S14, UL(1236535329)); /* 16 */
+
+  /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+  GG ( a, b, c, d, in[ 1], S21, UL(4129170786)); /* 17 */
+  GG ( d, a, b, c, in[ 6], S22, UL(3225465664)); /* 18 */
+  GG ( c, d, a, b, in[11], S23, UL( 643717713)); /* 19 */
+  GG ( b, c, d, a, in[ 0], S24, UL(3921069994)); /* 20 */
+  GG ( a, b, c, d, in[ 5], S21, UL(3593408605)); /* 21 */
+  GG ( d, a, b, c, in[10], S22, UL(  38016083)); /* 22 */
+  GG ( c, d, a, b, in[15], S23, UL(3634488961)); /* 23 */
+  GG ( b, c, d, a, in[ 4], S24, UL(3889429448)); /* 24 */
+  GG ( a, b, c, d, in[ 9], S21, UL( 568446438)); /* 25 */
+  GG ( d, a, b, c, in[14], S22, UL(3275163606)); /* 26 */
+  GG ( c, d, a, b, in[ 3], S23, UL(4107603335)); /* 27 */
+  GG ( b, c, d, a, in[ 8], S24, UL(1163531501)); /* 28 */
+  GG ( a, b, c, d, in[13], S21, UL(2850285829)); /* 29 */
+  GG ( d, a, b, c, in[ 2], S22, UL(4243563512)); /* 30 */
+  GG ( c, d, a, b, in[ 7], S23, UL(1735328473)); /* 31 */
+  GG ( b, c, d, a, in[12], S24, UL(2368359562)); /* 32 */
+
+  /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+  HH ( a, b, c, d, in[ 5], S31, UL(4294588738)); /* 33 */
+  HH ( d, a, b, c, in[ 8], S32, UL(2272392833)); /* 34 */
+  HH ( c, d, a, b, in[11], S33, UL(1839030562)); /* 35 */
+  HH ( b, c, d, a, in[14], S34, UL(4259657740)); /* 36 */
+  HH ( a, b, c, d, in[ 1], S31, UL(2763975236)); /* 37 */
+  HH ( d, a, b, c, in[ 4], S32, UL(1272893353)); /* 38 */
+  HH ( c, d, a, b, in[ 7], S33, UL(4139469664)); /* 39 */
+  HH ( b, c, d, a, in[10], S34, UL(3200236656)); /* 40 */
+  HH ( a, b, c, d, in[13], S31, UL( 681279174)); /* 41 */
+  HH ( d, a, b, c, in[ 0], S32, UL(3936430074)); /* 42 */
+  HH ( c, d, a, b, in[ 3], S33, UL(3572445317)); /* 43 */
+  HH ( b, c, d, a, in[ 6], S34, UL(  76029189)); /* 44 */
+  HH ( a, b, c, d, in[ 9], S31, UL(3654602809)); /* 45 */
+  HH ( d, a, b, c, in[12], S32, UL(3873151461)); /* 46 */
+  HH ( c, d, a, b, in[15], S33, UL( 530742520)); /* 47 */
+  HH ( b, c, d, a, in[ 2], S34, UL(3299628645)); /* 48 */
+
+  /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+  II ( a, b, c, d, in[ 0], S41, UL(4096336452)); /* 49 */
+  II ( d, a, b, c, in[ 7], S42, UL(1126891415)); /* 50 */
+  II ( c, d, a, b, in[14], S43, UL(2878612391)); /* 51 */
+  II ( b, c, d, a, in[ 5], S44, UL(4237533241)); /* 52 */
+  II ( a, b, c, d, in[12], S41, UL(1700485571)); /* 53 */
+  II ( d, a, b, c, in[ 3], S42, UL(2399980690)); /* 54 */
+  II ( c, d, a, b, in[10], S43, UL(4293915773)); /* 55 */
+  II ( b, c, d, a, in[ 1], S44, UL(2240044497)); /* 56 */
+  II ( a, b, c, d, in[ 8], S41, UL(1873313359)); /* 57 */
+  II ( d, a, b, c, in[15], S42, UL(4264355552)); /* 58 */
+  II ( c, d, a, b, in[ 6], S43, UL(2734768916)); /* 59 */
+  II ( b, c, d, a, in[13], S44, UL(1309151649)); /* 60 */
+  II ( a, b, c, d, in[ 4], S41, UL(4149444226)); /* 61 */
+  II ( d, a, b, c, in[11], S42, UL(3174756917)); /* 62 */
+  II ( c, d, a, b, in[ 2], S43, UL( 718787259)); /* 63 */
+  II ( b, c, d, a, in[ 9], S44, UL(3951481745)); /* 64 */
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+/*
+ ***********************************************************************
+ ** End of md5.c                                                      **
+ ******************************** (cut) ********************************
+ */
diff --git a/tools/firmware-utils/src/md5.h b/tools/firmware-utils/src/md5.h
new file mode 100644 (file)
index 0000000..f7a0c96
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ ***********************************************************************
+ ** md5.h -- header file for implementation of MD5                    **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version               **
+ ** Revised (for MD5): RLR 4/27/91                                    **
+ **   -- G modified to have y&~z instead of y&z                       **
+ **   -- FF, GG, HH modified to add in last register done             **
+ **   -- Access pattern: round 2 works mod 5, round 3 works mod 3     **
+ **   -- distinct additive constant for each step                     **
+ **   -- round 4 added, working mod 7                                 **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#ifndef __MD5_INCLUDE__
+
+/* typedef a 32-bit type */
+#ifdef _LP64
+typedef unsigned int UINT4;
+typedef int          INT4;
+#else
+typedef unsigned long UINT4;
+typedef long          INT4;
+#endif
+#define _UINT4_T
+
+/* Data structure for MD5 (Message-Digest) computation */
+typedef struct {
+  UINT4 i[2];                   /* number of _bits_ handled mod 2^64 */
+  UINT4 buf[4];                                    /* scratch buffer */
+  unsigned char in[64];                              /* input buffer */
+  unsigned char digest[16];     /* actual digest after MD5Final call */
+} MD5_CTX;
+
+void MD5_Init ();
+void MD5_Update ();
+void MD5_Final ();
+
+#define __MD5_INCLUDE__
+#endif /* __MD5_INCLUDE__ */
diff --git a/tools/firmware-utils/src/mktplinkfw.c b/tools/firmware-utils/src/mktplinkfw.c
new file mode 100644 (file)
index 0000000..3aea729
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This tool was based on:
+ *   TP-Link WR941 V2 firmware checksum fixing tool.
+ *   Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>     /* for unlink() */
+#include <libgen.h>
+#include <getopt.h>     /* for getopt() */
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "md5.h"
+
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#  define HOST_TO_BE32(x)      (x)
+#  define BE32_TO_HOST(x)      (x)
+#else
+#  define HOST_TO_BE32(x)      bswap_32(x)
+#  define BE32_TO_HOST(x)      bswap_32(x)
+#endif
+
+#define HEADER_VERSION_V1      0x01000000
+#define HWID_TL_WR941ND_V2     0x09410002
+
+#define MD5SUM_LEN     16
+
+struct file_info {
+       char            *file_name;     /* name of the file */
+       uint32_t        file_size;      /* length of the file */
+};
+
+struct fw_header {
+       uint32_t        version;        /* header version */
+       char            vendor_name[24];
+       char            fw_version[36];
+       uint32_t        hw_id;          /* hardware id */
+       uint32_t        hw_rev;         /* hardware revision */
+       uint32_t        unk1;
+       uint8_t         md5sum1[MD5SUM_LEN];
+       uint32_t        unk2;
+       uint8_t         md5sum2[MD5SUM_LEN];
+       uint32_t        unk3;
+       uint32_t        kernel_la;      /* kernel load address */
+       uint32_t        kernel_ep;      /* kernel entry point */
+       uint32_t        fw_length;      /* total length of the firmware */
+       uint32_t        kernel_ofs;     /* kernel data offset */
+       uint32_t        kernel_len;     /* kernel data length */
+       uint32_t        rootfs_ofs;     /* rootfs data offset */
+       uint32_t        rootfs_len;     /* rootfs data length */
+       uint32_t        boot_ofs;       /* bootloader data offset */
+       uint32_t        boot_len;       /* bootloader data length */
+       uint8_t         pad[360];
+} __attribute__ ((packed));
+
+struct board_info {
+       char            *id;
+       uint32_t        hw_id;
+       uint32_t        hw_rev;
+       uint32_t        fw_max_len;
+       uint32_t        kernel_la;
+       uint32_t        kernel_ep;
+       uint32_t        rootfs_ofs;
+};
+
+/*
+ * Globals
+ */
+static char *ofname;
+static char *progname;
+static char *vendor = "TP-LINK Technologies";
+static char *version = "ver. 1.0";
+
+static char *board_id;
+static struct board_info *board;
+static struct file_info kernel_info;
+static struct file_info rootfs_info;
+static struct file_info boot_info;
+
+char md5salt_normal[MD5SUM_LEN] = {
+       0xdc, 0xd7, 0x3a, 0xa5, 0xc3, 0x95, 0x98, 0xfb,
+       0xdd, 0xf9, 0xe7, 0xf4, 0x0e, 0xae, 0x47, 0x38,
+};
+
+char md5salt_boot[MD5SUM_LEN] = {
+       0x8c, 0xef, 0x33, 0x5b, 0xd5, 0xc5, 0xce, 0xfa,
+       0xa7, 0x9c, 0x28, 0xda, 0xb2, 0xe9, 0x0f, 0x42,
+};
+
+static struct board_info boards[] = {
+       {
+               .id             = "TL-WR941NDv2",
+               .hw_id          = HWID_TL_WR941ND_V2,
+               .hw_rev         = 2,
+               .fw_max_len     = 0x3c0000,
+               .kernel_la      = 0x80060000,
+               .kernel_ep      = 0x80060000,
+               .rootfs_ofs     = 0x120000,
+       }, {
+               /* terminating entry */
+       }
+};
+
+/*
+ * Message macros
+ */
+#define ERR(fmt, ...) do { \
+       fflush(0); \
+       fprintf(stderr, "[%s] *** error: " fmt "\n", \
+                       progname, ## __VA_ARGS__ ); \
+} while (0)
+
+#define ERRS(fmt, ...) do { \
+       int save = errno; \
+       fflush(0); \
+       fprintf(stderr, "[%s] *** error: " fmt "\n", \
+                       progname, ## __VA_ARGS__, strerror(save)); \
+} while (0)
+
+#define DBG(fmt, ...) do { \
+       fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
+} while (0)
+
+static struct board_info *find_board(char *id)
+{
+       struct board_info *ret;
+       struct board_info *board;
+
+       ret = NULL;
+       for (board = boards; board->id != NULL; board++){
+               if (strcasecmp(id, board->id) == 0) {
+                       ret = board;
+                       break;
+               }
+       };
+
+       return ret;
+}
+
+static void usage(int status)
+{
+       FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
+       struct board_info *board;
+
+       fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
+       fprintf(stream,
+"\n"
+"Options:\n"
+"  -B <board>      create image for the board specified with <board>\n"
+"  -k <file>       read kernel image from the file <file>\n"
+"  -r <file>       read rootfs image from the file <file>\n"
+"  -o <file>       write output to the file <file>\n"
+"  -v <version>    set image version to <version>\n"
+"  -h              show this screen\n"
+       );
+
+       exit(status);
+}
+
+static int get_md5(char *data, int size, char *md5)
+{
+       MD5_CTX ctx;
+
+       MD5_Init(&ctx);
+       MD5_Update(&ctx, data, size);
+       MD5_Final(md5, &ctx);
+}
+
+static int get_file_stat(struct file_info *fdata)
+{
+       struct stat st;
+       int res;
+
+       if (fdata->file_name == NULL)
+               return 0;
+
+       res = stat(fdata->file_name, &st);
+       if (res){
+               ERRS("stat failed on %s", fdata->file_name);
+               return res;
+       }
+
+       fdata->file_size = st.st_size;
+       return 0;
+}
+
+static int read_to_buf(struct file_info *fdata, char *buf)
+{
+       FILE *f;
+       int ret = EXIT_FAILURE;
+
+       f = fopen(fdata->file_name, "r");
+       if (f == NULL) {
+               ERRS("could not open \"%s\" for reading", fdata->file_name);
+               goto out;
+       }
+
+       errno = 0;
+       fread(buf, fdata->file_size, 1, f);
+       if (errno != 0) {
+               ERRS("unable to read from file \"%s\"", fdata->file_name);
+               goto out_close;
+       }
+
+       ret = EXIT_SUCCESS;
+
+ out_close:
+       fclose(f);
+ out:
+       return ret;
+}
+
+static int check_options(void)
+{
+       int ret;
+
+       if (board_id == NULL) {
+               ERR("no board specified");
+               return -1;
+       }
+
+       board = find_board(board_id);
+       if (board == NULL) {
+               ERR("unknown/unsupported board id \"%s\"", board_id);
+               return -1;
+       }
+
+       if (kernel_info.file_name == NULL) {
+               ERR("no kernel image specified");
+               return -1;
+       }
+
+       ret = get_file_stat(&kernel_info);
+       if (ret)
+               return ret;
+
+       if (kernel_info.file_size > board->rootfs_ofs - sizeof(struct fw_header)) {
+               ERR("kernel image is too big");
+               return -1;
+       }
+
+       if (rootfs_info.file_name == NULL) {
+               ERR("no rootfs image specified");
+               return -1;
+       }
+
+       ret = get_file_stat(&rootfs_info);
+       if (ret)
+               return ret;
+
+       if (rootfs_info.file_size > (board->fw_max_len - board->rootfs_ofs)) {
+               ERR("rootfs image is too big");
+               return -1;
+       }
+
+       if (ofname == NULL) {
+               ERR("no output file specified");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void fill_header(char *buf, int len)
+{
+       struct fw_header *hdr = (struct fw_header *)buf;
+
+       memset(hdr, 0, sizeof(struct fw_header));
+
+       hdr->version = HOST_TO_BE32(HEADER_VERSION_V1);
+       strncpy(hdr->vendor_name, vendor, sizeof(hdr->vendor_name));
+       strncpy(hdr->fw_version, version, sizeof(hdr->fw_version));
+       hdr->hw_id = HOST_TO_BE32(board->hw_id);
+       hdr->hw_rev = HOST_TO_BE32(board->hw_rev);
+
+       if (boot_info.file_size == 0)
+               memcpy(hdr->md5sum1, md5salt_normal, sizeof(hdr->md5sum1));
+       else
+               memcpy(hdr->md5sum1, md5salt_boot, sizeof(hdr->md5sum1));
+
+       hdr->kernel_la = HOST_TO_BE32(board->kernel_la);
+       hdr->kernel_ep = HOST_TO_BE32(board->kernel_ep);
+       hdr->fw_length = HOST_TO_BE32(board->fw_max_len);
+       hdr->kernel_ofs = HOST_TO_BE32(sizeof(struct fw_header));
+       hdr->kernel_len = HOST_TO_BE32(kernel_info.file_size);
+       hdr->rootfs_ofs = HOST_TO_BE32(board->rootfs_ofs);
+       hdr->rootfs_len = HOST_TO_BE32(rootfs_info.file_size);
+
+       get_md5(buf, len, hdr->md5sum1);
+}
+
+static int write_fw(char *data, int len)
+{
+       FILE *f;
+       int ret = EXIT_FAILURE;
+
+       f = fopen(ofname, "w");
+       if (f == NULL) {
+               ERRS("could not open \"%s\" for writing", ofname);
+               goto out;
+       }
+
+       errno = 0;
+       fwrite(data, len, 1, f);
+       if (errno) {
+               ERRS("unable to write output file");
+               goto out_flush;
+       }
+
+       DBG("firmware file \"%s\" completed", ofname);
+
+       ret = EXIT_SUCCESS;
+
+ out_flush:
+       fflush(f);
+       fclose(f);
+       if (ret != EXIT_SUCCESS) {
+               unlink(ofname);
+       }
+ out:
+       return ret;
+}
+
+static int build_fw(void)
+{
+       int buflen;
+       char *buf;
+       char *p;
+       int ret = EXIT_FAILURE;
+
+       buflen = board->fw_max_len;
+
+       buf = malloc(buflen);
+       if (!buf) {
+               ERR("no memory for buffer\n");
+               goto out;
+       }
+
+       memset(buf, 0xff, buflen);
+       p = buf + sizeof(struct fw_header);
+       ret = read_to_buf(&kernel_info, p);
+       if (ret)
+               goto out_free_buf;
+
+       p = buf + board->rootfs_ofs;
+       ret = read_to_buf(&rootfs_info, p);
+       if (ret)
+               goto out_free_buf;
+
+       fill_header(buf, buflen);
+
+       ret = write_fw(buf, buflen);
+       if (ret)
+               goto out_free_buf;
+
+       ret = EXIT_SUCCESS;
+
+ out_free_buf:
+       free(buf);
+ out:
+       return ret;
+}
+
+int main(int argc, char *argv[])
+{
+       int ret = EXIT_FAILURE;
+       int err;
+
+       FILE *outfile;
+
+       progname = basename(argv[0]);
+
+       while ( 1 ) {
+               int c;
+
+               c = getopt(argc, argv, "B:V:N:k:r:o:v:h:");
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case 'B':
+                       board_id = optarg;
+                       break;
+               case 'V':
+                       version = optarg;
+                       break;
+               case 'N':
+                       vendor = optarg;
+                       break;
+               case 'k':
+                       kernel_info.file_name = optarg;
+                       break;
+               case 'r':
+                       rootfs_info.file_name = optarg;
+                       break;
+               case 'o':
+                       ofname = optarg;
+                       break;
+               case 'v':
+                       version = optarg;
+                       break;
+               case 'h':
+                       usage(EXIT_SUCCESS);
+                       break;
+               default:
+                       usage(EXIT_FAILURE);
+                       break;
+               }
+       }
+
+       ret = check_options();
+       if (ret)
+               goto out;
+
+       ret = build_fw();
+
+ out:
+       return ret;
+}
+