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 + +int main(int argc, char **argv) +{ + (void) argc; + (void) argv; + return 0; +} +endef +export LINUX_MTD_TEST + define LINUX_SPI_TEST #include #include @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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);