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