Skip to content

Conversation

@michallenc
Copy link
Contributor

Summary

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.

Impact

None on current implementation. The newly added code is compiled only if MTD_PARTITION_REGISTER is configured.

The goal is to provide the direct access to raw flash. If the application needs to write directly to flash (usually NOR), it can utilize FTL and BCH layer. But these two have some disadvantages. FTL layer performs erase before every write, thus it has to read the entire erase page, erase it and write it with changes. This means the entire erase page has to be buffered (can be several kilo bytes of RAM memory) and that flash wear level is much higher than it has to be. The FTL layer can be used with BCH layer with write buffering enabled. This limits flash wear as erase/write is performed only when different erase page is accessed (or during flush), but brings another erase page size large buffer, thus yet again increases memory consumption. Last but not least, the write operation performed by FTL takes significant amount of time because of page erase, meaning the data may be lost during power cut off.

FTL and BCH combination is a good option if you have a lot of RAM and want simple application that doesn't care about whether it has to erase the page before writing to it. The newly introduced option brings the possibility of less memory consumption and potentially faster writes, but imposes further requirements on the application using it.

The access is similar to the one provided by Linux with MTD layer.

Testing

Tested with SAMv7 custom board on w25q NOR flash and SAMV71-Xult evaluation kit on s25fl1 NOR flash. The behavior was tested with a custom logging application that reads/writes/erases the partitions and the following code. The board registers the partition to /dev/mtd devpath and performs write and seek operations. The partition was erased prior to writes with flash_eraseall /dev/mtd

#include <nuttx/config.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, FAR char *argv[])
{
  int ret = 0;

  int fd = open("/dev/mtd", O_RDWR);
  if (fd < 0) {
    printf("failed to open %d", errno);
    return -1;
  }

  char buf[4] = {0x33, 0x22, 0x00, 0xab};
  ret = write(fd, buf, sizeof buf);
  if (ret < 0) {
    printf("failed to write: %d\n", errno);
  }

  lseek(fd, 32, SEEK_SET);
  ret = write(fd, buf, sizeof buf);
  if (ret < 0) {
    printf("failed to write: %d\n", errno);
  }

  buf[0] = 0xff;
  ret = write(fd, buf, sizeof buf);
  if (ret < 0) {
    printf("failed to write: %d\n", errno);
  }

  lseek(fd, -4, SEEK_CUR);
  buf[0] &= ~(1 << 7);
  ret = write(fd, &buf[0], 1);
  if (ret < 0) {
    printf("failed to write: %d\n", errno);
  }

  close(fd);
  return 0;
}

The subsequent hexdump output is as expected:

nsh> hexdump /dev/mtd count=128
/dev/mtd at 00000000:
0000: 33 22 00 ab ff ff ff ff ff ff ff ff ff ff ff ff 3"..............
0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0020: 33 22 00 ab 7f 22 00 ab ff ff ff ff ff ff ff ff 3"..."..........
0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0060: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................

@github-actions github-actions bot added Area: Drivers Drivers issues Size: L The size of the change in this PR is large labels Jun 25, 2025
@michallenc
Copy link
Contributor Author

michallenc commented Jun 25, 2025

The example configuration for the test on samv71-xult and s25fl1 is in #16625

*
****************************************************************************/

static off_t mtd_seek(FAR struct file *filep, off_t offset, int whence)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reuse bch code in this driver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean call the same function? There is a different lock and different calculation for SEEK_END in bch driver, it has to calculate from sectors. Otherwise the code for seek is basically just a copy paste.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, the flash erase size is very larger than the flash program size, so it's possible to reuse BCH but skip FTL which could avoid the erase buffer at FTL layer, but program buffer at BCH layer still exist.

@acassis
Copy link
Contributor

acassis commented Jun 26, 2025

@michallenc please include a Documentation/ about this feature and include this test code as an usage example.

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 <michallenc@seznam.cz>
@github-actions github-actions bot added the Area: Documentation Improvements or additions to documentation label Jun 26, 2025
@michallenc
Copy link
Contributor Author

@michallenc please include a Documentation/ about this feature and include this test code as an usage example.

@acassis I have added documentation entry, not quite sure where to put the test code. It is just a quick test sample, nothing interesting as it just calls standard POSIX interface, the same as BCH layer. More interesting is how to register it, which will be in #16625

Signed-off-by: Michal Lenc <michallenc@seznam.cz>
@acassis
Copy link
Contributor

acassis commented Jun 26, 2025

@michallenc please include a Documentation/ about this feature and include this test code as an usage example.

@acassis I have added documentation entry, not quite sure where to put the test code. It is just a quick test sample, nothing interesting as it just calls standard POSIX interface, the same as BCH layer. More interesting is how to register it, which will be in #16625

Agree! Maybe we could include it latter as a mtd partition test inside apps/testing/

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Jun 26, 2025

Summary

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).

@michallenc This approach has one major limitation: user can't access flash indirectly through BCH/FTL or directly at the same time.

Another approach is skipping the buffering at runtime if user pass some special flag(e.g. O_DIRECT) to open, so the ordinary application still work as before, but the special designed application could save the memory.

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.

Actually, we found the similar issue internally, and decide to take the second approach to reduce the memory consumption and improve the compatibility at the same time.

buf = kmm_zalloc(nblocks * dev->blksize);
if (buf == NULL)
{
nxmutex_unlock(&dev->lock);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no MTD_WRITE interface but only the MTD_BWRITE interface, your modification applies for the space of page size to complete the page size aligned write (of course, write page size is very small), I believe this is repetitive with the bch logic. We should be able to reuse the logic of bch directly.

@jingfei195887
Copy link
Contributor

Actually, we found the similar issue internally, and decide to take the second approach to reduce the memory consumption and improve the compatibility at the same time.

I recently optimized the BCH, BCH more interface is exposed, externally to make a better, I think the new driver layer can be directly call BCH library of the read/write/open/close/seek to achieve, because the code is very similar, we don't need to write it again. I will submit this change tomorrow for your reference

@jingfei195887
Copy link
Contributor

@michallenc @xiaoxiang781216 What might require further consideration is:

  1. Whether MTD_EARSE needs to be added, for example, to perform MTD_EARSE at addresses aligned with erase sector size, so as not to occupy additional ram space. It is more useful for log-type applications when recording in sequence. We don't have to erase large blocks in advance
  2. Due to the lack of bad block handling in FTL, this implementation seems to be an implementation of NorFlash or MRAM/RRAM. Nand Flash will no longer be applicable to this implementation, which requires our discussion


startblock = filep->f_pos / dev->blksize;
nblocks = (len / dev->blksize) + 1;
buf = kmm_zalloc(nblocks * dev->blksize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question: why do we need zero allocated memory?


dev->size = geo.erasesize * geo.neraseblocks;
dev->blksize = geo.blocksize;
dev->readonly = readonly;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we let the open flag decide this driver is readonly or writeonly ?

open(/dev/mtd_partition_dev, O_RDOK); or open(/dev/mtd_partition_dev, O_WRONLY);

Copy link
Contributor Author

@michallenc michallenc Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reused this from bchdev_register. It has an advantage that you can limit the access from board level. You basically say "this partition is read only" to be sure an application won't overwrite your data. It has a big hole though, application can still erase the entire partition...

/* Byte read not supported, use block read */

startblock = filep->f_pos / dev->blksize;
nblocks = (len / dev->blksize) + 1;
Copy link
Contributor

@jingfei195887 jingfei195887 Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

off_t end_pos = (filep->f_pos + len) / dev->blksize;
nblocks = endblock - startblock + 1;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A large amount of memory might be allocated here :
buf = kmm_zalloc(nblocks * dev->blksize);

but in fact, we only need to dynamically apply for one blksize:

  1. Possible misalignment handling of the starting position
  2. Addresses that are aligned in the middle do not require memory processing of this blksize size
  3. Possible misalignment at the end

This is exactly the job of bch

if (buf)
{
offset = filep->f_pos - (startblock * dev->blksize);
ret = MTD_BREAD(dev->mtd, startblock, nblocks,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MTD_BREAD(dev->mtd, startblock, nblocks, (FAR uint8_t *)buffer) should changed to :
MTD_BREAD(dev->mtd, startblock, nblocks, (FAR uint8_t *)buf) ?

#endif
{
startblock = filep->f_pos / dev->blksize;
nblocks = (len / dev->blksize) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

/* Byte read not supported, use block read */

startblock = filep->f_pos / dev->blksize;
nblocks = (len / dev->blksize) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A large amount of memory might be allocated here :
buf = kmm_zalloc(nblocks * dev->blksize);

but in fact, we only need to dynamically apply for one blksize:

  1. Possible misalignment handling of the starting position
  2. Addresses that are aligned in the middle do not require memory processing of this blksize size
  3. Possible misalignment at the end

This is exactly the job of bch

@michallenc michallenc marked this pull request as draft June 27, 2025 06:17
@michallenc
Copy link
Contributor Author

@xiaoxiang781216 @jingfei195887 Thanks for the feedback. I have converted this to draft for now, will wait for your changes in BCH layer. Regarding some comments.

Another approach is skipping the buffering at runtime if user pass some special flag(e.g. O_DIRECT) to open, so the ordinary application still work as before, but the special designed application could save the memory.

This seems like a good option if we manage to use BCH layer for this. It would solve the need to initialize separate drivers. But this would require some changes in FTL layer as well, because even with write buffer disabled, it still reads the entire erase page, erase it, and then write. If we want to skip it, FTL would basically just had to call NOR flash specific write/read function and skip all of its own logic.

Also, and I haven't seen your changes yet, BCH layer is sector oriented, it doesn't utilize byte access now, so we have to read/write the entire page. If I would just call BCH functions from this new MTD driver, then I could call them only if byte access is not possible. But that would mean a separate driver.

Whether MTD_EARSE needs to be added, for example, to perform MTD_EARSE at addresses aligned with erase sector size, so as not to occupy additional ram space. It is more useful for log-type applications when recording in sequence. We don't have to erase large blocks in advance

I am not against the option to perform erases from BCH layer, if that's what you mean, but wouldn't do it automatically for two reasons. It takes some time to erase a page and sometimes I need to write the data fast because of power cut off. It is possible to ensure your board has enough reserve to write one write page, but not if it does erase before. And it might not be always needed, I actually have a logging library with sequence logging, but still erase one block in advance, because it is a rotation log, so I use it to detect start/end of the log.

Due to the lack of bad block handling in FTL, this implementation seems to be an implementation of NorFlash or MRAM/RRAM. Nand Flash will no longer be applicable to this implementation, which requires our discussion

Is it actually possible to use Nand Flash with BCH/FTL now? I thought the implementation is not suitable even now.

@jingfei195887
Copy link
Contributor

jingfei195887 commented Jun 27, 2025

@michallenc @xiaoxiang781216
#16642
This patch exposes more BCH interfaces for other drivers to reference.

@xiaoxiang781216
Copy link
Contributor

@xiaoxiang781216 @jingfei195887 Thanks for the feedback. I have converted this to draft for now, will wait for your changes in BCH layer. Regarding some comments.

Thanks, let's align our effort to come a best solution.

Another approach is skipping the buffering at runtime if user pass some special flag(e.g. O_DIRECT) to open, so the ordinary application still work as before, but the special designed application could save the memory.

This seems like a good option if we manage to use BCH layer for this. It would solve the need to initialize separate drivers. But this would require some changes in FTL layer as well, because even with write buffer disabled, it still reads the entire erase page, erase it, and then write. If we want to skip it, FTL would basically just had to call NOR flash specific write/read function and skip all of its own logic.

Yes, you are right. BCH/FTL need some modification to achieve this goal.

Also, and I haven't seen your changes yet, BCH layer is sector oriented, it doesn't utilize byte access now, so we have to read/write the entire page. If I would just call BCH functions from this new MTD driver, then I could call them only if byte access is not possible. But that would mean a separate driver.

@jingfei195887 will provide the related change in the next couple day.

Whether MTD_EARSE needs to be added, for example, to perform MTD_EARSE at addresses aligned with erase sector size, so as not to occupy additional ram space. It is more useful for log-type applications when recording in sequence. We don't have to erase large blocks in advance

I am not against the option to perform erases from BCH layer, if that's what you mean, but wouldn't do it automatically for two reasons. It takes some time to erase a page and sometimes I need to write the data fast because of power cut off. It is possible to ensure your board has enough reserve to write one write page, but not if it does erase before. And it might not be always needed, I actually have a logging library with sequence logging, but still erase one block in advance, because it is a rotation log, so I use it to detect start/end of the log.

we can use open flag or ioctl to control the behavior.

Due to the lack of bad block handling in FTL, this implementation seems to be an implementation of NorFlash or MRAM/RRAM. Nand Flash will no longer be applicable to this implementation, which requires our discussion

Is it actually possible to use Nand Flash with BCH/FTL now? I thought the implementation is not suitable even now.

we use BCH/FTL for NAND flash too. Of course, ECC need be handled by lowerhalf MTD driver.

@michallenc
Copy link
Contributor Author

@xiaoxiang781216 @jingfei195887

Looking at the code and your modifications, I basically see two options how to use BCH in direct access to MTD without buffering.

Separate driver that uses BCH functions:

  • we would need to call bchlib_write and similar functions.
  • this would require some even larger modifications in BCH layer, because we would need to initialize BCH layer from MTD layer as these functions still use bch specific stuff (buffer, lock, etc) and it calls write function from FTL (though this can be changed to any block based driver)
  • bchlib_write works better than my write in the mtd driver, but I don't think it is necessary to read the sector, modifying it and then passing it to write. This is done in BCH layer because it expects FTL layer to read entire erase page, erase it and then write it back modified. But as direct access would not erase the page, we can just fill the buffer with erase state and write it.

But better option seems the changes @xiaoxiang781216 suggested: change the behavior of BCH/FTL layer to support non buffered access without page erase before write if some specific flag is passed during open operation. This should be pretty straightforward, just some if statements. The bigger issue is how to support byte access as FTL (or any other block based driver) write function expects to get blocks to write, not bytes.

@jingfei195887
Copy link
Contributor

@michallenc @xiaoxiang781216 I believe that the byte access flash of FTL you mentioned is what I am currently working to implement. Please revisit issue #16642, as some new patches related to FTL have been introduced.
I have attempted to incorporate file operations within FTL. When an MTD node is opened in O_DIRECT mode, the previous process of reading back the entire sector before writing with FTL will no longer be utilized. Instead, a streamlined writing process will be adopted: erasure will occur when the size aligns with the erase sector, and sequential writing will be employed prior to that.

Let us compare the advantages and disadvantages of the two implementations:

  1. Separate MTD driver
  2. change the behavior of BCH/FTL layer

@michallenc
Copy link
Contributor Author

michallenc commented Jun 30, 2025

@michallenc @xiaoxiang781216 I believe that the byte access flash of FTL you mentioned is what I am currently working to implement. Please revisit issue #16642, as some new patches related to FTL have been introduced. I have attempted to incorporate file operations within FTL. When an MTD node is opened in O_DIRECT mode, the previous process of reading back the entire sector before writing with FTL will no longer be utilized. Instead, a streamlined writing process will be adopted: erasure will occur when the size aligns with the erase sector, and sequential writing will be employed prior to that.

Let us compare the advantages and disadvantages of the two implementations:

1. Separate MTD driver

2. change the behavior of BCH/FTL layer

I think this is going in a right direction. Looking at the recent changes (just a short look, I am on a children summer camp this week and don't have much time), the only reservation I have is this part in ftl_flush_direct.

if (offset == 0)
  {
    ret = ftl_mtd_erase(dev, starteraseblock);
    if (ret < 0)
      {
        return ret;
      }
  }

While this is true for some (maybe most) use cases, it might not be for others. And this basically requires the sequential access inside an erase page (you can't write first write page after the others, otherwise the changes are lost). This is a reasonable requirement for application access anyway, but maybe I would keep it configurable, use some sort of ioctl that would toggle it?

Just to clarify the major reason of this PR and separate MTD driver, I was trying to limit RAM memory usage. Our design has quite large number of partition on NOR flash (several for logging, some for firmware update, etc) and the buffers alone consumed about 60 kBs, almost 20 % of available RAM space. So the main reason was to avoid buffering and limit unnecessary flash erases if possible. I think your BCH/FTL changes will do that once finished, so I am completely fine with it. And it is even better if it is (at least partially) ready for NAND flashes.

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Jul 7, 2025

@michallenc #16642 is ready for review, could you look at whether the implementation match your case?
Note: both erase and buffer could be skipped by special open flag.

@jerpelea jerpelea changed the title [FEATURE] drivers/mtd: add support for direct MTD access to raw flash drivers/mtd: add support for direct MTD access to raw flash Jul 7, 2025
@michallenc
Copy link
Contributor Author

Closed as obsolete with #16642 merged

@michallenc michallenc closed this Jul 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Documentation Improvements or additions to documentation Area: Drivers Drivers issues Size: L The size of the change in this PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants