diff --git a/doc/kwboot.1 b/doc/kwboot.1
deleted file mode 100644
index 25fe69a..0000000
--- a/doc/kwboot.1
+++ /dev/null
@@ -1,84 +0,0 @@
-.TH KWBOOT 1 "2012-05-19"
-
-.SH NAME
-kwboot \- Boot Marvell Kirkwood SoCs over a serial link.
-.SH SYNOPSIS
-.B kwboot
-.RB [ "-b \fIimage\fP" ]
-.RB [ "-p" ]
-.RB [ "-t" ]
-.RB [ "-B \fIbaudrate\fP" ]
-.RB \fITTY\fP
-.SH "DESCRIPTION"
-
-The \fBmkimage\fP program boots boards based on Marvell's Kirkwood
-platform over their integrated UART. Boot image files will typically
-contain a second stage boot loader, such as U-Boot. The image file
-must conform to Marvell's BootROM firmware image format
-(\fIkwbimage\fP), created using a tool such as \fBmkimage\fP.
-
-Following power-up or a system reset, system BootROM code polls the
-UART for a brief period of time, sensing a handshake message which
-initiates an image upload. This program sends this boot message until
-it receives a positive acknowledgement. The image is transfered using
-Xmodem.
-
-Additionally, this program implements a minimal terminal mode, which
-can be used either standalone, or entered immediately following boot
-image transfer completion. This is often useful to catch early boot
-messages, or to manually interrupt a default boot procedure performed
-by the second-stage loader.
-
-.SH "OPTIONS"
-
-.TP
-.BI "\-b \fIimage\fP"
-Handshake; then upload file \fIimage\fP over \fITTY\fP.
-
-Note that for the encapsulated boot code to be executed, \fIimage\fP
-must be of type "UART boot" (0x69). Boot images of different types,
-such as backup images of vendor firmware downloaded from flash memory
-(type 0x8B), will not work (or not as expected). See \fB-p\fP for a
-workaround.
-
-This mode writes handshake status and upload progress indication to
-stdout.
-
-.TP
-.BI "\-p"
-In combination with \fB-b\fP, patches the header in \fIimage\fP prior
-to upload, to "UART boot" type.
-
-This option attempts on-the-fly conversion of some none-UART image
-types, such as images which were originally formatted to be stored in
-flash memory.
-
-Conversion is performed in memory. The contents of \fIimage\fP will
-not be altered.
-
-.TP
-.BI "\-t"
-Run a terminal program, connecting standard input and output to
-.RB \fITTY\fP.
-
-If used in combination with \fB-b\fP, terminal mode is entered
-immediately following a successful image upload.
-
-If standard I/O streams connect to a console, this mode will terminate
-after receiving 'ctrl-\\' followed by 'c' from console input.
-
-.TP
-.BI "\-B \fIbaudrate\fP"
-Adjust the baud rate on \fITTY\fP. Default rate is 115200.
-
-.SH "SEE ALSO"
-.PP
-\fBmkimage\fP(1)
-
-.SH "AUTHORS"
-
-Daniel Stodden <daniel.stodden@gmail.com>
-.br
-Luka Perkov <luka@openwrt.org>
-.br
-David Purdy <david.c.purdy@gmail.com>
diff --git a/doc/mvboot.1 b/doc/mvboot.1
new file mode 100644
index 0000000..563d0e5
--- /dev/null
+++ b/doc/mvboot.1
@@ -0,0 +1,84 @@
+.TH MVBOOT 1 "2012-05-19"
+
+.SH NAME
+mvboot \- Boot Marvell Kirkwood SoCs over a serial link.
+.SH SYNOPSIS
+.B mvboot
+.RB [ "-b \fIimage\fP" ]
+.RB [ "-p" ]
+.RB [ "-t" ]
+.RB [ "-B \fIbaudrate\fP" ]
+.RB \fITTY\fP
+.SH "DESCRIPTION"
+
+The \fBmkimage\fP program boots boards based on Marvell's Kirkwood
+platform over their integrated UART. Boot image files will typically
+contain a second stage boot loader, such as U-Boot. The image file
+must conform to Marvell's BootROM firmware image format
+(\fIkwbimage\fP), created using a tool such as \fBmkimage\fP.
+
+Following power-up or a system reset, system BootROM code polls the
+UART for a brief period of time, sensing a handshake message which
+initiates an image upload. This program sends this boot message until
+it receives a positive acknowledgement. The image is transfered using
+Xmodem.
+
+Additionally, this program implements a minimal terminal mode, which
+can be used either standalone, or entered immediately following boot
+image transfer completion. This is often useful to catch early boot
+messages, or to manually interrupt a default boot procedure performed
+by the second-stage loader.
+
+.SH "OPTIONS"
+
+.TP
+.BI "\-b \fIimage\fP"
+Handshake; then upload file \fIimage\fP over \fITTY\fP.
+
+Note that for the encapsulated boot code to be executed, \fIimage\fP
+must be of type "UART boot" (0x69). Boot images of different types,
+such as backup images of vendor firmware downloaded from flash memory
+(type 0x8B), will not work (or not as expected). See \fB-p\fP for a
+workaround.
+
+This mode writes handshake status and upload progress indication to
+stdout.
+
+.TP
+.BI "\-p"
+In combination with \fB-b\fP, patches the header in \fIimage\fP prior
+to upload, to "UART boot" type.
+
+This option attempts on-the-fly conversion of some none-UART image
+types, such as images which were originally formatted to be stored in
+flash memory.
+
+Conversion is performed in memory. The contents of \fIimage\fP will
+not be altered.
+
+.TP
+.BI "\-t"
+Run a terminal program, connecting standard input and output to
+.RB \fITTY\fP.
+
+If used in combination with \fB-b\fP, terminal mode is entered
+immediately following a successful image upload.
+
+If standard I/O streams connect to a console, this mode will terminate
+after receiving 'ctrl-\\' followed by 'c' from console input.
+
+.TP
+.BI "\-B \fIbaudrate\fP"
+Adjust the baud rate on \fITTY\fP. Default rate is 115200.
+
+.SH "SEE ALSO"
+.PP
+\fBmkimage\fP(1)
+
+.SH "AUTHORS"
+
+Daniel Stodden <daniel.stodden@gmail.com>
+.br
+Luka Perkov <luka@openwrt.org>
+.br
+David Purdy <david.c.purdy@gmail.com>
diff --git a/tools/.gitignore b/tools/.gitignore
index 9bce719..89debe3 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -2,10 +2,10 @@
 /envcrc
 /gen_eth_addr
 /img2srec
-/kwboot
 /mkenvimage
 /mkimage
 /mpc86x_clk
+/mvboot
 /mxsboot
 /ncb
 /ncp
diff --git a/tools/Makefile b/tools/Makefile
index 686840a..adfb643 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -72,7 +72,7 @@ BIN_FILES-$(CONFIG_SMDK5250) += mksmdk5250spl$(SFX)
 BIN_FILES-$(CONFIG_MX28) += mxsboot$(SFX)
 BIN_FILES-$(CONFIG_NETCONSOLE) += ncb$(SFX)
 BIN_FILES-$(CONFIG_SHA1_CHECK_UB_IMG) += ubsha1$(SFX)
-BIN_FILES-$(CONFIG_KIRKWOOD) += kwboot$(SFX)
+BIN_FILES-$(CONFIG_KIRKWOOD) += mvboot$(SFX)
 
 # Source files which exist outside the tools directory
 EXT_OBJ_FILES-$(CONFIG_BUILD_ENVCRC) += common/env_embedded.o
@@ -103,7 +103,7 @@ OBJ_FILES-$(CONFIG_NETCONSOLE) += ncb.o
 NOPED_OBJ_FILES-y += os_support.o
 OBJ_FILES-$(CONFIG_SHA1_CHECK_UB_IMG) += ubsha1.o
 NOPED_OBJ_FILES-y += ublimage.o
-OBJ_FILES-$(CONFIG_KIRKWOOD) += kwboot.o
+OBJ_FILES-$(CONFIG_KIRKWOOD) += mvboot.o
 
 # Don't build by default
 #ifeq ($(ARCH),ppc)
@@ -239,7 +239,7 @@ $(obj)ncb$(SFX):	$(obj)ncb.o
 $(obj)ubsha1$(SFX):	$(obj)os_support.o $(obj)sha1.o $(obj)ubsha1.o
 	$(HOSTCC) $(HOSTCFLAGS) $(HOSTLDFLAGS) -o $@ $^
 
-$(obj)kwboot$(SFX): $(obj)kwboot.o
+$(obj)mvboot$(SFX): $(obj)mvboot.o
 	$(HOSTCC) $(HOSTCFLAGS) $(HOSTLDFLAGS) -o $@ $^
 	$(HOSTSTRIP) $@
 
diff --git a/tools/kwboot.c b/tools/kwboot.c
deleted file mode 100644
index e773f01..0000000
--- a/tools/kwboot.c
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * Boot a Marvell Kirkwood SoC, with Xmodem over UART0.
- *
- * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com>
- *
- * References: marvell.com, "88F6180, 88F6190, 88F6192, and 88F6281
- *   Integrated Controller: Functional Specifications" December 2,
- *   2008. Chapter 24.2 "BootROM Firmware".
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
-#include <libgen.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdint.h>
-#include <termios.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-
-#include "kwbimage.h"
-
-#ifdef __GNUC__
-#define PACKED __attribute((packed))
-#else
-#define PACKED
-#endif
-
-/*
- * Marvell BootROM UART Sensing
- */
-
-static unsigned char kwboot_msg_boot[] = {
-	0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
-};
-
-#define KWBOOT_MSG_REQ_DELAY	10 /* ms */
-#define KWBOOT_MSG_RSP_TIMEO	50 /* ms */
-
-/*
- * Xmodem Transfers
- */
-
-#define SOH	1	/* sender start of block header */
-#define EOT	4	/* sender end of block transfer */
-#define ACK	6	/* target block ack */
-#define NAK	21	/* target block negative ack */
-#define CAN	24	/* target/sender transfer cancellation */
-
-struct kwboot_block {
-	uint8_t soh;
-	uint8_t pnum;
-	uint8_t _pnum;
-	uint8_t data[128];
-	uint8_t csum;
-} PACKED;
-
-#define KWBOOT_BLK_RSP_TIMEO 1000 /* ms */
-
-static int kwboot_verbose;
-
-static void
-kwboot_printv(const char *fmt, ...)
-{
-	va_list ap;
-
-	if (kwboot_verbose) {
-		va_start(ap, fmt);
-		vprintf(fmt, ap);
-		va_end(ap);
-		fflush(stdout);
-	}
-}
-
-static void
-__spinner(void)
-{
-	const char seq[] = { '-', '\\', '|', '/' };
-	const int div = 8;
-	static int state, bs;
-
-	if (state % div == 0) {
-		fputc(bs, stdout);
-		fputc(seq[state / div % sizeof(seq)], stdout);
-		fflush(stdout);
-	}
-
-	bs = '\b';
-	state++;
-}
-
-static void
-kwboot_spinner(void)
-{
-	if (kwboot_verbose)
-		__spinner();
-}
-
-static void
-__progress(int pct, char c)
-{
-	const int width = 70;
-	static const char *nl = "";
-	static int pos;
-
-	if (pos % width == 0)
-		printf("%s%3d %% [", nl, pct);
-
-	fputc(c, stdout);
-
-	nl = "]\n";
-	pos++;
-
-	if (pct == 100) {
-		while (pos++ < width)
-			fputc(' ', stdout);
-		fputs(nl, stdout);
-	}
-
-	fflush(stdout);
-
-}
-
-static void
-kwboot_progress(int _pct, char c)
-{
-	static int pct;
-
-	if (_pct != -1)
-		pct = _pct;
-
-	if (kwboot_verbose)
-		__progress(pct, c);
-}
-
-static int
-kwboot_tty_recv(int fd, void *buf, size_t len, int timeo)
-{
-	int rc, nfds;
-	fd_set rfds;
-	struct timeval tv;
-	ssize_t n;
-
-	rc = -1;
-
-	FD_ZERO(&rfds);
-	FD_SET(fd, &rfds);
-
-	tv.tv_sec = 0;
-	tv.tv_usec = timeo * 1000;
-	if (tv.tv_usec > 1000000) {
-		tv.tv_sec += tv.tv_usec / 1000000;
-		tv.tv_usec %= 1000000;
-	}
-
-	do {
-		nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
-		if (nfds < 0)
-			goto out;
-		if (!nfds) {
-			errno = ETIMEDOUT;
-			goto out;
-		}
-
-		n = read(fd, buf, len);
-		if (n < 0)
-			goto out;
-
-		buf = (char *)buf + n;
-		len -= n;
-	} while (len > 0);
-
-	rc = 0;
-out:
-	return rc;
-}
-
-static int
-kwboot_tty_send(int fd, const void *buf, size_t len)
-{
-	int rc;
-	ssize_t n;
-
-	rc = -1;
-
-	do {
-		n = write(fd, buf, len);
-		if (n < 0)
-			goto out;
-
-		buf = (char *)buf + n;
-		len -= n;
-	} while (len > 0);
-
-	rc = tcdrain(fd);
-out:
-	return rc;
-}
-
-static int
-kwboot_tty_send_char(int fd, unsigned char c)
-{
-	return kwboot_tty_send(fd, &c, 1);
-}
-
-static speed_t
-kwboot_tty_speed(int baudrate)
-{
-	switch (baudrate) {
-	case 115200:
-		return B115200;
-	case 57600:
-		return B57600;
-	case 38400:
-		return B38400;
-	case 19200:
-		return B19200;
-	case 9600:
-		return B9600;
-	}
-
-	return -1;
-}
-
-static int
-kwboot_open_tty(const char *path, speed_t speed)
-{
-	int rc, fd;
-	struct termios tio;
-
-	rc = -1;
-
-	fd = open(path, O_RDWR|O_NOCTTY|O_NDELAY);
-	if (fd < 0)
-		goto out;
-
-	memset(&tio, 0, sizeof(tio));
-
-	tio.c_iflag = 0;
-	tio.c_cflag = CREAD|CLOCAL|CS8;
-
-	tio.c_cc[VMIN] = 1;
-	tio.c_cc[VTIME] = 10;
-
-	cfsetospeed(&tio, speed);
-	cfsetispeed(&tio, speed);
-
-	rc = tcsetattr(fd, TCSANOW, &tio);
-	if (rc)
-		goto out;
-
-	rc = fd;
-out:
-	if (rc < 0) {
-		if (fd >= 0)
-			close(fd);
-	}
-
-	return rc;
-}
-
-static int
-kwboot_bootmsg(int tty, void *msg)
-{
-	int rc;
-	char c;
-
-	kwboot_printv("Sending boot message. Please reboot the target...");
-
-	do {
-		rc = tcflush(tty, TCIOFLUSH);
-		if (rc)
-			break;
-
-		rc = kwboot_tty_send(tty, msg, 8);
-		if (rc) {
-			usleep(KWBOOT_MSG_REQ_DELAY * 1000);
-			continue;
-		}
-
-		rc = kwboot_tty_recv(tty, &c, 1, KWBOOT_MSG_RSP_TIMEO);
-
-		kwboot_spinner();
-
-	} while (rc || c != NAK);
-
-	kwboot_printv("\n");
-
-	return rc;
-}
-
-static int
-kwboot_xm_makeblock(struct kwboot_block *block, const void *data,
-		    size_t size, int pnum)
-{
-	const size_t blksz = sizeof(block->data);
-	size_t n;
-	int i;
-
-	block->pnum = pnum;
-	block->_pnum = ~block->pnum;
-
-	n = size < blksz ? size : blksz;
-	memcpy(&block->data[0], data, n);
-	memset(&block->data[n], 0, blksz - n);
-
-	block->csum = 0;
-	for (i = 0; i < n; i++)
-		block->csum += block->data[i];
-
-	return n;
-}
-
-static int
-kwboot_xm_sendblock(int fd, struct kwboot_block *block)
-{
-	int rc, retries;
-	char c;
-
-	retries = 16;
-	do {
-		rc = kwboot_tty_send(fd, block, sizeof(*block));
-		if (rc)
-			break;
-
-		rc = kwboot_tty_recv(fd, &c, 1, KWBOOT_BLK_RSP_TIMEO);
-		if (rc)
-			break;
-
-		if (c != ACK)
-			kwboot_progress(-1, '+');
-
-	} while (c == NAK && retries-- > 0);
-
-	rc = -1;
-
-	switch (c) {
-	case ACK:
-		rc = 0;
-		break;
-	case NAK:
-		errno = EBADMSG;
-		break;
-	case CAN:
-		errno = ECANCELED;
-		break;
-	default:
-		errno = EPROTO;
-		break;
-	}
-
-	return rc;
-}
-
-static int
-kwboot_xmodem(int tty, const void *_data, size_t size)
-{
-	const uint8_t *data = _data;
-	int rc, pnum, N, err;
-
-	pnum = 1;
-	N = 0;
-
-	kwboot_printv("Sending boot image...\n");
-
-	do {
-		struct kwboot_block block;
-		int n;
-
-		n = kwboot_xm_makeblock(&block,
-					data + N, size - N,
-					pnum++);
-		if (n < 0)
-			goto can;
-
-		if (!n)
-			break;
-
-		rc = kwboot_xm_sendblock(tty, &block);
-		if (rc)
-			goto out;
-
-		N += n;
-		kwboot_progress(N * 100 / size, '.');
-	} while (1);
-
-	rc = kwboot_tty_send_char(tty, EOT);
-
-out:
-	return rc;
-
-can:
-	err = errno;
-	kwboot_tty_send_char(tty, CAN);
-	errno = err;
-	goto out;
-}
-
-static int
-kwboot_term_pipe(int in, int out, char *quit, int *s)
-{
-	ssize_t nin, nout;
-	char _buf[128], *buf = _buf;
-
-	nin = read(in, buf, sizeof(buf));
-	if (nin < 0)
-		return -1;
-
-	if (quit) {
-		int i;
-
-		for (i = 0; i < nin; i++) {
-			if (*buf == quit[*s]) {
-				(*s)++;
-				if (!quit[*s])
-					return 0;
-				buf++;
-				nin--;
-			} else
-				while (*s > 0) {
-					nout = write(out, quit, *s);
-					if (nout <= 0)
-						return -1;
-					(*s) -= nout;
-				}
-		}
-	}
-
-	while (nin > 0) {
-		nout = write(out, buf, nin);
-		if (nout <= 0)
-			return -1;
-		nin -= nout;
-	}
-
-	return 0;
-}
-
-static int
-kwboot_terminal(int tty)
-{
-	int rc, in, s;
-	char *quit = "\34c";
-	struct termios otio, tio;
-
-	rc = -1;
-
-	in = STDIN_FILENO;
-	if (isatty(in)) {
-		rc = tcgetattr(in, &otio);
-		if (!rc) {
-			tio = otio;
-			cfmakeraw(&tio);
-			rc = tcsetattr(in, TCSANOW, &tio);
-		}
-		if (rc) {
-			perror("tcsetattr");
-			goto out;
-		}
-
-		kwboot_printv("[Type Ctrl-%c + %c to quit]\r\n",
-			      quit[0]|0100, quit[1]);
-	} else
-		in = -1;
-
-	rc = 0;
-	s = 0;
-
-	do {
-		fd_set rfds;
-		int nfds = 0;
-
-		FD_SET(tty, &rfds);
-		nfds = nfds < tty ? tty : nfds;
-
-		if (in >= 0) {
-			FD_SET(in, &rfds);
-			nfds = nfds < in ? in : nfds;
-		}
-
-		nfds = select(nfds + 1, &rfds, NULL, NULL, NULL);
-		if (nfds < 0)
-			break;
-
-		if (FD_ISSET(tty, &rfds)) {
-			rc = kwboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL);
-			if (rc)
-				break;
-		}
-
-		if (FD_ISSET(in, &rfds)) {
-			rc = kwboot_term_pipe(in, tty, quit, &s);
-			if (rc)
-				break;
-		}
-	} while (quit[s] != 0);
-
-	tcsetattr(in, TCSANOW, &otio);
-out:
-	return rc;
-}
-
-static void *
-kwboot_mmap_image(const char *path, size_t *size, int prot)
-{
-	int rc, fd, flags;
-	struct stat st;
-	void *img;
-
-	rc = -1;
-	fd = -1;
-	img = NULL;
-
-	fd = open(path, O_RDONLY);
-	if (fd < 0)
-		goto out;
-
-	rc = fstat(fd, &st);
-	if (rc)
-		goto out;
-
-	flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED;
-
-	img = mmap(NULL, st.st_size, prot, flags, fd, 0);
-	if (img == MAP_FAILED) {
-		img = NULL;
-		goto out;
-	}
-
-	rc = 0;
-	*size = st.st_size;
-out:
-	if (rc && img) {
-		munmap(img, st.st_size);
-		img = NULL;
-	}
-	if (fd >= 0)
-		close(fd);
-
-	return img;
-}
-
-static uint8_t
-kwboot_img_csum8(void *_data, size_t size)
-{
-	uint8_t *data = _data, csum;
-
-	for (csum = 0; size-- > 0; data++)
-		csum += *data;
-
-	return csum;
-}
-
-static int
-kwboot_img_patch_hdr(void *img, size_t size)
-{
-	int rc;
-	bhr_t *hdr;
-	uint8_t csum;
-	const size_t hdrsz = sizeof(*hdr);
-
-	rc = -1;
-	hdr = img;
-
-	if (size < hdrsz) {
-		errno = EINVAL;
-		goto out;
-	}
-
-	csum = kwboot_img_csum8(hdr, hdrsz) - hdr->checkSum;
-	if (csum != hdr->checkSum) {
-		errno = EINVAL;
-		goto out;
-	}
-
-	if (hdr->blockid == IBR_HDR_UART_ID) {
-		rc = 0;
-		goto out;
-	}
-
-	hdr->blockid = IBR_HDR_UART_ID;
-
-	hdr->nandeccmode = IBR_HDR_ECC_DISABLED;
-	hdr->nandpagesize = 0;
-
-	hdr->srcaddr = hdr->ext
-		? sizeof(struct kwb_header)
-		: sizeof(*hdr);
-
-	hdr->checkSum = kwboot_img_csum8(hdr, hdrsz) - csum;
-
-	rc = 0;
-out:
-	return rc;
-}
-
-static void
-kwboot_usage(FILE *stream, char *progname)
-{
-	fprintf(stream,
-		"Usage: %s -b <image> [ -p ] [ -t ] "
-		"[-B <baud> ] <TTY>\n", progname);
-	fprintf(stream, "\n");
-	fprintf(stream, "  -b <image>: boot <image>\n");
-	fprintf(stream, "  -p: patch <image> to type 0x69 (uart boot)\n");
-	fprintf(stream, "\n");
-	fprintf(stream, "  -t: mini terminal\n");
-	fprintf(stream, "\n");
-	fprintf(stream, "  -B <baud>: set baud rate\n");
-	fprintf(stream, "\n");
-}
-
-int
-main(int argc, char **argv)
-{
-	const char *ttypath, *imgpath;
-	int rv, rc, tty, term, prot, patch;
-	void *bootmsg;
-	void *img;
-	size_t size;
-	speed_t speed;
-
-	rv = 1;
-	tty = -1;
-	bootmsg = NULL;
-	imgpath = NULL;
-	img = NULL;
-	term = 0;
-	patch = 0;
-	size = 0;
-	speed = B115200;
-
-	kwboot_verbose = isatty(STDOUT_FILENO);
-
-	do {
-		int c = getopt(argc, argv, "hb:ptB:");
-		if (c < 0)
-			break;
-
-		switch (c) {
-		case 'b':
-			bootmsg = kwboot_msg_boot;
-			imgpath = optarg;
-			break;
-
-		case 'p':
-			patch = 1;
-			break;
-
-		case 't':
-			term = 1;
-			break;
-
-		case 'B':
-			speed = kwboot_tty_speed(atoi(optarg));
-			if (speed == -1)
-				goto usage;
-			break;
-
-		case 'h':
-			rv = 0;
-		default:
-			goto usage;
-		}
-	} while (1);
-
-	if (!bootmsg && !term)
-		goto usage;
-
-	if (patch && !imgpath)
-		goto usage;
-
-	if (argc - optind < 1)
-		goto usage;
-
-	ttypath = argv[optind++];
-
-	tty = kwboot_open_tty(ttypath, speed);
-	if (tty < 0) {
-		perror(ttypath);
-		goto out;
-	}
-
-	if (imgpath) {
-		prot = PROT_READ | (patch ? PROT_WRITE : 0);
-
-		img = kwboot_mmap_image(imgpath, &size, prot);
-		if (!img) {
-			perror(imgpath);
-			goto out;
-		}
-	}
-
-	if (patch) {
-		rc = kwboot_img_patch_hdr(img, size);
-		if (rc) {
-			fprintf(stderr, "%s: Invalid image.\n", imgpath);
-			goto out;
-		}
-	}
-
-	if (bootmsg) {
-		rc = kwboot_bootmsg(tty, bootmsg);
-		if (rc) {
-			perror("bootmsg");
-			goto out;
-		}
-	}
-
-	if (img) {
-		rc = kwboot_xmodem(tty, img, size);
-		if (rc) {
-			perror("xmodem");
-			goto out;
-		}
-	}
-
-	if (term) {
-		rc = kwboot_terminal(tty);
-		if (rc && !(errno == EINTR)) {
-			perror("terminal");
-			goto out;
-		}
-	}
-
-	rv = 0;
-out:
-	if (tty >= 0)
-		close(tty);
-
-	if (img)
-		munmap(img, size);
-
-	return rv;
-
-usage:
-	kwboot_usage(rv ? stderr : stdout, basename(argv[0]));
-	goto out;
-}
diff --git a/tools/mvboot.c b/tools/mvboot.c
new file mode 100644
index 0000000..94fb0dd
--- /dev/null
+++ b/tools/mvboot.c
@@ -0,0 +1,742 @@
+/*
+ * Boot a Marvell Kirkwood SoC, with Xmodem over UART0.
+ *
+ * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com>
+ *
+ * References: marvell.com, "88F6180, 88F6190, 88F6192, and 88F6281
+ *   Integrated Controller: Functional Specifications" December 2,
+ *   2008. Chapter 24.2 "BootROM Firmware".
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <termios.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include "kwbimage.h"
+
+#ifdef __GNUC__
+#define PACKED __attribute((packed))
+#else
+#define PACKED
+#endif
+
+/*
+ * Marvell BootROM UART Sensing
+ */
+
+static unsigned char mvboot_msg_boot[] = {
+	0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
+};
+
+#define MVBOOT_MSG_REQ_DELAY	10 /* ms */
+#define MVBOOT_MSG_RSP_TIMEO	50 /* ms */
+
+/*
+ * Xmodem Transfers
+ */
+
+#define SOH	1	/* sender start of block header */
+#define EOT	4	/* sender end of block transfer */
+#define ACK	6	/* target block ack */
+#define NAK	21	/* target block negative ack */
+#define CAN	24	/* target/sender transfer cancellation */
+
+struct mvboot_block {
+	uint8_t soh;
+	uint8_t pnum;
+	uint8_t _pnum;
+	uint8_t data[128];
+	uint8_t csum;
+} PACKED;
+
+#define MVBOOT_BLK_RSP_TIMEO 1000 /* ms */
+
+static int mvboot_verbose;
+
+static void
+mvboot_printv(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (mvboot_verbose) {
+		va_start(ap, fmt);
+		vprintf(fmt, ap);
+		va_end(ap);
+		fflush(stdout);
+	}
+}
+
+static void
+__spinner(void)
+{
+	const char seq[] = { '-', '\\', '|', '/' };
+	const int div = 8;
+	static int state, bs;
+
+	if (state % div == 0) {
+		fputc(bs, stdout);
+		fputc(seq[state / div % sizeof(seq)], stdout);
+		fflush(stdout);
+	}
+
+	bs = '\b';
+	state++;
+}
+
+static void
+mvboot_spinner(void)
+{
+	if (mvboot_verbose)
+		__spinner();
+}
+
+static void
+__progress(int pct, char c)
+{
+	const int width = 70;
+	static const char *nl = "";
+	static int pos;
+
+	if (pos % width == 0)
+		printf("%s%3d %% [", nl, pct);
+
+	fputc(c, stdout);
+
+	nl = "]\n";
+	pos++;
+
+	if (pct == 100) {
+		while (pos++ < width)
+			fputc(' ', stdout);
+		fputs(nl, stdout);
+	}
+
+	fflush(stdout);
+
+}
+
+static void
+mvboot_progress(int _pct, char c)
+{
+	static int pct;
+
+	if (_pct != -1)
+		pct = _pct;
+
+	if (mvboot_verbose)
+		__progress(pct, c);
+}
+
+static int
+mvboot_tty_recv(int fd, void *buf, size_t len, int timeo)
+{
+	int rc, nfds;
+	fd_set rfds;
+	struct timeval tv;
+	ssize_t n;
+
+	rc = -1;
+
+	FD_ZERO(&rfds);
+	FD_SET(fd, &rfds);
+
+	tv.tv_sec = 0;
+	tv.tv_usec = timeo * 1000;
+	if (tv.tv_usec > 1000000) {
+		tv.tv_sec += tv.tv_usec / 1000000;
+		tv.tv_usec %= 1000000;
+	}
+
+	do {
+		nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+		if (nfds < 0)
+			goto out;
+		if (!nfds) {
+			errno = ETIMEDOUT;
+			goto out;
+		}
+
+		n = read(fd, buf, len);
+		if (n < 0)
+			goto out;
+
+		buf = (char *)buf + n;
+		len -= n;
+	} while (len > 0);
+
+	rc = 0;
+out:
+	return rc;
+}
+
+static int
+mvboot_tty_send(int fd, const void *buf, size_t len)
+{
+	int rc;
+	ssize_t n;
+
+	rc = -1;
+
+	do {
+		n = write(fd, buf, len);
+		if (n < 0)
+			goto out;
+
+		buf = (char *)buf + n;
+		len -= n;
+	} while (len > 0);
+
+	rc = tcdrain(fd);
+out:
+	return rc;
+}
+
+static int
+mvboot_tty_send_char(int fd, unsigned char c)
+{
+	return mvboot_tty_send(fd, &c, 1);
+}
+
+static speed_t
+mvboot_tty_speed(int baudrate)
+{
+	switch (baudrate) {
+	case 115200:
+		return B115200;
+	case 57600:
+		return B57600;
+	case 38400:
+		return B38400;
+	case 19200:
+		return B19200;
+	case 9600:
+		return B9600;
+	}
+
+	return -1;
+}
+
+static int
+mvboot_open_tty(const char *path, speed_t speed)
+{
+	int rc, fd;
+	struct termios tio;
+
+	rc = -1;
+
+	fd = open(path, O_RDWR|O_NOCTTY|O_NDELAY);
+	if (fd < 0)
+		goto out;
+
+	memset(&tio, 0, sizeof(tio));
+
+	tio.c_iflag = 0;
+	tio.c_cflag = CREAD|CLOCAL|CS8;
+
+	tio.c_cc[VMIN] = 1;
+	tio.c_cc[VTIME] = 10;
+
+	cfsetospeed(&tio, speed);
+	cfsetispeed(&tio, speed);
+
+	rc = tcsetattr(fd, TCSANOW, &tio);
+	if (rc)
+		goto out;
+
+	rc = fd;
+out:
+	if (rc < 0) {
+		if (fd >= 0)
+			close(fd);
+	}
+
+	return rc;
+}
+
+static int
+mvboot_bootmsg(int tty, void *msg)
+{
+	int rc;
+	char c;
+
+	mvboot_printv("Sending boot message. Please reboot the target...");
+
+	do {
+		rc = tcflush(tty, TCIOFLUSH);
+		if (rc)
+			break;
+
+		rc = mvboot_tty_send(tty, msg, 8);
+		if (rc) {
+			usleep(MVBOOT_MSG_REQ_DELAY * 1000);
+			continue;
+		}
+
+		rc = mvboot_tty_recv(tty, &c, 1, MVBOOT_MSG_RSP_TIMEO);
+
+		mvboot_spinner();
+
+	} while (rc || c != NAK);
+
+	mvboot_printv("\n");
+
+	return rc;
+}
+
+static int
+mvboot_xm_makeblock(struct mvboot_block *block, const void *data,
+		    size_t size, int pnum)
+{
+	const size_t blksz = sizeof(block->data);
+	size_t n;
+	int i;
+
+	block->pnum = pnum;
+	block->_pnum = ~block->pnum;
+
+	n = size < blksz ? size : blksz;
+	memcpy(&block->data[0], data, n);
+	memset(&block->data[n], 0, blksz - n);
+
+	block->csum = 0;
+	for (i = 0; i < n; i++)
+		block->csum += block->data[i];
+
+	return n;
+}
+
+static int
+mvboot_xm_sendblock(int fd, struct mvboot_block *block)
+{
+	int rc, retries;
+	char c;
+
+	retries = 16;
+	do {
+		rc = mvboot_tty_send(fd, block, sizeof(*block));
+		if (rc)
+			break;
+
+		rc = mvboot_tty_recv(fd, &c, 1, MVBOOT_BLK_RSP_TIMEO);
+		if (rc)
+			break;
+
+		if (c != ACK)
+			mvboot_progress(-1, '+');
+
+	} while (c == NAK && retries-- > 0);
+
+	rc = -1;
+
+	switch (c) {
+	case ACK:
+		rc = 0;
+		break;
+	case NAK:
+		errno = EBADMSG;
+		break;
+	case CAN:
+		errno = ECANCELED;
+		break;
+	default:
+		errno = EPROTO;
+		break;
+	}
+
+	return rc;
+}
+
+static int
+mvboot_xmodem(int tty, const void *_data, size_t size)
+{
+	const uint8_t *data = _data;
+	int rc, pnum, N, err;
+
+	pnum = 1;
+	N = 0;
+
+	mvboot_printv("Sending boot image...\n");
+
+	do {
+		struct mvboot_block block;
+		int n;
+
+		n = mvboot_xm_makeblock(&block,
+					data + N, size - N,
+					pnum++);
+		if (n < 0)
+			goto can;
+
+		if (!n)
+			break;
+
+		rc = mvboot_xm_sendblock(tty, &block);
+		if (rc)
+			goto out;
+
+		N += n;
+		mvboot_progress(N * 100 / size, '.');
+	} while (1);
+
+	rc = mvboot_tty_send_char(tty, EOT);
+
+out:
+	return rc;
+
+can:
+	err = errno;
+	mvboot_tty_send_char(tty, CAN);
+	errno = err;
+	goto out;
+}
+
+static int
+mvboot_term_pipe(int in, int out, char *quit, int *s)
+{
+	ssize_t nin, nout;
+	char _buf[128], *buf = _buf;
+
+	nin = read(in, buf, sizeof(buf));
+	if (nin < 0)
+		return -1;
+
+	if (quit) {
+		int i;
+
+		for (i = 0; i < nin; i++) {
+			if (*buf == quit[*s]) {
+				(*s)++;
+				if (!quit[*s])
+					return 0;
+				buf++;
+				nin--;
+			} else
+				while (*s > 0) {
+					nout = write(out, quit, *s);
+					if (nout <= 0)
+						return -1;
+					(*s) -= nout;
+				}
+		}
+	}
+
+	while (nin > 0) {
+		nout = write(out, buf, nin);
+		if (nout <= 0)
+			return -1;
+		nin -= nout;
+	}
+
+	return 0;
+}
+
+static int
+mvboot_terminal(int tty)
+{
+	int rc, in, s;
+	char *quit = "\34c";
+	struct termios otio, tio;
+
+	rc = -1;
+
+	in = STDIN_FILENO;
+	if (isatty(in)) {
+		rc = tcgetattr(in, &otio);
+		if (!rc) {
+			tio = otio;
+			cfmakeraw(&tio);
+			rc = tcsetattr(in, TCSANOW, &tio);
+		}
+		if (rc) {
+			perror("tcsetattr");
+			goto out;
+		}
+
+		mvboot_printv("[Type Ctrl-%c + %c to quit]\r\n",
+			      quit[0]|0100, quit[1]);
+	} else
+		in = -1;
+
+	rc = 0;
+	s = 0;
+
+	do {
+		fd_set rfds;
+		int nfds = 0;
+
+		FD_SET(tty, &rfds);
+		nfds = nfds < tty ? tty : nfds;
+
+		if (in >= 0) {
+			FD_SET(in, &rfds);
+			nfds = nfds < in ? in : nfds;
+		}
+
+		nfds = select(nfds + 1, &rfds, NULL, NULL, NULL);
+		if (nfds < 0)
+			break;
+
+		if (FD_ISSET(tty, &rfds)) {
+			rc = mvboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL);
+			if (rc)
+				break;
+		}
+
+		if (FD_ISSET(in, &rfds)) {
+			rc = mvboot_term_pipe(in, tty, quit, &s);
+			if (rc)
+				break;
+		}
+	} while (quit[s] != 0);
+
+	tcsetattr(in, TCSANOW, &otio);
+out:
+	return rc;
+}
+
+static void *
+mvboot_mmap_image(const char *path, size_t *size, int prot)
+{
+	int rc, fd, flags;
+	struct stat st;
+	void *img;
+
+	rc = -1;
+	fd = -1;
+	img = NULL;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		goto out;
+
+	rc = fstat(fd, &st);
+	if (rc)
+		goto out;
+
+	flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED;
+
+	img = mmap(NULL, st.st_size, prot, flags, fd, 0);
+	if (img == MAP_FAILED) {
+		img = NULL;
+		goto out;
+	}
+
+	rc = 0;
+	*size = st.st_size;
+out:
+	if (rc && img) {
+		munmap(img, st.st_size);
+		img = NULL;
+	}
+	if (fd >= 0)
+		close(fd);
+
+	return img;
+}
+
+static uint8_t
+mvboot_img_csum8(void *_data, size_t size)
+{
+	uint8_t *data = _data, csum;
+
+	for (csum = 0; size-- > 0; data++)
+		csum += *data;
+
+	return csum;
+}
+
+static int
+mvboot_img_patch_hdr(void *img, size_t size)
+{
+	int rc;
+	bhr_t *hdr;
+	uint8_t csum;
+	const size_t hdrsz = sizeof(*hdr);
+
+	rc = -1;
+	hdr = img;
+
+	if (size < hdrsz) {
+		errno = EINVAL;
+		goto out;
+	}
+
+	csum = mvboot_img_csum8(hdr, hdrsz) - hdr->checkSum;
+	if (csum != hdr->checkSum) {
+		errno = EINVAL;
+		goto out;
+	}
+
+	if (hdr->blockid == IBR_HDR_UART_ID) {
+		rc = 0;
+		goto out;
+	}
+
+	hdr->blockid = IBR_HDR_UART_ID;
+
+	hdr->nandeccmode = IBR_HDR_ECC_DISABLED;
+	hdr->nandpagesize = 0;
+
+	hdr->srcaddr = hdr->ext
+		? sizeof(struct kwb_header)
+		: sizeof(*hdr);
+
+	hdr->checkSum = mvboot_img_csum8(hdr, hdrsz) - csum;
+
+	rc = 0;
+out:
+	return rc;
+}
+
+static void
+mvboot_usage(FILE *stream, char *progname)
+{
+	fprintf(stream,
+		"Usage: %s -b <image> [ -p ] [ -t ] "
+		"[-B <baud> ] <TTY>\n", progname);
+	fprintf(stream, "\n");
+	fprintf(stream, "  -b <image>: boot <image>\n");
+	fprintf(stream, "  -p: patch <image> to type 0x69 (uart boot)\n");
+	fprintf(stream, "\n");
+	fprintf(stream, "  -t: mini terminal\n");
+	fprintf(stream, "\n");
+	fprintf(stream, "  -B <baud>: set baud rate\n");
+	fprintf(stream, "\n");
+}
+
+int
+main(int argc, char **argv)
+{
+	const char *ttypath, *imgpath;
+	int rv, rc, tty, term, prot, patch;
+	void *bootmsg;
+	void *img;
+	size_t size;
+	speed_t speed;
+
+	rv = 1;
+	tty = -1;
+	bootmsg = NULL;
+	imgpath = NULL;
+	img = NULL;
+	term = 0;
+	patch = 0;
+	size = 0;
+	speed = B115200;
+
+	mvboot_verbose = isatty(STDOUT_FILENO);
+
+	do {
+		int c = getopt(argc, argv, "hb:ptB:");
+		if (c < 0)
+			break;
+
+		switch (c) {
+		case 'b':
+			bootmsg = mvboot_msg_boot;
+			imgpath = optarg;
+			break;
+
+		case 'p':
+			patch = 1;
+			break;
+
+		case 't':
+			term = 1;
+			break;
+
+		case 'B':
+			speed = mvboot_tty_speed(atoi(optarg));
+			if (speed == -1)
+				goto usage;
+			break;
+
+		case 'h':
+			rv = 0;
+		default:
+			goto usage;
+		}
+	} while (1);
+
+	if (!bootmsg && !term)
+		goto usage;
+
+	if (patch && !imgpath)
+		goto usage;
+
+	if (argc - optind < 1)
+		goto usage;
+
+	ttypath = argv[optind++];
+
+	tty = mvboot_open_tty(ttypath, speed);
+	if (tty < 0) {
+		perror(ttypath);
+		goto out;
+	}
+
+	if (imgpath) {
+		prot = PROT_READ | (patch ? PROT_WRITE : 0);
+
+		img = mvboot_mmap_image(imgpath, &size, prot);
+		if (!img) {
+			perror(imgpath);
+			goto out;
+		}
+	}
+
+	if (patch) {
+		rc = mvboot_img_patch_hdr(img, size);
+		if (rc) {
+			fprintf(stderr, "%s: Invalid image.\n", imgpath);
+			goto out;
+		}
+	}
+
+	if (bootmsg) {
+		rc = mvboot_bootmsg(tty, bootmsg);
+		if (rc) {
+			perror("bootmsg");
+			goto out;
+		}
+	}
+
+	if (img) {
+		rc = mvboot_xmodem(tty, img, size);
+		if (rc) {
+			perror("xmodem");
+			goto out;
+		}
+	}
+
+	if (term) {
+		rc = mvboot_terminal(tty);
+		if (rc && !(errno == EINTR)) {
+			perror("terminal");
+			goto out;
+		}
+	}
+
+	rv = 0;
+out:
+	if (tty >= 0)
+		close(tty);
+
+	if (img)
+		munmap(img, size);
+
+	return rv;
+
+usage:
+	mvboot_usage(rv ? stderr : stdout, basename(argv[0]));
+	goto out;
+}
