summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile35
-rw-r--r--flash.h1
-rw-r--r--flashrom.8.tmpl15
-rw-r--r--flashrom.c15
-rw-r--r--internal.c3
-rw-r--r--linux_mtd.c390
-rw-r--r--programmer.h18
7 files changed, 475 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index d150dcb08..943d88d76 100644
--- a/Makefile
+++ b/Makefile
@@ -341,6 +341,11 @@ endif
ifneq ($(TARGET_OS), Linux)
# Android is handled internally as separate OS, but it supports CONFIG_LINUX_SPI and CONFIG_MSTARDDC_SPI
ifneq ($(TARGET_OS), Android)
+ifeq ($(CONFIG_LINUX_MTD), yes)
+UNSUPPORTED_FEATURES += CONFIG_LINUX_MTD=yes
+else
+override CONFIG_LINUX_MTD = no
+endif
ifeq ($(CONFIG_LINUX_SPI), yes)
UNSUPPORTED_FEATURES += CONFIG_LINUX_SPI=yes
else
@@ -624,7 +629,8 @@ CONFIG_DEDIPROG ?= yes
# Always enable Marvell SATA controllers for now.
CONFIG_SATAMV ?= yes
-# Enable Linux spidev interface by default. We disable it on non-Linux targets.
+# Enable Linux spidev and MTD interfaces by default. We disable them on non-Linux targets.
+CONFIG_LINUX_MTD ?= yes
CONFIG_LINUX_SPI ?= yes
# Always enable ITE IT8212F PATA controllers for now.
@@ -902,6 +908,12 @@ PROGRAMMER_OBJS += satamv.o
NEED_LIBPCI += CONFIG_SATAMV
endif
+ifeq ($(CONFIG_LINUX_MTD), yes)
+# This is a totally ugly hack.
+FEATURE_CFLAGS += $(call debug_shell,grep -q "LINUX_MTD_SUPPORT := yes" .features && printf "%s" "-D'CONFIG_LINUX_MTD=1'")
+PROGRAMMER_OBJS += linux_mtd.o
+endif
+
ifeq ($(CONFIG_LINUX_SPI), yes)
# This is a totally ugly hack.
FEATURE_CFLAGS += $(call debug_shell,grep -q "LINUX_SPI_SUPPORT := yes" .features && printf "%s" "-D'CONFIG_LINUX_SPI=1'")
@@ -1277,6 +1289,18 @@ int main(int argc, char **argv)
endef
export UTSNAME_TEST
+define LINUX_MTD_TEST
+#include <mtd/mtd-user.h>
+
+int main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+ return 0;
+}
+endef
+export LINUX_MTD_TEST
+
define LINUX_SPI_TEST
#include <linux/types.h>
#include <linux/spi/spidev.h>
@@ -1333,6 +1357,15 @@ ifneq ($(NEED_LIBFTDI), )
( echo "not found."; echo "FTDISUPPORT := no" >> .features.tmp ) } \
2>>$(BUILD_DETAILS_FILE) | tee -a $(BUILD_DETAILS_FILE)
endif
+ifeq ($(CONFIG_LINUX_MTD), yes)
+ @printf "Checking if Linux MTD headers are present... " | tee -a $(BUILD_DETAILS_FILE)
+ @echo "$$LINUX_MTD_TEST" > .featuretest.c
+ @printf "\nexec: %s\n" "$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX)" >>$(BUILD_DETAILS_FILE)
+ @ { $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX) >&2 && \
+ ( echo "yes."; echo "LINUX_MTD_SUPPORT := yes" >> .features.tmp ) || \
+ ( echo "no."; echo "LINUX_MTD_SUPPORT := no" >> .features.tmp ) } \
+ 2>>$(BUILD_DETAILS_FILE) | tee -a $(BUILD_DETAILS_FILE)
+endif
ifeq ($(CONFIG_LINUX_SPI), yes)
@printf "Checking if Linux SPI headers are present... " | tee -a $(BUILD_DETAILS_FILE)
@echo "$$LINUX_SPI_TEST" > .featuretest.c
diff --git a/flash.h b/flash.h
index 72d345a4d..fca377506 100644
--- a/flash.h
+++ b/flash.h
@@ -131,6 +131,7 @@ enum write_granularity {
* other flash chips, such as the ENE KB9012 internal flash, work the opposite way.
*/
#define FEATURE_ERASED_ZERO (1 << 16)
+#define FEATURE_NO_ERASE (1 << 17)
#define ERASED_VALUE(flash) (((flash)->chip->feature_bits & FEATURE_ERASED_ZERO) ? 0x00 : 0xff)
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 160011374..e732bcf21 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -289,6 +289,8 @@ bitbanging adapter)
.sp
.BR "* ogp_spi" " (for SPI flash ROMs on Open Graphics Project graphics card)"
.sp
+.BR "* linux_mtd" " (for SPI flash ROMs accessible via /dev/mtdX on Linux)"
+.sp
.BR "* linux_spi" " (for SPI flash ROMs accessible via /dev/spidevX.Y on Linux)"
.sp
.BR "* usbblaster_spi" " (for SPI flash ROMs attached to an Altera USB-Blaster compatible cable)"
@@ -1007,6 +1009,19 @@ parameter as explained in the
.B nic3com et al.\&
section above.
.SS
+.BR "linux_mtd " programmer
+.IP
+You may specify the MTD device to use with the
+.sp
+.B " flashrom \-p linux_mtd:dev=/dev/mtdX"
+.sp
+syntax where
+.B /dev/mtdX
+is the Linux device node for your MTD device. If left unspecified the first MTD
+device found (e.g. /dev/mtd0) will be used by default.
+.sp
+Please note that the linux_mtd driver only works on Linux.
+.SS
.BR "linux_spi " programmer
.IP
You have to specify the SPI controller to use with the
diff --git a/flashrom.c b/flashrom.c
index 776ad70b7..f85dbb1f8 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -341,6 +341,18 @@ const struct programmer_entry programmer_table[] = {
},
#endif
+#if CONFIG_LINUX_MTD == 1
+ {
+ .name = "linux_mtd",
+ .type = OTHER,
+ .devs.note = "Device files /dev/mtd*\n",
+ .init = linux_mtd_init,
+ .map_flash_region = fallback_map,
+ .unmap_flash_region = fallback_unmap,
+ .delay = internal_delay,
+ },
+#endif
+
#if CONFIG_LINUX_SPI == 1
{
.name = "linux_spi",
@@ -1772,7 +1784,8 @@ static int read_erase_write_block(struct flashctx *const flashctx,
bool skipped = true;
uint8_t *const curcontents = info->curcontents + info->erase_start;
const uint8_t erased_value = ERASED_VALUE(flashctx);
- if (need_erase(curcontents, newcontents, erase_len, flashctx->chip->gran, erased_value)) {
+ if (!(flashctx->chip->feature_bits & FEATURE_NO_ERASE) &&
+ need_erase(curcontents, newcontents, erase_len, flashctx->chip->gran, erased_value)) {
if (erase_block(flashctx, info, erasefn))
goto _free_ret;
/* Erase was successful. Adjust curcontents. */
diff --git a/internal.c b/internal.c
index 9d2d8ad2f..1d6cff6b6 100644
--- a/internal.c
+++ b/internal.c
@@ -230,6 +230,9 @@ int internal_init(void)
*/
internal_buses_supported = BUS_NONSPI;
+ if (try_mtd() == 0)
+ return 0;
+
/* Initialize PCI access for flash enables */
if (pci_init_common() != 0)
return 1;
diff --git a/linux_mtd.c b/linux_mtd.c
new file mode 100644
index 000000000..e65f093c0
--- /dev/null
+++ b/linux_mtd.c
@@ -0,0 +1,390 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2018-present Facebook, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <mtd/mtd-user.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "flash.h"
+#include "programmer.h"
+
+#define LINUX_DEV_ROOT "/dev"
+#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
+
+static FILE *dev_fp = NULL;
+
+static int mtd_device_is_writeable;
+
+static int mtd_no_erase;
+
+/* Size info is presented in bytes in sysfs. */
+static unsigned long int mtd_total_size;
+static unsigned long int mtd_numeraseregions;
+static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
+
+/* read a string from a sysfs file and sanitize it */
+static int read_sysfs_string(const char *sysfs_path, const char *filename, char *buf, int len)
+{
+ int i;
+ size_t bytes_read;
+ FILE *fp;
+ char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ msg_perr("Cannot open %s\n", path);
+ return 1;
+ }
+
+ clearerr(fp);
+ bytes_read = fread(buf, 1, (size_t)len, fp);
+ if (!feof(fp) && ferror(fp)) {
+ msg_perr("Error occurred when reading %s\n", path);
+ fclose(fp);
+ return 1;
+ }
+
+ buf[bytes_read] = '\0';
+
+ /*
+ * Files from sysfs sometimes contain a newline or other garbage that
+ * can confuse functions like strtoul() and ruin formatting in print
+ * statements. Replace the first non-printable character (space is
+ * considered printable) with a proper string terminator.
+ */
+ for (i = 0; i < len; i++) {
+ if (!isprint(buf[i])) {
+ buf[i] = '\0';
+ break;
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+static int read_sysfs_int(const char *sysfs_path, const char *filename, unsigned long int *val)
+{
+ char buf[32];
+ char *endptr;
+
+ if (read_sysfs_string(sysfs_path, filename, buf, sizeof(buf)))
+ return 1;
+
+ errno = 0;
+ *val = strtoul(buf, &endptr, 0);
+ if (*endptr != '\0') {
+ msg_perr("Error reading %s\n", filename);
+ return 1;
+ }
+
+ if (errno) {
+ msg_perr("Error reading %s: %s\n", filename, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int popcnt(unsigned int u)
+{
+ int count = 0;
+
+ while (u) {
+ u &= u - 1;
+ count++;
+ }
+
+ return count;
+}
+
+/* returns 0 to indicate success, non-zero to indicate error */
+static int get_mtd_info(const char *sysfs_path)
+{
+ unsigned long int tmp;
+ char mtd_device_name[32];
+
+ /* Flags */
+ if (read_sysfs_int(sysfs_path, "flags", &tmp))
+ return 1;
+ if (tmp & MTD_WRITEABLE) {
+ /* cache for later use by write function */
+ mtd_device_is_writeable = 1;
+ }
+ if (tmp & MTD_NO_ERASE) {
+ mtd_no_erase = 1;
+ }
+
+ /* Device name */
+ if (read_sysfs_string(sysfs_path, "name", mtd_device_name, sizeof(mtd_device_name)))
+ return 1;
+
+ /* Total size */
+ if (read_sysfs_int(sysfs_path, "size", &mtd_total_size))
+ return 1;
+ if (popcnt(mtd_total_size) != 1) {
+ msg_perr("MTD size is not a power of 2\n");
+ return 1;
+ }
+
+ /* Erase size */
+ if (read_sysfs_int(sysfs_path, "erasesize", &mtd_erasesize))
+ return 1;
+ if (popcnt(mtd_erasesize) != 1) {
+ msg_perr("MTD erase size is not a power of 2\n");
+ return 1;
+ }
+
+ /* Erase regions */
+ if (read_sysfs_int(sysfs_path, "numeraseregions", &mtd_numeraseregions))
+ return 1;
+ if (mtd_numeraseregions != 0) {
+ msg_perr("Non-uniform eraseblock size is unsupported.\n");
+ return 1;
+ }
+
+ msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, "
+ "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
+ __func__, mtd_device_name, mtd_device_is_writeable,
+ mtd_numeraseregions, mtd_total_size, mtd_erasesize);
+
+ return 0;
+}
+
+static int linux_mtd_probe(struct flashctx *flash)
+{
+ if (mtd_no_erase)
+ flash->chip->feature_bits |= FEATURE_NO_ERASE;
+ flash->chip->tested = TEST_OK_PREW;
+ flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
+ flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
+ flash->chip->block_erasers[0].eraseblocks[0].count = mtd_total_size / mtd_erasesize;
+ return 1;
+}
+
+static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
+ unsigned int start, unsigned int len)
+{
+ unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
+ unsigned int i;
+
+ if (fseek(dev_fp, start, SEEK_SET) != 0) {
+ msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
+ return 1;
+ }
+
+ for (i = 0; i < len; ) {
+ /*
+ * Try to align reads to eraseblock size.
+ * FIXME: Shouldn't actually be necessary, but not all MTD
+ * drivers handle arbitrary large reads well.
+ */
+ unsigned int step = eb_size - ((start + i) % eb_size);
+ step = min(step, len - i);
+
+ if (fread(buf + i, step, 1, dev_fp) != 1) {
+ msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
+ step, start + i, strerror(errno));
+ return 1;
+ }
+
+ i += step;
+ }
+
+ return 0;
+}
+
+/* this version assumes we must divide the write request into chunks ourselves */
+static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
+ unsigned int start, unsigned int len)
+{
+ unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
+ unsigned int i;
+
+ if (!mtd_device_is_writeable)
+ return 1;
+
+ if (fseek(dev_fp, start, SEEK_SET) != 0) {
+ msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
+ return 1;
+ }
+
+ /*
+ * Try to align writes to eraseblock size. We want these large enough
+ * to give MTD room for optimizing performance.
+ * FIXME: Shouldn't need to divide this up at all, but not all MTD
+ * drivers handle arbitrary large writes well.
+ */
+ for (i = 0; i < len; ) {
+ unsigned int step = chunksize - ((start + i) % chunksize);
+ step = min(step, len - i);
+
+ if (fwrite(buf + i, step, 1, dev_fp) != 1) {
+ msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i);
+ return 1;
+ }
+
+ if (fflush(dev_fp) == EOF) {
+ msg_perr("Failed to flush buffer: %s\n", strerror(errno));
+ return 1;
+ }
+
+ i += step;
+ }
+
+ return 0;
+}
+
+static int linux_mtd_erase(struct flashctx *flash,
+ unsigned int start, unsigned int len)
+{
+ uint32_t u;
+
+ if (mtd_no_erase) {
+ msg_perr("%s: device does not support erasing. Please file a "
+ "bug report at flashrom@flashrom.org\n", __func__);
+ return 1;
+ }
+
+ if (mtd_numeraseregions != 0) {
+ /* TODO: Support non-uniform eraseblock size using
+ use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
+ msg_perr("%s: mtd_numeraseregions must be 0\n", __func__);
+ return 1;
+ }
+
+ for (u = 0; u < len; u += mtd_erasesize) {
+ struct erase_info_user erase_info = {
+ .start = start + u,
+ .length = mtd_erasesize,
+ };
+
+ if (ioctl(fileno(dev_fp), MEMERASE, &erase_info) == -1) {
+ msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static struct opaque_master programmer_linux_mtd = {
+ /* max_data_{read,write} don't have any effect for this programmer */
+ .max_data_read = MAX_DATA_UNSPECIFIED,
+ .max_data_write = MAX_DATA_UNSPECIFIED,
+ .probe = linux_mtd_probe,
+ .read = linux_mtd_read,
+ .write = linux_mtd_write,
+ .erase = linux_mtd_erase,
+};
+
+/* Returns 0 if setup is successful, non-zero to indicate error */
+static int linux_mtd_setup(int dev_num)
+{
+ char sysfs_path[32];
+ int ret = 1;
+
+ /* Start by checking /sys/class/mtd/mtdN/type which should be "nor" for NOR flash */
+ if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
+ goto linux_mtd_setup_exit;
+
+ char buf[4];
+ memset(buf, 0, sizeof(buf));
+ if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf)))
+ return 1;
+
+ if (strcmp(buf, "nor")) {
+ msg_perr("MTD device %d type is not \"nor\"\n", dev_num);
+ goto linux_mtd_setup_exit;
+ }
+
+ /* sysfs shows the correct device type, see if corresponding device node exists */
+ char dev_path[32];
+ struct stat s;
+ snprintf(dev_path, sizeof(dev_path), "%s/mtd%d", LINUX_DEV_ROOT, dev_num);
+ errno = 0;
+ if (stat(dev_path, &s) < 0) {
+ msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
+ goto linux_mtd_setup_exit;
+ }
+
+ /* so far so good, get more info from other files in this dir */
+ if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0)
+ goto linux_mtd_setup_exit;
+ if (get_mtd_info(sysfs_path))
+ goto linux_mtd_setup_exit;
+
+ /* open file stream and go! */
+ if ((dev_fp = fopen(dev_path, "r+")) == NULL) {
+ msg_perr("Cannot open file stream for %s\n", dev_path);
+ goto linux_mtd_setup_exit;
+ }
+ msg_pinfo("Opened %s successfully\n", dev_path);
+
+ ret = 0;
+linux_mtd_setup_exit:
+ return ret;
+}
+
+static int linux_mtd_shutdown(void *data)
+{
+ if (dev_fp != NULL) {
+ fclose(dev_fp);
+ dev_fp = NULL;
+ }
+
+ return 0;
+}
+
+int linux_mtd_init(void)
+{
+ char *param;
+ int dev_num = 0;
+ int ret = 1;
+
+ param = extract_programmer_param("dev");
+ if (param) {
+ char *endptr;
+
+ dev_num = strtol(param, &endptr, 0);
+ if ((*endptr != '\0') || (dev_num < 0)) {
+ msg_perr("Invalid device number %s. Use flashrom -p "
+ "linux_mtd:dev=N where N is a valid MTD\n"
+ "device number.\n", param);
+ goto linux_mtd_init_exit;
+ }
+ }
+
+ if (linux_mtd_setup(dev_num))
+ goto linux_mtd_init_exit;
+
+ if (register_shutdown(linux_mtd_shutdown, NULL))
+ goto linux_mtd_init_exit;
+
+ register_opaque_master(&programmer_linux_mtd);
+
+ ret = 0;
+linux_mtd_init_exit:
+ return ret;
+}
diff --git a/programmer.h b/programmer.h
index 5d9610a46..e49f2621e 100644
--- a/programmer.h
+++ b/programmer.h
@@ -94,6 +94,9 @@ enum programmer {
#if CONFIG_SATAMV == 1
PROGRAMMER_SATAMV,
#endif
+#if CONFIG_LINUX_MTD == 1
+ PROGRAMMER_LINUX_MTD,
+#endif
#if CONFIG_LINUX_SPI == 1
PROGRAMMER_LINUX_SPI,
#endif
@@ -523,6 +526,11 @@ int register_spi_bitbang_master(const struct bitbang_spi_master *master);
int buspirate_spi_init(void);
#endif
+/* linux_mtd.c */
+#if CONFIG_LINUX_MTD == 1
+int linux_mtd_init(void);
+#endif
+
/* linux_spi.c */
#if CONFIG_LINUX_SPI == 1
int linux_spi_init(void);
@@ -585,6 +593,9 @@ enum spi_controller {
#if CONFIG_OGP_SPI == 1 || CONFIG_NICINTEL_SPI == 1 || CONFIG_RAYER_SPI == 1 || CONFIG_PONY_SPI == 1 || (CONFIG_INTERNAL == 1 && (defined(__i386__) || defined(__x86_64__)))
SPI_CONTROLLER_BITBANG,
#endif
+#if CONFIG_LINUX_MTD == 1
+ SPI_CONTROLLER_LINUX_MTD,
+#endif
#if CONFIG_LINUX_SPI == 1
SPI_CONTROLLER_LINUX,
#endif
@@ -678,6 +689,13 @@ void exit_conf_mode_ite(uint16_t port);
void probe_superio_ite(void);
int init_superio_ite(void);
+#if CONFIG_LINUX_MTD == 1
+/* trivial wrapper to avoid cluttering internal_init() with #if */
+static inline int try_mtd(void) { return linux_mtd_init(); };
+#else
+static inline int try_mtd(void) { return 1; };
+#endif
+
/* mcp6x_spi.c */
int mcp6x_spi_init(int want_spi);