iptime-naspkg: add image header tool for ipTIME NAS series
[project/firmware-utils.git] / src / iptime-naspkg.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2020 Sungbo Eo <mans0n@gorani.run>
4 *
5 * This code is based on mkdhpimg.c and mkzcfw.c
6 * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org>
7 * Copyright (c) 2016 FUKAUMI Naoki <naobsd@gmail.com>
8 *
9 * Checksum algorithm is derived from EFM's mknas utility
10 * found in GPL'ed T16000 source.
11 */
12
13 #include <byteswap.h>
14 #include <endian.h>
15 #include <err.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <stdint.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <time.h>
24 #include <unistd.h>
25
26 #if !defined(__BYTE_ORDER)
27 #error "Unknown byte order"
28 #endif
29
30 #if (__BYTE_ORDER == __BIG_ENDIAN)
31 #define HOST_TO_LE32(x) bswap_32(x)
32 #elif (__BYTE_ORDER == __LITTLE_ENDIAN)
33 #define HOST_TO_LE32(x) (x)
34 #else
35 #error "Unsupported endianness"
36 #endif
37
38 #define FW_HEADER_SIZE 0x400
39 #define FW_VERSION "0.0.00"
40 #define FW_MAGIC "EFM_NAS_PKG"
41
42 struct fw_header {
43 uint8_t model[32];
44 uint8_t version[32];
45 uint8_t ctime[32];
46 uint32_t size;
47 uint32_t checksum;
48 uint32_t offset_header;
49 uint32_t offset_rootfs;
50 uint32_t offset_app;
51 uint32_t checksum_kr;
52 uint8_t magic[16];
53
54 uint32_t size_kra;
55 uint32_t checksum_kra;
56 uint32_t offset_ext;
57 } __attribute__ ((packed));
58
59 enum board_type {
60 BOARD_KIRKWOOD,
61 BOARD_ARMADA380,
62 };
63
64 struct board_type_info {
65 int bootloader_size;
66 int block_size;
67 };
68
69 struct board_info {
70 const char *model;
71 enum board_type type;
72 };
73
74 struct board_type_info board_types[] = {
75 /* BOARD_KIRKWOOD */
76 { .bootloader_size = 0x40000, .block_size = 0x0 },
77 /* BOARD_ARMADA380 */
78 { .bootloader_size = 0x100000, .block_size = 0x10000 },
79 };
80
81 struct board_info boards[] = {
82 { .model = "nas1", .type = BOARD_KIRKWOOD },
83 { .model = "nas1dual", .type = BOARD_ARMADA380 },
84 { /* sentinel */ }
85 };
86
87 struct board_info *find_board(const char *model)
88 {
89 struct board_info *ret = NULL;
90 struct board_info *board;
91
92 for (board = boards; board->model != NULL; board++) {
93 if (strcmp(model, board->model) == 0) {
94 ret = board;
95 break;
96 }
97 }
98
99 return ret;
100 }
101
102 /* (FW_HEADER_SIZE + size_in + padding) % block_size == 0 */
103 size_t calc_padding(enum board_type type, size_t size_in)
104 {
105 int block_size, remainder;
106
107 block_size = board_types[type].block_size;
108 if (block_size == 0)
109 return 0;
110 remainder = (FW_HEADER_SIZE + size_in) % block_size;
111 return remainder ? block_size - remainder : 0;
112 }
113
114 char *get_ctime(void)
115 {
116 char *env = getenv("SOURCE_DATE_EPOCH");
117 char *endptr = env;
118 time_t timestamp = -1;
119
120 if (env && *env) {
121 errno = 0;
122 timestamp = strtoull(env, &endptr, 10);
123
124 if (errno || (endptr && *endptr != '\0')) {
125 fprintf(stderr, "Invalid SOURCE_DATE_EPOCH\n");
126 timestamp = -1;
127 }
128 }
129
130 if (timestamp == -1)
131 time(&timestamp);
132
133 return asctime(gmtime(&timestamp));
134 }
135
136 uint32_t make_checksum(const char *model_name, uint8_t *bytes, int length)
137 {
138 int i;
139 uint32_t sum = 0;
140 uint32_t magic = 0x19283745;
141
142 for (i = 0; i < length; i++)
143 sum += bytes[i];
144 return ((uint32_t)strlen(model_name) * magic + ~sum) ^ sum;
145 }
146
147 void make_header(struct board_info *board, uint8_t *buffer, size_t img_size)
148 {
149 struct fw_header *header = (struct fw_header *)buffer;
150 char *time_created;
151 uint32_t checksum;
152 size_t bootloader_size, image_end_offset;
153
154 time_created = get_ctime();
155 checksum = make_checksum(board->model, buffer + FW_HEADER_SIZE, img_size);
156 bootloader_size = board_types[board->type].bootloader_size;
157 image_end_offset = bootloader_size + FW_HEADER_SIZE + img_size;
158
159 strncpy((char *)header->model, board->model, sizeof(header->model)-1);
160 strncpy((char *)header->version, FW_VERSION, sizeof(header->version)-1);
161 strncpy((char *)header->ctime, time_created, sizeof(header->ctime)-1);
162 header->size = HOST_TO_LE32(img_size);
163 header->checksum = HOST_TO_LE32(checksum);
164 header->offset_header = HOST_TO_LE32(bootloader_size);
165 header->offset_rootfs = HOST_TO_LE32(image_end_offset);
166 header->offset_app = HOST_TO_LE32(image_end_offset);
167 header->checksum_kr = HOST_TO_LE32(checksum);
168 strncpy((char *)header->magic, FW_MAGIC, sizeof(header->magic)-1);
169
170 if (board->type == BOARD_ARMADA380) {
171 header->size_kra = HOST_TO_LE32(img_size);
172 header->checksum_kra = HOST_TO_LE32(checksum);
173 header->offset_ext = HOST_TO_LE32(image_end_offset);
174 }
175 }
176
177 int main(int argc, const char *argv[])
178 {
179 const char *model_name, *img_in, *img_out;
180 struct board_info *board;
181 int file_in, file_out;
182 struct stat stat_in;
183 size_t size_in, size_in_padded, size_out;
184 uint8_t *buffer;
185
186 if (argc != 4) {
187 fprintf(stderr, "Usage: %s <model> <input> <output>\n", argv[0]);
188 return EXIT_FAILURE;
189 }
190 model_name = argv[1];
191 img_in = argv[2];
192 img_out = argv[3];
193
194 board = find_board(model_name);
195 if (board == NULL) {
196 fprintf(stderr, "%s: Not supported model\n", model_name);
197 return EXIT_FAILURE;
198 }
199
200 if ((file_in = open(img_in, O_RDONLY)) == -1)
201 err(EXIT_FAILURE, "%s", img_in);
202
203 if (fstat(file_in, &stat_in) == -1)
204 err(EXIT_FAILURE, "%s", img_in);
205
206 size_in = stat_in.st_size;
207 size_in_padded = size_in + calc_padding(board->type, size_in);
208 size_out = FW_HEADER_SIZE + size_in_padded;
209
210 if ((buffer = malloc(size_out)) == NULL)
211 err(EXIT_FAILURE, "malloc");
212
213 read(file_in, buffer + FW_HEADER_SIZE, size_in);
214 close(file_in);
215
216 memset(buffer, 0, FW_HEADER_SIZE);
217
218 make_header(board, buffer, size_in_padded);
219
220 if ((file_out = creat(img_out, 0644)) == -1)
221 err(EXIT_FAILURE, "%s", img_out);
222 write(file_out, buffer, size_out);
223 close(file_out);
224
225 free(buffer);
226
227 return EXIT_SUCCESS;
228 }