mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #5624 from OTAkeys/pr/mtdi_flash
mtd: add a generic low level flash interface
This commit is contained in:
commit
1b2b5d9df9
@ -1,3 +1,7 @@
|
|||||||
ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE)))
|
ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE)))
|
||||||
USEMODULE += netdev_tap
|
USEMODULE += netdev_tap
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter mtd,$(USEMODULE)))
|
||||||
|
USEMODULE += mtd_native
|
||||||
|
endif
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
|
|
||||||
#include "board_internal.h"
|
#include "board_internal.h"
|
||||||
|
|
||||||
|
#ifdef MODULE_MTD
|
||||||
|
#include "mtd_native.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nothing to initialize at the moment.
|
* Nothing to initialize at the moment.
|
||||||
* Turns the red LED on and the green LED off.
|
* Turns the red LED on and the green LED off.
|
||||||
@ -28,3 +32,28 @@ void board_init(void)
|
|||||||
LED1_ON;
|
LED1_ON;
|
||||||
puts("RIOT native board initialized.");
|
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
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef MODULE_MTD
|
||||||
|
#include "mtd_native.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief LED handlers
|
* @brief LED handlers
|
||||||
* @{
|
* @{
|
||||||
@ -49,6 +53,13 @@ void _native_LED_RED_TOGGLE(void);
|
|||||||
#define LED1_TOGGLE (_native_LED_GREEN_TOGGLE())
|
#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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -6,6 +6,9 @@ DIRS += vfs
|
|||||||
ifneq (,$(filter netdev_tap,$(USEMODULE)))
|
ifneq (,$(filter netdev_tap,$(USEMODULE)))
|
||||||
DIRS += netdev_tap
|
DIRS += netdev_tap
|
||||||
endif
|
endif
|
||||||
|
ifneq (,$(filter mtd_native,$(USEMODULE)))
|
||||||
|
DIRS += mtd
|
||||||
|
endif
|
||||||
|
|
||||||
include $(RIOTBASE)/Makefile.base
|
include $(RIOTBASE)/Makefile.base
|
||||||
|
|
||||||
|
46
cpu/native/include/mtd_native.h
Normal file
46
cpu/native/include/mtd_native.h
Normal file
@ -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 <vincent@otakeys.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 */
|
||||||
|
|
||||||
|
/** @} */
|
@ -124,6 +124,10 @@ extern int (*real_unlink)(const char *);
|
|||||||
extern long int (*real_random)(void);
|
extern long int (*real_random)(void);
|
||||||
extern const char* (*real_gai_strerror)(int errcode);
|
extern const char* (*real_gai_strerror)(int errcode);
|
||||||
extern FILE* (*real_fopen)(const char *path, const char *mode);
|
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 mode_t (*real_umask)(mode_t cmask);
|
||||||
extern ssize_t (*real_writev)(int fildes, const struct iovec *iov, int iovcnt);
|
extern ssize_t (*real_writev)(int fildes, const struct iovec *iov, int iovcnt);
|
||||||
|
|
||||||
|
5
cpu/native/mtd/Makefile
Normal file
5
cpu/native/mtd/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
MODULE := mtd_native
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.base
|
||||||
|
|
||||||
|
INCLUDES = $(NATIVEINCLUDES)
|
154
cpu/native/mtd/mtd_native.c
Normal file
154
cpu/native/mtd/mtd_native.c
Normal file
@ -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 <vincent@otakeys.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @} */
|
@ -65,7 +65,16 @@ const char *_native_unix_socket_path = NULL;
|
|||||||
netdev_tap_params_t netdev_tap_params[NETDEV_TAP_MAX];
|
netdev_tap_params_t netdev_tap_params[NETDEV_TAP_MAX];
|
||||||
#endif
|
#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[] = {
|
static const struct option long_opts[] = {
|
||||||
{ "help", no_argument, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "id", required_argument, NULL, 'i' },
|
{ "id", required_argument, NULL, 'i' },
|
||||||
@ -75,6 +84,9 @@ static const struct option long_opts[] = {
|
|||||||
{ "stderr-noredirect", no_argument, NULL, 'E' },
|
{ "stderr-noredirect", no_argument, NULL, 'E' },
|
||||||
{ "stdout-pipe", no_argument, NULL, 'o' },
|
{ "stdout-pipe", no_argument, NULL, 'o' },
|
||||||
{ "uart-tty", required_argument, NULL, 'c' },
|
{ "uart-tty", required_argument, NULL, 'c' },
|
||||||
|
#ifdef MODULE_MTD_NATIVE
|
||||||
|
{ "mtd", required_argument, NULL, 'm' },
|
||||||
|
#endif
|
||||||
{ NULL, 0, NULL, '\0' },
|
{ NULL, 0, NULL, '\0' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -230,6 +242,11 @@ void usage_exit(int status)
|
|||||||
" -c <tty>, --uart-tty=<tty>\n"
|
" -c <tty>, --uart-tty=<tty>\n"
|
||||||
" specify TTY device for UART. This argument can be used multiple\n"
|
" specify TTY device for UART. This argument can be used multiple\n"
|
||||||
" times (up to UART_NUMOF)\n");
|
" times (up to UART_NUMOF)\n");
|
||||||
|
#ifdef MODULE_MTD_NATIVE
|
||||||
|
real_printf(
|
||||||
|
" -m <mtd>, --mtd=<mtd>\n"
|
||||||
|
" specify the file name of mtd emulated device\n");
|
||||||
|
#endif
|
||||||
real_exit(status);
|
real_exit(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +302,11 @@ __attribute__((constructor)) static void startup(int argc, char **argv)
|
|||||||
case 'c':
|
case 'c':
|
||||||
tty_uart_setup(uart++, optarg);
|
tty_uart_setup(uart++, optarg);
|
||||||
break;
|
break;
|
||||||
|
#ifdef MODULE_MTD_NATIVE
|
||||||
|
case 'm':
|
||||||
|
mtd0.fname = strndup(optarg, PATH_MAX - 1);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
usage_exit(EXIT_FAILURE);
|
usage_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,10 @@ int (*real_unlink)(const char *);
|
|||||||
long int (*real_random)(void);
|
long int (*real_random)(void);
|
||||||
const char* (*real_gai_strerror)(int errcode);
|
const char* (*real_gai_strerror)(int errcode);
|
||||||
FILE* (*real_fopen)(const char *path, const char *mode);
|
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);
|
mode_t (*real_umask)(mode_t cmask);
|
||||||
ssize_t (*real_writev)(int fildes, const struct iovec *iov, int iovcnt);
|
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_clearerr) = dlsym(RTLD_NEXT, "clearerr");
|
||||||
*(void **)(&real_umask) = dlsym(RTLD_NEXT, "umask");
|
*(void **)(&real_umask) = dlsym(RTLD_NEXT, "umask");
|
||||||
*(void **)(&real_writev) = dlsym(RTLD_NEXT, "writev");
|
*(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__
|
#ifdef __MACH__
|
||||||
#else
|
#else
|
||||||
*(void **)(&real_clock_gettime) = dlsym(RTLD_NEXT, "clock_gettime");
|
*(void **)(&real_clock_gettime) = dlsym(RTLD_NEXT, "clock_gettime");
|
||||||
|
243
drivers/include/mtd.h
Normal file
243
drivers/include/mtd.h
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* 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 <aurelien.gonce@altran.com>
|
||||||
|
* @author Vincent Dupont <vincent@otakeys.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MTD_H
|
||||||
|
#define MTD_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#if MODULE_VFS
|
||||||
|
#include "vfs.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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);
|
||||||
|
|
||||||
|
#if defined(MODULE_VFS) || defined(DOXYGEN)
|
||||||
|
/**
|
||||||
|
* @brief MTD driver for VFS
|
||||||
|
*/
|
||||||
|
extern const vfs_file_ops_t mtd_vfs_ops;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
#endif /* MTD_H */
|
3
drivers/mtd/Makefile
Normal file
3
drivers/mtd/Makefile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
MODULE = mtd
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.base
|
139
drivers/mtd/mtd-vfs.c
Normal file
139
drivers/mtd/mtd-vfs.c
Normal file
@ -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 <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#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 <joakim.nohlgard@eistec.se>
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 */
|
95
drivers/mtd/mtd.c
Normal file
95
drivers/mtd/mtd.c
Normal file
@ -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 <vincent@otakeys.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @} */
|
1
tests/unittests/tests-mtd/Makefile
Normal file
1
tests/unittests/tests-mtd/Makefile
Normal file
@ -0,0 +1 @@
|
|||||||
|
include $(RIOTBASE)/Makefile.base
|
2
tests/unittests/tests-mtd/Makefile.include
Normal file
2
tests/unittests/tests-mtd/Makefile.include
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
USEMODULE += mtd
|
||||||
|
USEMODULE += vfs
|
297
tests/unittests/tests-mtd/tests-mtd.c
Normal file
297
tests/unittests/tests-mtd/tests-mtd.c
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* 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 <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "embUnit.h"
|
||||||
|
|
||||||
|
#include "mtd.h"
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#if MODULE_VFS
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "vfs.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
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),
|
||||||
|
#if MODULE_VFS
|
||||||
|
new_TestFixture(test_mtd_vfs),
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
EMB_UNIT_TESTCALLER(mtd_tests, setup_teardown, setup_teardown, fixtures);
|
||||||
|
|
||||||
|
return (Test *)&mtd_tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tests_mtd(void)
|
||||||
|
{
|
||||||
|
TESTS_RUN(tests_mtd_tests());
|
||||||
|
}
|
||||||
|
/** @} */
|
37
tests/unittests/tests-mtd/tests-mtd.h
Normal file
37
tests/unittests/tests-mtd/tests-mtd.h
Normal file
@ -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 <vincent@otakeys.com>
|
||||||
|
*/
|
||||||
|
#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 */
|
||||||
|
/** @} */
|
Loading…
Reference in New Issue
Block a user