From 49ebf55b0dd4f5ba3d87b44961c448e5ab5541dc Mon Sep 17 00:00:00 2001 From: Aurelien Gonce Date: Mon, 4 Jul 2016 17:48:35 +0200 Subject: [PATCH 1/5] mtd: add a low level generic driver for flash file system --- drivers/include/mtd.h | 233 ++++++++++++++++++++++++++++++++++++++++++ drivers/mtd/Makefile | 3 + drivers/mtd/mtd.c | 95 +++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 drivers/include/mtd.h create mode 100644 drivers/mtd/Makefile create mode 100644 drivers/mtd/mtd.c diff --git a/drivers/include/mtd.h b/drivers/include/mtd.h new file mode 100644 index 000000000..bc32eb280 --- /dev/null +++ b/drivers/include/mtd.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup mtd Memory Technology Device + * @{ + * @brief Low level Memory Technology Device interface + * + * Generic memory technology device interface + * + * @file + * + * @author Aurelien Gonce + * @author Vincent Dupont + */ + +#ifndef MTD_H +#define MTD_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MTD power states + */ +enum mtd_power_state { + MTD_POWER_UP, /**< Power up */ + MTD_POWER_DOWN, /**< Power down */ +}; + +/** + * @brief MTD driver interface + * + * This define the functions to access a MTD. + * + * A MTD is composed of pages combined into sectors. A sector is the smallest erasable unit. + * The number of pages in a sector must be constant for the whole MTD. + * + * The erase operation is available only for entire sectors. + */ +typedef struct mtd_desc mtd_desc_t; + +/** + * @brief MTD device descriptor + */ +typedef struct { + const mtd_desc_t *driver; /**< MTD driver */ + uint32_t sector_count; /**< Number of sector in the MTD */ + uint32_t pages_per_sector; /**< Number of pages by sector in the MTD */ + uint32_t page_size; /**< Size of the pages in the MTD */ +} mtd_dev_t; + +/** + * @brief MTD driver interface + * + * This define the functions to access a MTD. + * + * A MTD is composed of pages combined into sectors. A sector is the smallest erasable unit. + * The number of pages in a sector must be constant for the whole MTD. + * + * The erase operation is available only for entire sectors. + */ +struct mtd_desc { + /** + * @brief Initialize Memory Technology Device (MTD) + * + * @param[in] dev Pointer to the selected driver + * + * @returns 0 on success + * @returns < 0 value in error + */ + int (*init)(mtd_dev_t *dev); + + /** + * @brief Read from the Memory Technology Device (MTD) + * + * No alignment is required on @p addr and @p size. + * + * @param[in] dev Pointer to the selected driver + * @param[out] buff Pointer to the data buffer to store read data + * @param[in] addr Starting address + * @param[in] size Number of bytes + * + * @return the number of bytes actually read + * @return < 0 value on error + */ + int (*read)(mtd_dev_t *dev, + void *buff, + uint32_t addr, + uint32_t size); + + /** + * @brief Write to the Memory Technology Device (MTD) + * + * @p addr + @p size must be inside a page boundary. @p addr can be anywhere + * but the buffer cannot overlap two pages. + * + * @param[in] dev Pointer to the selected driver + * @param[in] buff Pointer to the data to be written + * @param[in] addr Starting address + * @param[in] size Number of bytes + * + * @return the number of bytes actually written + * @return < 0 value on error + */ + int (*write)(mtd_dev_t *dev, + const void *buff, + uint32_t addr, + uint32_t size); + + /** + * @brief Erase sector(s) over the Memory Technology Device (MTD) + * + * @p addr must be aligned on a sector boundary. @p size must be a multiple of a sector size. + * + * @param[in] dev Pointer to the selected driver + * @param[in] addr Starting address + * @param[in] size Number of bytes + * + * @return 0 on success + * @return < 0 value on error + */ + int (*erase)(mtd_dev_t *dev, + uint32_t addr, + uint32_t size); + + /** + * @brief Control power of Memory Technology Device (MTD) + * + * @param[in] dev Pointer to the selected driver + * @param[in] power Power state to apply (from @ref mtd_power_state) + * + * @return 0 on success + * @return < 0 value on error + */ + int (*power)(mtd_dev_t *dev, enum mtd_power_state power); +}; + +/** + * @brief mtd_init Initialize a MTD device + * + * @param mtd the device to initialize + * + * @return + */ +int mtd_init(mtd_dev_t *mtd); + +/** + * @brief mtd_read Read data from a MTD device + * + * No alignment is required on @p addr and @p count. + * + * @param mtd the device to read from + * @param[out] dest the buffer to fill in + * @param[in] addr the start address to read from + * @param[in] count the number of bytes to read + * + * @return the number of byte actually read + * @return < 0 if an error occured + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p count are not valid, i.e. outside memory + * @return -EIO if I/O error occured + */ +int mtd_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t count); + +/** + * @brief mtd_read write data to a MTD device + * + * @p addr + @p count must be inside a page boundary. @p addr can be anywhere + * but the buffer cannot overlap two pages. + * + * @param mtd the device to write to + * @param[in] src the buffer to write + * @param[in] addr the start address to write to + * @param[in] count the number of bytes to write + * + * @return the number of byte actually written + * @return < 0 if an error occured + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p count are not valid, i.e. outside memory, + * or overlapping two pages + * @return -EIO if I/O error occured + */ +int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count); + +/** + * @brief mtd_erase Erase sectors of a MTD device + * + * @p addr must be aligned on a sector boundary. @p count must be a multiple of a sector size. + * + * @param mtd the device to erase + * @param[in] addr the address of the first sector to erase + * @param[in] count the number of bytes to erase + * + * @return 0 if erase successful + * @return < 0 if an error occured + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation is not supported on @p mtd + * @return -EOVERFLOW if @p addr or @p count are not valid, i.e. outside memory + * @return -EIO if I/O error occured + */ +int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count); + +/** + * @brief mtd_power Set power mode on a MTD device + * + * @param mtd the device to access + * @param[in] power the power mode to set + * + * @return 0 if power mode successfully set + * @return < 0 if an error occured + * @return -ENODEV if @p mtd is not a valid device + * @return -ENOTSUP if operation or @p power state is not supported on @p mtd + * @return -EIO if I/O error occured + */ +int mtd_power(mtd_dev_t *mtd, enum mtd_power_state power); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif /* MTD_H */ diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile new file mode 100644 index 000000000..f31de9a1a --- /dev/null +++ b/drivers/mtd/Makefile @@ -0,0 +1,3 @@ +MODULE = mtd + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mtd/mtd.c b/drivers/mtd/mtd.c new file mode 100644 index 000000000..53f285382 --- /dev/null +++ b/drivers/mtd/mtd.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup mtd + * @{ + * @brief Low level Memory Technology Device interface + * + * Generic memory technology device interface + * + * @file + * + * @author Vincent Dupont + */ + +#include + +#include "mtd.h" + +int mtd_init(mtd_dev_t *mtd) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (mtd->driver->init) { + return mtd->driver->init(mtd); + } + else { + return -ENOTSUP; + } +} + +int mtd_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (mtd->driver->read) { + return mtd->driver->read(mtd, dest, addr, count); + } + else { + return -ENOTSUP; + } +} + +int mtd_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (mtd->driver->write) { + return mtd->driver->write(mtd, src, addr, count); + } + else { + return -ENOTSUP; + } +} + +int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (mtd->driver->erase) { + return mtd->driver->erase(mtd, addr, count); + } + else { + return -ENOTSUP; + } +} + +int mtd_power(mtd_dev_t *mtd, enum mtd_power_state power) +{ + if (!mtd || !mtd->driver) { + return -ENODEV; + } + + if (mtd->driver->power) { + return mtd->driver->power(mtd, power); + } + else { + return -ENOTSUP; + } +} + +/** @} */ From 9e8cb3d13588dcae420290493f3e9767ea42878b Mon Sep 17 00:00:00 2001 From: Vincent Dupont Date: Mon, 11 Jul 2016 09:53:13 +0200 Subject: [PATCH 2/5] native: add mtd-compliant flash emulation --- boards/native/Makefile.dep | 4 + boards/native/board_init.c | 29 +++++ boards/native/include/board.h | 11 ++ cpu/native/Makefile | 3 + cpu/native/include/mtd_native.h | 46 ++++++++ cpu/native/include/native_internal.h | 4 + cpu/native/mtd/Makefile | 5 + cpu/native/mtd/mtd_native.c | 154 +++++++++++++++++++++++++++ cpu/native/startup.c | 24 ++++- cpu/native/syscalls.c | 8 ++ 10 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 cpu/native/include/mtd_native.h create mode 100644 cpu/native/mtd/Makefile create mode 100644 cpu/native/mtd/mtd_native.c diff --git a/boards/native/Makefile.dep b/boards/native/Makefile.dep index 0806ebe2a..31ac5d3e6 100644 --- a/boards/native/Makefile.dep +++ b/boards/native/Makefile.dep @@ -1,3 +1,7 @@ ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE))) USEMODULE += netdev2_tap endif + +ifneq (,$(filter mtd,$(USEMODULE))) + USEMODULE += mtd_native +endif diff --git a/boards/native/board_init.c b/boards/native/board_init.c index e94f69a3d..6ecec3fa2 100644 --- a/boards/native/board_init.c +++ b/boards/native/board_init.c @@ -18,6 +18,10 @@ #include "board_internal.h" +#ifdef MODULE_MTD +#include "mtd_native.h" +#endif + /** * Nothing to initialize at the moment. * Turns the red LED on and the green LED off. @@ -28,3 +32,28 @@ void board_init(void) LED1_ON; puts("RIOT native board initialized."); } + +#ifdef MODULE_MTD +#ifndef MTD_NATIVE_PAGE_SIZE +#define MTD_NATIVE_PAGE_SIZE 256 +#endif +#ifndef MTD_NATIVE_SECTOR_SIZE +#define MTD_NATIVE_SECTOR_SIZE 4096 +#endif +#ifndef MTD_NATIVE_SECTOR_NUM +#define MTD_NATIVE_SECTOR_NUM 2048 +#endif +#ifndef MTD_NATIVE_FILENAME +#define MTD_NATIVE_FILENAME "MEMORY.bin" +#endif + +mtd_native_dev_t mtd0 = { + .dev = { + .driver = &native_flash_driver, + .sector_count = MTD_NATIVE_SECTOR_NUM, + .pages_per_sector = MTD_NATIVE_SECTOR_SIZE / MTD_NATIVE_PAGE_SIZE, + .page_size = MTD_NATIVE_PAGE_SIZE, + }, + .fname = MTD_NATIVE_FILENAME, +}; +#endif diff --git a/boards/native/include/board.h b/boards/native/include/board.h index a40493370..ca89ad643 100644 --- a/boards/native/include/board.h +++ b/boards/native/include/board.h @@ -30,6 +30,10 @@ extern "C" { #endif +#ifdef MODULE_MTD +#include "mtd_native.h" +#endif + /** * @brief LED handlers * @{ @@ -49,6 +53,13 @@ void _native_LED_RED_TOGGLE(void); #define LED1_TOGGLE (_native_LED_GREEN_TOGGLE()) /** @} */ +#ifdef MODULE_MTD +#define MTD_0 mtd0 + +/** mtd flash emulation device */ +extern mtd_native_dev_t mtd0; +#endif + #ifdef __cplusplus } #endif diff --git a/cpu/native/Makefile b/cpu/native/Makefile index d66f5a971..2e970b1cd 100644 --- a/cpu/native/Makefile +++ b/cpu/native/Makefile @@ -6,6 +6,9 @@ DIRS += vfs ifneq (,$(filter netdev2_tap,$(USEMODULE))) DIRS += netdev2_tap endif +ifneq (,$(filter mtd_native,$(USEMODULE))) + DIRS += mtd +endif include $(RIOTBASE)/Makefile.base diff --git a/cpu/native/include/mtd_native.h b/cpu/native/include/mtd_native.h new file mode 100644 index 000000000..bc412ccb2 --- /dev/null +++ b/cpu/native/include/mtd_native.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup mtd + * @defgroup mtd_native Native MTD + * @{ + * @brief mtd flash emulation for native + * + * @file + * + * @author Vincent Dupont + */ + +#ifndef MTD_NATIVE_H +#define MTD_NATIVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mtd.h" + +/** mtd native descriptor */ +typedef struct mtd_native_dev { + mtd_dev_t dev; /**< mtd generic device */ + const char *fname; /**< filename to use for memory emulation */ +} mtd_native_dev_t; + +/** + * @brief Native mtd flash driver + */ +extern const mtd_desc_t native_flash_driver; + +#ifdef __cplusplus +} +#endif + +#endif /* MTD_NATIVE_H */ + +/** @} */ diff --git a/cpu/native/include/native_internal.h b/cpu/native/include/native_internal.h index b2778f056..961b2d222 100644 --- a/cpu/native/include/native_internal.h +++ b/cpu/native/include/native_internal.h @@ -124,6 +124,10 @@ extern int (*real_unlink)(const char *); extern long int (*real_random)(void); extern const char* (*real_gai_strerror)(int errcode); extern FILE* (*real_fopen)(const char *path, const char *mode); +extern int (*real_fclose)(FILE *stream); +extern int (*real_fseek)(FILE *stream, long offset, int whence); +extern int (*real_fputc)(int c, FILE *stream); +extern int (*real_fgetc)(FILE *stream); extern mode_t (*real_umask)(mode_t cmask); extern ssize_t (*real_writev)(int fildes, const struct iovec *iov, int iovcnt); diff --git a/cpu/native/mtd/Makefile b/cpu/native/mtd/Makefile new file mode 100644 index 000000000..f0006b95d --- /dev/null +++ b/cpu/native/mtd/Makefile @@ -0,0 +1,5 @@ +MODULE := mtd_native + +include $(RIOTBASE)/Makefile.base + +INCLUDES = $(NATIVEINCLUDES) diff --git a/cpu/native/mtd/mtd_native.c b/cpu/native/mtd/mtd_native.c new file mode 100644 index 000000000..be6e361e2 --- /dev/null +++ b/cpu/native/mtd/mtd_native.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup mtd_native + * @{ + * @brief mtd flash emulation for native + * + * @file + * + * @author Vincent Dupont + */ + +#include +#include +#include +#include + +#include "mtd.h" +#include "mtd_native.h" + +#include "native_internal.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static int _init(mtd_dev_t *dev) +{ + mtd_native_dev_t *_dev = (mtd_native_dev_t*) dev; + + DEBUG("mtd_native: init, filename=%s\n", _dev->fname); + + FILE *f = real_fopen(_dev->fname, "r"); + + if (!f) { + DEBUG("mtd_native: init: creating file %s\n", name); + f = real_fopen(_dev->fname, "w+"); + if (!f) { + return -EIO; + } + size_t size = dev->sector_count * dev->pages_per_sector * dev->page_size; + for (size_t i = 0; i < size; i++) { + real_fputc(0xff, f); + } + } + + real_fclose(f); + + return 0; +} + +static int _read(mtd_dev_t *dev, void *buff, uint32_t addr, uint32_t size) +{ + mtd_native_dev_t *_dev = (mtd_native_dev_t*) dev; + size_t mtd_size = dev->sector_count * dev->pages_per_sector * dev->page_size; + + DEBUG("mtd_native: read from page %" PRIu32 " count %" PRIu32 "\n", addr, size); + + if (addr + size > mtd_size) { + return -EOVERFLOW; + } + + FILE *f = real_fopen(_dev->fname, "r"); + if (!f) { + return -EIO; + } + real_fseek(f, addr, SEEK_SET); + size = real_fread(buff, 1, size, f); + real_fclose(f); + + return size; +} + +static int _write(mtd_dev_t *dev, const void *buff, uint32_t addr, uint32_t size) +{ + mtd_native_dev_t *_dev = (mtd_native_dev_t*) dev; + size_t mtd_size = dev->sector_count * dev->pages_per_sector * dev->page_size; + size_t sector_size = dev->pages_per_sector * dev->page_size; + + DEBUG("mtd_native: write from page %" PRIu32 " count %" PRIu32 "\n", addr, size); + + if (addr + size > mtd_size) { + return -EOVERFLOW; + } + if (((addr % sector_size) + size) > sector_size) { + return -EOVERFLOW; + } + + FILE *f = real_fopen(_dev->fname, "r+"); + if (!f) { + return -EIO; + } + real_fseek(f, addr, SEEK_SET); + for (size_t i = 0; i < size; i++) { + uint8_t c = real_fgetc(f); + real_fseek(f, -1, SEEK_CUR); + real_fputc(c & ((uint8_t*)buff)[i], f); + } + real_fclose(f); + + return size; +} + +static int _erase(mtd_dev_t *dev, uint32_t addr, uint32_t size) +{ + mtd_native_dev_t *_dev = (mtd_native_dev_t*) dev; + size_t mtd_size = dev->sector_count * dev->pages_per_sector * dev->page_size; + size_t sector_size = dev->pages_per_sector * dev->page_size; + + DEBUG("mtd_native: erase from sector %" PRIu32 " count %" PRIu32 "\n", addr, size); + + if (addr + size > mtd_size) { + return -EOVERFLOW; + } + if (((addr % sector_size) != 0) || ((size % sector_size) != 0)) { + return -EOVERFLOW; + } + + FILE *f = real_fopen(_dev->fname, "r+"); + if (!f) { + return -EIO; + } + real_fseek(f, addr, SEEK_SET); + for (size_t i = 0; i < size; i++) { + real_fputc(0xff, f); + } + real_fclose(f); + + return 0; +} + +static int _power(mtd_dev_t *dev, enum mtd_power_state power) +{ + (void) dev; + (void) power; + + return -ENOTSUP; +} + + +const mtd_desc_t native_flash_driver = { + .read = _read, + .power = _power, + .write = _write, + .erase = _erase, + .init = _init, +}; + +/** @} */ diff --git a/cpu/native/startup.c b/cpu/native/startup.c index 281f2db4a..2d8903b0e 100644 --- a/cpu/native/startup.c +++ b/cpu/native/startup.c @@ -65,7 +65,16 @@ const char *_native_unix_socket_path = NULL; netdev2_tap_params_t netdev2_tap_params[NETDEV2_TAP_MAX]; #endif -static const char short_opts[] = ":hi:s:deEoc:"; +#ifdef MODULE_MTD_NATIVE +#include "board.h" +#include "mtd_native.h" +#endif + +static const char short_opts[] = ":hi:s:deEoc:" +#ifdef MODULE_MTD_NATIVE + "m:" +#endif + ""; static const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "id", required_argument, NULL, 'i' }, @@ -75,6 +84,9 @@ static const struct option long_opts[] = { { "stderr-noredirect", no_argument, NULL, 'E' }, { "stdout-pipe", no_argument, NULL, 'o' }, { "uart-tty", required_argument, NULL, 'c' }, +#ifdef MODULE_MTD_NATIVE + { "mtd", required_argument, NULL, 'm' }, +#endif { NULL, 0, NULL, '\0' }, }; @@ -230,6 +242,11 @@ void usage_exit(int status) " -c , --uart-tty=\n" " specify TTY device for UART. This argument can be used multiple\n" " times (up to UART_NUMOF)\n"); +#ifdef MODULE_MTD_NATIVE + real_printf( +" -m , --mtd=\n" +" specify the file name of mtd emulated device\n"); +#endif real_exit(status); } @@ -285,6 +302,11 @@ __attribute__((constructor)) static void startup(int argc, char **argv) case 'c': tty_uart_setup(uart++, optarg); break; +#ifdef MODULE_MTD_NATIVE + case 'm': + mtd0.fname = strndup(optarg, PATH_MAX - 1); + break; +#endif default: usage_exit(EXIT_FAILURE); } diff --git a/cpu/native/syscalls.c b/cpu/native/syscalls.c index acbd5af43..db568d09a 100644 --- a/cpu/native/syscalls.c +++ b/cpu/native/syscalls.c @@ -89,6 +89,10 @@ int (*real_unlink)(const char *); long int (*real_random)(void); const char* (*real_gai_strerror)(int errcode); FILE* (*real_fopen)(const char *path, const char *mode); +int (*real_fclose)(FILE *stream); +int (*real_fseek)(FILE *stream, long offset, int whence); +int (*real_fputc)(int c, FILE *stream); +int (*real_fgetc)(FILE *stream); mode_t (*real_umask)(mode_t cmask); ssize_t (*real_writev)(int fildes, const struct iovec *iov, int iovcnt); @@ -472,6 +476,10 @@ void _native_init_syscalls(void) *(void **)(&real_clearerr) = dlsym(RTLD_NEXT, "clearerr"); *(void **)(&real_umask) = dlsym(RTLD_NEXT, "umask"); *(void **)(&real_writev) = dlsym(RTLD_NEXT, "writev"); + *(void **)(&real_fclose) = dlsym(RTLD_NEXT, "fclose"); + *(void **)(&real_fseek) = dlsym(RTLD_NEXT, "fseek"); + *(void **)(&real_fputc) = dlsym(RTLD_NEXT, "fputc"); + *(void **)(&real_fgetc) = dlsym(RTLD_NEXT, "fgetc"); #ifdef __MACH__ #else *(void **)(&real_clock_gettime) = dlsym(RTLD_NEXT, "clock_gettime"); From 81149f184329c5bc8afe18f314b796ab50e05edb Mon Sep 17 00:00:00 2001 From: Vincent Dupont Date: Thu, 16 Mar 2017 10:07:38 +0100 Subject: [PATCH 3/5] unittests: add mtd unittests --- tests/unittests/tests-mtd/Makefile | 1 + tests/unittests/tests-mtd/Makefile.include | 1 + tests/unittests/tests-mtd/tests-mtd.c | 260 +++++++++++++++++++++ tests/unittests/tests-mtd/tests-mtd.h | 37 +++ 4 files changed, 299 insertions(+) create mode 100644 tests/unittests/tests-mtd/Makefile create mode 100644 tests/unittests/tests-mtd/Makefile.include create mode 100644 tests/unittests/tests-mtd/tests-mtd.c create mode 100644 tests/unittests/tests-mtd/tests-mtd.h diff --git a/tests/unittests/tests-mtd/Makefile b/tests/unittests/tests-mtd/Makefile new file mode 100644 index 000000000..48422e909 --- /dev/null +++ b/tests/unittests/tests-mtd/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-mtd/Makefile.include b/tests/unittests/tests-mtd/Makefile.include new file mode 100644 index 000000000..0169be417 --- /dev/null +++ b/tests/unittests/tests-mtd/Makefile.include @@ -0,0 +1 @@ +USEMODULE += mtd diff --git a/tests/unittests/tests-mtd/tests-mtd.c b/tests/unittests/tests-mtd/tests-mtd.c new file mode 100644 index 000000000..e9c5950f8 --- /dev/null +++ b/tests/unittests/tests-mtd/tests-mtd.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + */ +#include +#include + +#include "embUnit.h" + +#include "mtd.h" +#include "board.h" + +/* Define MTD_0 in board.h to use the board mtd if any */ +#ifdef MTD_0 +#define _dev MTD_0 +#else +/* Test mock object implementing a simple RAM-based mtd */ +#ifndef SECTOR_COUNT +#define SECTOR_COUNT 4 +#endif +#ifndef PAGE_PER_SECTOR +#define PAGE_PER_SECTOR 4 +#endif +#ifndef PAGE_SIZE +#define PAGE_SIZE 128 +#endif + +static uint8_t dummy_memory[PAGE_PER_SECTOR * PAGE_SIZE * SECTOR_COUNT]; + +static int init(mtd_dev_t *dev) +{ + (void)dev; + + memset(dummy_memory, 0xff, sizeof(dummy_memory)); + return 0; +} + +static int read(mtd_dev_t *dev, void *buff, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + memcpy(buff, dummy_memory + addr, size); + + return size; +} + +static int write(mtd_dev_t *dev, const void *buff, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + if (size > PAGE_SIZE) { + return -EOVERFLOW; + } + memcpy(dummy_memory + addr, buff, size); + + return size; +} + +static int erase(mtd_dev_t *dev, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (size % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) { + return -EOVERFLOW; + } + if (addr % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) { + return -EOVERFLOW; + } + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + memset(dummy_memory + addr, 0xff, size); + + return 0; +} + +static int power(mtd_dev_t *dev, enum mtd_power_state power) +{ + (void)dev; + (void)power; + return 0; +} + +static const mtd_desc_t driver = { + .init = init, + .read = read, + .write = write, + .erase = erase, + .power = power, +}; + +static mtd_dev_t _dev = { + .driver = &driver, + .sector_count = SECTOR_COUNT, + .pages_per_sector = PAGE_PER_SECTOR, + .page_size = PAGE_SIZE, +}; +#endif /* MTD_0 */ + +static void setup_teardown(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + + mtd_erase(dev, 0, dev->pages_per_sector * dev->page_size); +} + +static void test_mtd_init(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + + int ret = mtd_init(dev); + TEST_ASSERT_EQUAL_INT(0, ret); +} + +static void test_mtd_erase(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + + /* Erase first sector */ + int ret = mtd_erase(dev, 0, dev->pages_per_sector * dev->page_size); + TEST_ASSERT_EQUAL_INT(0, ret); + + /* Erase with wrong size (les than sector size) */ + ret = mtd_erase(dev, 0, dev->page_size); + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, ret); + + /* Unaligned erase */ + ret = mtd_erase(dev, dev->page_size, dev->page_size); + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, ret); + + /* Erase 2nd - 3rd sector */ + ret = mtd_erase(dev, dev->pages_per_sector * dev->page_size, + dev->pages_per_sector * dev->page_size * 2); + TEST_ASSERT_EQUAL_INT(0, ret); + + /* Erase out of memory area */ + ret = mtd_erase(dev, dev->pages_per_sector * dev->page_size * dev->sector_count, + dev->pages_per_sector * dev->page_size); + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, ret); +} + +static void test_mtd_write_erase(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + const char buf[] = "ABCDEFGHIJK"; + uint8_t buf_empty[] = {0xff, 0xff, 0xff}; + char buf_read[sizeof(buf) + sizeof(buf_empty)]; + memset(buf_read, 0, sizeof(buf_read)); + + int ret = mtd_write(dev, buf, sizeof(buf_empty), sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), ret); + + ret = mtd_erase(dev, 0, dev->pages_per_sector * dev->page_size); + TEST_ASSERT_EQUAL_INT(0, ret); + + uint8_t expected[sizeof(buf_read)]; + memset(expected, 0xff, sizeof(expected)); + ret = mtd_read(dev, buf_read, 0, sizeof(buf_read)); + TEST_ASSERT_EQUAL_INT(sizeof(buf_read), ret); + TEST_ASSERT_EQUAL_INT(0, memcmp(expected, buf_read, sizeof(buf_read))); + +} + +static void test_mtd_write_read(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + const char buf[] = "ABCDEFGH"; + uint8_t buf_empty[] = {0xff, 0xff, 0xff}; + char buf_read[sizeof(buf) + sizeof(buf_empty)]; + memset(buf_read, 0, sizeof(buf_read)); + + /* Basic write / read */ + int ret = mtd_write(dev, buf, 0, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), ret); + + ret = mtd_read(dev, buf_read, 0, sizeof(buf_read)); + TEST_ASSERT_EQUAL_INT(sizeof(buf_read), ret); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf, buf_read, sizeof(buf))); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf_empty, buf_read + sizeof(buf), sizeof(buf_empty))); + + /* Unaligned write / read */ + ret = mtd_write(dev, buf, dev->page_size + sizeof(buf_empty), sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), ret); + + ret = mtd_read(dev, buf_read, dev->page_size, sizeof(buf_read)); + TEST_ASSERT_EQUAL_INT(sizeof(buf_read), ret); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf_empty, buf_read, sizeof(buf_empty))); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf, buf_read + sizeof(buf_empty), sizeof(buf))); + + /* out of bounds write (addr) */ + ret = mtd_write(dev, buf, dev->pages_per_sector * dev->page_size * dev->sector_count, + sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, ret); + + /* out of bounds write (addr + count) */ + ret = mtd_write(dev, buf, (dev->pages_per_sector * dev->page_size * dev->sector_count) + - (sizeof(buf) / 2), sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, ret); +} + +static void test_mtd_write_read_flash(void) +{ + mtd_dev_t *dev = (mtd_dev_t*) &_dev; + const uint8_t buf1[] = {0xee, 0xdd, 0xcc}; + const uint8_t buf2[] = {0x33, 0x33, 0x33}; + const uint8_t buf_expected[] = {0x22, 0x11, 0x0}; + uint8_t buf_empty[] = {0xff, 0xff, 0xff}; + char buf_read[sizeof(buf_expected) + sizeof(buf_empty)]; + memset(buf_read, 0, sizeof(buf_read)); + + /* Test flash AND behavior. This test will fail if the mtd is not a flash */ + + /* Basic write / read */ + int ret = mtd_write(dev, buf1, 0, sizeof(buf1)); + TEST_ASSERT_EQUAL_INT(sizeof(buf1), ret); + ret = mtd_write(dev, buf2, 0, sizeof(buf2)); + TEST_ASSERT_EQUAL_INT(sizeof(buf2), ret); + + ret = mtd_read(dev, buf_read, 0, sizeof(buf_read)); + TEST_ASSERT_EQUAL_INT(sizeof(buf_read), ret); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf_expected, buf_read, sizeof(buf_expected))); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf_empty, buf_read + sizeof(buf_expected), sizeof(buf_empty))); +} + + +Test *tests_mtd_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_mtd_init), + new_TestFixture(test_mtd_erase), + new_TestFixture(test_mtd_write_erase), + new_TestFixture(test_mtd_write_read), + new_TestFixture(test_mtd_write_read_flash), + }; + + EMB_UNIT_TESTCALLER(mtd_tests, setup_teardown, setup_teardown, fixtures); + + return (Test *)&mtd_tests; +} + +void tests_mtd(void) +{ + TESTS_RUN(tests_mtd_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-mtd/tests-mtd.h b/tests/unittests/tests-mtd/tests-mtd.h new file mode 100644 index 000000000..146f96ea4 --- /dev/null +++ b/tests/unittests/tests-mtd/tests-mtd.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the ``mtd`` module + * + * @author Vincent Dupont + */ +#ifndef TESTS_MTD_H +#define TESTS_MTD_H + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_mtd(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_MTD_H */ +/** @} */ From 6d3304f52cb41f13a6855236e98b39ca83316733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Nohlg=C3=A5rd?= Date: Wed, 20 Jul 2016 10:43:57 +0200 Subject: [PATCH 4/5] mtd: Add MTD VFS wrapper for use with DevFS --- drivers/include/mtd.h | 10 +++ drivers/mtd/mtd-vfs.c | 139 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 drivers/mtd/mtd-vfs.c diff --git a/drivers/include/mtd.h b/drivers/include/mtd.h index bc32eb280..2d6c5c802 100644 --- a/drivers/include/mtd.h +++ b/drivers/include/mtd.h @@ -23,6 +23,9 @@ #define MTD_H #include +#if MODULE_VFS +#include "vfs.h" +#endif #ifdef __cplusplus extern "C" { @@ -225,6 +228,13 @@ int mtd_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t count); */ int mtd_power(mtd_dev_t *mtd, enum mtd_power_state power); +#if defined(MODULE_VFS) || defined(DOXYGEN) +/** + * @brief MTD driver for VFS + */ +extern const vfs_file_ops_t mtd_vfs_ops; +#endif + #ifdef __cplusplus } #endif diff --git a/drivers/mtd/mtd-vfs.c b/drivers/mtd/mtd-vfs.c new file mode 100644 index 000000000..f0f10903b --- /dev/null +++ b/drivers/mtd/mtd-vfs.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 Eistec AB + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +#if MODULE_VFS + +#include +#include +#include + +#include "mtd.h" +#include "vfs.h" + +/** + * @ingroup mtd + * @{ + * + * @file + * + * @brief MTD generic VFS operations + * + * This allows the MTD driver to register as a node on DevFS + * + * See boards/mulle or tests/unittests/tests-devfs for examples on how to use. + * + * Tested with mtd_spi_nor on Mulle + * + * @author Joakim NohlgÄrd + */ + +static int mtd_vfs_fstat(vfs_file_t *filp, struct stat *buf); +static off_t mtd_vfs_lseek(vfs_file_t *filp, off_t off, int whence); +static ssize_t mtd_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes); +static ssize_t mtd_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes); + +const vfs_file_ops_t mtd_vfs_ops = { + .fstat = mtd_vfs_fstat, + .lseek = mtd_vfs_lseek, + .read = mtd_vfs_read, + .write = mtd_vfs_write, +}; + +static int mtd_vfs_fstat(vfs_file_t *filp, struct stat *buf) +{ + if (buf == NULL) { + return -EFAULT; + } + mtd_dev_t *mtd = filp->private_data.ptr; + if (mtd == NULL) { + return -EFAULT; + } + buf->st_nlink = 1; + buf->st_size = mtd->page_size * mtd->sector_count * mtd->pages_per_sector; + return 0; +} + +static off_t mtd_vfs_lseek(vfs_file_t *filp, off_t off, int whence) +{ + mtd_dev_t *mtd = filp->private_data.ptr; + if (mtd == NULL) { + return -EFAULT; + } + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + off += filp->pos; + break; + case SEEK_END: + off += mtd->page_size * mtd->sector_count * mtd->pages_per_sector; + break; + default: + return -EINVAL; + } + if (off < 0) { + /* the resulting file offset would be negative */ + return -EINVAL; + } + /* POSIX allows seeking past the end of the file */ + filp->pos = off; + return off; +} + +static ssize_t mtd_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + mtd_dev_t *mtd = filp->private_data.ptr; + if (mtd == NULL) { + return -EFAULT; + } + uint32_t size = mtd->page_size * mtd->sector_count * mtd->pages_per_sector; + uint32_t src = filp->pos; + if (src >= size) { + return 0; + } + if ((src + nbytes) > size) { + nbytes = size - src; + } + int res = mtd_read(mtd, dest, src, nbytes); + if (res < 0) { + return res; + } + /* Advance file position */ + filp->pos += res; + return res; +} + +static ssize_t mtd_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + mtd_dev_t *mtd = filp->private_data.ptr; + if (mtd == NULL) { + return -EFAULT; + } + uint32_t size = mtd->page_size * mtd->sector_count * mtd->pages_per_sector; + uint32_t dest = filp->pos; + if (dest >= size) { + /* attempt to write outside the device memory */ + return -ENOSPC; + } + if ((dest + nbytes) > size) { + nbytes = size - dest; + } + int res = mtd_write(mtd, src, dest, nbytes); + if (res < 0) { + return res; + } + /* Advance file position */ + filp->pos += res; + return res; +} + +/** @} */ + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_VFS */ From 9782be1d911e5e05cbbb6add5379eba41ccf80ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Nohlg=C3=A5rd?= Date: Thu, 16 Mar 2017 15:55:58 +0100 Subject: [PATCH 5/5] unittests: add unit test for mtd-vfs --- tests/unittests/tests-mtd/Makefile.include | 1 + tests/unittests/tests-mtd/tests-mtd.c | 37 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/unittests/tests-mtd/Makefile.include b/tests/unittests/tests-mtd/Makefile.include index 0169be417..d663df8ac 100644 --- a/tests/unittests/tests-mtd/Makefile.include +++ b/tests/unittests/tests-mtd/Makefile.include @@ -1 +1,2 @@ USEMODULE += mtd +USEMODULE += vfs diff --git a/tests/unittests/tests-mtd/tests-mtd.c b/tests/unittests/tests-mtd/tests-mtd.c index e9c5950f8..36938bb9a 100644 --- a/tests/unittests/tests-mtd/tests-mtd.c +++ b/tests/unittests/tests-mtd/tests-mtd.c @@ -19,6 +19,12 @@ #include "mtd.h" #include "board.h" +#if MODULE_VFS +#include +#include +#include "vfs.h" +#endif + /* Define MTD_0 in board.h to use the board mtd if any */ #ifdef MTD_0 #define _dev MTD_0 @@ -237,6 +243,34 @@ static void test_mtd_write_read_flash(void) TEST_ASSERT_EQUAL_INT(0, memcmp(buf_empty, buf_read + sizeof(buf_expected), sizeof(buf_empty))); } +#if MODULE_VFS +static void test_mtd_vfs(void) +{ + int fd; + fd = vfs_bind(VFS_ANY_FD, O_RDWR, &mtd_vfs_ops, &_dev); + const char buf[] = "mnopqrst"; + uint8_t buf_empty[] = {0xff, 0xff, 0xff}; + char buf_read[sizeof(buf) + sizeof(buf_empty)]; + memset(buf_read, 0, sizeof(buf_read)); + + int ret = vfs_lseek(fd, sizeof(buf_empty), SEEK_SET); + TEST_ASSERT_EQUAL_INT(sizeof(buf_empty), ret); + ret = vfs_write(fd, buf, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), ret); + ret = vfs_lseek(fd, 0, SEEK_SET); + TEST_ASSERT_EQUAL_INT(0, ret); + ret = vfs_read(fd, buf_read, sizeof(buf_read)); + TEST_ASSERT_EQUAL_INT(sizeof(buf_read), ret); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf_empty, buf_read, sizeof(buf_empty))); + TEST_ASSERT_EQUAL_INT(0, memcmp(buf, buf_read + sizeof(buf_empty), sizeof(buf))); + + ret = vfs_lseek(fd, 0, SEEK_END); + TEST_ASSERT(ret > 0); + ret = vfs_write(fd, buf, sizeof(buf)); + /* Attempted to write past the device memory */ + TEST_ASSERT(ret < 0); +} +#endif Test *tests_mtd_tests(void) { @@ -246,6 +280,9 @@ Test *tests_mtd_tests(void) new_TestFixture(test_mtd_write_erase), new_TestFixture(test_mtd_write_read), new_TestFixture(test_mtd_write_read_flash), +#if MODULE_VFS + new_TestFixture(test_mtd_vfs), +#endif }; EMB_UNIT_TESTCALLER(mtd_tests, setup_teardown, setup_teardown, fixtures);