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