

18 changed files with 1104 additions and 1 deletions
@ -1,3 +1,7 @@
|
||||
ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE))) |
||||
USEMODULE += netdev_tap
|
||||
endif |
||||
|
||||
ifneq (,$(filter mtd,$(USEMODULE))) |
||||
USEMODULE += mtd_native
|
||||
endif |
||||
|
@ -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 */ |
||||
|
||||
/** @} */ |
@ -0,0 +1,5 @@
|
||||
MODULE := mtd_native
|
||||
|
||||
include $(RIOTBASE)/Makefile.base |
||||
|
||||
INCLUDES = $(NATIVEINCLUDES)
|
@ -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, |
||||
}; |
||||
|
||||
/** @} */ |
@ -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 */ |
@ -0,0 +1,3 @@
|
||||
MODULE = mtd
|
||||
|
||||
include $(RIOTBASE)/Makefile.base |
@ -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 */ |
@ -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; |
||||
} |
||||
} |
||||
|
||||
/** @} */ |
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base |
@ -0,0 +1,2 @@
|
||||
USEMODULE += mtd
|
||||
USEMODULE += vfs
|
@ -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()); |
||||
} |
||||
/** @} */ |
@ -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