summaryrefslogtreecommitdiffstats
path: root/util/arm_boot_tools/mksunxiboot/mksunxiboot.c
blob: 58765b1a6f327a6c8fae8e955f33867c0b88f2a6 (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
 * A simple tool to generate bootable image for sunxi platform.
 *
 * Copyright (C) 2007-2011 Allwinner Technology Co., Ltd.
 *	Tom Cubie <tangliang@allwinnertech.com>
 * Copyright (C) 2014 Alexandru Gagniuc <mr.nuke.me@gmail.com>
 * Subject to the GNU GPL v2, or (at your option) any later version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

/* boot head definition from sun4i boot code */
struct boot_file_head {
	uint32_t jump_instruction;	/* one intruction jumping to real code */
	uint8_t magic[8];		/* ="eGON.BT0" or "eGON.BT1", not C-style str */
	uint32_t check_sum;		/* generated by PC */
	uint32_t length;		/* generated by PC */
	/* We use a simplified header, only filling in what is needed by the
	 * boot ROM. To be compatible with Allwinner tools the larger header
	 * below should be used, followed by a custom header if desired. */
	uint8_t pad[12];		/* align to 32 bytes */
};

static const char *BOOT0_MAGIC = "eGON.BT0";
static const uint32_t STAMP_VALUE = 0x5F0A6C39;
static const int HEADER_SIZE = 32;
/* Checksum at most 24 KiB */
#define SRAM_LOAD_MAX_SIZE ((24 << 10) - sizeof(struct boot_file_head))
static const int BLOCK_SIZE = 512;

inline static uint32_t le32_to_h(const void *src)
{
	const uint8_t *b = src;
	return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | (b[0] << 0));
}

inline static void h_to_le32(uint32_t val32, void *dest)
{
	uint8_t *b = dest;
	b[0] = (val32 >> 0) & 0xff;
	b[1] = (val32 >> 8) & 0xff;
	b[2] = (val32 >> 16) & 0xff;
	b[3] = (val32 >> 24) & 0xff;
};

static void serialize_header(void *dest, const struct boot_file_head *hdr)
{
	/* Unused fields are zero */
	memset(dest, 0, HEADER_SIZE);

	h_to_le32(hdr->jump_instruction, dest + 0);
	memcpy(dest + 4, BOOT0_MAGIC, 8);
	h_to_le32(hdr->check_sum, dest + 12);
	h_to_le32(hdr->length, dest + 16);
}

/* Check sum function from sun4i boot code */
static int fill_check_sum(struct boot_file_head *hdr, const void *boot_code)
{
	size_t i;
	uint8_t raw_hdr[HEADER_SIZE];
	uint32_t chksum;

	if ((hdr->length & 0x3) != 0) {
		fprintf(stderr, "BUG! Load size is not 4-byte aligned\n");
		return EXIT_FAILURE;
	}

	/* Fill in checksum seed */
	hdr->check_sum = STAMP_VALUE;

	chksum = 0;
	/* Checksum the header */
	serialize_header(raw_hdr, hdr);
	for (i = 0; i < HEADER_SIZE; i += 4)
		chksum += le32_to_h(raw_hdr + i);

	/* Checksum the boot code */
	for (i = 0; i < hdr->length - HEADER_SIZE; i += 4)
		chksum += le32_to_h(boot_code + i);

	/* write back check sum */
	hdr->check_sum = chksum;

	return EXIT_SUCCESS;
}

static uint32_t align(uint32_t size, uint32_t alignment)
{
	return ((size + alignment - 1) / alignment) * alignment;
}

static void fill_header(struct boot_file_head *hdr, size_t load_size)
{
	/* B instruction */
	hdr->jump_instruction = 0xEA000000;
	/* Jump to the first instr after the header */
	hdr->jump_instruction |= (sizeof(*hdr) / sizeof(uint32_t) - 2);
	/* No '0' termination in magic string */
	memcpy(&hdr->magic, BOOT0_MAGIC, 8);

	hdr->length = align(load_size + sizeof(hdr), BLOCK_SIZE);
}

static long int fsize(FILE *file)
{
	struct stat s;
	int fd = fileno(file);
	if (fd == -1) return -1;
	if (fstat(fd, &s) == -1) return -1;
	return s.st_size;
}

int main(int argc, char *argv[])
{
	FILE *fd_in, *fd_out;
	struct boot_file_head hdr;
	long int file_size, load_size;
	void *file_data;
	uint8_t raw_hdr[HEADER_SIZE];
	int count;

	/*
	 * TODO: We could take an additional argument to see how much of the
	 * file to checksum. This way, the build system can tell us how large
	 * the bootblock is, so we can tell the BROM to load only the bootblock.
	 */
	if (argc < 2) {
		printf("\tThis program makes an input bin file to sun4i "
		       "bootable image.\n"
		       "\tUsage: %s input_file out_putfile\n", argv[0]);
		return EXIT_FAILURE;
	}

	fd_in = fopen(argv[1], "rb");
	if (!fd_in) {
		fprintf(stderr, "Cannot open input %s", argv[1]);
		return EXIT_FAILURE;
	}

	/* Get input file size */
	file_size = fsize(fd_in);
	if (file_size == -1) {
		fprintf(stderr, "can't determine file size\n");
		return EXIT_FAILURE;
	}
	if ((file_data = malloc(file_size)) == NULL) {
		fprintf(stderr, "Cannot allocate memory\n");
		return EXIT_FAILURE;
	}

	printf("File size: 0x%x\n", file_size);
	if (fread(file_data, file_size, 1, fd_in) != 1) {
		fprintf(stderr, "Cannot read %s: %s\n", argv[1],
			strerror(errno));
		return EXIT_FAILURE;
	}

	load_size = align(file_size, sizeof(uint32_t));

	if (load_size > SRAM_LOAD_MAX_SIZE)
		load_size = SRAM_LOAD_MAX_SIZE;

	printf("Load size: 0x%x\n", load_size);

	fd_out = fopen(argv[2], "w");
	if (!fd_out) {
		fprintf(stderr, "Cannot open output %s\n", argv[2]);
		return EXIT_FAILURE;
	}

	/* Fill the header */
	fill_header(&hdr, load_size);
	fill_check_sum(&hdr, file_data);

	/* Now write the header */
	serialize_header(raw_hdr, &hdr);
	if (fwrite(raw_hdr, HEADER_SIZE, 1, fd_out) != 1) {
		fprintf(stderr, "Cannot write header to %s: %s\n", argv[1],
			strerror(errno));
		return EXIT_FAILURE;
	}

	/* And finally, the boot code */
	if (fwrite(file_data, file_size, 1, fd_out) != 1) {
		fprintf(stderr, "Cannot write to %s: %s\n", argv[1],
			strerror(errno));
		return EXIT_FAILURE;
	}

	fclose(fd_in);
	fclose(fd_out);

	return EXIT_SUCCESS;
}