You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ac...@apache.org on 2026/10/07 21:45:51 UTC

(nuttx) branch master updated (d3e856c56e2 -> e7f9aaa52b9)

This is an automated email from the ASF dual-hosted git repository.

acassis pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


    from d3e856c56e2 arch/arm/rp23xx: Add RTC alarm support on the POWMAN always-on timer.
     new ecc0036470e arch/arm/rp23xx: Save and restore the QSPI state around flash writes.
     new 1d8898d7d61 arch/arm/rp23xx: Restore XIP with the bootrom XIP setup function.
     new 75f8249405e arch/arm/rp23xx: Erase and program the flash in small steps.
     new cb87f99b6e6 arch/arm/rp23xx: Keep flash writes off PSRAM and flash data.
     new e7f9aaa52b9 arch/arm/rp23xx: Check the flash MTD region against the flash size.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../rp23xx/boards/pimoroni-pico-2-plus/index.rst   |   7 +-
 Documentation/platforms/arm/rp23xx/index.rst       |  23 +-
 arch/arm/src/rp23xx/Kconfig                        |  20 +-
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c             | 495 +++++++++++++++++----
 arch/arm/src/rp23xx/rp23xx_psram.c                 |  16 +-
 arch/arm/src/rp23xx/rp23xx_psram.h                 |  15 -
 6 files changed, 430 insertions(+), 146 deletions(-)


(nuttx) 02/05: arch/arm/rp23xx: Restore XIP with the bootrom XIP setup function.

Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 1d8898d7d61522feedc3c88bc38e84c29fdd1f0a
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:12:40 2026 +0200

    arch/arm/rp23xx: Restore XIP with the bootrom XIP setup function.
    
    After a flash operation the driver called flash_select_xip_read_mode()
    with a fixed EBh quad mode and clock divisor 4, and called
    flash_enter_cmd_xip() if it "failed".  But that ROM function returns
    void, so the check read a random r0.  The fixed mode and divisor can
    also be different from the ones the bootrom found at boot.
    
    The datasheet (5.2.7, 5.4.8.10) and the Pico SDK use a different
    method: after a flash boot the bootrom leaves an XIP setup function in
    the first 256 bytes of boot RAM.  It restores the read mode and clock
    divisor found at boot.  Boot RAM is not executable, so copy the
    function to SRAM once at initialization, and call the copy.
    
    If boot RAM is empty (no flash boot), use flash_enter_cmd_xip(), as
    RP23XX_FLASH_MTD_SAFE_XIP does.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
 Documentation/platforms/arm/rp23xx/index.rst |  8 ++--
 arch/arm/src/rp23xx/Kconfig                  | 12 ++---
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c       | 71 +++++++++++++++-------------
 3 files changed, 49 insertions(+), 42 deletions(-)

diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst
index 0e8c9df7754..69345e5af5e 100644
--- a/Documentation/platforms/arm/rp23xx/index.rst
+++ b/Documentation/platforms/arm/rp23xx/index.rst
@@ -328,11 +328,13 @@ Erase and program go through the bootrom flash routines.  Because those
 operations stall instruction fetch from the same flash, the driver runs them
 from SRAM with interrupts disabled and, on SMP builds, the other core parked;
 expect interrupt latency to suffer for the duration of a write.  Afterwards the
-QSPI interface is put back into execute-in-place mode -- by default restoring
-the fast read mode the bootrom set up at boot, or, with
+QSPI interface is put back into execute-in-place mode -- by default with a
+copy of the XIP setup function that the bootrom leaves in boot RAM, which
+restores the read mode found at boot, or, with
 ``RP23XX_FLASH_MTD_SAFE_XIP``, always through the bootrom
 ``flash_enter_cmd_xip`` routine, which is slower to execute from but depends
-only on the documented bootrom entry point.
+only on the documented bootrom entry point.  The driver also saves and
+restores the QSPI pads and the PSRAM configuration on chip select 1.
 
 The driver answers the ``BIOC_XIPBASE`` ioctl with the memory-mapped address of
 the region, so a filesystem that supports execute in place can hand out real
diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig
index 7c3e4180c53..dc3af5774ce 100644
--- a/arch/arm/src/rp23xx/Kconfig
+++ b/arch/arm/src/rp23xx/Kconfig
@@ -1247,14 +1247,14 @@ config RP23XX_FLASH_MTD_SAFE_XIP
 	default n
 	---help---
 		After each erase or program the QSPI interface must be put back
-		into execute-in-place mode.  By default the driver restores the
-		fast read mode the bootrom configured at boot, which is what code
-		executing in place from this flash needs.
+		into execute-in-place mode.  By default the driver runs a copy of
+		the XIP setup function that the bootrom leaves in boot RAM, as the
+		Pico SDK does.  It restores the read mode and clock divisor that
+		the bootrom found at boot.
 
 		Enable this to always use the bootrom flash_enter_cmd_xip routine
-		instead.  That is slower to execute from, but depends on nothing
-		beyond the documented bootrom entry point, so it is the safe
-		choice when bringing up a new board.
+		instead.  That gives a slow 03h serial read mode, but depends on
+		nothing beyond the documented bootrom entry point.
 
 endif # RP23XX_FLASH_MTD
 
diff --git a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
index e4279b4f46c..d229dca87f1 100644
--- a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
+++ b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
@@ -114,15 +114,11 @@
 
 #define RP23XX_FLASH_MAX_SIZE     0x04000000
 
-/* Bootrom XIP read modes, and the clock divisor to run them at.  Quad is
- * the fast one; the bootrom validates the part can do it.
+/* Size of the XIP setup function the bootrom leaves at the start of boot
+ * RAM (datasheet 5.2.7).
  */
 
-#define RP23XX_XIP_MODE_03H_SERIAL 0
-#define RP23XX_XIP_MODE_0BH_SERIAL 1
-#define RP23XX_XIP_MODE_BBH_DUAL   2
-#define RP23XX_XIP_MODE_EBH_QUAD   3
-#define RP23XX_XIP_CLKDIV          4
+#define XIP_SETUP_WORDS           64
 
 /* Smallest unit that can be programmed, and smallest that can be erased */
 
@@ -172,7 +168,7 @@ 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_enter_cmd_xip_f)(void);
-typedef bool (*select_xip_read_mode_f)(uint32_t mode, uint8_t clkdiv);
+typedef void (*xip_setup_f)(void);
 
 #ifdef CONFIG_SMP
 /* Locks coordinating "pause" and "resume" with the handler that blocks a
@@ -245,15 +241,18 @@ static struct
   flash_flush_cache_f      flash_flush_cache;
   flash_enter_cmd_xip_f    flash_enter_cmd_xip;
 
-  /* Restores a fast XIP read mode.  flash_enter_cmd_xip works everywhere
-   * but leaves the flash in a slow 03h serial mode, which costs roughly an
-   * order of magnitude of read bandwidth -- and the base firmware executes
-   * from this same flash, so it is not a cost confined to the filesystem.
+  /* SRAM copy of the bootrom XIP setup function.  It restores the read
+   * mode and clock divisor found at boot.  NULL if there is none:
+   * flash_enter_cmd_xip then gives a slow 03h serial mode.
    */
 
-  select_xip_read_mode_f   select_xip_read_mode;
+  xip_setup_f              xip_setup;
 } g_rom;
 
+#ifndef CONFIG_RP23XX_FLASH_MTD_SAFE_XIP
+static uint32_t g_xip_setup[XIP_SETUP_WORDS];
+#endif
+
 /* End of the NuttX image in flash, provided by the linker script.  Declared
  * weak so that a RAM-only memory map, which does not define it, still
  * links.
@@ -454,14 +453,11 @@ static void RAM_CODE(rp23xx_flash_end)(struct rp23xx_qspi_state_s *state)
 
   g_rom.flash_flush_cache();
 
-  /* Ask the bootrom to put the flash back into a fast quad read mode.  It
-   * reports whether it managed to, so a part that cannot do quad falls
-   * back rather than leaving the interface unusable.
-   */
-
-  if (g_rom.select_xip_read_mode == NULL ||
-      !g_rom.select_xip_read_mode(RP23XX_XIP_MODE_EBH_QUAD,
-                                  RP23XX_XIP_CLKDIV))
+  if (g_rom.xip_setup != NULL)
+    {
+      g_rom.xip_setup();
+    }
+  else
     {
       g_rom.flash_enter_cmd_xip();
     }
@@ -766,6 +762,10 @@ static int rp23xx_flash_ioctl(struct mtd_dev_s *dev, int cmd,
 
 struct mtd_dev_s *rp23xx_flash_mtd_initialize(void)
 {
+#ifndef CONFIG_RP23XX_FLASH_MTD_SAFE_XIP
+  int i;
+#endif
+
   if (g_initialized)
     {
       set_errno(EBUSY);
@@ -816,24 +816,29 @@ struct mtd_dev_s *rp23xx_flash_mtd_initialize(void)
       return NULL;
     }
 
-  /* Resolve the fast XIP read mode selector.  Unlike the bootrom's saved
-   * XIP setup pointer -- which is a data table entry whose semantics this
-   * driver got wrong, and calling it with flash torn down hangs the core
-   * unrecoverably -- this is an ordinary ROM function looked up exactly
-   * like the others above, and it returns a status.
+  /* Copy the bootrom XIP setup function out of boot RAM, which is not
+   * executable, as the Pico SDK does.  Boot RAM is empty when the image
+   * was not started by a flash boot.
    */
 
 #ifndef CONFIG_RP23XX_FLASH_MTD_SAFE_XIP
-  g_rom.select_xip_read_mode =
-    rom_func_lookup(ROM_FUNC_FLASH_SELECT_XIP_READ_MODE);
+  for (i = 0; i < XIP_SETUP_WORDS; i++)
+    {
+      g_xip_setup[i] = getreg32(RP23XX_BOOTRAM_BASE + 4 * i);
+    }
 
-  if (g_rom.select_xip_read_mode == NULL)
+  UP_DSB();
+  UP_ISB();
+
+  if (g_xip_setup[0] != 0)
+    {
+      g_rom.xip_setup = (xip_setup_f)((uintptr_t)g_xip_setup | 1);
+    }
+  else
     {
-      fwarn("rp23xx_flash: no fast XIP selector; reads will be slow after "
-            "every flash operation\n");
+      fwarn("rp23xx_flash: no XIP setup function in boot RAM; reads will "
+            "be slow after every flash operation\n");
     }
-#else
-  g_rom.select_xip_read_mode = NULL;
 #endif
 
   g_initialized = true;


(nuttx) 01/05: arch/arm/rp23xx: Save and restore the QSPI state around flash writes.

Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit ecc0036470ed5364bd08b1e107214545eab4cc8c
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:10:56 2026 +0200

    arch/arm/rp23xx: Save and restore the QSPI state around flash writes.
    
    The bootrom flash functions reset the QMI window 1 (chip select 1)
    registers and the QSPI pads.  flash_flush_cache() also discards dirty
    XIP cache lines.  The flash MTD driver did not save anything, so after
    the first erase or program the PSRAM on chip select 1 read garbage,
    and PSRAM writes still in the cache were lost.
    
    Do what the Pico SDK hardware_flash library does:
    
    - Clean the XIP cache before the operation.  Clean by set/way through
      the top of the maintenance window, to avoid erratum RP2350-E11.
    - Save the QSPI pads and the five QMI M1 registers before, and write
      them back after XIP is restored.  Also keep XIP_CTRL.WRITABLE_M1.
    
    rp23xx_psram_restore() was the earlier fix for this, but nothing called
    it.  Remove it.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
 .../rp23xx/boards/pimoroni-pico-2-plus/index.rst   |   7 +-
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c             | 112 ++++++++++++++++++---
 arch/arm/src/rp23xx/rp23xx_psram.c                 |  16 +--
 arch/arm/src/rp23xx/rp23xx_psram.h                 |  15 ---
 4 files changed, 102 insertions(+), 48 deletions(-)

diff --git a/Documentation/platforms/arm/rp23xx/boards/pimoroni-pico-2-plus/index.rst b/Documentation/platforms/arm/rp23xx/boards/pimoroni-pico-2-plus/index.rst
index e24530a933b..057848efc8f 100644
--- a/Documentation/platforms/arm/rp23xx/boards/pimoroni-pico-2-plus/index.rst
+++ b/Documentation/platforms/arm/rp23xx/boards/pimoroni-pico-2-plus/index.rst
@@ -66,10 +66,9 @@ heap, separate heap, or user heap) is selectable.  See the
 
    The PSRAM is considerably slower than the internal SRAM.  The QMI is
    shared with the flash: writing to the flash goes through the bootrom,
-   which reconfigures the QMI for chip select 0 and disturbs the PSRAM
-   configuration on chip select 1.  ``rp23xx_psram_restore()`` re-applies it
-   and is intended to be called from the flash write path after each erase or
-   program.
+   which resets the PSRAM configuration on chip select 1.  The flash MTD
+   driver (``RP23XX_FLASH_MTD``) saves that configuration before each erase
+   or program and restores it afterwards.
 
 Supported Capabilities
 ======================
diff --git a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
index 890ba8e6ce9..e4279b4f46c 100644
--- a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
+++ b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
@@ -74,6 +74,9 @@
 #include "rp23xx_flash_mtd.h"
 #include "rp23xx_rom.h"
 #include "hardware/rp23xx_memorymap.h"
+#include "hardware/rp23xx_pads_qspi.h"
+#include "hardware/rp23xx_qmi.h"
+#include "hardware/rp23xx_xip.h"
 
 /****************************************************************************
  * Pre-processor Definitions
@@ -87,6 +90,24 @@
 
 #define RP23XX_XIP_NOCACHE_BASE   0x14000000
 
+/* XIP cache maintenance window.  A clean by set/way must use the top of the
+ * window to avoid erratum RP2350-E11, as the Pico SDK does.
+ */
+
+#define RP23XX_XIP_MAINT_BASE     0x18000000
+#define XIP_CACHE_SIZE            (16 * 1024)
+#define XIP_CACHE_LINE_SIZE       8
+#define XIP_CACHE_CLEAN_SET_WAY   1
+#define XIP_CACHE_CLEAN_BASE      (RP23XX_XIP_MAINT_BASE + 0x04000000 - \
+                                   XIP_CACHE_SIZE + XIP_CACHE_CLEAN_SET_WAY)
+
+/* QSPI pads (SCLK, SD0-SD3, SS) and QMI window 1 registers (TIMING, RFMT,
+ * RCMD, WFMT, WCMD).  The bootrom flash functions change both.
+ */
+
+#define QSPI_PAD_COUNT            6
+#define QMI_M1_REG_COUNT          5
+
 /* Largest flash the XIP window can address, used only to sanity check a
  * pointer before it is called with the flash interface torn down.
  */
@@ -136,6 +157,15 @@ struct rp23xx_flash_dev_s
   mutex_t          lock;
 };
 
+/* QSPI state saved over a flash operation */
+
+struct rp23xx_qspi_state_s
+{
+  uint32_t pads[QSPI_PAD_COUNT];
+  uint32_t m1[QMI_M1_REG_COUNT];
+  uint32_t xip_ctrl;
+};
+
 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);
@@ -368,16 +398,60 @@ static void leave_smp_isolation(struct smp_isolation_s *const data)
 #endif /* CONFIG_SMP */
 
 /****************************************************************************
- * Name: rp23xx_flash_restore_xip
+ * Name: rp23xx_flash_begin
+ *
+ * Description:
+ *   Save the QSPI state and put the flash in serial command mode, as the
+ *   Pico SDK does.  Must run from RAM: XIP is not available until
+ *   rp23xx_flash_end() returns.
+ *
+ ****************************************************************************/
+
+static void RAM_CODE(rp23xx_flash_begin)(struct rp23xx_qspi_state_s *state)
+{
+  int i;
+
+  /* Write back dirty PSRAM lines.  flash_flush_cache() discards them. */
+
+  for (i = 0; i < XIP_CACHE_SIZE; i += XIP_CACHE_LINE_SIZE)
+    {
+      putreg8(0, XIP_CACHE_CLEAN_BASE + i);
+    }
+
+  UP_DSB();
+  UP_ISB();
+
+  for (i = 0; i < QSPI_PAD_COUNT; i++)
+    {
+      state->pads[i] = getreg32(RP23XX_PADS_QSPI_GPIO_QSPI_SCLK + 4 * i);
+    }
+
+  for (i = 0; i < QMI_M1_REG_COUNT; i++)
+    {
+      state->m1[i] = getreg32(RP23XX_QMI_M1_TIMING + 4 * i);
+    }
+
+  state->xip_ctrl = getreg32(RP23XX_XIP_CTRL_BASE);
+
+  __asm__ volatile ("" : : : "memory");
+
+  g_rom.connect_internal_flash();
+  g_rom.flash_exit_xip();
+}
+
+/****************************************************************************
+ * Name: rp23xx_flash_end
  *
  * Description:
- *   Put the QSPI interface back into execute-in-place mode.  Must run from
- *   RAM: until it returns, nothing can be fetched from flash.
+ *   Put the QSPI interface back into execute-in-place mode, then restore
+ *   the pads and the chip select 1 (PSRAM) window that the bootrom reset.
  *
  ****************************************************************************/
 
-static void RAM_CODE(rp23xx_flash_restore_xip)(void)
+static void RAM_CODE(rp23xx_flash_end)(struct rp23xx_qspi_state_s *state)
 {
+  int i;
+
   g_rom.flash_flush_cache();
 
   /* Ask the bootrom to put the flash back into a fast quad read mode.  It
@@ -391,6 +465,20 @@ static void RAM_CODE(rp23xx_flash_restore_xip)(void)
     {
       g_rom.flash_enter_cmd_xip();
     }
+
+  for (i = 0; i < QSPI_PAD_COUNT; i++)
+    {
+      putreg32(state->pads[i], RP23XX_PADS_QSPI_GPIO_QSPI_SCLK + 4 * i);
+    }
+
+  for (i = 0; i < QMI_M1_REG_COUNT; i++)
+    {
+      putreg32(state->m1[i], RP23XX_QMI_M1_TIMING + 4 * i);
+    }
+
+  putreg32(getreg32(RP23XX_XIP_CTRL_BASE) |
+           (state->xip_ctrl & RP23XX_XIP_CTRL_WRITABLE_M1),
+           RP23XX_XIP_CTRL_BASE);
 }
 
 /****************************************************************************
@@ -404,10 +492,9 @@ static void RAM_CODE(rp23xx_flash_restore_xip)(void)
 
 static void RAM_CODE(do_erase)(uint32_t addr, size_t count)
 {
-  __asm__ volatile ("" : : : "memory");
+  struct rp23xx_qspi_state_s state;
 
-  g_rom.connect_internal_flash();
-  g_rom.flash_exit_xip();
+  rp23xx_flash_begin(&state);
 
   /* The bootrom erases whole 64K blocks where address and length allow it
    * and falls back to 4K sectors otherwise.
@@ -416,7 +503,7 @@ static void RAM_CODE(do_erase)(uint32_t addr, size_t count)
   g_rom.flash_range_erase(addr, count, FLASH_BLOCK_SIZE,
                           FLASH_BLOCK_ERASE_CMD);
 
-  rp23xx_flash_restore_xip();
+  rp23xx_flash_end(&state);
 }
 
 /****************************************************************************
@@ -426,14 +513,11 @@ static void RAM_CODE(do_erase)(uint32_t addr, size_t count)
 static void RAM_CODE(do_write)(uint32_t addr, const uint8_t *data,
                                size_t count)
 {
-  __asm__ volatile ("" : : : "memory");
-
-  g_rom.connect_internal_flash();
-  g_rom.flash_exit_xip();
+  struct rp23xx_qspi_state_s state;
 
+  rp23xx_flash_begin(&state);
   g_rom.flash_range_program(addr, data, count);
-
-  rp23xx_flash_restore_xip();
+  rp23xx_flash_end(&state);
 }
 
 /****************************************************************************
diff --git a/arch/arm/src/rp23xx/rp23xx_psram.c b/arch/arm/src/rp23xx/rp23xx_psram.c
index 1acc2a2e546..82b83762907 100644
--- a/arch/arm/src/rp23xx/rp23xx_psram.c
+++ b/arch/arm/src/rp23xx/rp23xx_psram.c
@@ -165,8 +165,7 @@ rp23xx_psram_wait_busy(void)
  *
  * Description:
  *   Program the QMI M1 timing/format/command registers and mark the region
- *   writable.  Shared by the initial configuration and the post-flash
- *   restore.  Runs from RAM.
+ *   writable.  Runs from RAM.
  *
  ****************************************************************************/
 
@@ -374,17 +373,4 @@ size_t rp23xx_psram_size(void)
   return g_psram_size;
 }
 
-/****************************************************************************
- * Name: rp23xx_psram_restore
- ****************************************************************************/
-
-void RP23XX_PSRAM_RAMFUNC
-rp23xx_psram_restore(void)
-{
-  if (g_psram_size != 0)
-    {
-      rp23xx_psram_apply_format();
-    }
-}
-
 #endif /* CONFIG_RP23XX_PSRAM */
diff --git a/arch/arm/src/rp23xx/rp23xx_psram.h b/arch/arm/src/rp23xx/rp23xx_psram.h
index 58d5ce907d2..88a5327cc83 100644
--- a/arch/arm/src/rp23xx/rp23xx_psram.h
+++ b/arch/arm/src/rp23xx/rp23xx_psram.h
@@ -71,19 +71,4 @@ size_t rp23xx_psramconfig(void);
 
 size_t rp23xx_psram_size(void);
 
-/****************************************************************************
- * Name: rp23xx_psram_restore
- *
- * Description:
- *   Re-apply the QMI M1 (PSRAM) format and timing registers.  Programming
- *   the flash goes through the bootrom, which reconfigures the shared QMI
- *   interface for chip select 0; this restores the CS1 configuration
- *   afterwards so the memory-mapped PSRAM keeps working across a flash
- *   erase or program.  Runs from RAM and does nothing when no PSRAM was
- *   detected.
- *
- ****************************************************************************/
-
-void rp23xx_psram_restore(void);
-
 #endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_PSRAM_H */


(nuttx) 03/05: arch/arm/rp23xx: Erase and program the flash in small steps.

Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 75f8249405ea32905e2d3790d27f3d3a038be2a9
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:15:59 2026 +0200

    arch/arm/rp23xx: Erase and program the flash in small steps.
    
    The flash MTD driver disabled interrupts for a whole request.  A
    multi-block erase or a large write kept them off for seconds.
    
    Erase one 64K block (or one 4K sector where the range is not block
    aligned) and program one 256 byte page per step.  Enable interrupts and
    release the other core between steps.  A single block erase is still
    long, but that is the limit of the flash.
    
    Also, on SMP:
    
    - Do not send the pause call to the CPU that does the operation.
      nxsched_smp_call_single_async() runs it at once on that CPU.
    - Keep the isolation data in a static, not on the stack.  The other
      CPU spins on it while the flash is busy.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
 Documentation/platforms/arm/rp23xx/index.rst |   6 +-
 arch/arm/src/rp23xx/Kconfig                  |   5 +-
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c       | 144 +++++++++++++++++----------
 3 files changed, 97 insertions(+), 58 deletions(-)

diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst
index 69345e5af5e..81a9fb5e4f3 100644
--- a/Documentation/platforms/arm/rp23xx/index.rst
+++ b/Documentation/platforms/arm/rp23xx/index.rst
@@ -326,8 +326,10 @@ offset fails at boot instead of corrupting the running firmware.
 
 Erase and program go through the bootrom flash routines.  Because those
 operations stall instruction fetch from the same flash, the driver runs them
-from SRAM with interrupts disabled and, on SMP builds, the other core parked;
-expect interrupt latency to suffer for the duration of a write.  Afterwards the
+from SRAM with interrupts disabled and, on SMP builds, the other core parked.
+It does so for one 64K block erase or one 256 byte page program at a time, and
+enables interrupts between them; expect interrupt latency to suffer for that
+long (a block erase can take hundreds of milliseconds).  Afterwards the
 QSPI interface is put back into execute-in-place mode -- by default with a
 copy of the XIP setup function that the bootrom leaves in boot RAM, which
 restores the read mode found at boot, or, with
diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig
index dc3af5774ce..1f166d81cd0 100644
--- a/arch/arm/src/rp23xx/Kconfig
+++ b/arch/arm/src/rp23xx/Kconfig
@@ -1220,8 +1220,9 @@ config RP23XX_FLASH_MTD
 
 		Note that erasing or programming this flash stalls all instruction
 		fetches from it.  The driver therefore runs those operations from
-		SRAM with interrupts disabled and the other core parked, which will
-		visibly affect interrupt latency for the duration.
+		SRAM with interrupts disabled and the other core parked, one 64K
+		block erase or one 256 byte page program at a time.  This will
+		visibly affect interrupt latency.
 
 if RP23XX_FLASH_MTD
 
diff --git a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
index d229dca87f1..07f1484435c 100644
--- a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
+++ b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
@@ -39,7 +39,8 @@
  *      boot by the linker script, which is the same mechanism the Pico SDK
  *      spells __not_in_flash_func(),
  *   2. runs with interrupts disabled, because an ISR vector or handler
- *      living in flash would be fetched mid-erase, and
+ *      living in flash would be fetched mid-erase -- one 64K block erase
+ *      or one 256 byte page program at a time, and
  *   3. parks the other core, because it is very likely executing from
  *      flash as well.
  *
@@ -153,6 +154,16 @@ struct rp23xx_flash_dev_s
   mutex_t          lock;
 };
 
+/* One flash operation.  It is static, so that it is in SRAM. */
+
+struct rp23xx_flash_op_s
+{
+  CODE void (*func)(FAR struct rp23xx_flash_op_s *op);
+  uint32_t addr;
+  FAR const uint8_t *data;
+  size_t count;
+};
+
 /* QSPI state saved over a flash operation */
 
 struct rp23xx_qspi_state_s
@@ -232,6 +243,12 @@ static struct rp23xx_flash_dev_s g_flash_dev =
 
 static bool g_initialized = false;
 
+static struct rp23xx_flash_op_s g_flash_op;
+
+#ifdef CONFIG_SMP
+static struct smp_isolation_s g_smp_isolation;
+#endif
+
 static struct
 {
   connect_internal_flash_f connect_internal_flash;
@@ -340,11 +357,11 @@ static void enter_smp_isolation(struct smp_isolation_s *const data)
           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);
+          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 every other CPU has actually parked */
@@ -481,24 +498,18 @@ static void RAM_CODE(rp23xx_flash_end)(struct rp23xx_qspi_state_s *state)
  * Name: do_erase
  *
  * Description:
- *   Erase a byte range.  Runs from RAM with interrupts already disabled and
- *   the other core already parked.
+ *   Erase one sector or block.  Runs from RAM with interrupts disabled and
+ *   the other core parked.
  *
  ****************************************************************************/
 
-static void RAM_CODE(do_erase)(uint32_t addr, size_t count)
+static void RAM_CODE(do_erase)(FAR struct rp23xx_flash_op_s *op)
 {
   struct rp23xx_qspi_state_s state;
 
   rp23xx_flash_begin(&state);
-
-  /* The bootrom erases whole 64K blocks where address and length allow it
-   * and falls back to 4K sectors otherwise.
-   */
-
-  g_rom.flash_range_erase(addr, count, FLASH_BLOCK_SIZE,
+  g_rom.flash_range_erase(op->addr, op->count, FLASH_BLOCK_SIZE,
                           FLASH_BLOCK_ERASE_CMD);
-
   rp23xx_flash_end(&state);
 }
 
@@ -506,32 +517,59 @@ static void RAM_CODE(do_erase)(uint32_t addr, size_t count)
  * Name: do_write
  ****************************************************************************/
 
-static void RAM_CODE(do_write)(uint32_t addr, const uint8_t *data,
-                               size_t count)
+static void RAM_CODE(do_write)(FAR struct rp23xx_flash_op_s *op)
 {
   struct rp23xx_qspi_state_s state;
 
   rp23xx_flash_begin(&state);
-  g_rom.flash_range_program(addr, data, count);
+  g_rom.flash_range_program(op->addr, op->data, op->count);
   rp23xx_flash_end(&state);
 }
 
+/****************************************************************************
+ * Name: rp23xx_flash_run
+ *
+ * Description:
+ *   Run g_flash_op with interrupts disabled and the other core parked.
+ *   The caller holds the device lock.
+ *
+ ****************************************************************************/
+
+static void rp23xx_flash_run(void)
+{
+  irqstate_t flags;
+
+#ifdef CONFIG_SMP
+  init_smp_isolation(&g_smp_isolation);
+  enter_smp_isolation(&g_smp_isolation);
+#endif
+
+  flags = enter_critical_section();
+  g_flash_op.func(&g_flash_op);
+  leave_critical_section(flags);
+
+#ifdef CONFIG_SMP
+  leave_smp_isolation(&g_smp_isolation);
+#endif
+}
+
 /****************************************************************************
  * Name: rp23xx_flash_erase
+ *
+ * Description:
+ *   Erase one 64K block or 4K sector at a time, so that interrupts are
+ *   disabled for one block erase at most.
+ *
  ****************************************************************************/
 
 static int rp23xx_flash_erase(struct mtd_dev_s *dev, off_t startblock,
                               size_t nblocks)
 {
   struct rp23xx_flash_dev_s *priv = (struct rp23xx_flash_dev_s *)dev;
-  irqstate_t flags;
+  uint32_t addr;
+  uint32_t end;
   int ret;
 
-#ifdef CONFIG_SMP
-  struct smp_isolation_s smp_isolation;
-  init_smp_isolation(&smp_isolation);
-#endif
-
   if (startblock < 0 || startblock + nblocks > FS_SECTORS)
     {
       return -EINVAL;
@@ -545,20 +583,23 @@ static int rp23xx_flash_erase(struct mtd_dev_s *dev, off_t startblock,
       return ret;
     }
 
-#ifdef CONFIG_SMP
-  enter_smp_isolation(&smp_isolation);
-#endif
-
-  flags = enter_critical_section();
+  addr = FS_OFFSET + startblock * FLASH_SECTOR_SIZE;
+  end  = addr + nblocks * FLASH_SECTOR_SIZE;
 
-  do_erase(FS_OFFSET + startblock * FLASH_SECTOR_SIZE,
-           nblocks * FLASH_SECTOR_SIZE);
+  while (addr < end)
+    {
+      g_flash_op.func  = do_erase;
+      g_flash_op.addr  = addr;
+      g_flash_op.count = FLASH_SECTOR_SIZE;
 
-  leave_critical_section(flags);
+      if ((addr % FLASH_BLOCK_SIZE) == 0 && end - addr >= FLASH_BLOCK_SIZE)
+        {
+          g_flash_op.count = FLASH_BLOCK_SIZE;
+        }
 
-#ifdef CONFIG_SMP
-  leave_smp_isolation(&smp_isolation);
-#endif
+      rp23xx_flash_run();
+      addr += g_flash_op.count;
+    }
 
   nxmutex_unlock(&priv->lock);
   return nblocks;
@@ -601,20 +642,20 @@ static ssize_t rp23xx_flash_bread(struct mtd_dev_s *dev, off_t startblock,
 
 /****************************************************************************
  * Name: rp23xx_flash_bwrite
+ *
+ * Description:
+ *   Program one page at a time, so that interrupts are disabled for one
+ *   page program at most.
+ *
  ****************************************************************************/
 
 static ssize_t rp23xx_flash_bwrite(struct mtd_dev_s *dev, off_t startblock,
                                    size_t nblocks, const uint8_t *buffer)
 {
   struct rp23xx_flash_dev_s *priv = (struct rp23xx_flash_dev_s *)dev;
-  irqstate_t flags;
+  size_t i;
   int ret;
 
-#ifdef CONFIG_SMP
-  struct smp_isolation_s smp_isolation;
-  init_smp_isolation(&smp_isolation);
-#endif
-
   if (startblock < 0 || startblock + nblocks > FS_PAGES)
     {
       return -EINVAL;
@@ -626,20 +667,15 @@ static ssize_t rp23xx_flash_bwrite(struct mtd_dev_s *dev, off_t startblock,
       return ret;
     }
 
-#ifdef CONFIG_SMP
-  enter_smp_isolation(&smp_isolation);
-#endif
-
-  flags = enter_critical_section();
-
-  do_write(FS_OFFSET + startblock * FLASH_PAGE_SIZE, buffer,
-           nblocks * FLASH_PAGE_SIZE);
-
-  leave_critical_section(flags);
+  for (i = 0; i < nblocks; i++)
+    {
+      g_flash_op.func  = do_write;
+      g_flash_op.addr  = FS_OFFSET + (startblock + i) * FLASH_PAGE_SIZE;
+      g_flash_op.data  = buffer + i * FLASH_PAGE_SIZE;
+      g_flash_op.count = FLASH_PAGE_SIZE;
 
-#ifdef CONFIG_SMP
-  leave_smp_isolation(&smp_isolation);
-#endif
+      rp23xx_flash_run();
+    }
 
   finfo("write page %ld count %zu\n", (long)startblock, nblocks);
 


(nuttx) 04/05: arch/arm/rp23xx: Keep flash writes off PSRAM and flash data.

Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit cb87f99b6e676e415b3658db87424b2697956a1f
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:17:28 2026 +0200

    arch/arm/rp23xx: Keep flash writes off PSRAM and flash data.
    
    While the bootrom erases or programs the flash, the QMI is in direct
    mode, and an access to the XIP space (flash or PSRAM) gives a bus
    fault.  The flash MTD driver accessed it in two cases:
    
    - The data to program was in flash or PSRAM.  flash_range_program()
      read it during the operation.  Now the driver copies each such page
      to an SRAM buffer first.
    - The caller's stack was in PSRAM.  This is the normal case with
      RP23XX_PSRAM_HEAP_USER, and possible with RP23XX_PSRAM_HEAP_SINGLE.
      The operation pushed to that stack.  Now the driver switches to a
      small SRAM stack for the operation if the stack is in the XIP space.
    
    The operation data is static (SRAM) since the previous commit.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
 Documentation/platforms/arm/rp23xx/index.rst |  5 +++
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c       | 64 +++++++++++++++++++++++++++-
 2 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst
index 81a9fb5e4f3..4de30f14006 100644
--- a/Documentation/platforms/arm/rp23xx/index.rst
+++ b/Documentation/platforms/arm/rp23xx/index.rst
@@ -338,6 +338,11 @@ restores the read mode found at boot, or, with
 only on the documented bootrom entry point.  The driver also saves and
 restores the QSPI pads and the PSRAM configuration on chip select 1.
 
+Flash and PSRAM cannot be read while an erase or program is in progress.  So
+the driver copies a page to SRAM before it programs it if the data is in flash
+or PSRAM, and a caller with its stack in PSRAM runs the operation on a small
+SRAM stack.
+
 The driver answers the ``BIOC_XIPBASE`` ioctl with the memory-mapped address of
 the region, so a filesystem that supports execute in place can hand out real
 flash pointers instead of copying into RAM.
diff --git a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
index 07f1484435c..a3e9d3ae655 100644
--- a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
+++ b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
@@ -109,6 +109,17 @@
 #define QSPI_PAD_COUNT            6
 #define QMI_M1_REG_COUNT          5
 
+/* True for an address in the XIP space (flash or PSRAM), which cannot be
+ * accessed while the QMI is in direct mode.
+ */
+
+#define IS_XIP_ADDR(a) \
+  ((uintptr_t)(a) >= RP23XX_FLASH_BASE && (uintptr_t)(a) < RP23XX_SRAM_BASE)
+
+/* SRAM stack for a flash operation called with its stack in PSRAM */
+
+#define FLASH_OP_STACK_SIZE       1024
+
 /* Largest flash the XIP window can address, used only to sanity check a
  * pointer before it is called with the flash interface torn down.
  */
@@ -245,6 +256,14 @@ static bool g_initialized = false;
 
 static struct rp23xx_flash_op_s g_flash_op;
 
+/* SRAM copy of a page whose source is in the XIP space */
+
+static uint8_t g_flash_page[FLASH_PAGE_SIZE] aligned_data(4);
+
+#ifdef CONFIG_RP23XX_PSRAM
+static uint64_t g_flash_stack[FLASH_OP_STACK_SIZE / 8];
+#endif
+
 #ifdef CONFIG_SMP
 static struct smp_isolation_s g_smp_isolation;
 #endif
@@ -526,6 +545,41 @@ static void RAM_CODE(do_write)(FAR struct rp23xx_flash_op_s *op)
   rp23xx_flash_end(&state);
 }
 
+/****************************************************************************
+ * Name: rp23xx_flash_call
+ *
+ * Description:
+ *   Call g_flash_op.func.  PSRAM is not accessible during the operation,
+ *   so if the stack is in PSRAM, switch to an SRAM stack first.
+ *
+ ****************************************************************************/
+
+static void rp23xx_flash_call(void)
+{
+#ifdef CONFIG_RP23XX_PSRAM
+  if (IS_XIP_ADDR(up_getsp()))
+    {
+      __asm__ __volatile__
+      (
+        "mov r4, sp\n\t"
+        "mov sp, %[top]\n\t"
+        "mov r0, %[op]\n\t"
+        "blx %[func]\n\t"
+        "mov sp, r4\n\t"
+        :
+        : [top] "r" (&g_flash_stack[FLASH_OP_STACK_SIZE / 8]),
+          [op] "r" (&g_flash_op),
+          [func] "r" (g_flash_op.func)
+        : "r0", "r1", "r2", "r3", "r4", "r12", "lr", "memory", "cc"
+      );
+
+      return;
+    }
+#endif
+
+  g_flash_op.func(&g_flash_op);
+}
+
 /****************************************************************************
  * Name: rp23xx_flash_run
  *
@@ -545,7 +599,7 @@ static void rp23xx_flash_run(void)
 #endif
 
   flags = enter_critical_section();
-  g_flash_op.func(&g_flash_op);
+  rp23xx_flash_call();
   leave_critical_section(flags);
 
 #ifdef CONFIG_SMP
@@ -645,7 +699,7 @@ static ssize_t rp23xx_flash_bread(struct mtd_dev_s *dev, off_t startblock,
  *
  * Description:
  *   Program one page at a time, so that interrupts are disabled for one
- *   page program at most.
+ *   page program at most.  Copy a page from flash or PSRAM to SRAM first.
  *
  ****************************************************************************/
 
@@ -674,6 +728,12 @@ static ssize_t rp23xx_flash_bwrite(struct mtd_dev_s *dev, off_t startblock,
       g_flash_op.data  = buffer + i * FLASH_PAGE_SIZE;
       g_flash_op.count = FLASH_PAGE_SIZE;
 
+      if (IS_XIP_ADDR(g_flash_op.data))
+        {
+          memcpy(g_flash_page, g_flash_op.data, FLASH_PAGE_SIZE);
+          g_flash_op.data = g_flash_page;
+        }
+
       rp23xx_flash_run();
     }
 


(nuttx) 05/05: arch/arm/rp23xx: Check the flash MTD region against the flash size.

Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit e7f9aaa52b96b3f52f97f8a4d6fe99e64c94deea
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:19:12 2026 +0200

    arch/arm/rp23xx: Check the flash MTD region against the flash size.
    
    RP23XX_FLASH_MTD_OFFSET and RP23XX_FLASH_MTD_SIZE come from Kconfig.
    If the region ends past the end of the flash, the flash wraps the
    address around, and an erase or program hits the start of the flash,
    where the NuttX image is.  For example, a 4M region at 1M does not
    fit on the 4M flash of a Raspberry Pi Pico 2.
    
    Read the JEDEC ID at initialization, in QMI direct mode as the Pico
    SDK flash_do_cmd() does, and refuse a region that does not fit.  The
    capacity byte is log2 of the size in bytes.  If the ID does not look
    valid, warn and do not check.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
 Documentation/platforms/arm/rp23xx/index.rst |   4 +-
 arch/arm/src/rp23xx/Kconfig                  |   3 +-
 arch/arm/src/rp23xx/rp23xx_flash_mtd.c       | 120 ++++++++++++++++++++++++++-
 3 files changed, 123 insertions(+), 4 deletions(-)

diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst
index 4de30f14006..9d650248181 100644
--- a/Documentation/platforms/arm/rp23xx/index.rst
+++ b/Documentation/platforms/arm/rp23xx/index.rst
@@ -322,7 +322,9 @@ The region is described by ``RP23XX_FLASH_MTD_OFFSET`` (byte offset from
 ``0x10000000``) and ``RP23XX_FLASH_MTD_SIZE``, both of which must be multiples
 of the 4096 byte erase sector.  The driver refuses to initialize if the region
 would overlap the NuttX image (it checks ``__flash_binary_end``), so a bad
-offset fails at boot instead of corrupting the running firmware.
+offset fails at boot instead of corrupting the running firmware.  It also
+reads the flash size from the JEDEC ID and refuses a region that ends past the
+end of the flash, because such an address wraps around to the image.
 
 Erase and program go through the bootrom flash routines.  Because those
 operations stall instruction fetch from the same flash, the driver runs them
diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig
index 1f166d81cd0..4cc45cd0c6b 100644
--- a/arch/arm/src/rp23xx/Kconfig
+++ b/arch/arm/src/rp23xx/Kconfig
@@ -1241,7 +1241,8 @@ config RP23XX_FLASH_MTD_SIZE
 	---help---
 		Size of the MTD region in bytes.  Must be a multiple of the 4096
 		byte erase sector, and must fit within the flash fitted to the
-		board.
+		board.  The driver reads the flash size from its JEDEC ID, and
+		refuses to initialize if the region does not fit.
 
 config RP23XX_FLASH_MTD_SAFE_XIP
 	bool "Always restore XIP with the bootrom command mode"
diff --git a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
index a3e9d3ae655..826c27e33cb 100644
--- a/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
+++ b/arch/arm/src/rp23xx/rp23xx_flash_mtd.c
@@ -142,6 +142,11 @@
 #define FLASH_BLOCK_SIZE          65536
 #define FLASH_BLOCK_ERASE_CMD     0xd8
 
+/* JEDEC ID: command, manufacturer, memory type, capacity (log2 bytes) */
+
+#define FLASH_READ_ID_CMD         0x9f
+#define FLASH_READ_ID_SIZE        4
+
 #define FS_OFFSET                 CONFIG_RP23XX_FLASH_MTD_OFFSET
 #define FS_SIZE                   CONFIG_RP23XX_FLASH_MTD_SIZE
 
@@ -171,7 +176,7 @@ struct rp23xx_flash_op_s
 {
   CODE void (*func)(FAR struct rp23xx_flash_op_s *op);
   uint32_t addr;
-  FAR const uint8_t *data;
+  FAR uint8_t *data;
   size_t count;
 };
 
@@ -545,6 +550,63 @@ static void RAM_CODE(do_write)(FAR struct rp23xx_flash_op_s *op)
   rp23xx_flash_end(&state);
 }
 
+/****************************************************************************
+ * Name: do_read_id
+ *
+ * Description:
+ *   Read the JEDEC ID in QMI direct mode, as the Pico SDK flash_do_cmd()
+ *   does.
+ *
+ ****************************************************************************/
+
+static void RAM_CODE(do_read_id)(FAR struct rp23xx_flash_op_s *op)
+{
+  struct rp23xx_qspi_state_s state;
+  size_t tx = 0;
+  size_t rx = 0;
+  uint32_t csr;
+
+  rp23xx_flash_begin(&state);
+
+  putreg32(getreg32(RP23XX_QMI_DIRECT_CSR) |
+           RP23XX_QMI_DIRECT_CSR_ASSERT_CS0N, RP23XX_QMI_DIRECT_CSR);
+  putreg32(getreg32(RP23XX_QMI_DIRECT_CSR) | RP23XX_QMI_DIRECT_CSR_EN,
+           RP23XX_QMI_DIRECT_CSR);
+
+  while ((getreg32(RP23XX_QMI_DIRECT_CSR) & RP23XX_QMI_DIRECT_CSR_BUSY) != 0)
+    {
+    }
+
+  while (tx < op->count || rx < op->count)
+    {
+      csr = getreg32(RP23XX_QMI_DIRECT_CSR);
+
+      if ((csr & RP23XX_QMI_DIRECT_CSR_TXFULL) == 0 && tx < op->count)
+        {
+          putreg32(tx == 0 ? FLASH_READ_ID_CMD : 0, RP23XX_QMI_DIRECT_TX);
+          tx++;
+        }
+
+      if ((csr & RP23XX_QMI_DIRECT_CSR_RXEMPTY) == 0 && rx < op->count)
+        {
+          op->data[rx++] = (uint8_t)getreg32(RP23XX_QMI_DIRECT_RX);
+        }
+    }
+
+  /* BUSY stays high for half an SCK after the last bit, for CS timing */
+
+  while ((getreg32(RP23XX_QMI_DIRECT_CSR) & RP23XX_QMI_DIRECT_CSR_BUSY) != 0)
+    {
+    }
+
+  putreg32(getreg32(RP23XX_QMI_DIRECT_CSR) & ~RP23XX_QMI_DIRECT_CSR_EN,
+           RP23XX_QMI_DIRECT_CSR);
+  putreg32(getreg32(RP23XX_QMI_DIRECT_CSR) &
+           ~RP23XX_QMI_DIRECT_CSR_ASSERT_CS0N, RP23XX_QMI_DIRECT_CSR);
+
+  rp23xx_flash_end(&state);
+}
+
 /****************************************************************************
  * Name: rp23xx_flash_call
  *
@@ -607,6 +669,42 @@ static void rp23xx_flash_run(void)
 #endif
 }
 
+/****************************************************************************
+ * Name: rp23xx_flash_size
+ *
+ * Description:
+ *   Return the flash size from its JEDEC ID, or 0 if the ID is not valid.
+ *
+ ****************************************************************************/
+
+static size_t rp23xx_flash_size(void)
+{
+  FAR uint8_t *id = g_flash_page;
+
+  if (nxmutex_lock(&g_flash_dev.lock) < 0)
+    {
+      return 0;
+    }
+
+  g_flash_op.func  = do_read_id;
+  g_flash_op.data  = id;
+  g_flash_op.count = FLASH_READ_ID_SIZE;
+
+  rp23xx_flash_run();
+  nxmutex_unlock(&g_flash_dev.lock);
+
+  finfo("rp23xx_flash: JEDEC ID %02x %02x %02x\n", id[1], id[2], id[3]);
+
+  /* Accept a capacity from 64K to 64M */
+
+  if (id[1] == 0x00 || id[1] == 0xff || id[3] < 16 || id[3] > 26)
+    {
+      return 0;
+    }
+
+  return (size_t)1 << id[3];
+}
+
 /****************************************************************************
  * Name: rp23xx_flash_erase
  *
@@ -725,7 +823,7 @@ static ssize_t rp23xx_flash_bwrite(struct mtd_dev_s *dev, off_t startblock,
     {
       g_flash_op.func  = do_write;
       g_flash_op.addr  = FS_OFFSET + (startblock + i) * FLASH_PAGE_SIZE;
-      g_flash_op.data  = buffer + i * FLASH_PAGE_SIZE;
+      g_flash_op.data  = (FAR uint8_t *)buffer + i * FLASH_PAGE_SIZE;
       g_flash_op.count = FLASH_PAGE_SIZE;
 
       if (IS_XIP_ADDR(g_flash_op.data))
@@ -858,6 +956,7 @@ static int rp23xx_flash_ioctl(struct mtd_dev_s *dev, int cmd,
 
 struct mtd_dev_s *rp23xx_flash_mtd_initialize(void)
 {
+  size_t size;
 #ifndef CONFIG_RP23XX_FLASH_MTD_SAFE_XIP
   int i;
 #endif
@@ -937,6 +1036,23 @@ struct mtd_dev_s *rp23xx_flash_mtd_initialize(void)
     }
 #endif
 
+  /* An address past the end of the flash wraps around to the start, where
+   * the NuttX image is.  Refuse a region that does not fit.
+   */
+
+  size = rp23xx_flash_size();
+  if (size == 0)
+    {
+      fwarn("rp23xx_flash: unknown flash size; not checked\n");
+    }
+  else if (FS_OFFSET + FS_SIZE > size)
+    {
+      merr("ERROR: flash MTD region ends at 0x%08x, past the end of the "
+           "%zu byte flash\n", (unsigned)(FS_OFFSET + FS_SIZE), size);
+      set_errno(EINVAL);
+      return NULL;
+    }
+
   g_initialized = true;
 
   finfo("rp23xx_flash: %u sectors of %u bytes at 0x%08x\n",