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