David Hendricks would like Shawn Nematbakhsh to review this change.

View Change

Initial MTD support

(This patch was ported from chromiumos @cebee89a)

This adds MTD support to flashrom so that we can read, erase, and
write content on a NOR flash chip via MTD.

BUG=chrome-os-partner:40208
BRANCH=none
TEST=read, write, and erase works on Oak

Signed-off-by: David Hendricks <dhendrix@chromium.org>
Change-Id: Ib3b8963874722ea80299e9298101409408d6c253
Reviewed-on: https://chromium-review.googlesource.com/272983
Reviewed-by: Shawn N <shawnn@chromium.org>
---
M Makefile
M flashrom.c
M internal.c
A linux_mtd.c
M programmer.h
5 files changed, 416 insertions(+), 1 deletion(-)

git pull ssh://review.coreboot.org:29418/flashrom refs/changes/01/22501/1
diff --git a/Makefile b/Makefile
index 26bf343..8b2f7c5 100644
--- a/Makefile
+++ b/Makefile
@@ -623,7 +623,8 @@
# 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.
@@ -901,6 +902,11 @@
NEED_LIBPCI += CONFIG_SATAMV
endif

+ifeq ($(CONFIG_LINUX_MTD), yes)
+FEATURE_CFLAGS += -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'")
diff --git a/flashrom.c b/flashrom.c
index 4fe1843..6329041 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -344,6 +344,17 @@
},
#endif

+#if CONFIG_LINUX_MTD == 1
+ {
+ .name = "linux_mtd",
+ .type = OTHER,
+ .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",
diff --git a/internal.c b/internal.c
index 4062b64..5395178 100644
--- a/internal.c
+++ b/internal.c
@@ -243,6 +243,11 @@
if (pci_init_common() != 0)
return 1;

+#if CONFIG_LINUX_MTD == 1
+ if (!linux_mtd_init())
+ return 0;
+#endif
+
if (processor_flash_enable()) {
msg_perr("Processor detection/init failed.\n"
"Aborting.\n");
diff --git a/linux_mtd.c b/linux_mtd.c
new file mode 100644
index 0000000..e1b11cd
--- /dev/null
+++ b/linux_mtd.c
@@ -0,0 +1,382 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright 2015 Google 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgen.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 "file.h"
+#include "flash.h"
+#include "programmer.h"
+
+#define LINUX_DEV_ROOT "/dev"
+#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
+
+/* enough space for LINUX_MTD_SYSFS_ROOT + directory name + filename */
+static char sysfs_path[PATH_MAX];
+
+static int dev_fd = -1;
+
+static char *mtd_device_name;
+static int mtd_device_is_writeable;
+
+/* 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 */
+
+static int stat_mtd_files(char *dev_path, char *sysfs_path)
+{
+ struct stat s;
+
+ errno = 0;
+ if (stat(dev_path, &s) < 0) {
+ msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
+ return 1;
+ }
+
+ if (lstat(sysfs_path, &s) < 0) {
+ msg_pdbg("Cannot stat \"%s\" : %s\n",
+ sysfs_path, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+/* read a string from a sysfs file and sanitize it */
+static int read_sysfs_string(const char *filename, char *buf, int len)
+{
+ int fd, bytes_read, i;
+ char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ msg_perr("Cannot open %s\n", path);
+ return 1;
+ }
+
+ if ((bytes_read = read(fd, buf, len - 1)) < 0) {
+ msg_perr("Cannot read %s\n", path);
+ close(fd);
+ 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;
+ }
+ }
+
+ close(fd);
+ return 0;
+}
+
+static int read_sysfs_int(const char *filename, unsigned long int *val)
+{
+ uint32_t tmp;
+ char buf[32];
+ char *endptr;
+
+ if (read_sysfs_string(filename, buf, sizeof(buf)))
+ return 1;
+
+ errno = 0;
+ *val = strtoul(buf, &endptr, 0);
+ if (endptr != &buf[strlen(buf)]) {
+ 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;
+}
+
+/* returns 0 to indicate success, non-zero to indicate error */
+static int get_mtd_info(void)
+{
+ unsigned long int tmp;
+ char mtd_device_name[32];
+
+ /* Flags */
+ if (read_sysfs_int("flags", &tmp))
+ return 1;
+ if (tmp & MTD_WRITEABLE) {
+ /* cache for later use by write function */
+ mtd_device_is_writeable = 1;
+ }
+
+ /* Device name */
+ if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
+ return 1;
+
+ /* Total size */
+ if (read_sysfs_int("size", &mtd_total_size))
+ return 1;
+ if (__builtin_popcount(mtd_total_size) != 1) {
+ msg_perr("MTD size is not a power of 2\n");
+ return 1;
+ }
+
+ /* Erase size */
+ if (read_sysfs_int("erasesize", &mtd_erasesize))
+ return 1;
+ if (__builtin_popcount(mtd_erasesize) != 1) {
+ msg_perr("MTD erase size is not a power of 2\n");
+ return 1;
+ }
+
+ /* Erase regions */
+ if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
+ return 1;
+ if (mtd_numeraseregions != 0) {
+ msg_perr("Non-uniform eraseblock size is unsupported.\n");
+ return 1;
+ }
+
+ msg_pspew("%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 flashchip *flash)
+{
+ flash->tested = TEST_OK_PREW;
+ flash->total_size = mtd_total_size / 1024; /* bytes -> kB */
+ flash->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
+ flash->block_erasers[0].eraseblocks[0].count =
+ mtd_total_size / mtd_erasesize;
+ return 1;
+}
+
+static int linux_mtd_read(struct flashchip *flash, uint8_t *buf,
+ unsigned int start, unsigned int len)
+{
+ if (lseek(dev_fd, start, SEEK_SET) != start) {
+ msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
+ return 1;
+ }
+
+ if (read(dev_fd, buf, len) != len) {
+ msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
+ start, len, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+/* this version assumes we must divide the write request into pages ourselves */
+static int linux_mtd_write(struct flashchip *flash, uint8_t *buf,
+ unsigned int start, unsigned int len)
+{
+ unsigned int page;
+ unsigned int chunksize, page_size;
+
+ chunksize = page_size = flash->page_size;
+
+ if (!mtd_device_is_writeable)
+ return 1;
+
+ for (page = start / page_size;
+ page <= (start + len - 1) / page_size; page++) {
+ unsigned int i, starthere, lenhere;
+
+ starthere = max(start, page * page_size);
+ lenhere = min(start + len, (page + 1) * page_size) - starthere;
+ for (i = 0; i < lenhere; i += chunksize) {
+ unsigned int towrite = min(chunksize, lenhere - i);
+
+ if (lseek(dev_fd, starthere, SEEK_SET) != starthere) {
+ msg_perr("Cannot seek to 0x%06x: %s\n",
+ start, strerror(errno));
+ return 1;
+ }
+
+ if (write(dev_fd, &buf[starthere - start], towrite) != towrite) {
+ msg_perr("Cannot read 0x%06x bytes at 0x%06x: "
+ "%s\n", start, len, strerror(errno));
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int linux_mtd_erase(struct flashchip *flash,
+ unsigned int start, unsigned int len)
+{
+ struct region_info_user region_info;
+ uint32_t u;
+
+ if (mtd_numeraseregions != 0) {
+ /* TODO: Support non-uniform eraseblock size using
+ use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
+ }
+
+ for (u = 0; u < len; u += mtd_erasesize) {
+ struct erase_info_user erase_info = {
+ .start = start + u,
+ .length = mtd_erasesize,
+ };
+
+ if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
+ msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static struct opaque_programmer programmer_linux_mtd = {
+ /* FIXME: Do we need to change these (especially write) as per
+ * page size requirements? */
+ .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 dev_path[16]; /* "/dev/mtdN" */
+ int ret = 1;
+
+ if (dev_num < 0) {
+ char *tmp, *p;
+
+ tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
+ if (!tmp) {
+ msg_perr("%s: NOR type device not found.\n", __func__);
+ goto linux_mtd_setup_exit;
+ }
+
+ /* "tmp" should be something like "/sys/blah/mtdN/type" */
+ p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
+ while (p[0] == '/')
+ p++;
+
+ if (sscanf(p, "mtd%d", &dev_num) != 1) {
+ msg_perr("Can't obtain device number from \"%s\"\n", p);
+ free(tmp);
+ goto linux_mtd_setup_exit;
+ }
+ free(tmp);
+ }
+
+ snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
+ LINUX_MTD_SYSFS_ROOT, dev_num);
+ snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
+ LINUX_DEV_ROOT, dev_num);
+ msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
+ __func__, sysfs_path, dev_path);
+
+ if (stat_mtd_files(dev_path, sysfs_path))
+ goto linux_mtd_setup_exit;
+
+ if (get_mtd_info())
+ goto linux_mtd_setup_exit;
+
+ if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
+ msg_pdbg("%s: failed to open %s: %s\n", __func__,
+ dev_path, strerror(errno));
+ goto linux_mtd_setup_exit;
+ }
+
+ ret = 0;
+linux_mtd_setup_exit:
+ return ret;
+}
+
+static int linux_mtd_shutdown(void *data)
+{
+ if (dev_fd != -1) {
+ close(dev_fd);
+ dev_fd = -1;
+ }
+
+ return 0;
+}
+
+int linux_mtd_init(void)
+{
+ char *param;
+ int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
+ int ret = 1;
+
+ if (alias && alias->type != ALIAS_HOST)
+ return 1;
+
+ param = extract_programmer_param("dev");
+ if (param) {
+ char *endptr;
+
+ dev_num = strtol(param, &endptr, 0);
+ if ((param == endptr) || (dev_num < 0)) {
+ msg_perr("Invalid device number %s. Use flashrom -p "
+ "linux_mtd:dev=N where N is a valid MTD "
+ "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_programmer(&programmer_linux_mtd);
+
+ ret = 0;
+linux_mtd_init_exit:
+ msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
+ return ret;
+}
diff --git a/programmer.h b/programmer.h
index a98b713..d3a77bc 100644
--- a/programmer.h
+++ b/programmer.h
@@ -99,6 +99,9 @@
#if CONFIG_LINUX_SPI == 1
PROGRAMMER_LINUX_SPI,
#endif
+#if CONFIG_LINUX_MTD == 1
+ PROGRAMMER_LINUX_MTD,
+#endif
#if CONFIG_USBBLASTER_SPI == 1
PROGRAMMER_USBBLASTER_SPI,
#endif
@@ -525,6 +528,11 @@
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);
@@ -587,6 +595,9 @@
#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

To view, visit change 22501. To unsubscribe, visit settings.

Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib3b8963874722ea80299e9298101409408d6c253
Gerrit-Change-Number: 22501
Gerrit-PatchSet: 1
Gerrit-Owner: David Hendricks <david.hendricks@gmail.com>
Gerrit-Reviewer: Shawn Nematbakhsh <shawnn@chromium.org>