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