blob: 2f8bac5978d38bbb2b6811f40c31ab9ba13aaaa2 [file] [log] [blame]
David Hendricksf9a30552015-05-23 20:30:30 -07001/*
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
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100034struct linux_mtd_data {
35 FILE *dev_fp;
Nikolai Artemiev6c331852021-05-09 11:37:38 +100036 int device_is_writeable;
37 int no_erase;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100038 /* Size info is presented in bytes in sysfs. */
Nikolai Artemiev6c331852021-05-09 11:37:38 +100039 unsigned long int total_size;
40 unsigned long int numeraseregions;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100041 /* only valid if numeraseregions is 0 */
Nikolai Artemiev6c331852021-05-09 11:37:38 +100042 unsigned long int erasesize;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100043};
David Hendricksf9a30552015-05-23 20:30:30 -070044
45/* read a string from a sysfs file and sanitize it */
46static 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
87static 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
110static 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 */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000123static int get_mtd_info(const char *sysfs_path, struct linux_mtd_data *data)
David Hendricksf9a30552015-05-23 20:30:30 -0700124{
125 unsigned long int tmp;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000126 char device_name[32];
David Hendricksf9a30552015-05-23 20:30:30 -0700127
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 */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000133 data->device_is_writeable = 1;
David Hendricksf9a30552015-05-23 20:30:30 -0700134 }
135 if (tmp & MTD_NO_ERASE) {
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000136 data->no_erase = 1;
David Hendricksf9a30552015-05-23 20:30:30 -0700137 }
138
139 /* Device name */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000140 if (read_sysfs_string(sysfs_path, "name", device_name, sizeof(device_name)))
David Hendricksf9a30552015-05-23 20:30:30 -0700141 return 1;
142
143 /* Total size */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000144 if (read_sysfs_int(sysfs_path, "size", &data->total_size))
David Hendricksf9a30552015-05-23 20:30:30 -0700145 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000146 if (popcnt(data->total_size) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700147 msg_perr("MTD size is not a power of 2\n");
148 return 1;
149 }
150
151 /* Erase size */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000152 if (read_sysfs_int(sysfs_path, "erasesize", &data->erasesize))
David Hendricksf9a30552015-05-23 20:30:30 -0700153 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000154 if (popcnt(data->erasesize) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700155 msg_perr("MTD erase size is not a power of 2\n");
156 return 1;
157 }
158
159 /* Erase regions */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000160 if (read_sysfs_int(sysfs_path, "numeraseregions", &data->numeraseregions))
David Hendricksf9a30552015-05-23 20:30:30 -0700161 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000162 if (data->numeraseregions != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700163 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",
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000169 __func__, device_name, data->device_is_writeable,
170 data->numeraseregions, data->total_size, data->erasesize);
David Hendricksf9a30552015-05-23 20:30:30 -0700171
172 return 0;
173}
174
175static int linux_mtd_probe(struct flashctx *flash)
176{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000177 struct linux_mtd_data *data = flash->mst->opaque.data;
178
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000179 if (data->no_erase)
David Hendricksf9a30552015-05-23 20:30:30 -0700180 flash->chip->feature_bits |= FEATURE_NO_ERASE;
181 flash->chip->tested = TEST_OK_PREW;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000182 flash->chip->total_size = data->total_size / 1024; /* bytes -> kB */
183 flash->chip->block_erasers[0].eraseblocks[0].size = data->erasesize;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000184 flash->chip->block_erasers[0].eraseblocks[0].count =
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000185 data->total_size / data->erasesize;
David Hendricksf9a30552015-05-23 20:30:30 -0700186 return 1;
187}
188
189static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
190 unsigned int start, unsigned int len)
191{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000192 struct linux_mtd_data *data = flash->mst->opaque.data;
David Hendricksf9a30552015-05-23 20:30:30 -0700193 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
194 unsigned int i;
195
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000196 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700197 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
198 return 1;
199 }
200
201 for (i = 0; i < len; ) {
202 /*
203 * Try to align reads to eraseblock size.
204 * FIXME: Shouldn't actually be necessary, but not all MTD
205 * drivers handle arbitrary large reads well.
206 */
207 unsigned int step = eb_size - ((start + i) % eb_size);
208 step = min(step, len - i);
209
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000210 if (fread(buf + i, step, 1, data->dev_fp) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700211 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
212 step, start + i, strerror(errno));
213 return 1;
214 }
215
216 i += step;
217 }
218
219 return 0;
220}
221
222/* this version assumes we must divide the write request into chunks ourselves */
223static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
224 unsigned int start, unsigned int len)
225{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000226 struct linux_mtd_data *data = flash->mst->opaque.data;
David Hendricksf9a30552015-05-23 20:30:30 -0700227 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
228 unsigned int i;
229
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000230 if (!data->device_is_writeable)
David Hendricksf9a30552015-05-23 20:30:30 -0700231 return 1;
232
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000233 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700234 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
235 return 1;
236 }
237
238 /*
239 * Try to align writes to eraseblock size. We want these large enough
240 * to give MTD room for optimizing performance.
241 * FIXME: Shouldn't need to divide this up at all, but not all MTD
242 * drivers handle arbitrary large writes well.
243 */
244 for (i = 0; i < len; ) {
245 unsigned int step = chunksize - ((start + i) % chunksize);
246 step = min(step, len - i);
247
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000248 if (fwrite(buf + i, step, 1, data->dev_fp) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700249 msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
250 return 1;
251 }
252
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000253 if (fflush(data->dev_fp) == EOF) {
David Hendricksf9a30552015-05-23 20:30:30 -0700254 msg_perr("Failed to flush buffer: %s\n", strerror(errno));
255 return 1;
256 }
257
258 i += step;
259 }
260
261 return 0;
262}
263
264static int linux_mtd_erase(struct flashctx *flash,
265 unsigned int start, unsigned int len)
266{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000267 struct linux_mtd_data *data = flash->mst->opaque.data;
David Hendricksf9a30552015-05-23 20:30:30 -0700268 uint32_t u;
269
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000270 if (data->no_erase) {
Nico Huberac90af62022-12-18 00:22:47 +0000271 msg_perr("%s: device does not support erasing.\n"
272 "Please file a bug report at flashrom-stable@flashrom.org\n",
273 __func__);
David Hendricksf9a30552015-05-23 20:30:30 -0700274 return 1;
275 }
276
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000277 if (data->numeraseregions != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700278 /* TODO: Support non-uniform eraseblock size using
279 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000280 msg_perr("%s: numeraseregions must be 0\n", __func__);
David Hendricksf9a30552015-05-23 20:30:30 -0700281 return 1;
282 }
283
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000284 for (u = 0; u < len; u += data->erasesize) {
David Hendricksf9a30552015-05-23 20:30:30 -0700285 struct erase_info_user erase_info = {
286 .start = start + u,
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000287 .length = data->erasesize,
David Hendricksf9a30552015-05-23 20:30:30 -0700288 };
289
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000290 int ret = ioctl(fileno(data->dev_fp), MEMERASE, &erase_info);
Nikolai Artemiev04fce472022-01-11 18:26:48 +1100291 if (ret < 0) {
292 msg_perr("%s: MEMERASE ioctl call returned %d, error: %s\n",
293 __func__, ret, strerror(errno));
294 return 1;
David Hendricksf9a30552015-05-23 20:30:30 -0700295 }
296 }
297
298 return 0;
299}
300
Anastasia Klimchuk842b4ee2021-05-13 12:56:51 +1000301static const struct opaque_master linux_mtd_opaque_master = {
David Hendricksf9a30552015-05-23 20:30:30 -0700302 /* max_data_{read,write} don't have any effect for this programmer */
303 .max_data_read = MAX_DATA_UNSPECIFIED,
304 .max_data_write = MAX_DATA_UNSPECIFIED,
305 .probe = linux_mtd_probe,
306 .read = linux_mtd_read,
307 .write = linux_mtd_write,
308 .erase = linux_mtd_erase,
309};
310
311/* Returns 0 if setup is successful, non-zero to indicate error */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000312static int linux_mtd_setup(int dev_num, struct linux_mtd_data *data)
David Hendricksf9a30552015-05-23 20:30:30 -0700313{
314 char sysfs_path[32];
315 int ret = 1;
316
317 /* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
318 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
319 goto linux_mtd_setup_exit;
320
Angel Pons7e134562021-06-07 13:29:13 +0200321 char buf[4] = { 0 };
David Hendricksf9a30552015-05-23 20:30:30 -0700322 if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
323 return 1;
324
325 if (strcmp(buf, "nor")) {
326 msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
327 goto linux_mtd_setup_exit;
328 }
329
330 /* sysfs shows the correct device type, see if corresponding device node exists */
331 char dev_path[32];
332 struct stat s;
333 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
334 errno = 0;
335 if (stat(dev_path, &s) < 0) {
336 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
337 goto linux_mtd_setup_exit;
338 }
339
340 /* so far so good, get more info from other files in this dir */
341 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
342 goto linux_mtd_setup_exit;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000343 if (get_mtd_info(sysfs_path, data))
David Hendricksf9a30552015-05-23 20:30:30 -0700344 goto linux_mtd_setup_exit;
345
346 /* open file stream and go! */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000347 if ((data->dev_fp = fopen(dev_path, "r+")) == NULL) {
David Hendricksf9a30552015-05-23 20:30:30 -0700348 msg_perr("Cannot open file stream for %s\n", dev_path);
349 goto linux_mtd_setup_exit;
350 }
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000351 ret = setvbuf(data->dev_fp, NULL, _IONBF, 0);
Douglas Anderson595c5d02021-01-29 16:35:24 -0800352 if (ret)
353 msg_pwarn("Failed to set MTD device to unbuffered: %d\n", ret);
354
David Hendricksf9a30552015-05-23 20:30:30 -0700355 msg_pinfo("Opened %s successfully\n", dev_path);
356
357 ret = 0;
358linux_mtd_setup_exit:
359 return ret;
360}
361
362static int linux_mtd_shutdown(void *data)
363{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000364 struct linux_mtd_data *mtd_data = data;
365 if (mtd_data->dev_fp != NULL) {
366 fclose(mtd_data->dev_fp);
David Hendricksf9a30552015-05-23 20:30:30 -0700367 }
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000368 free(data);
David Hendricksf9a30552015-05-23 20:30:30 -0700369
370 return 0;
371}
372
Thomas Heijligencc853d82021-05-04 15:32:17 +0200373static int linux_mtd_init(void)
David Hendricksf9a30552015-05-23 20:30:30 -0700374{
375 char *param;
376 int dev_num = 0;
377 int ret = 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000378 struct linux_mtd_data *data = NULL;
David Hendricksf9a30552015-05-23 20:30:30 -0700379
380 param = extract_programmer_param("dev");
381 if (param) {
382 char *endptr;
383
384 dev_num = strtol(param, &endptr, 0);
385 if ((*endptr != '\0') || (dev_num < 0)) {
386 msg_perr("Invalid device number %s. Use flashrom -p "
387 "linux_mtd:dev=N where N is a valid MTD\n"
388 "device number.\n", param);
389 goto linux_mtd_init_exit;
390 }
391 }
392
David Hendricksb0247b32018-05-23 21:50:18 -0700393 /*
394 * If user specified the MTD device number then error out if it doesn't
395 * appear to exist. Otherwise assume the error is benign and print a
396 * debug message. Bail out in either case.
397 */
398 char sysfs_path[32];
399 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
400 goto linux_mtd_init_exit;
401
402 struct stat s;
403 if (stat(sysfs_path, &s) < 0) {
404 if (param)
405 msg_perr("%s does not exist\n", sysfs_path);
406 else
407 msg_pdbg("%s does not exist\n", sysfs_path);
408 goto linux_mtd_init_exit;
409 }
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000410 free(param);
David Hendricksb0247b32018-05-23 21:50:18 -0700411
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000412 data = calloc(1, sizeof(*data));
413 if (!data) {
414 msg_perr("Unable to allocate memory for linux_mtd_data\n");
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000415 return 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000416 }
David Hendricksf9a30552015-05-23 20:30:30 -0700417
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000418 /* Get MTD info and store it in `data` */
419 if (linux_mtd_setup(dev_num, data)) {
420 free(data);
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000421 return 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000422 }
423
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000424 if (register_shutdown(linux_mtd_shutdown, (void *)data)) {
425 free(data);
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000426 return 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000427 }
David Hendricksf9a30552015-05-23 20:30:30 -0700428
Anastasia Klimchuk842b4ee2021-05-13 12:56:51 +1000429 register_opaque_master(&linux_mtd_opaque_master, data);
David Hendricksf9a30552015-05-23 20:30:30 -0700430
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000431 return 0;
432
David Hendricksf9a30552015-05-23 20:30:30 -0700433linux_mtd_init_exit:
Jacob Garberba719992019-08-12 12:07:03 -0600434 free(param);
David Hendricksf9a30552015-05-23 20:30:30 -0700435 return ret;
436}
Thomas Heijligencc853d82021-05-04 15:32:17 +0200437
438const struct programmer_entry programmer_linux_mtd = {
439 .name = "linux_mtd",
440 .type = OTHER,
441 .devs.note = "Device files /dev/mtd*\n",
442 .init = linux_mtd_init,
443 .map_flash_region = fallback_map,
444 .unmap_flash_region = fallback_unmap,
445 .delay = internal_delay,
446};