blob: 23041d46345ef241199626fd60e5fc4bd2a96845 [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>
Felix Singerbd82fa92022-08-19 02:40:39 +020020#include <stdbool.h>
David Hendricksf9a30552015-05-23 20:30:30 -070021#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
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100035struct linux_mtd_data {
36 FILE *dev_fp;
Felix Singerbd82fa92022-08-19 02:40:39 +020037 bool device_is_writeable;
38 bool no_erase;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100039 /* Size info is presented in bytes in sysfs. */
Nikolai Artemiev6c331852021-05-09 11:37:38 +100040 unsigned long int total_size;
41 unsigned long int numeraseregions;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100042 /* only valid if numeraseregions is 0 */
Nikolai Artemiev6c331852021-05-09 11:37:38 +100043 unsigned long int erasesize;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +100044};
David Hendricksf9a30552015-05-23 20:30:30 -070045
46/* read a string from a sysfs file and sanitize it */
47static 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[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
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
88static 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
111static 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 */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000124static int get_mtd_info(const char *sysfs_path, struct linux_mtd_data *data)
David Hendricksf9a30552015-05-23 20:30:30 -0700125{
126 unsigned long int tmp;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000127 char device_name[32];
David Hendricksf9a30552015-05-23 20:30:30 -0700128
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 */
Felix Singerbd82fa92022-08-19 02:40:39 +0200134 data->device_is_writeable = true;
David Hendricksf9a30552015-05-23 20:30:30 -0700135 }
136 if (tmp & MTD_NO_ERASE) {
Felix Singerbd82fa92022-08-19 02:40:39 +0200137 data->no_erase = true;
David Hendricksf9a30552015-05-23 20:30:30 -0700138 }
139
140 /* Device name */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000141 if (read_sysfs_string(sysfs_path, "name", device_name, sizeof(device_name)))
David Hendricksf9a30552015-05-23 20:30:30 -0700142 return 1;
143
144 /* Total size */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000145 if (read_sysfs_int(sysfs_path, "size", &data->total_size))
David Hendricksf9a30552015-05-23 20:30:30 -0700146 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000147 if (popcnt(data->total_size) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700148 msg_perr("MTD size is not a power of 2\n");
149 return 1;
150 }
151
152 /* Erase size */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000153 if (read_sysfs_int(sysfs_path, "erasesize", &data->erasesize))
David Hendricksf9a30552015-05-23 20:30:30 -0700154 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000155 if (popcnt(data->erasesize) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700156 msg_perr("MTD erase size is not a power of 2\n");
157 return 1;
158 }
159
160 /* Erase regions */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000161 if (read_sysfs_int(sysfs_path, "numeraseregions", &data->numeraseregions))
David Hendricksf9a30552015-05-23 20:30:30 -0700162 return 1;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000163 if (data->numeraseregions != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700164 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",
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000170 __func__, device_name, data->device_is_writeable,
171 data->numeraseregions, data->total_size, data->erasesize);
David Hendricksf9a30552015-05-23 20:30:30 -0700172
173 return 0;
174}
175
176static int linux_mtd_probe(struct flashctx *flash)
177{
Nico Huber9a11cbf2023-01-13 01:19:07 +0100178 struct linux_mtd_data *data = flash->mst.opaque->data;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000179
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000180 if (data->no_erase)
David Hendricksf9a30552015-05-23 20:30:30 -0700181 flash->chip->feature_bits |= FEATURE_NO_ERASE;
182 flash->chip->tested = TEST_OK_PREW;
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000183 flash->chip->total_size = data->total_size / 1024; /* bytes -> kB */
184 flash->chip->block_erasers[0].eraseblocks[0].size = data->erasesize;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000185 flash->chip->block_erasers[0].eraseblocks[0].count =
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000186 data->total_size / data->erasesize;
David Hendricksf9a30552015-05-23 20:30:30 -0700187 return 1;
188}
189
190static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
191 unsigned int start, unsigned int len)
192{
Nico Huber9a11cbf2023-01-13 01:19:07 +0100193 struct linux_mtd_data *data = flash->mst.opaque->data;
David Hendricksf9a30552015-05-23 20:30:30 -0700194 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
195 unsigned int i;
196
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000197 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700198 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
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000211 if (fread(buf + i, step, 1, data->dev_fp) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700212 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;
Richard Hughes842d6782021-01-15 09:48:12 +0000218 flashprog_progress_add(flash, step);
David Hendricksf9a30552015-05-23 20:30:30 -0700219 }
220
221 return 0;
222}
223
224/* this version assumes we must divide the write request into chunks ourselves */
225static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
226 unsigned int start, unsigned int len)
227{
Nico Huber9a11cbf2023-01-13 01:19:07 +0100228 struct linux_mtd_data *data = flash->mst.opaque->data;
David Hendricksf9a30552015-05-23 20:30:30 -0700229 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
230 unsigned int i;
231
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000232 if (!data->device_is_writeable)
David Hendricksf9a30552015-05-23 20:30:30 -0700233 return 1;
234
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000235 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700236 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
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000250 if (fwrite(buf + i, step, 1, data->dev_fp) != 1) {
David Hendricksf9a30552015-05-23 20:30:30 -0700251 msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
252 return 1;
253 }
254
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000255 if (fflush(data->dev_fp) == EOF) {
David Hendricksf9a30552015-05-23 20:30:30 -0700256 msg_perr("Failed to flush buffer: %s\n", strerror(errno));
257 return 1;
258 }
259
260 i += step;
Richard Hughes842d6782021-01-15 09:48:12 +0000261 flashprog_progress_add(flash, step);
David Hendricksf9a30552015-05-23 20:30:30 -0700262 }
263
264 return 0;
265}
266
267static int linux_mtd_erase(struct flashctx *flash,
268 unsigned int start, unsigned int len)
269{
Nico Huber9a11cbf2023-01-13 01:19:07 +0100270 struct linux_mtd_data *data = flash->mst.opaque->data;
David Hendricksf9a30552015-05-23 20:30:30 -0700271 uint32_t u;
272
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000273 if (data->no_erase) {
Nico Huberac90af62022-12-18 00:22:47 +0000274 msg_perr("%s: device does not support erasing.\n"
Nico Huberc3b02dc2023-08-12 01:13:45 +0200275 "Please file a bug report at flashprog@flashprog.org\n",
Nico Huberac90af62022-12-18 00:22:47 +0000276 __func__);
David Hendricksf9a30552015-05-23 20:30:30 -0700277 return 1;
278 }
279
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000280 if (data->numeraseregions != 0) {
David Hendricksf9a30552015-05-23 20:30:30 -0700281 /* TODO: Support non-uniform eraseblock size using
282 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000283 msg_perr("%s: numeraseregions must be 0\n", __func__);
David Hendricksf9a30552015-05-23 20:30:30 -0700284 return 1;
285 }
286
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000287 for (u = 0; u < len; u += data->erasesize) {
David Hendricksf9a30552015-05-23 20:30:30 -0700288 struct erase_info_user erase_info = {
289 .start = start + u,
Nikolai Artemiev6c331852021-05-09 11:37:38 +1000290 .length = data->erasesize,
David Hendricksf9a30552015-05-23 20:30:30 -0700291 };
292
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000293 int ret = ioctl(fileno(data->dev_fp), MEMERASE, &erase_info);
Nikolai Artemiev04fce472022-01-11 18:26:48 +1100294 if (ret < 0) {
295 msg_perr("%s: MEMERASE ioctl call returned %d, error: %s\n",
296 __func__, ret, strerror(errno));
297 return 1;
David Hendricksf9a30552015-05-23 20:30:30 -0700298 }
299 }
300
301 return 0;
302}
303
Nico Huber72c02ff2023-01-08 02:00:06 +0100304static int linux_mtd_shutdown(void *data);
305
Anastasia Klimchuk842b4ee2021-05-13 12:56:51 +1000306static const struct opaque_master linux_mtd_opaque_master = {
David Hendricksf9a30552015-05-23 20:30:30 -0700307 /* max_data_{read,write} don't have any effect for this programmer */
308 .max_data_read = MAX_DATA_UNSPECIFIED,
309 .max_data_write = MAX_DATA_UNSPECIFIED,
310 .probe = linux_mtd_probe,
311 .read = linux_mtd_read,
312 .write = linux_mtd_write,
313 .erase = linux_mtd_erase,
Nico Huber72c02ff2023-01-08 02:00:06 +0100314 .shutdown = linux_mtd_shutdown,
David Hendricksf9a30552015-05-23 20:30:30 -0700315};
316
317/* Returns 0 if setup is successful, non-zero to indicate error */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000318static int linux_mtd_setup(int dev_num, struct linux_mtd_data *data)
David Hendricksf9a30552015-05-23 20:30:30 -0700319{
320 char sysfs_path[32];
321 int ret = 1;
322
323 /* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
324 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
325 goto linux_mtd_setup_exit;
326
Angel Pons7e134562021-06-07 13:29:13 +0200327 char buf[4] = { 0 };
David Hendricksf9a30552015-05-23 20:30:30 -0700328 if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
329 return 1;
330
331 if (strcmp(buf, "nor")) {
332 msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
333 goto linux_mtd_setup_exit;
334 }
335
336 /* sysfs shows the correct device type, see if corresponding device node exists */
337 char dev_path[32];
338 struct stat s;
339 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
340 errno = 0;
341 if (stat(dev_path, &s) < 0) {
342 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
343 goto linux_mtd_setup_exit;
344 }
345
346 /* so far so good, get more info from other files in this dir */
347 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
348 goto linux_mtd_setup_exit;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000349 if (get_mtd_info(sysfs_path, data))
David Hendricksf9a30552015-05-23 20:30:30 -0700350 goto linux_mtd_setup_exit;
351
352 /* open file stream and go! */
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000353 if ((data->dev_fp = fopen(dev_path, "r+")) == NULL) {
David Hendricksf9a30552015-05-23 20:30:30 -0700354 msg_perr("Cannot open file stream for %s\n", dev_path);
355 goto linux_mtd_setup_exit;
356 }
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000357 ret = setvbuf(data->dev_fp, NULL, _IONBF, 0);
Douglas Anderson595c5d02021-01-29 16:35:24 -0800358 if (ret)
359 msg_pwarn("Failed to set MTD device to unbuffered: %d\n", ret);
360
David Hendricksf9a30552015-05-23 20:30:30 -0700361 msg_pinfo("Opened %s successfully\n", dev_path);
362
363 ret = 0;
364linux_mtd_setup_exit:
365 return ret;
366}
367
368static int linux_mtd_shutdown(void *data)
369{
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000370 struct linux_mtd_data *mtd_data = data;
371 if (mtd_data->dev_fp != NULL) {
372 fclose(mtd_data->dev_fp);
David Hendricksf9a30552015-05-23 20:30:30 -0700373 }
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000374 free(data);
David Hendricksf9a30552015-05-23 20:30:30 -0700375
376 return 0;
377}
378
Nico Hubere3a26882023-01-11 21:45:51 +0100379static int linux_mtd_init(struct flashprog_programmer *const prog)
David Hendricksf9a30552015-05-23 20:30:30 -0700380{
381 char *param;
382 int dev_num = 0;
383 int ret = 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000384 struct linux_mtd_data *data = NULL;
David Hendricksf9a30552015-05-23 20:30:30 -0700385
386 param = extract_programmer_param("dev");
387 if (param) {
388 char *endptr;
389
390 dev_num = strtol(param, &endptr, 0);
391 if ((*endptr != '\0') || (dev_num < 0)) {
Nico Huberc3b02dc2023-08-12 01:13:45 +0200392 msg_perr("Invalid device number %s. Use flashprog -p "
David Hendricksf9a30552015-05-23 20:30:30 -0700393 "linux_mtd:dev=N where N is a valid MTD\n"
394 "device number.\n", param);
395 goto linux_mtd_init_exit;
396 }
397 }
398
David Hendricksb0247b32018-05-23 21:50:18 -0700399 /*
400 * If user specified the MTD device number then error out if it doesn't
401 * appear to exist. Otherwise assume the error is benign and print a
402 * debug message. Bail out in either case.
403 */
404 char sysfs_path[32];
405 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
406 goto linux_mtd_init_exit;
407
408 struct stat s;
409 if (stat(sysfs_path, &s) < 0) {
410 if (param)
411 msg_perr("%s does not exist\n", sysfs_path);
412 else
413 msg_pdbg("%s does not exist\n", sysfs_path);
414 goto linux_mtd_init_exit;
415 }
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000416 free(param);
David Hendricksb0247b32018-05-23 21:50:18 -0700417
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000418 data = calloc(1, sizeof(*data));
419 if (!data) {
420 msg_perr("Unable to allocate memory for linux_mtd_data\n");
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000421 return 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000422 }
David Hendricksf9a30552015-05-23 20:30:30 -0700423
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000424 /* Get MTD info and store it in `data` */
425 if (linux_mtd_setup(dev_num, data)) {
426 free(data);
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000427 return 1;
Nikolai Artemiev3b32edb2021-05-08 19:00:06 +1000428 }
429
Nico Huber72c02ff2023-01-08 02:00:06 +0100430 return register_opaque_master(&linux_mtd_opaque_master, data);
Anastasia Klimchukfd3a2252021-08-03 15:26:19 +1000431
David Hendricksf9a30552015-05-23 20:30:30 -0700432linux_mtd_init_exit:
Jacob Garberba719992019-08-12 12:07:03 -0600433 free(param);
David Hendricksf9a30552015-05-23 20:30:30 -0700434 return ret;
435}
Thomas Heijligencc853d82021-05-04 15:32:17 +0200436
Brian Norris03ad4a42024-02-27 13:01:23 -0800437static void linux_mtd_nop_delay(unsigned int usecs)
438{
439 /*
440 * Ignore delay requests. The Linux MTD framework brokers all flash
441 * protocol, including timing, resets, etc.
442 */
443}
444
Thomas Heijligencc853d82021-05-04 15:32:17 +0200445const struct programmer_entry programmer_linux_mtd = {
446 .name = "linux_mtd",
447 .type = OTHER,
448 .devs.note = "Device files /dev/mtd*\n",
449 .init = linux_mtd_init,
Brian Norris03ad4a42024-02-27 13:01:23 -0800450 .delay = linux_mtd_nop_delay,
Thomas Heijligencc853d82021-05-04 15:32:17 +0200451};