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