Hello Shawn Nematbakhsh,
I'd like you to do a code review. Please visit
https://review.coreboot.org/22501
to review the following change.
Change subject: Initial MTD support ......................................................................
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