aboutsummaryrefslogtreecommitdiff
path: root/miscutils/nandwrite.c
blob: f42242687c1b1e4d1ba7151494991d4075d93924 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * nandwrite.c - ported to busybox from mtd-utils
 *
 * Author: Baruch Siach <baruch@tkos.co.il>, Orex Computed Radiography
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 *
 * TODO: add support for large (>4GB) MTD devices
 */

//applet:IF_NANDWRITE(APPLET(nandwrite, _BB_DIR_USR_SBIN, _BB_SUID_DROP))

//kbuild:lib-$(CONFIG_NANDWRITE) += nandwrite.o

//config:config NANDWRITE
//config:	bool "nandwrite"
//config:	default n
//config:	depends on PLATFORM_LINUX
//config:	help
//config:	  Write to the specified MTD device, with bad blocks awareness

#include "libbb.h"
#include <mtd/mtd-user.h>

//usage:#define nandwrite_trivial_usage
//usage:	"[-p] [-s ADDR] MTD_DEVICE [FILE]"
//usage:#define nandwrite_full_usage "\n\n"
//usage:	"Write to the specified MTD device\n"
//usage:     "\nOptions:"
//usage:     "\n	-p	Pad to page size"
//usage:     "\n	-s ADDR	Start address"

static unsigned next_good_eraseblock(int fd, struct mtd_info_user *meminfo,
		unsigned block_offset)
{
	while (1) {
		loff_t offs;
		if (block_offset >= meminfo->size)
			bb_error_msg_and_die("not enough space in MTD device");
		offs = block_offset;
		if (xioctl(fd, MEMGETBADBLOCK, &offs) == 0)
			return block_offset;
		/* ioctl returned 1 => "bad block" */
		printf("Skipping bad block at 0x%08x\n", block_offset);
		block_offset += meminfo->erasesize;
	}
}

int nandwrite_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int nandwrite_main(int argc UNUSED_PARAM, char **argv)
{
	unsigned opts;
	int fd;
	ssize_t cnt;
	unsigned mtdoffset, meminfo_writesize, blockstart;
	struct mtd_info_user meminfo;
	unsigned char *filebuf;
	const char *opt_s = "0";
	enum {
		OPT_p = (1 << 0),
		OPT_s = (1 << 1),
	};

	opt_complementary = "-1:?2";
	opts = getopt32(argv, "ps:", &opt_s);
	argv += optind;

	if (argv[1])
		xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);

	fd = xopen(argv[0], O_RDWR);
	xioctl(fd, MEMGETINFO, &meminfo);

	mtdoffset = bb_strtou(opt_s, NULL, 0);
	if (errno)
		bb_error_msg_and_die("invalid number '%s'", opt_s);

	/* Pull it into a CPU register (hopefully) - smaller code that way */
	meminfo_writesize = meminfo.writesize;

	if (mtdoffset & (meminfo_writesize - 1))
		bb_error_msg_and_die("start address is not page aligned");

	filebuf = xmalloc(meminfo_writesize);

	blockstart = mtdoffset & ~(meminfo.erasesize - 1);
	if (blockstart != mtdoffset) {
		unsigned tmp;
		/* mtdoffset is in the middle of an erase block, verify that
		 * this block is OK. Advance mtdoffset only if this block is
		 * bad.
		 */
		tmp = next_good_eraseblock(fd, &meminfo, blockstart);
		if (tmp != blockstart) /* bad block(s), advance mtdoffset */
			mtdoffset = tmp;
	}

	cnt = -1;
	while (mtdoffset < meminfo.size) {
		blockstart = mtdoffset & ~(meminfo.erasesize - 1);
		if (blockstart == mtdoffset) {
			/* starting a new eraseblock */
			mtdoffset = next_good_eraseblock(fd, &meminfo, blockstart);
			printf("Writing at 0x%08x\n", mtdoffset);
		}
		/* get some more data from input */
		cnt = full_read(STDIN_FILENO, filebuf, meminfo_writesize);
		if (cnt == 0) {
			/* even with -p, we do not pad past the end of input
			 * (-p only zero-pads last incomplete page)
			 */
			break;
		}
		if (cnt < meminfo_writesize) {
			if (!(opts & OPT_p))
				bb_error_msg_and_die("input size is not rounded up to page size, "
						"use -p to zero pad");
			/* zero pad to end of write block */
			memset(filebuf + cnt, 0, meminfo_writesize - cnt);
		}
		xlseek(fd, mtdoffset, SEEK_SET);
		xwrite(fd, filebuf, meminfo_writesize);
		mtdoffset += meminfo_writesize;
		if (cnt < meminfo_writesize)
			break;
	}

	if (cnt != 0) {
		/* We filled entire MTD, but did we reach EOF on input? */
		if (full_read(STDIN_FILENO, filebuf, meminfo_writesize) != 0) {
			/* no */
			bb_error_msg_and_die("not enough space in MTD device");
		}
	}

	if (ENABLE_FEATURE_CLEAN_UP) {
		free(filebuf);
		close(fd);
	}

	return EXIT_SUCCESS;
}