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 <stdio.h>
21 #include <stdlib.h>
22 #include <mtd/mtd-user.h>
23 #include <string.h>
24 #include <sys/ioctl.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 
28 #include "flash.h"
29 #include "programmer.h"
30 
31 #define LINUX_DEV_ROOT			"/dev"
32 #define LINUX_MTD_SYSFS_ROOT		"/sys/class/mtd"
33 
34 static FILE *dev_fp = NULL;
35 
36 static int mtd_device_is_writeable;
37 
38 static int mtd_no_erase;
39 
40 /* Size info is presented in bytes in sysfs. */
41 static unsigned long int mtd_total_size;
42 static unsigned long int mtd_numeraseregions;
43 static unsigned long int mtd_erasesize;	/* only valid if numeraseregions is 0 */
44 
45 /* read a string from a sysfs file and sanitize it */
read_sysfs_string(const char * sysfs_path,const char * filename,char * buf,int len)46 static int read_sysfs_string(const char *sysfs_path, const char *filename, char *buf, int len)
47 {
48 	int i;
49 	size_t bytes_read;
50 	FILE *fp;
51 	char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
52 
53 	snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
54 
55 	if ((fp = fopen(path, "r")) == NULL) {
56 		msg_perr("Cannot open %s\n", path);
57 		return 1;
58 	}
59 
60 	clearerr(fp);
61 	bytes_read = fread(buf, 1, (size_t)len, fp);
62 	if (!feof(fp) && ferror(fp)) {
63 		msg_perr("Error occurred when reading %s\n", path);
64 		fclose(fp);
65 		return 1;
66 	}
67 
68 	buf[bytes_read] = '\0';
69 
70 	/*
71 	 * Files from sysfs sometimes contain a newline or other garbage that
72 	 * can confuse functions like strtoul() and ruin formatting in print
73 	 * statements. Replace the first non-printable character (space is
74 	 * considered printable) with a proper string terminator.
75 	 */
76 	for (i = 0; i < len; i++) {
77 		if (!isprint(buf[i])) {
78 			buf[i] = '\0';
79 			break;
80 		}
81 	}
82 
83 	fclose(fp);
84 	return 0;
85 }
86 
read_sysfs_int(const char * sysfs_path,const char * filename,unsigned long int * val)87 static int read_sysfs_int(const char *sysfs_path, const char *filename, unsigned long int *val)
88 {
89 	char buf[32];
90 	char *endptr;
91 
92 	if (read_sysfs_string(sysfs_path, filename, buf, sizeof(buf)))
93 		return 1;
94 
95 	errno = 0;
96 	*val = strtoul(buf, &endptr, 0);
97 	if (*endptr != '\0') {
98 		msg_perr("Error reading %s\n", filename);
99 		return 1;
100 	}
101 
102 	if (errno) {
103 		msg_perr("Error reading %s: %s\n", filename, strerror(errno));
104 		return 1;
105 	}
106 
107 	return 0;
108 }
109 
popcnt(unsigned int u)110 static int popcnt(unsigned int u)
111 {
112 	int count = 0;
113 
114 	while (u) {
115 		u &= u - 1;
116 		count++;
117 	}
118 
119 	return count;
120 }
121 
122 /* returns 0 to indicate success, non-zero to indicate error */
get_mtd_info(const char * sysfs_path)123 static int get_mtd_info(const char *sysfs_path)
124 {
125 	unsigned long int tmp;
126 	char mtd_device_name[32];
127 
128 	/* Flags */
129 	if (read_sysfs_int(sysfs_path, "flags", &tmp))
130 		return 1;
131 	if (tmp & MTD_WRITEABLE) {
132 		/* cache for later use by write function */
133 		mtd_device_is_writeable = 1;
134 	}
135 	if (tmp & MTD_NO_ERASE) {
136 		mtd_no_erase = 1;
137 	}
138 
139 	/* Device name */
140 	if (read_sysfs_string(sysfs_path, "name", mtd_device_name, sizeof(mtd_device_name)))
141 		return 1;
142 
143 	/* Total size */
144 	if (read_sysfs_int(sysfs_path, "size", &mtd_total_size))
145 		return 1;
146 	if (popcnt(mtd_total_size) != 1) {
147 		msg_perr("MTD size is not a power of 2\n");
148 		return 1;
149 	}
150 
151 	/* Erase size */
152 	if (read_sysfs_int(sysfs_path, "erasesize", &mtd_erasesize))
153 		return 1;
154 	if (popcnt(mtd_erasesize) != 1) {
155 		msg_perr("MTD erase size is not a power of 2\n");
156 		return 1;
157 	}
158 
159 	/* Erase regions */
160 	if (read_sysfs_int(sysfs_path, "numeraseregions", &mtd_numeraseregions))
161 		return 1;
162 	if (mtd_numeraseregions != 0) {
163 		msg_perr("Non-uniform eraseblock size is unsupported.\n");
164 		return 1;
165 	}
166 
167 	msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, "
168 		"numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
169 		__func__, mtd_device_name, mtd_device_is_writeable,
170 		mtd_numeraseregions, mtd_total_size, mtd_erasesize);
171 
172 	return 0;
173 }
174 
linux_mtd_probe(struct flashctx * flash)175 static int linux_mtd_probe(struct flashctx *flash)
176 {
177 	if (mtd_no_erase)
178 		flash->chip->feature_bits |= FEATURE_NO_ERASE;
179 	flash->chip->tested = TEST_OK_PREW;
180 	flash->chip->total_size = mtd_total_size / 1024;	/* bytes -> kB */
181 	flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
182 	flash->chip->block_erasers[0].eraseblocks[0].count = mtd_total_size / mtd_erasesize;
183 	return 1;
184 }
185 
linux_mtd_read(struct flashctx * flash,uint8_t * buf,unsigned int start,unsigned int len)186 static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
187 			  unsigned int start, unsigned int len)
188 {
189 	unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
190 	unsigned int i;
191 
192 	if (fseek(dev_fp, start, SEEK_SET) != 0) {
193 		msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
194 		return 1;
195 	}
196 
197 	for (i = 0; i < len; ) {
198 		/*
199 		 * Try to align reads to eraseblock size.
200 		 * FIXME: Shouldn't actually be necessary, but not all MTD
201 		 * drivers handle arbitrary large reads well.
202 		 */
203 		unsigned int step = eb_size - ((start + i) % eb_size);
204 		step = min(step, len - i);
205 
206 		if (fread(buf + i, step, 1, dev_fp) != 1) {
207 			msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
208 					step, start + i, strerror(errno));
209 			return 1;
210 		}
211 
212 		i += step;
213 	}
214 
215 	return 0;
216 }
217 
218 /* 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)219 static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
220 				unsigned int start, unsigned int len)
221 {
222 	unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
223 	unsigned int i;
224 
225 	if (!mtd_device_is_writeable)
226 		return 1;
227 
228 	if (fseek(dev_fp, start, SEEK_SET) != 0) {
229 		msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
230 		return 1;
231 	}
232 
233 	/*
234 	 * Try to align writes to eraseblock size. We want these large enough
235 	 * to give MTD room for optimizing performance.
236 	 * FIXME: Shouldn't need to divide this up at all, but not all MTD
237 	 * drivers handle arbitrary large writes well.
238 	 */
239 	for (i = 0; i < len; ) {
240 		unsigned int step = chunksize - ((start + i) % chunksize);
241 		step = min(step, len - i);
242 
243 		if (fwrite(buf + i, step, 1, dev_fp) != 1) {
244 			msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
245 			return 1;
246 		}
247 
248 		if (fflush(dev_fp) == EOF) {
249 			msg_perr("Failed to flush buffer: %s\n", strerror(errno));
250 			return 1;
251 		}
252 
253 		i += step;
254 	}
255 
256 	return 0;
257 }
258 
linux_mtd_erase(struct flashctx * flash,unsigned int start,unsigned int len)259 static int linux_mtd_erase(struct flashctx *flash,
260 			unsigned int start, unsigned int len)
261 {
262 	uint32_t u;
263 
264 	if (mtd_no_erase) {
265 		msg_perr("%s: device does not support erasing. Please file a "
266 				"bug report at flashrom@flashrom.org\n", __func__);
267 		return 1;
268 	}
269 
270 	if (mtd_numeraseregions != 0) {
271 		/* TODO: Support non-uniform eraseblock size using
272 		   use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
273 		msg_perr("%s: mtd_numeraseregions must be 0\n", __func__);
274 		return 1;
275 	}
276 
277 	for (u = 0; u < len; u += mtd_erasesize) {
278 		struct erase_info_user erase_info = {
279 			.start = start + u,
280 			.length = mtd_erasesize,
281 		};
282 
283 		if (ioctl(fileno(dev_fp), MEMERASE, &erase_info) == -1) {
284 			msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
285 			return 1;
286 		}
287 	}
288 
289 	return 0;
290 }
291 
292 static struct opaque_master programmer_linux_mtd = {
293 	/* max_data_{read,write} don't have any effect for this programmer */
294 	.max_data_read	= MAX_DATA_UNSPECIFIED,
295 	.max_data_write	= MAX_DATA_UNSPECIFIED,
296 	.probe		= linux_mtd_probe,
297 	.read		= linux_mtd_read,
298 	.write		= linux_mtd_write,
299 	.erase		= linux_mtd_erase,
300 };
301 
302 /* Returns 0 if setup is successful, non-zero to indicate error */
linux_mtd_setup(int dev_num)303 static int linux_mtd_setup(int dev_num)
304 {
305 	char sysfs_path[32];
306 	int ret = 1;
307 
308 	/* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
309 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
310 		goto linux_mtd_setup_exit;
311 
312 	char buf[4];
313 	memset(buf, 0, sizeof(buf));
314 	if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
315 		return 1;
316 
317 	if (strcmp(buf, "nor")) {
318 		msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
319 		goto linux_mtd_setup_exit;
320 	}
321 
322 	/* sysfs shows the correct device type, see if corresponding device node exists */
323 	char dev_path[32];
324 	struct stat s;
325 	snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
326 	errno = 0;
327 	if (stat(dev_path, &s) < 0) {
328 		msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
329 		goto linux_mtd_setup_exit;
330 	}
331 
332 	/* so far so good, get more info from other files in this dir */
333 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
334 		goto linux_mtd_setup_exit;
335 	if (get_mtd_info(sysfs_path))
336 		goto linux_mtd_setup_exit;
337 
338 	/* open file stream and go! */
339 	if ((dev_fp = fopen(dev_path, "r+")) == NULL) {
340 		msg_perr("Cannot open file stream for %s\n", dev_path);
341 		goto linux_mtd_setup_exit;
342 	}
343 	msg_pinfo("Opened %s successfully\n", dev_path);
344 
345 	ret = 0;
346 linux_mtd_setup_exit:
347 	return ret;
348 }
349 
linux_mtd_shutdown(void * data)350 static int linux_mtd_shutdown(void *data)
351 {
352 	if (dev_fp != NULL) {
353 		fclose(dev_fp);
354 		dev_fp = NULL;
355 	}
356 
357 	return 0;
358 }
359 
linux_mtd_init(void)360 int linux_mtd_init(void)
361 {
362 	char *param;
363 	int dev_num = 0;
364 	int ret = 1;
365 
366 	param = extract_programmer_param("dev");
367 	if (param) {
368 		char *endptr;
369 
370 		dev_num = strtol(param, &endptr, 0);
371 		if ((*endptr != '\0') || (dev_num < 0)) {
372 			msg_perr("Invalid device number %s. Use flashrom -p "
373 				"linux_mtd:dev=N where N is a valid MTD\n"
374 				"device number.\n", param);
375 			goto linux_mtd_init_exit;
376 		}
377 	}
378 
379 	/*
380 	 * If user specified the MTD device number then error out if it doesn't
381 	 * appear to exist. Otherwise assume the error is benign and print a
382 	 * debug message. Bail out in either case.
383 	 */
384 	char sysfs_path[32];
385 	if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
386 		goto linux_mtd_init_exit;
387 
388 	struct stat s;
389 	if (stat(sysfs_path, &s) < 0) {
390 		if (param)
391 			msg_perr("%s does not exist\n", sysfs_path);
392 		else
393 			msg_pdbg("%s does not exist\n", sysfs_path);
394 		goto linux_mtd_init_exit;
395 	}
396 
397 	if (linux_mtd_setup(dev_num))
398 		goto linux_mtd_init_exit;
399 
400 	if (register_shutdown(linux_mtd_shutdown, NULL))
401 		goto linux_mtd_init_exit;
402 
403 	register_opaque_master(&programmer_linux_mtd);
404 
405 	ret = 0;
406 linux_mtd_init_exit:
407 	free(param);
408 	return ret;
409 }
410