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