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