diff --git a/Documentation/components/drivers/special/mtd.rst b/Documentation/components/drivers/special/mtd.rst index d9eb3163129ce..0ca54593b46cd 100644 --- a/Documentation/components/drivers/special/mtd.rst +++ b/Documentation/components/drivers/special/mtd.rst @@ -49,8 +49,29 @@ See include/nuttx/mtd/mtd.h for additional information. #. Provide that instance to the initialization method of the higher level device driver. +- **Registering MTD Drivers**. MTD partition can be registered as a + standard character device driver. This allows to perform standard + ``open/read/write/ioctl`` calls directly on the MTD layer (and thus + directly on raw flash). This has some advantages compared to FTL + and BCH layers used to register flash device. FTL layer has to buffer + the entire erase page, because write operation leads to erase page + read, erase and then write the modified buffer. Apart from larger + RAM memory consumption, the data may be lost during power cut off, + because erase takes a considerable amount of time. On the other hand, + an access with FTL/BCH layer takes care of page erase if needed, thus + simplifying the application logic. + + Direct access through MTD layer can be faster and lower RAM consumption, + but the application has to take care of page erase before write if needed. + The erase can be performed by ioctl call ``MTDIOC_ERASESECTORS``. + The driver is registered with ``mtd_partition_register`` function call. + + Configuration option ``CONFIG_MTD_PARTITION_REGISTER`` has to be selected + to support this feature. + - **Examples**: ``drivers/mtd/m25px.c`` and ``drivers/mtd/ftl.c`` + EEPROM ====== diff --git a/Documentation/implementation/drivers_design.rst b/Documentation/implementation/drivers_design.rst index 4127bee6cb84e..4643644a2ff9b 100644 --- a/Documentation/implementation/drivers_design.rst +++ b/Documentation/implementation/drivers_design.rst @@ -57,6 +57,12 @@ on top of another MTD driver, it changes the apparent page size of the FLASH to ``drivers/mtd/mtd_partitions.c`` can be used to break up a large FLASH into separate, independent partitions, each of which looks like another MTD driver. +It is possible to register the partition as a character device driver. This +provides a direct access to the raw flash, but doesn't ensure any logic +needed to operate the flash (page erase prior to write, wear leveling, +fault block detection). The driver is implemented in +``drivers/mtd/mtd_register.c``. + ``drivers/mtd/ftl.c`` is also interesting. FTL stands for FLASH Translation Layer. The FTL driver is an MTD driver that when layered on top of another MTD driver, converts the MTD driver to a block driver. The permutations are endless. diff --git a/drivers/mtd/CMakeLists.txt b/drivers/mtd/CMakeLists.txt index c50988e663e0f..d03111772632a 100644 --- a/drivers/mtd/CMakeLists.txt +++ b/drivers/mtd/CMakeLists.txt @@ -33,6 +33,10 @@ if(CONFIG_MTD) list(APPEND SRCS mtd_partition.c) endif() + if(CONFIG_MTD_PARTITION_REGISTER) + list(APPEND SRCS mtd_register.c) + endif() + if(CONFIG_MTD_SECT512) list(APPEND SRCS sector512.c) endif() diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 38a364f58ee7a..20b0ec67359ea 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -40,6 +40,25 @@ config MTD_PARTITION managing the sub-region of flash beginning at 'offset' (in blocks) and of size 'nblocks' on the device specified by 'mtd'. +config MTD_PARTITION_REGISTER + bool "Register MTD partition as a driver" + default n + depends on MTD_PARTITION + ---help--- + This option allows to register created MTD partition as a device + driver and perform standard open/close/read/write/ioctl calls + directly on the MTD layer. This has some pros and cons compared + to the access through FTL or/and BCH layers. FTL layer buffers + the erase page, thus increasing RAM memory consumption. It also + means the data may be lost during power cutoff as flash write + means read, erase, write and erase operation may take time. On the + other hand, the access through FTL/BCH takes care of page erase + if needed, thus is easier to manage from an application. + + Direct access to MTD layer can be faster and lower RAM memory + consumption, but the application has to take care of page erase + before writes if needed. This can be done with ioctl calls. + config FTL_WRITEBUFFER bool "Enable write buffering in the FTL layer" default n diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index e357e8c84cea1..d843c0742308b 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -41,6 +41,10 @@ ifeq ($(CONFIG_MTD_PARTITION),y) CSRCS += mtd_partition.c endif +ifeq ($(CONFIG_MTD_PARTITION_REGISTER),y) +CSRCS += mtd_register.c +endif + ifeq ($(CONFIG_MTD_SECT512),y) CSRCS += sector512.c endif diff --git a/drivers/mtd/mtd_register.c b/drivers/mtd/mtd_register.c new file mode 100644 index 0000000000000..c82747be92b5b --- /dev/null +++ b/drivers/mtd/mtd_register.c @@ -0,0 +1,516 @@ +/**************************************************************************** + * drivers/mtd/mtd_register.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef CONFIG_FS_PROCFS +# include +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct mtd_struct_s +{ + FAR struct mtd_dev_s *mtd; /* MTD layer representing flash partition */ + uint32_t blksize; /* Size of one write page */ + uint16_t refs; /* Number of references */ + uint8_t erasestate; /* Erase state of flash partition */ + size_t size; /* Size of the partition in bytes */ + mutex_t lock; /* Lock for the driver access */ + bool readonly; /* True if the partition supposed to be + * read only. This will block write access. + */ +}; + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int mtd_open(FAR struct file *filep); +static int mtd_close(FAR struct file *filep); +static off_t mtd_seek(FAR struct file *filep, off_t offset, int whence); +static ssize_t mtd_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t mtd_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int mtd_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_fops = +{ + mtd_open, /* open */ + mtd_close, /* close */ + mtd_read, /* read */ + mtd_write, /* write */ + mtd_seek, /* seek */ + mtd_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mtd_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int mtd_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *mtd; + int ret = OK; + + DEBUGASSERT(inode->i_private); + mtd = inode->i_private; + + /* Increment the reference count */ + + ret = nxmutex_lock(&mtd->lock); + if (ret < 0) + { + return ret; + } + + if (mtd->refs == 255) + { + ret = -EMFILE; + } + else + { + mtd->refs++; + } + + nxmutex_unlock(&mtd->lock); + return ret; +} + +/**************************************************************************** + * Name: mtd_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int mtd_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *mtd; + int ret = OK; + + DEBUGASSERT(inode->i_private); + mtd = inode->i_private; + + /* Get exclusive access */ + + ret = nxmutex_lock(&mtd->lock); + if (ret < 0) + { + return ret; + } + + /* Decrement the reference count */ + + if (mtd->refs == 0) + { + ret = -EIO; + } + else + { + mtd->refs--; + } + + nxmutex_unlock(&mtd->lock); + return ret; +} + +/**************************************************************************** + * Name: mtd_read + * + * Description: Read the specified number of bytes. + * + ****************************************************************************/ + +static ssize_t mtd_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *dev; + FAR char *buf; + off_t startblock; + off_t offset; + size_t nblocks; + ssize_t ret; + + DEBUGASSERT(inode->i_private); + dev = inode->i_private; + + if (len < 1) + { + return 0; + } + + ret = nxmutex_lock(&dev->lock); + if (ret < 0) + { + return ret; + } + + if (filep->f_pos >= dev->size) + { + /* End of file */ + + nxmutex_unlock(&dev->lock); + return 0; + } + + ret = MTD_READ(dev->mtd, filep->f_pos, len, (FAR uint8_t *)buffer); + if (ret < 0) + { + if (ret == -ENOSYS) + { + /* Byte read not supported, use block read */ + + startblock = filep->f_pos / dev->blksize; + nblocks = (len / dev->blksize) + 1; + buf = kmm_zalloc(nblocks * dev->blksize); + if (buf) + { + offset = filep->f_pos - (startblock * dev->blksize); + ret = MTD_BREAD(dev->mtd, startblock, nblocks, + (FAR uint8_t *)buffer); + ret *= dev->blksize; + if (ret >= offset) + { + memcpy(buffer, buf + offset, len); + ret -= offset; + filep->f_pos += ret > len ? len : ret; + } + else + { + /* We haven't read enough bytes to obtain the desired + * offset, return EOF. + */ + + ret = 0; + } + + kmm_free(buf); + } + else + { + ret = -ENOMEM; + } + } + } + else + { + filep->f_pos += ret; + } + + nxmutex_unlock(&dev->lock); + return ret; +} + +/**************************************************************************** + * Name: mtd_write + * + * Description: Read the specified number of bytes + * + ****************************************************************************/ + +static ssize_t mtd_write(FAR struct file *filep, FAR const char *buffer, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *dev; + off_t offset; + off_t startblock; + size_t nblocks; + FAR char *buf; + ssize_t ret = -EACCES; + + DEBUGASSERT(inode->i_private); + dev = inode->i_private; + + if (dev->readonly) + { + return -EACCES; + } + + if (len < 1) + { + return 0; + } + + ret = nxmutex_lock(&dev->lock); + if (ret < 0) + { + return ret; + } + + if (filep->f_pos >= dev->size) + { + nxmutex_unlock(&dev->lock); + return -EFBIG; + } + +#ifdef CONFIG_MTD_BYTE_WRITE + ret = MTD_WRITE(dev->mtd, filep->f_pos, len, (FAR uint8_t *)buffer); + if (ret == -ENOSYS) +#endif + { + startblock = filep->f_pos / dev->blksize; + nblocks = (len / dev->blksize) + 1; + buf = kmm_zalloc(nblocks * dev->blksize); + if (buf == NULL) + { + nxmutex_unlock(&dev->lock); + return -ENOMEM; + } + + memset(buf, dev->erasestate, nblocks * dev->blksize); + + offset = filep->f_pos - (startblock * dev->blksize); + memcpy(buf + offset, buffer, len); + + ret = MTD_BWRITE(dev->mtd, startblock, nblocks, (FAR uint8_t *)buf); + ret *= dev->blksize; + if (ret >= offset) + { + ret -= offset; + if (ret > len) + { + ret = len; + } + } + + kmm_free(buf); + } + + if (ret > 0) + { + filep->f_pos += ret; + } + + nxmutex_unlock(&dev->lock); + return ret; +} + +/**************************************************************************** + * Name: mtd_seek + * + * Description: Seek to the specific offset. + * + ****************************************************************************/ + +static off_t mtd_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *dev; + off_t newpos; + off_t ret; + + DEBUGASSERT(inode->i_private); + + dev = inode->i_private; + ret = nxmutex_lock(&dev->lock); + if (ret < 0) + { + return ret; + } + + /* Determine the new, requested file position */ + + switch (whence) + { + case SEEK_CUR: + newpos = filep->f_pos + offset; + break; + + case SEEK_SET: + newpos = offset; + break; + + case SEEK_END: + newpos = (off_t)dev->size + offset; + break; + + default: + + /* Return EINVAL if the whence argument is invalid */ + + nxmutex_unlock(&dev->lock); + return -EINVAL; + } + + /* Opengroup.org: + * + * "The lseek() function shall allow the file offset to be set beyond the + * end of the existing data in the file. If data is later written at this + * point, subsequent reads of data in the gap shall return bytes with the + * value 0 until data is actually written into the gap." + * + * We can conform to the first part, but not the second. But return -EINVAL + * if: + * + * "...the resulting file offset would be negative for a regular file, + * block special file, or directory." + */ + + if (newpos >= 0) + { + filep->f_pos = newpos; + ret = newpos; + } + else + { + ret = -EINVAL; + } + + nxmutex_unlock(&dev->lock); + return ret; +} + +/**************************************************************************** + * Name: mtd_ioctl + * + * Description: + * Handle IOCTL commands + * + ****************************************************************************/ + +static int mtd_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mtd_struct_s *dev; + int ret = -ENOTTY; + + DEBUGASSERT(inode->i_private); + dev = inode->i_private; + + /* Process the call according to the command */ + + switch (cmd) + { + default: + { + /* Currently there are no specific IOCTL calls, let the MTD + * partition handle the common ones. + */ + + ret = MTD_IOCTL(dev->mtd, cmd, arg); + } + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int mtd_partition_register(FAR struct mtd_dev_s *mtd, FAR const char *path, + bool readonly) +{ + int ret; + struct mtd_struct_s *dev; + struct mtd_geometry_s geo; + + if (mtd == NULL) + { + return -EINVAL; + } + + ret = mtd->ioctl(mtd, MTDIOC_GEOMETRY, (unsigned long)(uintptr_t)&geo); + if (ret < 0) + { + ferr("ERROR: MTDIOC_GEOMETRY ioctl failed: %d\n", ret); + return ret; + } + + dev = kmm_zalloc(sizeof(struct mtd_struct_s)); + if (dev == NULL) + { + return -ENOMEM; + } + + dev->mtd = mtd; + ret = mtd->ioctl(mtd, MTDIOC_ERASESTATE, + (unsigned long)(uintptr_t)&dev->erasestate); + if (ret < 0) + { + ferr("ERROR: MTDIOC_ERASESTATE ioctl failed: %d\n", ret); + kmm_free(dev); + return ret; + } + + dev->size = geo.erasesize * geo.neraseblocks; + dev->blksize = geo.blocksize; + dev->readonly = readonly; + nxmutex_init(&dev->lock); + + ret = register_driver(path, &g_fops, 0666, (FAR void *)dev); + if (ret < 0) + { + ferr("ERROR: register_driver failed: %d\n", ret); + kmm_free(dev); + } + + return ret; +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index 171f2e583bf61..9230b4f2483cf 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -259,6 +259,30 @@ extern "C" FAR struct mtd_dev_s *mtd_partition(FAR struct mtd_dev_s *mtd, off_t firstblock, off_t nblocks); +/**************************************************************************** + * Name: mtd_partition_register + * + * Description: + * Registers an MTD partition as a device driver with standard + * open/read/write/ioctl call support. This provides the direct access + * to the raw flash without the need to uzilize FTL or/and BCH layers. + * + * + * Input Parameters: + * mtd - The MTD device + * path - Path where the driver should be registered + * readonly * True if the underlying partition is read only. + * + * Returned Value: + * OK on success, negated errno on error. + * + ****************************************************************************/ + +#ifdef CONFIG_MTD_PARTITION_REGISTER +int mtd_partition_register(FAR struct mtd_dev_s *mtd, FAR const char *path, + bool readonly); +#endif + /**************************************************************************** * Name: mtd_setpartitionname *