blob: 4bd74c439dfff6a1ebc5b176f50eb9e657ae0ecf [file] [log] [blame]
David Hendrickscebee892015-05-23 20:30:30 -07001/*
2 * This file is part of the flashrom project.
3 *
4 * Copyright 2015 Google Inc.
Nikolai Artemiev255f3352020-07-17 16:38:50 +10005 * Copyright 2018-present Facebook, Inc.
David Hendrickscebee892015-05-23 20:30:30 -07006 *
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.
David Hendrickscebee892015-05-23 20:30:30 -070015 */
16
17#include <ctype.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <inttypes.h>
21#include <libgen.h>
Gwendal Grignoudb133582018-03-23 12:28:20 -070022#include <limits.h>
David Hendrickscebee892015-05-23 20:30:30 -070023#include <stdio.h>
24#include <stdlib.h>
25#include <mtd/mtd-user.h>
26#include <string.h>
27#include <sys/ioctl.h>
28#include <sys/stat.h>
29#include <unistd.h>
30
David Hendrickscebee892015-05-23 20:30:30 -070031#include "flash.h"
32#include "programmer.h"
David Hendricks85f61c52015-06-04 19:52:59 -070033#include "writeprotect.h"
David Hendrickscebee892015-05-23 20:30:30 -070034
35#define LINUX_DEV_ROOT "/dev"
36#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
37
Nikolai Artemievb340c242021-05-08 19:00:06 +100038struct linux_mtd_data {
39 FILE *dev_fp;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +100040 int device_is_writeable;
41 int no_erase;
Nikolai Artemievb340c242021-05-08 19:00:06 +100042 /* Size info is presented in bytes in sysfs. */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +100043 unsigned long int total_size;
44 unsigned long int numeraseregions;
Nikolai Artemievb340c242021-05-08 19:00:06 +100045 /* only valid if numeraseregions is 0 */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +100046 unsigned long int erasesize;
Nikolai Artemievb340c242021-05-08 19:00:06 +100047};
David Hendrickscebee892015-05-23 20:30:30 -070048
David Hendricks85f61c52015-06-04 19:52:59 -070049static struct wp wp_mtd; /* forward declaration */
50
David Hendrickscebee892015-05-23 20:30:30 -070051/* read a string from a sysfs file and sanitize it */
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +100052static int read_sysfs_string(const char *sysfs_path, const char *filename, char *buf, int len)
David Hendrickscebee892015-05-23 20:30:30 -070053{
Nikolai Artemievb81761e2020-07-29 23:22:07 +100054 int i;
55 size_t bytes_read;
56 FILE *fp;
David Hendrickscebee892015-05-23 20:30:30 -070057 char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
58
59 snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
60
Nikolai Artemievb81761e2020-07-29 23:22:07 +100061 if ((fp = fopen(path, "r")) == NULL) {
David Hendrickscebee892015-05-23 20:30:30 -070062 msg_perr("Cannot open %s\n", path);
63 return 1;
64 }
65
Nikolai Artemievb81761e2020-07-29 23:22:07 +100066 clearerr(fp);
67 bytes_read = fread(buf, 1, (size_t)len, fp);
68 if (!feof(fp) && ferror(fp)) {
69 msg_perr("Error occurred when reading %s\n", path);
70 fclose(fp);
David Hendrickscebee892015-05-23 20:30:30 -070071 return 1;
72 }
73
74 buf[bytes_read] = '\0';
75
76 /*
77 * Files from sysfs sometimes contain a newline or other garbage that
78 * can confuse functions like strtoul() and ruin formatting in print
79 * statements. Replace the first non-printable character (space is
80 * considered printable) with a proper string terminator.
81 */
82 for (i = 0; i < len; i++) {
83 if (!isprint(buf[i])) {
84 buf[i] = '\0';
85 break;
86 }
87 }
88
Nikolai Artemievb81761e2020-07-29 23:22:07 +100089 fclose(fp);
David Hendrickscebee892015-05-23 20:30:30 -070090 return 0;
91}
92
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +100093static int read_sysfs_int(const char *sysfs_path, const char *filename, unsigned long int *val)
David Hendrickscebee892015-05-23 20:30:30 -070094{
David Hendrickscebee892015-05-23 20:30:30 -070095 char buf[32];
96 char *endptr;
97
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +100098 if (read_sysfs_string(sysfs_path, filename, buf, sizeof(buf)))
David Hendrickscebee892015-05-23 20:30:30 -070099 return 1;
100
101 errno = 0;
102 *val = strtoul(buf, &endptr, 0);
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000103 if (*endptr != '\0') {
David Hendrickscebee892015-05-23 20:30:30 -0700104 msg_perr("Error reading %s\n", filename);
105 return 1;
106 }
107
108 if (errno) {
109 msg_perr("Error reading %s: %s\n", filename, strerror(errno));
110 return 1;
111 }
112
113 return 0;
114}
115
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000116static int popcnt(unsigned int u)
117{
118 int count = 0;
119
120 while (u) {
121 u &= u - 1;
122 count++;
123 }
124
125 return count;
126}
127
David Hendrickscebee892015-05-23 20:30:30 -0700128/* returns 0 to indicate success, non-zero to indicate error */
Nikolai Artemievb340c242021-05-08 19:00:06 +1000129static int get_mtd_info(const char *sysfs_path, struct linux_mtd_data *data)
David Hendrickscebee892015-05-23 20:30:30 -0700130{
131 unsigned long int tmp;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000132 char device_name[32];
David Hendrickscebee892015-05-23 20:30:30 -0700133
134 /* Flags */
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +1000135 if (read_sysfs_int(sysfs_path, "flags", &tmp))
David Hendrickscebee892015-05-23 20:30:30 -0700136 return 1;
137 if (tmp & MTD_WRITEABLE) {
138 /* cache for later use by write function */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000139 data->device_is_writeable = 1;
David Hendrickscebee892015-05-23 20:30:30 -0700140 }
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700141 if (tmp & MTD_NO_ERASE) {
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000142 data->no_erase = 1;
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700143 }
David Hendrickscebee892015-05-23 20:30:30 -0700144
145 /* Device name */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000146 if (read_sysfs_string(sysfs_path, "name", device_name, sizeof(device_name)))
David Hendrickscebee892015-05-23 20:30:30 -0700147 return 1;
148
149 /* Total size */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000150 if (read_sysfs_int(sysfs_path, "size", &data->total_size))
David Hendrickscebee892015-05-23 20:30:30 -0700151 return 1;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000152 if (popcnt(data->total_size) != 1) {
David Hendrickscebee892015-05-23 20:30:30 -0700153 msg_perr("MTD size is not a power of 2\n");
154 return 1;
155 }
156
157 /* Erase size */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000158 if (read_sysfs_int(sysfs_path, "erasesize", &data->erasesize))
David Hendrickscebee892015-05-23 20:30:30 -0700159 return 1;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000160 if (popcnt(data->erasesize) != 1) {
David Hendrickscebee892015-05-23 20:30:30 -0700161 msg_perr("MTD erase size is not a power of 2\n");
162 return 1;
163 }
164
165 /* Erase regions */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000166 if (read_sysfs_int(sysfs_path, "numeraseregions", &data->numeraseregions))
David Hendrickscebee892015-05-23 20:30:30 -0700167 return 1;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000168 if (data->numeraseregions != 0) {
David Hendrickscebee892015-05-23 20:30:30 -0700169 msg_perr("Non-uniform eraseblock size is unsupported.\n");
170 return 1;
171 }
172
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000173 msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, "
David Hendrickscebee892015-05-23 20:30:30 -0700174 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000175 __func__, device_name, data->device_is_writeable,
176 data->numeraseregions, data->total_size, data->erasesize);
David Hendrickscebee892015-05-23 20:30:30 -0700177
178 return 0;
179}
180
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700181static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700182{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000183 struct linux_mtd_data *data = flash->mst->opaque.data;
184
Patrick Georgif3fa2992017-02-02 16:24:44 +0100185 flash->chip->wp = &wp_mtd;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000186 if (data->no_erase)
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700187 flash->chip->feature_bits |= FEATURE_NO_ERASE;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100188 flash->chip->tested = TEST_OK_PREW;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000189 flash->chip->total_size = data->total_size / 1024; /* bytes -> kB */
190 flash->chip->block_erasers[0].eraseblocks[0].size = data->erasesize;
Nikolai Artemievb340c242021-05-08 19:00:06 +1000191 flash->chip->block_erasers[0].eraseblocks[0].count =
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000192 data->total_size / data->erasesize;
David Hendrickscebee892015-05-23 20:30:30 -0700193 return 1;
194}
195
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700196static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700197 unsigned int start, unsigned int len)
198{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000199 struct linux_mtd_data *data = flash->mst->opaque.data;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100200 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700201 unsigned int i;
202
Nikolai Artemievb340c242021-05-08 19:00:06 +1000203 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
David Hendrickscebee892015-05-23 20:30:30 -0700204 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
205 return 1;
206 }
207
Brian Norris99c8ad22016-05-10 15:36:02 -0700208 for (i = 0; i < len; ) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700209 /*
210 * Try to align reads to eraseblock size.
211 * FIXME: Shouldn't actually be necessary, but not all MTD
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000212 * drivers handle arbitrary large reads well.
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700213 */
Brian Norris99c8ad22016-05-10 15:36:02 -0700214 unsigned int step = eb_size - ((start + i) % eb_size);
215 step = min(step, len - i);
216
Nikolai Artemievb340c242021-05-08 19:00:06 +1000217 if (fread(buf + i, step, 1, data->dev_fp) != 1) {
Brian Norris99c8ad22016-05-10 15:36:02 -0700218 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
219 step, start + i, strerror(errno));
220 return 1;
221 }
222
223 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700224 }
225
226 return 0;
227}
228
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700229/* this version assumes we must divide the write request into chunks ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100230static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700231 unsigned int start, unsigned int len)
232{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000233 struct linux_mtd_data *data = flash->mst->opaque.data;
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700234 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
235 unsigned int i;
David Hendrickscebee892015-05-23 20:30:30 -0700236
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000237 if (!data->device_is_writeable)
David Hendrickscebee892015-05-23 20:30:30 -0700238 return 1;
239
Nikolai Artemievb340c242021-05-08 19:00:06 +1000240 if (fseek(data->dev_fp, start, SEEK_SET) != 0) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700241 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
242 return 1;
243 }
David Hendrickscebee892015-05-23 20:30:30 -0700244
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700245 /*
246 * Try to align writes to eraseblock size. We want these large enough
247 * to give MTD room for optimizing performance.
248 * FIXME: Shouldn't need to divide this up at all, but not all MTD
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000249 * drivers handle arbitrary large writes well.
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700250 */
251 for (i = 0; i < len; ) {
252 unsigned int step = chunksize - ((start + i) % chunksize);
253 step = min(step, len - i);
David Hendrickscebee892015-05-23 20:30:30 -0700254
Nikolai Artemievb340c242021-05-08 19:00:06 +1000255 if (fwrite(buf + i, step, 1, data->dev_fp) != 1) {
Nikolai Artemievb81761e2020-07-29 23:22:07 +1000256 msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
257 return 1;
258 }
259
Nikolai Artemievb340c242021-05-08 19:00:06 +1000260 if (fflush(data->dev_fp) == EOF) {
Nikolai Artemievb81761e2020-07-29 23:22:07 +1000261 msg_perr("Failed to flush buffer: %s\n", strerror(errno));
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700262 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700263 }
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700264
265 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700266 }
267
268 return 0;
269}
270
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700271static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700272 unsigned int start, unsigned int len)
273{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000274 struct linux_mtd_data *data = flash->mst->opaque.data;
David Hendrickscebee892015-05-23 20:30:30 -0700275 uint32_t u;
276
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000277 if (data->no_erase) {
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700278 msg_perr("%s: device does not support erasing. Please file a "
279 "bug report at flashrom@flashrom.org\n", __func__);
280 return 1;
281 }
282
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000283 if (data->numeraseregions != 0) {
David Hendrickscebee892015-05-23 20:30:30 -0700284 /* TODO: Support non-uniform eraseblock size using
285 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000286 msg_perr("%s: numeraseregions must be 0\n", __func__);
Nikolai Artemievd783fb92020-08-26 03:43:19 +1000287 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700288 }
289
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000290 for (u = 0; u < len; u += data->erasesize) {
David Hendrickscebee892015-05-23 20:30:30 -0700291 struct erase_info_user erase_info = {
292 .start = start + u,
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000293 .length = data->erasesize,
David Hendrickscebee892015-05-23 20:30:30 -0700294 };
295
Nikolai Artemievb340c242021-05-08 19:00:06 +1000296 if (ioctl(fileno(data->dev_fp), MEMERASE, &erase_info) == -1) {
David Hendrickscebee892015-05-23 20:30:30 -0700297 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
298 return 1;
299 }
300 }
301
302 return 0;
303}
304
Edward O'Callaghanabd30192019-05-14 15:58:19 +1000305static struct opaque_master programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700306 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700307 .max_data_read = MAX_DATA_UNSPECIFIED,
308 .max_data_write = MAX_DATA_UNSPECIFIED,
309 .probe = linux_mtd_probe,
310 .read = linux_mtd_read,
311 .write = linux_mtd_write,
312 .erase = linux_mtd_erase,
313};
314
315/* Returns 0 if setup is successful, non-zero to indicate error */
Nikolai Artemievb340c242021-05-08 19:00:06 +1000316static int linux_mtd_setup(int dev_num, struct linux_mtd_data *data)
David Hendrickscebee892015-05-23 20:30:30 -0700317{
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +1000318 char sysfs_path[32];
David Hendrickscebee892015-05-23 20:30:30 -0700319 int ret = 1;
320
Nikolai Artemievd783fb92020-08-26 03:43:19 +1000321 /* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
322 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
323 goto linux_mtd_setup_exit;
David Hendrickscebee892015-05-23 20:30:30 -0700324
Nikolai Artemievd783fb92020-08-26 03:43:19 +1000325 char buf[4];
326 memset(buf, 0, sizeof(buf));
327 if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
328 return 1;
329
330 if (strcmp(buf, "nor")) {
331 msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
332 goto linux_mtd_setup_exit;
333 }
334
335 /* sysfs shows the correct device type, see if corresponding device node exists */
Nikolai Artemieve739e092020-08-20 16:17:51 +1000336 char dev_path[32];
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +1000337 struct stat s;
Nikolai Artemieve739e092020-08-20 16:17:51 +1000338 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +1000339 errno = 0;
340 if (stat(dev_path, &s) < 0) {
341 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
342 goto linux_mtd_setup_exit;
343 }
344
Nikolai Artemievd783fb92020-08-26 03:43:19 +1000345 /* so far so good, get more info from other files in this dir */
346 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
Nikolai Artemiev4a4d4d92020-07-17 15:45:48 +1000347 goto linux_mtd_setup_exit;
Nikolai Artemievb340c242021-05-08 19:00:06 +1000348 if (get_mtd_info(sysfs_path, data))
David Hendrickscebee892015-05-23 20:30:30 -0700349 goto linux_mtd_setup_exit;
350
Nikolai Artemievb81761e2020-07-29 23:22:07 +1000351 /* open file stream and go! */
Nikolai Artemievb340c242021-05-08 19:00:06 +1000352 if ((data->dev_fp = fopen(dev_path, "r+")) == NULL) {
Nikolai Artemievb81761e2020-07-29 23:22:07 +1000353 msg_perr("Cannot open file stream for %s\n", dev_path);
David Hendrickscebee892015-05-23 20:30:30 -0700354 goto linux_mtd_setup_exit;
355 }
Nikolai Artemievb340c242021-05-08 19:00:06 +1000356 ret = setvbuf(data->dev_fp, NULL, _IONBF, 0);
Douglas Andersond45c6d02021-01-29 16:35:24 -0800357 if (ret)
358 msg_pwarn("Failed to set MTD device to unbuffered: %d\n", ret);
359
Nikolai Artemievd783fb92020-08-26 03:43:19 +1000360 msg_pinfo("Opened %s successfully\n", dev_path);
David Hendrickscebee892015-05-23 20:30:30 -0700361
362 ret = 0;
363linux_mtd_setup_exit:
364 return ret;
365}
366
David Hendricks93784b42016-08-09 17:00:38 -0700367static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700368{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000369 struct linux_mtd_data *mtd_data = data;
370 if (mtd_data->dev_fp != NULL) {
371 fclose(mtd_data->dev_fp);
David Hendrickscebee892015-05-23 20:30:30 -0700372 }
Nikolai Artemievb340c242021-05-08 19:00:06 +1000373 free(data);
David Hendrickscebee892015-05-23 20:30:30 -0700374
375 return 0;
376}
377
David Hendricksac1d25c2016-08-09 17:00:58 -0700378int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700379{
380 char *param;
Nikolai Artemieve739e092020-08-20 16:17:51 +1000381 int dev_num = 0;
David Hendrickscebee892015-05-23 20:30:30 -0700382 int ret = 1;
Nikolai Artemievb340c242021-05-08 19:00:06 +1000383 struct linux_mtd_data *data = NULL;
David Hendrickscebee892015-05-23 20:30:30 -0700384
David Hendrickscebee892015-05-23 20:30:30 -0700385 param = extract_programmer_param("dev");
386 if (param) {
387 char *endptr;
388
389 dev_num = strtol(param, &endptr, 0);
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000390 if ((*endptr != '\0') || (dev_num < 0)) {
David Hendrickscebee892015-05-23 20:30:30 -0700391 msg_perr("Invalid device number %s. Use flashrom -p "
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000392 "linux_mtd:dev=N where N is a valid MTD\n"
393 "device number.\n", param);
David Hendrickscebee892015-05-23 20:30:30 -0700394 goto linux_mtd_init_exit;
395 }
396 }
397
David Hendricks58f870e2018-05-23 21:50:18 -0700398 /*
399 * If user specified the MTD device number then error out if it doesn't
400 * appear to exist. Otherwise assume the error is benign and print a
401 * debug message. Bail out in either case.
402 */
403 char sysfs_path[32];
404 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
405 goto linux_mtd_init_exit;
406
407 struct stat s;
408 if (stat(sysfs_path, &s) < 0) {
409 if (param)
410 msg_perr("%s does not exist\n", sysfs_path);
411 else
412 msg_pdbg("%s does not exist\n", sysfs_path);
413 goto linux_mtd_init_exit;
414 }
415
Nikolai Artemievb340c242021-05-08 19:00:06 +1000416 data = calloc(1, sizeof(*data));
417 if (!data) {
418 msg_perr("Unable to allocate memory for linux_mtd_data\n");
David Hendrickscebee892015-05-23 20:30:30 -0700419 goto linux_mtd_init_exit;
Nikolai Artemievb340c242021-05-08 19:00:06 +1000420 }
David Hendrickscebee892015-05-23 20:30:30 -0700421
Nikolai Artemievb340c242021-05-08 19:00:06 +1000422 /* Get MTD info and store it in `data` */
423 if (linux_mtd_setup(dev_num, data)) {
424 free(data);
David Hendrickscebee892015-05-23 20:30:30 -0700425 goto linux_mtd_init_exit;
Nikolai Artemievb340c242021-05-08 19:00:06 +1000426 }
427
428 programmer_linux_mtd.data = data;
429 if (register_shutdown(linux_mtd_shutdown, (void *)data)) {
430 free(data);
431 goto linux_mtd_init_exit;
432 }
David Hendrickscebee892015-05-23 20:30:30 -0700433
Edward O'Callaghanabd30192019-05-14 15:58:19 +1000434 register_opaque_master(&programmer_linux_mtd);
David Hendrickscebee892015-05-23 20:30:30 -0700435
436 ret = 0;
437linux_mtd_init_exit:
Nikolai Artemievb81761e2020-07-29 23:22:07 +1000438 free(param);
David Hendrickscebee892015-05-23 20:30:30 -0700439 return ret;
440}
David Hendricks85f61c52015-06-04 19:52:59 -0700441
442/*
443 * Write-protect functions.
444 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700445static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700446{
447 /* TODO: implement this */
448 msg_perr("--wp-list is not currently implemented for MTD.\n");
449 return 1;
450}
451
452/*
453 * We only have MEMLOCK to enable write-protection for a particular block,
454 * so we need to do force the user to use --wp-range and --wp-enable
455 * command-line arguments simultaneously. (Fortunately, CrOS factory
456 * installer does this already).
457 *
458 * The --wp-range argument is processed first and will set these variables
459 * which --wp-enable will use afterward.
460 */
461static unsigned int wp_range_start;
462static unsigned int wp_range_len;
463static int wp_set_range_called = 0;
464
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700465static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700466 unsigned int start, unsigned int len)
467{
468 wp_range_start = start;
469 wp_range_len = len;
470
471 wp_set_range_called = 1;
472 return 0;
473}
474
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700475static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700476{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000477 struct linux_mtd_data *data = flash->mst->opaque.data;
478
David Hendricks85f61c52015-06-04 19:52:59 -0700479 struct erase_info_user entire_chip = {
480 .start = 0,
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000481 .length = data->total_size,
David Hendricks85f61c52015-06-04 19:52:59 -0700482 };
483 struct erase_info_user desired_range = {
484 .start = wp_range_start,
485 .length = wp_range_len,
486 };
487
488 if (!wp_set_range_called) {
489 msg_perr("For MTD, --wp-range and --wp-enable must be "
490 "used simultaneously.\n");
491 return 1;
492 }
493
494 /*
495 * MTD handles write-protection additively, so whatever new range is
496 * specified is added to the range which is currently protected. To be
497 * consistent with flashrom behavior with other programmer interfaces,
498 * we need to disable the current write protection and then enable
499 * it for the desired range.
500 */
Nikolai Artemievb340c242021-05-08 19:00:06 +1000501 if (ioctl(fileno(data->dev_fp), MEMUNLOCK, &entire_chip) == -1) {
David Hendricks85f61c52015-06-04 19:52:59 -0700502 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
503 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700504 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700505 return 1;
506 }
507
Nikolai Artemievb340c242021-05-08 19:00:06 +1000508 if (ioctl(fileno(data->dev_fp), MEMLOCK, &desired_range) == -1) {
David Hendricks85f61c52015-06-04 19:52:59 -0700509 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
510 __func__, strerror(errno));
511 return 1;
512 }
513
514 return 0;
515}
516
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700517static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700518{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000519 struct linux_mtd_data *data = flash->mst->opaque.data;
David Hendricks85f61c52015-06-04 19:52:59 -0700520 struct erase_info_user erase_info;
521
522 if (wp_set_range_called) {
523 erase_info.start = wp_range_start;
524 erase_info.length = wp_range_len;
525 } else {
526 erase_info.start = 0;
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000527 erase_info.length = data->total_size;
David Hendricks85f61c52015-06-04 19:52:59 -0700528 }
529
Nikolai Artemievb340c242021-05-08 19:00:06 +1000530 if (ioctl(fileno(data->dev_fp), MEMUNLOCK, &erase_info) == -1) {
David Hendricks85f61c52015-06-04 19:52:59 -0700531 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700532 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700533 return 1;
534 }
535
536 return 0;
537}
538
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700539static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700540{
Nikolai Artemievb340c242021-05-08 19:00:06 +1000541 struct linux_mtd_data *data = flash->mst->opaque.data;
Brian Norris3ce4c472016-06-02 16:40:14 -0700542 uint32_t start = 0, len = 0;
543 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700544 unsigned int u;
545
546 /* For now, assume only one contiguous region can be locked (NOR) */
547 /* FIXME: use flash struct members instead of raw MTD values here */
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000548 for (u = 0; u < data->total_size; u += data->erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700549 int rc;
550 struct erase_info_user erase_info = {
551 .start = u,
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000552 .length = data->erasesize,
David Hendricks85f61c52015-06-04 19:52:59 -0700553 };
554
Nikolai Artemievb340c242021-05-08 19:00:06 +1000555 rc = ioctl(fileno(data->dev_fp), MEMISLOCKED, &erase_info);
David Hendricks85f61c52015-06-04 19:52:59 -0700556 if (rc < 0) {
557 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
558 return 1;
559 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800560 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700561 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800562 start_found = 1;
563 }
Nikolai Artemiev6aab7522021-05-09 11:37:38 +1000564 len += data->erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700565 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800566 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700567 /* TODO: changes required for supporting non-contiguous locked regions */
568 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800569 }
David Hendricks85f61c52015-06-04 19:52:59 -0700570 }
571
David Hendricks85f61c52015-06-04 19:52:59 -0700572 }
573
Wei-Ning Huangca907072016-04-27 11:30:17 +0800574 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700575 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800576 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700577 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800578
David Hendricks85f61c52015-06-04 19:52:59 -0700579 return 0;
580}
581
582static struct wp wp_mtd = {
583 .list_ranges = mtd_wp_list_ranges,
584 .set_range = mtd_wp_set_range,
585 .enable = mtd_wp_enable_writeprotect,
586 .disable = mtd_wp_disable_writeprotect,
587 .wp_status = mtd_wp_status,
588};