blob: 44f9c9736ab41fd7d0db51c6358b5d1c68bd9d80 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/rp2040/rp2040_flash_mtd.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.
*
****************************************************************************/
/****************************************************************************
* This code implements a Sector Mapped Allocation for Really Tiny (SMART)
* filesystem in the RP2040 flash memory chip. It uses the space not
* otherwise used by the NuttX binary and supports both read and write
* access.
*
* There initial contents of this filesystem may be configured when a NuttX
* binary is built (using tools/rp2040/make_flash_fs.c), but any changes
* subsequently written to the filesystem will persist over re-boots of the
* RP2040.
*
* Note: Although read access to any data stored in this filesystem is very
* rapid; because of how the RP2040's flash access routines work with
* the normal execute-in-place (XIP) access to that chip, no code can
* access flash in the normal manner when this filesystem is erasing
* or writing blocks. This means that interrupts must be disabled
* during any such access. The main issue with this is that any
* application which requires precise timing or rapid response may
* be negatively impacted by such operations.
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/mutex.h>
#include <nuttx/spinlock.h>
#include "rp2040_flash_mtd.h"
#include "rp2040_rom.h"
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define XIP_BASE 0x10000000
#define XIP_NOCACHE_NOALLOC_BASE 0x13000000
#define FLASH_BLOCK_ERASE_CMD 0x20
#define BOOT_2_SIZE 256
#define FLASH_START_OFFSET (rp2040_smart_flash_start - (uint8_t *)XIP_BASE)
#define FLASH_END_OFFSET (rp2040_smart_flash_end - (uint8_t *)XIP_BASE)
#define FLASH_START_READ (rp2040_smart_flash_start + 0x03000000)
/* Note: There is some ambiguity in terminology when it comes to flash.
* Some call the chunk that can be erased a sector where others
* call that a block.
*
* Some call the chunk that can be written a sector where others
* call that a page.
*/
/* Blocks are the smallest unit that can be erased */
#define FLASH_BLOCK_SIZE (4 * 1024)
#define FLASH_BLOCK_COUNT (CONFIG_RP2040_FLASH_LENGTH - FLASH_START_OFFSET)\
/ FLASH_BLOCK_SIZE
/* Sectors are the smallest unit that can be written */
#define FLASH_SECTOR_SIZE 256
#define FLASH_SECTOR_COUNT (CONFIG_RP2040_FLASH_LENGTH - FLASH_START_OFFSET)\
/ FLASH_SECTOR_SIZE
/****************************************************************************
* Private Types
****************************************************************************/
typedef struct rp2040_flash_dev_s
{
struct mtd_dev_s mtd_dev; /* Embedded mdt_dev structure */
mutex_t lock; /* file access serialization */
uint32_t boot_2[BOOT_2_SIZE / 4]; /* RAM copy of boot_2 */
} rp2040_flash_dev_t;
typedef void (*connect_internal_flash_f)(void);
typedef void (*flash_exit_xip_f)(void);
typedef void (*flash_range_erase_f)(uint32_t, size_t, uint32_t, uint8_t);
typedef void (*flash_range_program_f)(uint32_t, const uint8_t *, size_t);
typedef void (*flash_flush_cache_f)(void);
typedef void (*flash_enable_xip_f)(void);
#ifdef CONFIG_SMP
/* The locks are used for coordinating "pause" and "resume" with the handler
* used for blocking a CPU.
*/
struct smp_isolation_data_s
{
volatile spinlock_t cpu_wait;
volatile spinlock_t cpu_pause;
volatile spinlock_t cpu_resume;
struct smp_call_data_s call_data;
};
struct smp_isolation_s
{
/* The id of the single CPU which is exempted from pausing. */
int isolated_cpuid;
struct smp_isolation_data_s cpu_data[CONFIG_SMP_NCPUS];
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int rp2040_flash_erase (struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks);
static ssize_t rp2040_flash_block_read (struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks,
uint8_t *buffer);
static ssize_t rp2040_flash_block_write(struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks,
const uint8_t *buffer);
static ssize_t rp2040_flash_byte_read (struct mtd_dev_s *dev,
off_t offset,
size_t nbytes,
uint8_t *buffer);
static int rp2040_flash_ioctl (struct mtd_dev_s *dev,
int cmd,
unsigned long arg);
/****************************************************************************
* Public Data
****************************************************************************/
extern const uint8_t rp2040_smart_flash_start[256];
extern const uint8_t rp2040_smart_flash_end[0];
/****************************************************************************
* Private Data
****************************************************************************/
static struct rp2040_flash_dev_s my_dev =
{
.mtd_dev =
{
rp2040_flash_erase,
rp2040_flash_block_read,
rp2040_flash_block_write,
rp2040_flash_byte_read,
#ifdef CONFIG_MTD_BYTE_WRITE
NULL,
#endif
rp2040_flash_ioctl,
NULL,
NULL,
"rp_flash"
},
.lock = NXMUTEX_INITIALIZER,
};
static bool initialized = false;
static struct
{
connect_internal_flash_f connect_internal_flash;
flash_exit_xip_f flash_exit_xip;
flash_range_erase_f flash_range_erase;
flash_range_program_f flash_range_program;
flash_flush_cache_f flash_flush_cache;
flash_enable_xip_f flash_enable_xip;
} rom_functions;
void *boot_2_copy = NULL;
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_SMP
/****************************************************************************
* Name: pause_cpu_handler
*
* Description:
* Busy-wait for "leave_smp_isolation" to release our lock.
* "enter_smp_isolation" triggers the execution of this function on all
* CPUs to be blocked temporarily.
*
****************************************************************************/
static int pause_cpu_handler(void *const context)
{
struct smp_isolation_data_s *const cpu_data =
(struct smp_isolation_data_s *)context;
/* Reserve the resumption lock. To be released after resuming. */
spin_lock(&cpu_data->cpu_resume);
/* Notify "enter_smp_isolation", that we are pausing now. */
spin_unlock(&cpu_data->cpu_pause);
/* Wait for "leave_smp_isolation". */
spin_lock(&cpu_data->cpu_wait);
spin_unlock(&cpu_data->cpu_wait);
/* Notify "leave_smp_isolation", that we have resumed. */
spin_unlock(&cpu_data->cpu_resume);
return OK;
}
/****************************************************************************
* Name: init_smp_isolation
*
* Description:
* Initialize an SMP isolation environment.
*
* Input Parameters:
* data - SMP isolation environment.
*
* Returned Value:
* None.
*
****************************************************************************/
static void init_smp_isolation(struct smp_isolation_s *const data)
{
struct smp_isolation_data_s *cpu_data;
for (int cpuid = 0; cpuid < CONFIG_SMP_NCPUS; cpuid++)
{
cpu_data = &data->cpu_data[cpuid];
spin_lock_init(&cpu_data->cpu_wait);
spin_lock_init(&cpu_data->cpu_pause);
spin_lock_init(&cpu_data->cpu_resume);
}
}
/****************************************************************************
* Name: enter_smp_isolation
*
* Description:
* Force all CPUs except for the currently active one to pause execution
* via a busy loop.
* Scheduling of the current CPU is disabled until the isolation is lifted
* again by calling "leave_smp_isolation".
*
* Input Parameters:
* data - SMP isolation environment.
*
* Returned Value:
* None.
*
****************************************************************************/
static void enter_smp_isolation(struct smp_isolation_s *const data)
{
struct smp_isolation_data_s *cpu_data;
/* TODO: remove "sched_lock" after "nxsched_smp_call_async" does not try to
* run the handler directly anymore, if the current CPU is part of the
* given cpuset.
*/
sched_lock();
data->isolated_cpuid = this_cpu();
/* Pause all other CPUs. */
for (int other_cpuid = 0; other_cpuid < CONFIG_SMP_NCPUS; other_cpuid++)
{
cpu_data = &data->cpu_data[other_cpuid];
if (other_cpuid != data->isolated_cpuid)
{
spin_lock(&cpu_data->cpu_wait);
spin_lock(&cpu_data->cpu_pause);
spin_unlock(&cpu_data->cpu_resume);
}
nxsched_smp_call_init(&cpu_data->call_data, pause_cpu_handler,
cpu_data);
nxsched_smp_call_single_async(other_cpuid, &cpu_data->call_data);
}
/* Wait until all other CPUs are paused. */
for (int other_cpuid = 0; other_cpuid < CONFIG_SMP_NCPUS; other_cpuid++)
{
cpu_data = &data->cpu_data[other_cpuid];
if (other_cpuid != data->isolated_cpuid)
{
spin_lock(&cpu_data->cpu_pause);
spin_unlock(&cpu_data->cpu_pause);
}
}
}
/****************************************************************************
* Name: leave_smp_isolation
*
* Description:
* Release all blocked CPUs.
* Call this function as early as possible after "enter_smp_isolation".
*
* Input Parameters:
* data - SMP isolation environment.
*
* Returned Value:
* None.
*
****************************************************************************/
static void leave_smp_isolation(struct smp_isolation_s *const data)
{
struct smp_isolation_data_s *cpu_data;
/* Tell all other CPUs to resume. */
for (int other_cpuid = 0; other_cpuid < CONFIG_SMP_NCPUS; other_cpuid++)
{
cpu_data = &data->cpu_data[other_cpuid];
if (other_cpuid != data->isolated_cpuid)
{
spin_unlock(&cpu_data->cpu_wait);
}
}
/* Wait until all other CPUs have resumed. */
for (int other_cpuid = 0; other_cpuid < CONFIG_SMP_NCPUS; other_cpuid++)
{
cpu_data = &data->cpu_data[other_cpuid];
if (other_cpuid != data->isolated_cpuid)
{
spin_lock(&cpu_data->cpu_resume);
spin_unlock(&cpu_data->cpu_resume);
}
}
sched_unlock();
}
#endif
/****************************************************************************
* Name: do_erase
****************************************************************************/
void RAM_CODE(do_erase)(uint32_t addr, size_t count)
{
/* Note: While we would prefer not to flush the cache, the
* flash_flush_cache call is needed to remove CSn IO force.
*/
__asm__ volatile ("" : : : "memory");
rom_functions.connect_internal_flash();
rom_functions.flash_exit_xip();
/* The range erase will try to erase 65536-byte blocks with the 0xd8 flash
* command. If it cannot, because either the addr or count are not
* multiple of 65536, it will fall back to erasing 4096-byte blocks as
* needed.
*/
rom_functions.flash_range_erase(addr, count, 65536, 0xd8);
rom_functions.flash_flush_cache();
rom_functions.flash_enable_xip();
}
/****************************************************************************
* Name: do_write
****************************************************************************/
void RAM_CODE(do_write)(uint32_t addr, const uint8_t *data, size_t count)
{
/* Note: While we would prefer not to flush the cache, the
* flash_flush_cache call is needed to remove CSn IO force.
*/
__asm__ volatile ("" : : : "memory");
rom_functions.connect_internal_flash();
rom_functions.flash_exit_xip();
rom_functions.flash_range_program(addr, (uint8_t *)data, count);
rom_functions.flash_flush_cache();
rom_functions.flash_enable_xip();
}
/****************************************************************************
* Name: rp2040_flash_erase
****************************************************************************/
static int rp2040_flash_erase(struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks)
{
rp2040_flash_dev_t *rp_dev = (rp2040_flash_dev_t *)dev;
irqstate_t flags;
int ret = OK;
finfo("FLASH: erase block: %8u (0x%08x) count:%5u (0x%08X)\n",
(unsigned)(startblock),
(unsigned)(FLASH_BLOCK_SIZE * startblock + FLASH_START_OFFSET),
nblocks,
FLASH_BLOCK_SIZE * nblocks);
#ifdef CONFIG_SMP
struct smp_isolation_s smp_isolation;
init_smp_isolation(&smp_isolation);
#endif
ret = nxmutex_lock(&rp_dev->lock);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_SMP
enter_smp_isolation(&smp_isolation);
#endif
flags = enter_critical_section();
do_erase(FLASH_BLOCK_SIZE * startblock + FLASH_START_OFFSET,
FLASH_BLOCK_SIZE * nblocks);
leave_critical_section(flags);
#ifdef CONFIG_SMP
leave_smp_isolation(&smp_isolation);
#endif
ret = nblocks;
nxmutex_unlock(&rp_dev->lock);
return ret;
}
/****************************************************************************
* Name: rp2040_flash_block_read
****************************************************************************/
static ssize_t rp2040_flash_block_read(struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks,
uint8_t *buffer)
{
rp2040_flash_dev_t *rp_dev = (rp2040_flash_dev_t *)dev;
int start;
int length;
int ret = OK;
ret = nxmutex_lock(&rp_dev->lock);
if (ret < 0)
{
return ret;
}
finfo("FLASH: read sector: %8u (0x%08x) count:%5u\n",
(unsigned)(startblock),
(unsigned)(FLASH_SECTOR_SIZE * startblock + FLASH_START_OFFSET),
nblocks);
start = FLASH_SECTOR_SIZE * startblock;
length = FLASH_SECTOR_SIZE * nblocks;
/* This reads starting at XIP_NOCACHE_NOALLOC_BASE to bypass the
* XIP cache. This is done because flash programming does not update
* the cache and we don't want to read stale data. Since we expect
* access to the flash filesystem to be rather infrequent this isn't
* really much of a burden.
*/
memcpy(buffer, FLASH_START_READ + start, length);
/* Update the file position */
nxmutex_unlock(&rp_dev->lock);
return nblocks;
}
/****************************************************************************
* Name: rp2040_flash_write
****************************************************************************/
static ssize_t rp2040_flash_block_write(struct mtd_dev_s *dev,
off_t startblock,
size_t nblocks,
const uint8_t *buffer)
{
rp2040_flash_dev_t *rp_dev = (rp2040_flash_dev_t *)dev;
irqstate_t flags;
int ret;
#ifdef CONFIG_SMP
struct smp_isolation_s smp_isolation;
init_smp_isolation(&smp_isolation);
#endif
ret = nxmutex_lock(&rp_dev->lock);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_SMP
enter_smp_isolation(&smp_isolation);
#endif
flags = enter_critical_section();
do_write(FLASH_SECTOR_SIZE * startblock + FLASH_START_OFFSET,
buffer,
FLASH_SECTOR_SIZE * nblocks);
leave_critical_section(flags);
#ifdef CONFIG_SMP
leave_smp_isolation(&smp_isolation);
#endif
finfo("FLASH: write sector: %8u (0x%08x) count:%5u\n",
(unsigned)(startblock),
(unsigned)(FLASH_SECTOR_SIZE * startblock + FLASH_START_OFFSET),
nblocks);
#ifdef CONFIG_DEBUG_FS_INFO
for (int i = 0; i < FLASH_SECTOR_SIZE * nblocks; i += 16)
{
for (int j = 0; j < 16; ++j)
{
printf("%02x, ", buffer[i + j]);
}
printf("\n");
}
#endif
ret = nblocks;
nxmutex_unlock(&rp_dev->lock);
return ret;
}
/****************************************************************************
* Name: rp2040_flash_byte_read
****************************************************************************/
static ssize_t rp2040_flash_byte_read(struct mtd_dev_s *dev,
off_t offset,
size_t nbytes,
uint8_t *buffer)
{
rp2040_flash_dev_t *rp_dev = (rp2040_flash_dev_t *)dev;
int length;
int ret = OK;
ret = nxmutex_lock(&rp_dev->lock);
if (ret < 0)
{
return ret;
}
length = nbytes;
finfo("FLASH: read bytes: %8u (0x%08x) count:%5u\n",
(unsigned)(offset),
(unsigned)(offset + FLASH_START_OFFSET),
nbytes);
/* This reads starting at XIP_NOCACHE_NOALLOC_BASE to bypass the
* XIP cache. This is done because flash programming does not update
* the cache and we don't want to read stale data. Since we expect
* access to the flash filesystem to be rather infrequent this isn't
* really much of a burden.
*/
memcpy(buffer, FLASH_START_READ + offset, length);
#ifdef CONFIG_DEBUG_FS_INFO
for (int j = 0; j < 16 && j < nbytes; ++j)
{
printf("%02x, ", buffer[j]);
}
printf("\n");
#endif
/* Update the file position */
nxmutex_unlock(&rp_dev->lock);
return length;
}
/****************************************************************************
* Name: rp2040_flash_ioctl
****************************************************************************/
static int rp2040_flash_ioctl(struct mtd_dev_s *dev,
int cmd,
unsigned long arg)
{
rp2040_flash_dev_t *rp_dev = (rp2040_flash_dev_t *)dev;
int ret = OK;
UNUSED(rp_dev);
switch (cmd)
{
case MTDIOC_GEOMETRY:
{
struct mtd_geometry_s *geo = (struct mtd_geometry_s *)arg;
if (geo != NULL)
{
memset(geo, 0, sizeof(*geo));
geo->blocksize = FLASH_SECTOR_SIZE;
geo->erasesize = FLASH_BLOCK_SIZE;
geo->neraseblocks = FLASH_BLOCK_COUNT;
}
break;
}
case MTDIOC_BULKERASE:
{
/* Erase all the filesystem blocks for the device. Remember that
* we share this device with XIP memory so we cannot erase entire
* device.
*/
ret = rp2040_flash_erase(dev, 0, FLASH_BLOCK_COUNT);
break;
}
default:
ret = -ENOTTY;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rp2040_flash_initialize
*
* Description:
* Bind a block mode driver that uses the built-in rp2040
* flash programming commands for read/write access to unused flash.
*
****************************************************************************/
struct mtd_dev_s *rp2040_flash_mtd_initialize(void)
{
if (initialized)
{
errno = EBUSY;
return NULL;
}
initialized = true;
if (FLASH_BLOCK_COUNT < 4)
{
errno = ENOMEM;
return NULL;
}
rom_functions.connect_internal_flash = ROM_LOOKUP(ROM_FLASH_CONNECT);
rom_functions.flash_exit_xip = ROM_LOOKUP(ROM_FLASH_EXIT_XIP);
rom_functions.flash_range_erase = ROM_LOOKUP(ROM_FLASH_ERASE);
rom_functions.flash_range_program = ROM_LOOKUP(ROM_FLASH_PROGRAM);
rom_functions.flash_flush_cache = ROM_LOOKUP(ROM_FLASH_FLUSH_CACHE);
/* Instead of using the rom_function for flash_enable_xip, we use the one
* from boot stage 2 loaded at the beginning of the XIP rom. We do this
* because the boot_rom version can result in slower access to the the
* XIP memory.
*
* We need to make our own copy of this code in ram since we cannot use
* the rom until after this call completes.
*/
memcpy(my_dev.boot_2, (void *)XIP_BASE, BOOT_2_SIZE);
rom_functions.flash_enable_xip = (flash_enable_xip_f)my_dev.boot_2 + 1;
/* Do we need to initialize the flash? */
if (memcmp(rp2040_smart_flash_start, "2040", 4) == 0)
{
uint8_t buffer[FLASH_SECTOR_SIZE];
irqstate_t flags = enter_critical_section();
/* OK, we found the "magic" tag... */
/* Erase all flash beyond what was loaded from NuttX binary. */
do_erase(FLASH_END_OFFSET,
CONFIG_RP2040_FLASH_LENGTH - FLASH_END_OFFSET);
/* Erase the "magic" flag, setting the first two bytes to zero. */
memcpy(buffer, rp2040_smart_flash_start, FLASH_SECTOR_SIZE);
buffer[0] = 0;
buffer[1] = 0;
do_write(FLASH_START_OFFSET, buffer, FLASH_SECTOR_SIZE);
leave_critical_section(flags);
}
return &(my_dev.mtd_dev);
}