From a52cfa26d63a5ae04765495f8d03ff1e5ebbbf33 Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Tue, 24 Jun 2025 13:05:03 +0200 Subject: [PATCH 1/2] drivers/mtd: add support for direct MTD access to raw flash This commit introduces the new option MTD_PARTITION_REGISTER that allows to register MTD partition with mtd_partition_register call as a standard device driver with support for open/read/write/seek calls. This brings to the possibility to directly access raw flash without the need for FTL layer (that causes unnecessary block erases) and BCH layer (usually used with buffers of erase block size, large memory consumption). The MTD access avoids any buffering (except for write page allocation if byte read/write is not available in the flash driver), but keeps the responsibility of flash erases on the user application. Therefore, write call only writes the given number of bytes, but doesn't check if erase is needed, wear leveling, false blocks and so on. Signed-off-by: Michal Lenc --- drivers/mtd/CMakeLists.txt | 4 + drivers/mtd/Kconfig | 19 ++ drivers/mtd/Make.defs | 4 + drivers/mtd/mtd_register.c | 516 +++++++++++++++++++++++++++++++++++++ include/nuttx/mtd/mtd.h | 24 ++ 5 files changed, 567 insertions(+) create mode 100644 drivers/mtd/mtd_register.c 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 * From e343b8ebf58e851127495b3b579c90852d5cba13 Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Thu, 26 Jun 2025 15:54:57 +0200 Subject: [PATCH 2/2] documentation: describe direct MTD driver access Signed-off-by: Michal Lenc --- .../components/drivers/special/mtd.rst | 21 +++++++++++++++++++ .../implementation/drivers_design.rst | 6 ++++++ 2 files changed, 27 insertions(+) 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.