]>
Commit | Line | Data |
---|---|---|
cb383cd2 ŁM |
1 | /* |
2 | * dfu.c -- DFU back-end routines | |
3 | * | |
4 | * Copyright (C) 2012 Samsung Electronics | |
5 | * author: Lukasz Majewski <[email protected]> | |
6 | * | |
1a459660 | 7 | * SPDX-License-Identifier: GPL-2.0+ |
cb383cd2 ŁM |
8 | */ |
9 | ||
10 | #include <common.h> | |
11 | #include <malloc.h> | |
1b6ca18b | 12 | #include <errno.h> |
ea2453d5 | 13 | #include <div64.h> |
cb383cd2 | 14 | #include <dfu.h> |
0e285b50 SW |
15 | #include <ext4fs.h> |
16 | #include <fat.h> | |
7d0b605a | 17 | #include <mmc.h> |
cb383cd2 | 18 | |
41ac233c | 19 | static unsigned char *dfu_file_buf; |
15970d87 | 20 | static u64 dfu_file_buf_len; |
411c5e57 | 21 | static long dfu_file_buf_filled; |
ea2453d5 | 22 | |
5a127c84 | 23 | static int mmc_block_op(enum dfu_op op, struct dfu_entity *dfu, |
ea2453d5 | 24 | u64 offset, void *buf, long *len) |
cb383cd2 | 25 | { |
7da6fa27 | 26 | struct mmc *mmc; |
7d0b605a | 27 | u32 blk_start, blk_count, n = 0; |
c8151b4a | 28 | int ret, part_num_bkp = 0; |
ea2453d5 | 29 | |
7da6fa27 PM |
30 | mmc = find_mmc_device(dfu->data.mmc.dev_num); |
31 | if (!mmc) { | |
32 | error("Device MMC %d - not found!", dfu->data.mmc.dev_num); | |
33 | return -ENODEV; | |
34 | } | |
35 | ||
ea2453d5 PA |
36 | /* |
37 | * We must ensure that we work in lba_blk_size chunks, so ALIGN | |
38 | * this value. | |
39 | */ | |
40 | *len = ALIGN(*len, dfu->data.mmc.lba_blk_size); | |
41 | ||
42 | blk_start = dfu->data.mmc.lba_start + | |
43 | (u32)lldiv(offset, dfu->data.mmc.lba_blk_size); | |
44 | blk_count = *len / dfu->data.mmc.lba_blk_size; | |
45 | if (blk_start + blk_count > | |
46 | dfu->data.mmc.lba_start + dfu->data.mmc.lba_size) { | |
47 | puts("Request would exceed designated area!\n"); | |
48 | return -EINVAL; | |
49 | } | |
cb383cd2 | 50 | |
c8151b4a | 51 | if (dfu->data.mmc.hw_partition >= 0) { |
797d1b9d | 52 | part_num_bkp = mmc_get_blk_desc(mmc)->hwpart; |
69f45cd5 SG |
53 | ret = blk_select_hwpart_devnum(IF_TYPE_MMC, |
54 | dfu->data.mmc.dev_num, | |
55 | dfu->data.mmc.hw_partition); | |
c8151b4a ŁM |
56 | if (ret) |
57 | return ret; | |
58 | } | |
59 | ||
7d0b605a | 60 | debug("%s: %s dev: %d start: %d cnt: %d buf: 0x%p\n", __func__, |
dd64827e SW |
61 | op == DFU_OP_READ ? "MMC READ" : "MMC WRITE", |
62 | dfu->data.mmc.dev_num, blk_start, blk_count, buf); | |
7d0b605a ŁM |
63 | switch (op) { |
64 | case DFU_OP_READ: | |
797d1b9d | 65 | n = blk_dread(mmc_get_blk_desc(mmc), blk_start, blk_count, buf); |
7d0b605a ŁM |
66 | break; |
67 | case DFU_OP_WRITE: | |
797d1b9d SG |
68 | n = blk_dwrite(mmc_get_blk_desc(mmc), blk_start, blk_count, |
69 | buf); | |
7d0b605a ŁM |
70 | break; |
71 | default: | |
72 | error("Operation not supported\n"); | |
73 | } | |
74 | ||
75 | if (n != blk_count) { | |
76 | error("MMC operation failed"); | |
c8151b4a | 77 | if (dfu->data.mmc.hw_partition >= 0) |
69f45cd5 SG |
78 | blk_select_hwpart_devnum(IF_TYPE_MMC, |
79 | dfu->data.mmc.dev_num, | |
80 | part_num_bkp); | |
7d0b605a ŁM |
81 | return -EIO; |
82 | } | |
cb383cd2 | 83 | |
c8151b4a | 84 | if (dfu->data.mmc.hw_partition >= 0) { |
69f45cd5 SG |
85 | ret = blk_select_hwpart_devnum(IF_TYPE_MMC, |
86 | dfu->data.mmc.dev_num, | |
87 | part_num_bkp); | |
c8151b4a ŁM |
88 | if (ret) |
89 | return ret; | |
90 | } | |
91 | ||
7d0b605a | 92 | return 0; |
cb383cd2 ŁM |
93 | } |
94 | ||
ea2453d5 | 95 | static int mmc_file_buffer(struct dfu_entity *dfu, void *buf, long *len) |
cb383cd2 | 96 | { |
ea2453d5 PA |
97 | if (dfu_file_buf_len + *len > CONFIG_SYS_DFU_MAX_FILE_SIZE) { |
98 | dfu_file_buf_len = 0; | |
99 | return -EINVAL; | |
100 | } | |
cb383cd2 | 101 | |
ea2453d5 PA |
102 | /* Add to the current buffer. */ |
103 | memcpy(dfu_file_buf + dfu_file_buf_len, buf, *len); | |
104 | dfu_file_buf_len += *len; | |
105 | ||
106 | return 0; | |
cb383cd2 ŁM |
107 | } |
108 | ||
5a127c84 | 109 | static int mmc_file_op(enum dfu_op op, struct dfu_entity *dfu, |
15970d87 | 110 | void *buf, u64 *len) |
cb383cd2 | 111 | { |
0e285b50 | 112 | const char *fsname, *opname; |
cb383cd2 ŁM |
113 | char cmd_buf[DFU_CMD_BUF_SIZE]; |
114 | char *str_env; | |
115 | int ret; | |
116 | ||
43e66272 ŁM |
117 | switch (dfu->layout) { |
118 | case DFU_FS_FAT: | |
0e285b50 | 119 | fsname = "fat"; |
43e66272 ŁM |
120 | break; |
121 | case DFU_FS_EXT4: | |
0e285b50 | 122 | fsname = "ext4"; |
43e66272 ŁM |
123 | break; |
124 | default: | |
125 | printf("%s: Layout (%s) not (yet) supported!\n", __func__, | |
126 | dfu_get_layout(dfu->layout)); | |
ea2453d5 | 127 | return -1; |
43e66272 | 128 | } |
cb383cd2 | 129 | |
0e285b50 SW |
130 | switch (op) { |
131 | case DFU_OP_READ: | |
132 | opname = "load"; | |
133 | break; | |
134 | case DFU_OP_WRITE: | |
135 | opname = "write"; | |
136 | break; | |
137 | case DFU_OP_SIZE: | |
138 | opname = "size"; | |
139 | break; | |
140 | default: | |
141 | return -1; | |
142 | } | |
143 | ||
144 | sprintf(cmd_buf, "%s%s mmc %d:%d", fsname, opname, | |
145 | dfu->data.mmc.dev, dfu->data.mmc.part); | |
146 | ||
147 | if (op != DFU_OP_SIZE) | |
e621c7ab | 148 | sprintf(cmd_buf + strlen(cmd_buf), " %p", buf); |
0e285b50 SW |
149 | |
150 | sprintf(cmd_buf + strlen(cmd_buf), " %s", dfu->name); | |
151 | ||
17eb1d8f | 152 | if (op == DFU_OP_WRITE) |
15970d87 | 153 | sprintf(cmd_buf + strlen(cmd_buf), " %llx", *len); |
17eb1d8f | 154 | |
cb383cd2 ŁM |
155 | debug("%s: %s 0x%p\n", __func__, cmd_buf, cmd_buf); |
156 | ||
157 | ret = run_command(cmd_buf, 0); | |
158 | if (ret) { | |
159 | puts("dfu: Read error!\n"); | |
160 | return ret; | |
161 | } | |
162 | ||
0e285b50 | 163 | if (op != DFU_OP_WRITE) { |
00caae6d | 164 | str_env = env_get("filesize"); |
cb383cd2 ŁM |
165 | if (str_env == NULL) { |
166 | puts("dfu: Wrong file size!\n"); | |
167 | return -1; | |
168 | } | |
169 | *len = simple_strtoul(str_env, NULL, 16); | |
170 | } | |
171 | ||
172 | return ret; | |
173 | } | |
174 | ||
ea2453d5 PA |
175 | int dfu_write_medium_mmc(struct dfu_entity *dfu, |
176 | u64 offset, void *buf, long *len) | |
cb383cd2 ŁM |
177 | { |
178 | int ret = -1; | |
179 | ||
180 | switch (dfu->layout) { | |
181 | case DFU_RAW_ADDR: | |
ea2453d5 | 182 | ret = mmc_block_op(DFU_OP_WRITE, dfu, offset, buf, len); |
cb383cd2 ŁM |
183 | break; |
184 | case DFU_FS_FAT: | |
43e66272 | 185 | case DFU_FS_EXT4: |
ea2453d5 | 186 | ret = mmc_file_buffer(dfu, buf, len); |
cb383cd2 ŁM |
187 | break; |
188 | default: | |
189 | printf("%s: Layout (%s) not (yet) supported!\n", __func__, | |
190 | dfu_get_layout(dfu->layout)); | |
191 | } | |
192 | ||
193 | return ret; | |
194 | } | |
195 | ||
ea2453d5 PA |
196 | int dfu_flush_medium_mmc(struct dfu_entity *dfu) |
197 | { | |
198 | int ret = 0; | |
199 | ||
200 | if (dfu->layout != DFU_RAW_ADDR) { | |
201 | /* Do stuff here. */ | |
41ac233c | 202 | ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf, |
ea2453d5 PA |
203 | &dfu_file_buf_len); |
204 | ||
205 | /* Now that we're done */ | |
206 | dfu_file_buf_len = 0; | |
207 | } | |
208 | ||
209 | return ret; | |
210 | } | |
211 | ||
15970d87 | 212 | int dfu_get_medium_size_mmc(struct dfu_entity *dfu, u64 *size) |
0e285b50 SW |
213 | { |
214 | int ret; | |
0e285b50 SW |
215 | |
216 | switch (dfu->layout) { | |
217 | case DFU_RAW_ADDR: | |
4de51201 PD |
218 | *size = dfu->data.mmc.lba_size * dfu->data.mmc.lba_blk_size; |
219 | return 0; | |
0e285b50 SW |
220 | case DFU_FS_FAT: |
221 | case DFU_FS_EXT4: | |
411c5e57 | 222 | dfu_file_buf_filled = -1; |
4de51201 | 223 | ret = mmc_file_op(DFU_OP_SIZE, dfu, NULL, size); |
0e285b50 SW |
224 | if (ret < 0) |
225 | return ret; | |
4de51201 | 226 | if (*size > CONFIG_SYS_DFU_MAX_FILE_SIZE) |
411c5e57 | 227 | return -1; |
4de51201 | 228 | return 0; |
0e285b50 SW |
229 | default: |
230 | printf("%s: Layout (%s) not (yet) supported!\n", __func__, | |
231 | dfu_get_layout(dfu->layout)); | |
232 | return -1; | |
233 | } | |
234 | } | |
235 | ||
411c5e57 SW |
236 | static int mmc_file_unbuffer(struct dfu_entity *dfu, u64 offset, void *buf, |
237 | long *len) | |
238 | { | |
239 | int ret; | |
15970d87 | 240 | u64 file_len; |
411c5e57 SW |
241 | |
242 | if (dfu_file_buf_filled == -1) { | |
243 | ret = mmc_file_op(DFU_OP_READ, dfu, dfu_file_buf, &file_len); | |
244 | if (ret < 0) | |
245 | return ret; | |
246 | dfu_file_buf_filled = file_len; | |
247 | } | |
248 | if (offset + *len > dfu_file_buf_filled) | |
249 | return -EINVAL; | |
250 | ||
251 | /* Add to the current buffer. */ | |
252 | memcpy(buf, dfu_file_buf + offset, *len); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
ea2453d5 PA |
257 | int dfu_read_medium_mmc(struct dfu_entity *dfu, u64 offset, void *buf, |
258 | long *len) | |
cb383cd2 ŁM |
259 | { |
260 | int ret = -1; | |
261 | ||
262 | switch (dfu->layout) { | |
263 | case DFU_RAW_ADDR: | |
ea2453d5 | 264 | ret = mmc_block_op(DFU_OP_READ, dfu, offset, buf, len); |
cb383cd2 ŁM |
265 | break; |
266 | case DFU_FS_FAT: | |
43e66272 | 267 | case DFU_FS_EXT4: |
411c5e57 | 268 | ret = mmc_file_unbuffer(dfu, offset, buf, len); |
cb383cd2 ŁM |
269 | break; |
270 | default: | |
271 | printf("%s: Layout (%s) not (yet) supported!\n", __func__, | |
272 | dfu_get_layout(dfu->layout)); | |
273 | } | |
274 | ||
275 | return ret; | |
276 | } | |
277 | ||
41ac233c PM |
278 | void dfu_free_entity_mmc(struct dfu_entity *dfu) |
279 | { | |
280 | if (dfu_file_buf) { | |
281 | free(dfu_file_buf); | |
282 | dfu_file_buf = NULL; | |
283 | } | |
284 | } | |
285 | ||
711b931f MZ |
286 | /* |
287 | * @param s Parameter string containing space-separated arguments: | |
288 | * 1st: | |
289 | * raw (raw read/write) | |
290 | * fat (files) | |
291 | * ext4 (^) | |
292 | * part (partition image) | |
293 | * 2nd and 3rd: | |
294 | * lba_start and lba_size, for raw write | |
295 | * mmc_dev and mmc_part, for filesystems and part | |
c8151b4a ŁM |
296 | * 4th (optional): |
297 | * mmcpart <num> (access to HW eMMC partitions) | |
711b931f | 298 | */ |
dd64827e | 299 | int dfu_fill_entity_mmc(struct dfu_entity *dfu, char *devstr, char *s) |
cb383cd2 | 300 | { |
711b931f MZ |
301 | const char *entity_type; |
302 | size_t second_arg; | |
303 | size_t third_arg; | |
cb383cd2 | 304 | |
711b931f | 305 | struct mmc *mmc; |
1b6ca18b | 306 | |
711b931f MZ |
307 | const char *argv[3]; |
308 | const char **parg = argv; | |
1b6ca18b | 309 | |
dd64827e SW |
310 | dfu->data.mmc.dev_num = simple_strtoul(devstr, NULL, 10); |
311 | ||
711b931f MZ |
312 | for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) { |
313 | *parg = strsep(&s, " "); | |
314 | if (*parg == NULL) { | |
315 | error("Invalid number of arguments.\n"); | |
1b6ca18b PA |
316 | return -ENODEV; |
317 | } | |
711b931f | 318 | } |
1b6ca18b | 319 | |
711b931f | 320 | entity_type = argv[0]; |
b7d4259a MZ |
321 | /* |
322 | * Base 0 means we'll accept (prefixed with 0x or 0) base 16, 8, | |
323 | * with default 10. | |
324 | */ | |
325 | second_arg = simple_strtoul(argv[1], NULL, 0); | |
326 | third_arg = simple_strtoul(argv[2], NULL, 0); | |
711b931f | 327 | |
dd64827e | 328 | mmc = find_mmc_device(dfu->data.mmc.dev_num); |
711b931f | 329 | if (mmc == NULL) { |
dd64827e SW |
330 | error("Couldn't find MMC device no. %d.\n", |
331 | dfu->data.mmc.dev_num); | |
711b931f MZ |
332 | return -ENODEV; |
333 | } | |
334 | ||
335 | if (mmc_init(mmc)) { | |
336 | error("Couldn't init MMC device.\n"); | |
337 | return -ENODEV; | |
338 | } | |
339 | ||
c8151b4a | 340 | dfu->data.mmc.hw_partition = -EINVAL; |
711b931f MZ |
341 | if (!strcmp(entity_type, "raw")) { |
342 | dfu->layout = DFU_RAW_ADDR; | |
343 | dfu->data.mmc.lba_start = second_arg; | |
344 | dfu->data.mmc.lba_size = third_arg; | |
345 | dfu->data.mmc.lba_blk_size = mmc->read_bl_len; | |
c8151b4a ŁM |
346 | |
347 | /* | |
348 | * Check for an extra entry at dfu_alt_info env variable | |
349 | * specifying the mmc HW defined partition number | |
350 | */ | |
351 | if (s) | |
352 | if (!strcmp(strsep(&s, " "), "mmcpart")) | |
353 | dfu->data.mmc.hw_partition = | |
354 | simple_strtoul(s, NULL, 0); | |
355 | ||
711b931f MZ |
356 | } else if (!strcmp(entity_type, "part")) { |
357 | disk_partition_t partinfo; | |
797d1b9d | 358 | struct blk_desc *blk_dev = mmc_get_blk_desc(mmc); |
711b931f MZ |
359 | int mmcdev = second_arg; |
360 | int mmcpart = third_arg; | |
361 | ||
3e8bd469 | 362 | if (part_get_info(blk_dev, mmcpart, &partinfo) != 0) { |
711b931f MZ |
363 | error("Couldn't find part #%d on mmc device #%d\n", |
364 | mmcpart, mmcdev); | |
1b6ca18b PA |
365 | return -ENODEV; |
366 | } | |
367 | ||
711b931f MZ |
368 | dfu->layout = DFU_RAW_ADDR; |
369 | dfu->data.mmc.lba_start = partinfo.start; | |
370 | dfu->data.mmc.lba_size = partinfo.size; | |
371 | dfu->data.mmc.lba_blk_size = partinfo.blksz; | |
372 | } else if (!strcmp(entity_type, "fat")) { | |
373 | dfu->layout = DFU_FS_FAT; | |
374 | } else if (!strcmp(entity_type, "ext4")) { | |
375 | dfu->layout = DFU_FS_EXT4; | |
cb383cd2 | 376 | } else { |
711b931f | 377 | error("Memory layout (%s) not supported!\n", entity_type); |
1b6ca18b | 378 | return -ENODEV; |
cb383cd2 ŁM |
379 | } |
380 | ||
711b931f MZ |
381 | /* if it's NOT a raw write */ |
382 | if (strcmp(entity_type, "raw")) { | |
383 | dfu->data.mmc.dev = second_arg; | |
384 | dfu->data.mmc.part = third_arg; | |
43e66272 ŁM |
385 | } |
386 | ||
711b931f | 387 | dfu->dev_type = DFU_DEV_MMC; |
0e285b50 | 388 | dfu->get_medium_size = dfu_get_medium_size_mmc; |
cb383cd2 ŁM |
389 | dfu->read_medium = dfu_read_medium_mmc; |
390 | dfu->write_medium = dfu_write_medium_mmc; | |
ea2453d5 | 391 | dfu->flush_medium = dfu_flush_medium_mmc; |
ea2453d5 | 392 | dfu->inited = 0; |
41ac233c PM |
393 | dfu->free_entity = dfu_free_entity_mmc; |
394 | ||
395 | /* Check if file buffer is ready */ | |
396 | if (!dfu_file_buf) { | |
397 | dfu_file_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, | |
398 | CONFIG_SYS_DFU_MAX_FILE_SIZE); | |
399 | if (!dfu_file_buf) { | |
400 | error("Could not memalign 0x%x bytes", | |
401 | CONFIG_SYS_DFU_MAX_FILE_SIZE); | |
402 | return -ENOMEM; | |
403 | } | |
404 | } | |
cb383cd2 ŁM |
405 | |
406 | return 0; | |
407 | } |