xref: /aosp_15_r20/external/flashrom/linux_mtd.c (revision 0d6140be3aa665ecc836e8907834fcd3e3b018fc)
1 /*
2  * This file is part of the flashrom project.
3  *
4  * Copyright 2015 Google Inc.
5  * Copyright 2018-present Facebook, Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 2 of the License.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 
17 #include <ctype.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <mtd/mtd-user.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 
29 #include "flash.h"
30 #include "programmer.h"
31 
32 #define LINUX_DEV_ROOT			"/dev"
33 #define LINUX_MTD_SYSFS_ROOT		"/sys/class/mtd"
34 
35 struct linux_mtd_data {
36 	FILE *dev_fp;
37 	bool device_is_writeable;
38 	bool no_erase;
39 	/* Size info is presented in bytes in sysfs. */
40 	unsigned long int total_size;
41 	unsigned long int numeraseregions;
42 	/* only valid if numeraseregions is 0 */
43 	unsigned long int erasesize;
44 };
45 
46 /* read a string from a sysfs file and sanitize it */
read_sysfs_string(const char * sysfs_path,const char * filename,char * buf,int len)47 static int read_sysfs_string(const char *sysfs_path, const char *filename, char *buf, int len)
48 {
49 	int i;
50 	size_t bytes_read;
51 	FILE *fp;
52 	char path[sizeof(LINUX_MTD_SYSFS_ROOT) + 31];
53 
54 	snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
55 
56 	if ((fp = fopen(path, "r")) == NULL) {
57 		msg_perr("Cannot open %s\n", path);
58 		return 1;
59 	}
60 
61 	clearerr(fp);
62 	bytes_read = fread(buf, 1, (size_t)len, fp);
63 	if (!feof(fp) && ferror(fp)) {
64 		msg_perr("Error occurred when reading %s\n", path);
65 		fclose(fp);
66 		return 1;
67 	}
68 
69 	buf[bytes_read] = '\0';
70 
71 	/*
72 	 * Files from sysfs sometimes contain a newline or other garbage that
73 	 * can confuse functions like strtoul() and ruin formatting in print
74 	 * statements. Replace the first non-printable character (space is
75 	 * considered printable) with a proper string terminator.
76 	 */
77 	for (i = 0; i < len; i++) {
78 		if (!isprint(buf[i])) {
79 			buf[i] = '\0';
80 			break;
81 		}
82 	}
83 
84 	fclose(fp);
85 	return 0;
86 }
87 
read_sysfs_int(const char * sysfs_path,const char * filename,unsigned long int * val)88 static int read_sysfs_int(const char *sysfs_path, const char *filename, unsigned long int *val)
89 {
90 	char buf[32];
91 	char *endptr;
92 
93 	if (read_sysfs_string(sysfs_path, filename, buf, sizeof(buf)))
94 		return 1;
95 
96 	errno = 0;
97 	*val = strtoul(buf, &endptr, 0);
98 	if (*endptr != '\0') {
99 		msg_perr("Error reading %s\n", filename);
100 		return 1;
101 	}
102 
103 	if (errno) {
104 		msg_perr("Error reading %s: %s\n", filename, strerror(errno));
105 		return 1;
106 	}
107 
108 	return 0;
109 }
110 
popcnt(unsigned int u)111 static int popcnt(unsigned int u)
112 {
113 	int count = 0;
114 
115 	while (u) {
116 		u &= u - 1;
117 		count++;
118 	}
119 
120 	return count;
121 }
122 
123 /* returns 0 to indicate success, non-zero to indicate error */
get_mtd_info(const char * sysfs_path,struct linux_mtd_data * data)124 static int get_mtd_info(const char *sysfs_path, struct linux_mtd_data *data)
125 {
126 	unsigned long int tmp;
127 	char device_name[32];
128 
129 	/* Flags */
130 	if (read_sysfs_int(sysfs_path, "flags", &tmp))
131 		return 1;
132 	if (tmp & MTD_WRITEABLE) {
133 		/* cache for later use by write function */
134 		data->device_is_writeable = true;
135 	}
136 	if (tmp & MTD_NO_ERASE) {
137 		data->no_erase = true;
138 	}
139 
140 	/* Device name */
141 	if (read_sysfs_string(sysfs_path, "name", device_name, sizeof(device_name)))
142 		return 1;
143 
144 	/* Total size */
145 	if (read_sysfs_int(sysfs_path, "size", &data->total_size))
146 		return 1;
147 	if (popcnt(data->total_size) != 1) {
148 		msg_perr("MTD size is not a power of 2\n");
149 		return 1;
150 	}
151 
152 	/* Erase size */
153 	if (read_sysfs_int(sysfs_path, "erasesize", &data->erasesize))
154 		return 1;
155 	if (popcnt(data->erasesize) != 1) {
156 		msg_perr("MTD erase size is not a power of 2\n");
157 		return 1;
158 	}
159 
160 	/* Erase regions */
161 	if (read_sysfs_int(sysfs_path, "numeraseregions", &data->numeraseregions))
162 		return 1;
163 	if (data->numeraseregions != 0) {
164 		msg_perr("Non-uniform eraseblock size is unsupported.\n");
165 		return 1;
166 	}
167 
168 	msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, "
169 		"numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
170 		__func__, device_name, data->device_is_writeable,
171 		data->numeraseregions, data->total_size, data->erasesize);
172 
173 	return 0;
174 }
175 
linux_mtd_probe(struct flashctx * flash)176 static int linux_mtd_probe(struct flashctx *flash)
177 {
178 	struct linux_mtd_data *data = flash->mst->opaque.data;
179 
180 	if (data->no_erase)
181 		flash->chip->feature_bits |= FEATURE_NO_ERASE;
182 	flash->chip->tested = TEST_OK_PREWB;
183 	flash->chip->total_size = data->total_size / 1024;	/* bytes -> kB */
184 	flash->chip->block_erasers[0].eraseblocks[0].size = data->erasesize;
185 	flash->chip->block_erasers[0].eraseblocks[0].count =
186 		data->total_size / data->erasesize;
187 	return 1;
188 }
189 
linux_mtd_read(struct flashctx * flash,uint8_t * buf,unsigned int start,unsigned int len)190 static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
191 			  unsigned int start, unsigned int len)
192 {
193 	struct linux_mtd_data *data = flash->mst->opaque.data;
194 	unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
195 	unsigned int i;
196 
197 	if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
198 		msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
199 		return 1;
200 	}
201 
202 	for (i = 0; i < len; ) {
203 		/*
204 		 * Try to align reads to eraseblock size.
205 		 * FIXME: Shouldn't actually be necessary, but not all MTD
206 		 * drivers handle arbitrary large reads well.
207 		 */
208 		unsigned int step = eb_size - ((start + i) % eb_size);
209 		step = min(step, len - i);
210 
211 		if (fread(buf + i, step, 1, data->dev_fp) != 1) {
212 			msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
213 					step, start + i, strerror(errno));
214 			return 1;
215 		}
216 
217 		i += step;
218 		update_progress(flash, FLASHROM_PROGRESS_READ, i, len);
219 	}
220 
221 	return 0;
222 }
223 
224 /* this version assumes we must divide the write request into chunks ourselves */
linux_mtd_write(struct flashctx * flash,const uint8_t * buf,unsigned int start,unsigned int len)225 static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
226 				unsigned int start, unsigned int len)
227 {
228 	struct linux_mtd_data *data = flash->mst->opaque.data;
229 	unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
230 	unsigned int i;
231 
232 	if (!data->device_is_writeable)
233 		return 1;
234 
235 	if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
236 		msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
237 		return 1;
238 	}
239 
240 	/*
241 	 * Try to align writes to eraseblock size. We want these large enough
242 	 * to give MTD room for optimizing performance.
243 	 * FIXME: Shouldn't need to divide this up at all, but not all MTD
244 	 * drivers handle arbitrary large writes well.
245 	 */
246 	for (i = 0; i < len; ) {
247 		unsigned int step = chunksize - ((start + i) % chunksize);
248 		step = min(step, len - i);
249 
250 		if (fwrite(buf + i, step, 1, data->dev_fp) != 1) {
251 			msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
252 			return 1;
253 		}
254 
255 		if (fflush(data->dev_fp) == EOF) {
256 			msg_perr("Failed to flush buffer: %s\n", strerror(errno));
257 			return 1;
258 		}
259 
260 		i += step;
261 		update_progress(flash, FLASHROM_PROGRESS_WRITE, i, len);
262 	}
263 
264 	return 0;
265 }
266 
linux_mtd_erase(struct flashctx * flash,unsigned int start,unsigned int len)267 static int linux_mtd_erase(struct flashctx *flash,
268 			unsigned int start, unsigned int len)
269 {
270 	struct linux_mtd_data *data = flash->mst->opaque.data;
271 	uint32_t u;
272 
273 	if (data->no_erase) {
274 		msg_perr("%s: device does not support erasing. Please file a "
275 				"bug report at [email protected]\n", __func__);
276 		return 1;
277 	}
278 
279 	if (data->numeraseregions != 0) {
280 		/* TODO: Support non-uniform eraseblock size using
281 		   use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
282 		msg_perr("%s: numeraseregions must be 0\n", __func__);
283 		return 1;
284 	}
285 
286 	for (u = 0; u < len; u += data->erasesize) {
287 		struct erase_info_user erase_info = {
288 			.start = start + u,
289 			.length = data->erasesize,
290 		};
291 
292 		int ret = ioctl(fileno(data->dev_fp), MEMERASE, &erase_info);
293 		if (ret < 0) {
294 		        msg_perr("%s: MEMERASE ioctl call returned %d, error: %s\n",
295 		                 __func__, ret, strerror(errno));
296 		        return 1;
297 		}
298 		update_progress(flash, FLASHROM_PROGRESS_ERASE, u + data->erasesize, len);
299 	}
300 
301 	return 0;
302 }
303 
linux_mtd_shutdown(void * data)304 static int linux_mtd_shutdown(void *data)
305 {
306 	struct linux_mtd_data *mtd_data = data;
307 	if (mtd_data->dev_fp != NULL) {
308 		fclose(mtd_data->dev_fp);
309 	}
310 	free(data);
311 
312 	return 0;
313 }
314 
linux_mtd_wp_read_cfg(struct flashrom_wp_cfg * cfg,struct flashctx * flash)315 static enum flashrom_wp_result linux_mtd_wp_read_cfg(struct flashrom_wp_cfg *cfg, struct flashctx *flash)
316 {
317 	struct linux_mtd_data *data = flash->mst->opaque.data;
318 	bool start_found = false;
319 	bool end_found = false;
320 
321 	cfg->mode = FLASHROM_WP_MODE_DISABLED;
322 	cfg->range.start = 0;
323 	cfg->range.len = 0;
324 
325 	/* Check protection status of each block */
326 	for (size_t u = 0; u < data->total_size; u += data->erasesize) {
327 		struct erase_info_user erase_info = {
328 			.start = u,
329 			.length = data->erasesize,
330 		};
331 
332 		int ret = ioctl(fileno(data->dev_fp), MEMISLOCKED, &erase_info);
333 		if (ret == 0) {
334 			/* Block is unprotected. */
335 
336 			if (start_found) {
337 				end_found = true;
338 			}
339 		} else if (ret == 1) {
340 			/* Block is protected. */
341 
342 			if (end_found) {
343 				/*
344 				 * We already found the end of another
345 				 * protection range, so this is the start of a
346 				 * new one.
347 				 */
348 				return FLASHROM_WP_ERR_OTHER;
349 			}
350 			if (!start_found) {
351 				cfg->range.start = erase_info.start;
352 				cfg->mode = FLASHROM_WP_MODE_HARDWARE;
353 				start_found = true;
354 			}
355 			cfg->range.len += data->erasesize;
356 		} else {
357 			msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
358 			return FLASHROM_WP_ERR_READ_FAILED;
359 		}
360 
361 	}
362 
363 	return FLASHROM_WP_OK;
364 }
365 
linux_mtd_wp_write_cfg(struct flashctx * flash,const struct flashrom_wp_cfg * cfg)366 static enum flashrom_wp_result linux_mtd_wp_write_cfg(struct flashctx *flash, const struct flashrom_wp_cfg *cfg)
367 {
368 	const struct linux_mtd_data *data = flash->mst->opaque.data;
369 
370 	const struct erase_info_user entire_chip = {
371 		.start = 0,
372 		.length = data->total_size,
373 	};
374 	const struct erase_info_user desired_range = {
375 		.start = cfg->range.start,
376 		.length = cfg->range.len,
377 	};
378 
379 	/*
380 	 * MTD ioctls will enable hardware status register protection if and
381 	 * only if the protected region is non-empty. Return an error if the
382 	 * cfg cannot be activated using the MTD interface.
383 	 */
384 	if ((cfg->range.len == 0) != (cfg->mode == FLASHROM_WP_MODE_DISABLED)) {
385 		return FLASHROM_WP_ERR_OTHER;
386 	}
387 
388 	/*
389 	 * MTD handles write-protection additively, so whatever new range is
390 	 * specified is added to the range which is currently protected. To
391 	 * just protect the requsted range, we need to disable the current
392 	 * write protection and then enable it for the desired range.
393 	 */
394 	int ret = ioctl(fileno(data->dev_fp), MEMUNLOCK, &entire_chip);
395 	if (ret < 0) {
396 		msg_perr("%s: Failed to disable write-protection, MEMUNLOCK ioctl "
397 			 "retuned %d, error: %s\n", __func__, ret, strerror(errno));
398 		return FLASHROM_WP_ERR_WRITE_FAILED;
399 	}
400 
401 	if (cfg->range.len > 0) {
402 		ret = ioctl(fileno(data->dev_fp), MEMLOCK, &desired_range);
403 		if (ret < 0) {
404 			msg_perr("%s: Failed to enable write-protection, "
405 				 "MEMLOCK ioctl retuned %d, error: %s\n",
406 				 __func__, ret, strerror(errno));
407 			return FLASHROM_WP_ERR_WRITE_FAILED;
408 		}
409 	}
410 
411 	/* Verify */
412 	struct flashrom_wp_cfg readback_cfg;
413 	enum flashrom_wp_result read_ret = linux_mtd_wp_read_cfg(&readback_cfg, flash);
414 	if (read_ret != FLASHROM_WP_OK)
415 		return read_ret;
416 
417 	if (readback_cfg.mode != cfg->mode ||
418 		readback_cfg.range.start != cfg->range.start ||
419 		readback_cfg.range.len != cfg->range.len) {
420 		return FLASHROM_WP_ERR_VERIFY_FAILED;
421 	}
422 
423 	return FLASHROM_WP_OK;
424 }
425 
linux_mtd_wp_get_available_ranges(struct flashrom_wp_ranges ** list,struct flashctx * flash)426 static enum flashrom_wp_result linux_mtd_wp_get_available_ranges(struct flashrom_wp_ranges **list, struct flashctx *flash)
427 {
428 	/* Not supported by MTD interface. */
429 	return FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE;
430 }
431 
linux_mtd_nop_delay(const struct flashctx * flash,unsigned int usecs)432 static void linux_mtd_nop_delay(const struct flashctx *flash, unsigned int usecs)
433 {
434 	/*
435 	 * Ignore delay requests. The Linux MTD framework brokers all flash
436 	 * protocol, including timing, resets, etc.
437 	 */
438 }
439 
440 static const struct opaque_master linux_mtd_opaque_master = {
441 	/* max_data_{read,write} don't have any effect for this programmer */
442 	.max_data_read	= MAX_DATA_UNSPECIFIED,
443 	.max_data_write	= MAX_DATA_UNSPECIFIED,
444 	.probe		= linux_mtd_probe,
445 	.read		= linux_mtd_read,
446 	.write		= linux_mtd_write,
447 	.erase		= linux_mtd_erase,
448 	.shutdown	= linux_mtd_shutdown,
449 	.wp_read_cfg	= linux_mtd_wp_read_cfg,
450 	.wp_write_cfg	= linux_mtd_wp_write_cfg,
451 	.wp_get_ranges	= linux_mtd_wp_get_available_ranges,
452 	.delay		= linux_mtd_nop_delay,
453 };
454 
455 /* Returns 0 if setup is successful, non-zero to indicate error */
linux_mtd_setup(int dev_num,struct linux_mtd_data * data)456 static int linux_mtd_setup(int dev_num, struct linux_mtd_data *data)
457 {
458 	char sysfs_path[32];
459 	int ret = 1;
460 
461 	/* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
462 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
463 		goto linux_mtd_setup_exit;
464 
465 	char buf[4] = { 0 };
466 	if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
467 		return 1;
468 
469 	if (strcmp(buf, "nor")) {
470 		msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
471 		goto linux_mtd_setup_exit;
472 	}
473 
474 	/* sysfs shows the correct device type, see if corresponding device node exists */
475 	char dev_path[32];
476 	struct stat s;
477 	snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
478 	errno = 0;
479 	if (stat(dev_path, &s) < 0) {
480 		msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
481 		goto linux_mtd_setup_exit;
482 	}
483 
484 	/* so far so good, get more info from other files in this dir */
485 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
486 		goto linux_mtd_setup_exit;
487 	if (get_mtd_info(sysfs_path, data))
488 		goto linux_mtd_setup_exit;
489 
490 	/* open file stream and go! */
491 	if ((data->dev_fp = fopen(dev_path, "r+")) == NULL) {
492 		msg_perr("Cannot open file stream for %s\n", dev_path);
493 		goto linux_mtd_setup_exit;
494 	}
495 	ret = setvbuf(data->dev_fp, NULL, _IONBF, 0);
496 	if (ret)
497 		msg_pwarn("Failed to set MTD device to unbuffered: %d\n", ret);
498 
499 	msg_pinfo("Opened %s successfully\n", dev_path);
500 
501 	ret = 0;
502 linux_mtd_setup_exit:
503 	return ret;
504 }
505 
linux_mtd_init(const struct programmer_cfg * cfg)506 static int linux_mtd_init(const struct programmer_cfg *cfg)
507 {
508 	char *param_str;
509 	int dev_num = 0;
510 	int ret = 1;
511 	struct linux_mtd_data *data = NULL;
512 
513 	param_str = extract_programmer_param_str(cfg, "dev");
514 	if (param_str) {
515 		char *endptr;
516 
517 		dev_num = strtol(param_str, &endptr, 0);
518 		if ((*endptr != '\0') || (dev_num < 0)) {
519 			msg_perr("Invalid device number %s. Use flashrom -p "
520 				"linux_mtd:dev=N where N is a valid MTD\n"
521 				"device number.\n", param_str);
522 			goto linux_mtd_init_exit;
523 		}
524 	}
525 
526 	/*
527 	 * If user specified the MTD device number then error out if it doesn't
528 	 * appear to exist. Otherwise assume the error is benign and print a
529 	 * debug message. Bail out in either case.
530 	 */
531 	char sysfs_path[32];
532 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
533 		goto linux_mtd_init_exit;
534 
535 	struct stat s;
536 	if (stat(sysfs_path, &s) < 0) {
537 		if (param_str)
538 			msg_perr("%s does not exist\n", sysfs_path);
539 		else
540 			msg_pdbg("%s does not exist\n", sysfs_path);
541 		goto linux_mtd_init_exit;
542 	}
543 	free(param_str);
544 
545 	data = calloc(1, sizeof(*data));
546 	if (!data) {
547 		msg_perr("Unable to allocate memory for linux_mtd_data\n");
548 		return 1;
549 	}
550 
551 	/* Get MTD info and store it in `data` */
552 	if (linux_mtd_setup(dev_num, data)) {
553 		free(data);
554 		return 1;
555 	}
556 
557 	return register_opaque_master(&linux_mtd_opaque_master, data);
558 
559 linux_mtd_init_exit:
560 	free(param_str);
561 	return ret;
562 }
563 
564 const struct programmer_entry programmer_linux_mtd = {
565 	.name			= "linux_mtd",
566 	.type			= OTHER,
567 	.devs.note		= "Device files /dev/mtd*\n",
568 	.init			= linux_mtd_init,
569 };
570