feat(mtd): add NOR FLASH support

This commit is contained in:
alexcekay
2026-06-02 13:47:43 +02:00
committed by Alexander Lerach
parent b5f69ac4e8
commit 6f1f7035a5
5 changed files with 243 additions and 107 deletions

View File

@@ -49,6 +49,7 @@ typedef struct {
const px4_mtd_types_t type;
const char *path;
const uint32_t nblocks;
const bool bypass_ftl;
} px4_mtd_part_t;
typedef struct {

View File

@@ -33,6 +33,10 @@
#pragma once
#include <stdint.h>
#if defined(__PX4_NUTTX)
#include <nuttx/config.h>
#endif
typedef enum {
MFT = 0,
MTD = 1,
@@ -49,9 +53,16 @@ typedef struct {
I2C = 0,
SPI = 1,
ONCHIP = 2,
FLEXSPI = 3
FLEXSPI = 3,
} bus_type;
enum px4_spi_driver {
SPI_DRIVER_DEFAULT = 0, /* Ramtron (FRAM) */
#if defined(CONFIG_MTD_MX25L)
SPI_DRIVER_MX25L = 1, /* Macronix MX25L NOR flash */
#endif
} spi_driver;
uint32_t devid;
} px4_mft_device_t;

View File

@@ -44,6 +44,7 @@ typedef struct {
int *partition_block_counts;
int *partition_types;
const char **partition_names;
bool *partition_bypass_ftl;
struct mtd_dev_s **part_dev;
uint32_t devid;
unsigned n_partitions_current;

View File

@@ -60,8 +60,13 @@
extern "C" {
struct mtd_dev_s *ramtron_initialize(FAR struct spi_dev_s *dev);
#if defined(CONFIG_MTD_MX25L)
struct mtd_dev_s *mx25l_initialize_spi(FAR struct spi_dev_s *dev);
#endif
struct mtd_dev_s *mtd_partition(FAR struct mtd_dev_s *mtd,
off_t firstblock, off_t nblocks);
int register_mtddriver(FAR const char *path, FAR struct mtd_dev_s *mtd,
unsigned int mode, FAR void *priv);
}
static int num_instances = 0;
static int total_blocks = 0;
@@ -131,6 +136,34 @@ static int ramtron_attach(mtd_instance_s &instance)
#endif
}
#if defined(CONFIG_SPI)
template<struct mtd_dev_s *(*Initialize)(struct spi_dev_s *)>
static int spi_nor_attach(mtd_instance_s &instance, uint32_t spi_speed_hz)
{
struct spi_dev_s *spi = px4_spibus_initialize(px4_find_spi_bus(instance.devid));
if (spi == nullptr) {
PX4_ERR("failed to locate spi bus");
return -ENXIO;
}
SPI_LOCK(spi, true);
SPI_SETFREQUENCY(spi, spi_speed_hz);
SPI_SETBITS(spi, 8);
SPI_SETMODE(spi, SPIDEV_MODE0);
SPI_SELECT(spi, instance.devid, false);
SPI_LOCK(spi, false);
instance.mtd_dev = Initialize(spi);
if (instance.mtd_dev == nullptr) {
PX4_ERR("failed to initialize NOR flash driver");
return -EIO;
}
return 0;
}
#endif
static int at24xxx_attach(mtd_instance_s &instance)
{
@@ -340,17 +373,47 @@ memoryout:
goto memoryout;
}
instances[i]->partition_bypass_ftl = new bool[nparts];
if (instances[i]->partition_bypass_ftl == nullptr) {
goto memoryout;
}
for (uint32_t p = 0; p < nparts; p++) {
instances[i]->partition_block_counts[p] = mtd_list->entries[num_entry]->partd[p].nblocks;
instances[i]->partition_names[p] = mtd_list->entries[num_entry]->partd[p].path;
instances[i]->partition_types[p] = mtd_list->entries[num_entry]->partd[p].type;
instances[i]->partition_bypass_ftl[p] = mtd_list->entries[num_entry]->partd[p].bypass_ftl;
}
unsigned long blocksize;
unsigned long erasesize;
unsigned long neraseblocks;
unsigned int blkpererase;
unsigned int nblocks;
unsigned int partsize;
char blockname[32];
unsigned long offset;
unsigned part;
if (mtd_list->entries[num_entry]->device->bus_type == px4_mft_device_t::I2C) {
rv = at24xxx_attach(*instances[i]);
} else if (mtd_list->entries[num_entry]->device->bus_type == px4_mft_device_t::SPI) {
rv = ramtron_attach(*instances[i]);
switch (mtd_list->entries[num_entry]->device->spi_driver) {
#if defined(CONFIG_MTD_MX25L)
case px4_mft_device_t::SPI_DRIVER_MX25L:
rv = spi_nor_attach<mx25l_initialize_spi>(*instances[i], CONFIG_MX25L_SPIFREQUENCY);
break;
#endif
case px4_mft_device_t::SPI_DRIVER_DEFAULT:
default:
rv = ramtron_attach(*instances[i]);
break;
}
#if defined(HAS_FLEXSPI)
} else if (mtd_list->entries[num_entry]->device->bus_type == px4_mft_device_t::FLEXSPI) {
@@ -370,13 +433,6 @@ memoryout:
goto errout;
}
unsigned long blocksize;
unsigned long erasesize;
unsigned long neraseblocks;
unsigned int blkpererase;
unsigned int nblocks;
unsigned int partsize;
rv = px4_mtd_get_geometry(instances[i], &blocksize, &erasesize, &neraseblocks, &blkpererase, &nblocks, &partsize);
if (rv != 0) {
@@ -385,11 +441,6 @@ memoryout:
/* Now create MTD FLASH partitions */
char blockname[32];
unsigned long offset;
unsigned part;
for (offset = 0, part = 0; rv == 0 && part < nparts; offset += instances[i]->partition_block_counts[part], part++) {
/* Create the partition */
@@ -403,26 +454,43 @@ memoryout:
goto errout;
}
/* Initialize to provide an FTL block driver on the MTD FLASH interface */
if (instances[i]->partition_bypass_ftl[part]) {
/* When requested: register MTD device directly so the filesystem can drive
* erase blocks natively, bypassing the FTL. Only works for filesystem that
* can directly work with MTD devices */
#if !defined(CONFIG_BCH)
PX4_WARN("%s bypasses the FTL but CONFIG_BCH is not set: mtd commands will fail!",
instances[i]->partition_names[part]);
#endif
rv = register_mtddriver(instances[i]->partition_names[part], instances[i]->part_dev[part], 0755, NULL);
snprintf(blockname, sizeof(blockname), "/dev/mtdblock%d", total_blocks);
if (rv < 0) {
PX4_ERR("register_mtddriver %s failed: %d", instances[i]->partition_names[part], rv);
goto errout;
}
rv = ftl_initialize(total_blocks, instances[i]->part_dev[part]);
} else {
/* Initialize to provide an FTL block driver on the MTD FLASH interface */
if (rv < 0) {
PX4_ERR("ftl_initialize %s failed: %d", blockname, rv);
goto errout;
}
snprintf(blockname, sizeof(blockname), "/dev/mtdblock%d", total_blocks);
total_blocks++;
rv = ftl_initialize(total_blocks, instances[i]->part_dev[part]);
/* Now create a character device on the block device */
if (rv < 0) {
PX4_ERR("ftl_initialize %s failed: %d", blockname, rv);
goto errout;
}
rv = bchdev_register(blockname, instances[i]->partition_names[part], false);
total_blocks++;
if (rv < 0) {
PX4_ERR("bchdev_register %s failed: %d", instances[i]->partition_names[part], rv);
goto errout;
/* Now create a character device on the block device */
rv = bchdev_register(blockname, instances[i]->partition_names[part], false);
if (rv < 0) {
PX4_ERR("bchdev_register %s failed: %d", instances[i]->partition_names[part], rv);
goto errout;
}
}
instances[i]->n_partitions_current++;

View File

@@ -228,6 +228,54 @@ int mtd_readtest(const mtd_instance_s &instance)
return 0;
}
/* rwtest for bypass_ftl: talk to the MTD partition directly without BCH to avoid overhead. */
static ssize_t mtd_part_brwtest(FAR struct mtd_dev_s *part, ssize_t expected_size)
{
FAR struct mtd_geometry_s geo;
if (MTD_IOCTL(part, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo)) < 0) {
PX4_ERR("Failed to get partition geometry");
return -1;
}
FAR uint8_t *v = new uint8_t[geo.blocksize];
FAR uint8_t *v2 = new uint8_t[geo.blocksize];
off_t nblocks = expected_size / geo.blocksize;
ssize_t count = 0;
for (off_t block = 0; block < nblocks; block++) {
if (MTD_BREAD(part, block, 1, v) != 1) {
PX4_ERR("bread failed at block %ld", (long)block);
count = -1;
break;
}
if (MTD_BWRITE(part, block, 1, v) != 1) {
PX4_ERR("bwrite failed at block %ld", (long)block);
count = -1;
break;
}
if (MTD_BREAD(part, block, 1, v2) != 1) {
PX4_ERR("bread failed at block %ld", (long)block);
count = -1;
break;
}
if (memcmp(v, v2, geo.blocksize) != 0) {
PX4_ERR("memcmp failed at block %ld", (long)block);
count = -1;
break;
}
count += geo.blocksize;
}
delete[] v;
delete[] v2;
return count;
}
/*
rwtest is useful during startup to validate the device is
responding on the bus for both reads and writes. It reads data in
@@ -239,9 +287,6 @@ int mtd_rwtest(const mtd_instance_s &instance)
uint8_t v[32], v2[32];
for (uint8_t i = 0; i < instance.n_partitions_current; i++) {
ssize_t count = 0;
off_t offset = 0;
ssize_t expected_size = px4_mtd_get_partition_size(&instance, instance.partition_names[i]);
if (expected_size == 0) {
@@ -251,88 +296,98 @@ int mtd_rwtest(const mtd_instance_s &instance)
printf("rwtest %s testing %zd bytes\n", instance.partition_names[i], expected_size);
bool run = true;
while (run) {
int fd = open(instance.partition_names[i], O_RDWR);
if (fd == -1) {
PX4_ERR("Failed to open partition");
if (instance.partition_bypass_ftl[i]) {
if (mtd_part_brwtest(instance.part_dev[i], expected_size) != expected_size) {
return 1;
}
if (read(fd, v, sizeof(v)) != sizeof(v)) {
PX4_ERR("read failed");
close(fd);
} else {
ssize_t count = 0;
off_t offset = 0;
bool run = true;
while (run) {
int fd = open(instance.partition_names[i], O_RDWR);
if (fd == -1) {
PX4_ERR("Failed to open partition");
return 1;
}
if (read(fd, v, sizeof(v)) != sizeof(v)) {
PX4_ERR("read failed");
close(fd);
return 1;
}
count += sizeof(v);
if (lseek(fd, offset, SEEK_SET) != offset) {
PX4_ERR("seek failed");
close(fd);
return 1;
}
if (write(fd, v, sizeof(v)) != sizeof(v)) {
PX4_ERR("write failed");
close(fd);
return 1;
}
//sync and close to discard data from the Block Device buffer
if (OK != fsync(fd)) {
PX4_ERR("Failed to fsync");
close(fd);
return 1;
}
if (OK != close(fd)) {
PX4_ERR("Failed to close partition");
return 1;
}
fd = open(instance.partition_names[i], O_RDONLY);
if (fd == -1) {
PX4_ERR("Failed to open partition");
return 1;
}
if (lseek(fd, offset, SEEK_SET) != offset) {
PX4_ERR("seek failed");
close(fd);
return 1;
}
if (read(fd, v2, sizeof(v2)) != sizeof(v2)) {
PX4_ERR("read failed");
close(fd);
return 1;
}
if (OK != close(fd)) {
PX4_ERR("Failed to close partition");
return 1;
}
if (memcmp(v, v2, sizeof(v2)) != 0) {
PX4_ERR("memcmp failed");
return 1;
}
offset += sizeof(v);
if (count >= expected_size) {
run = false;
}
}
if (count != expected_size) {
PX4_ERR("Failed to read partition - got %zd/%zd bytes", count, expected_size);
return 1;
}
count += sizeof(v);
if (lseek(fd, offset, SEEK_SET) != offset) {
PX4_ERR("seek failed");
close(fd);
return 1;
}
if (write(fd, v, sizeof(v)) != sizeof(v)) {
PX4_ERR("write failed");
close(fd);
return 1;
}
//sync and close to discard data from the Block Device buffer
if (OK != fsync(fd)) {
PX4_ERR("Failed to fsync");
close(fd);
return 1;
}
if (OK != close(fd)) {
PX4_ERR("Failed to close partition");
return 1;
}
fd = open(instance.partition_names[i], O_RDONLY);
if (fd == -1) {
PX4_ERR("Failed to open partition");
return 1;
}
if (lseek(fd, offset, SEEK_SET) != offset) {
PX4_ERR("seek failed");
close(fd);
return 1;
}
if (read(fd, v2, sizeof(v2)) != sizeof(v2)) {
PX4_ERR("read failed");
close(fd);
return 1;
}
if (OK != close(fd)) {
PX4_ERR("Failed to close partition");
return 1;
}
if (memcmp(v, v2, sizeof(v2)) != 0) {
PX4_ERR("memcmp failed");
return 1;
}
offset += sizeof(v);
if (count >= expected_size) {
run = false;
}
}
if (count != expected_size) {
PX4_ERR("Failed to read partition - got %zd/%zd bytes", count, expected_size);
return 1;
}
}