remind-me-later opened a new pull request, #20461:
URL: https://github.com/apache/nuttx/pull/20461

   ## Summary
   
   With `CONFIG_STM32_ICACHE=y`, any read of the STM32H5 OTP, read-only (UID,
   flash size, package) or high-cycle data (EDATA) flash areas raises a precise
   bus fault. These areas only accept 16/32-bit accesses (RM0481 Table 77), and
   the manual requires the MPU to disable local cacheability for them
   (RM0481 7.3.2).
   
   The current code works around this piecemeal:
   
   - #19978 disables the ICACHE around `stm32_get_uniqueid()`;
   - `flash_read_eccsafe16()` in `stm32h563xx_flash.c` does the same for the
     OTP and EDATA word reads;
   - `nucleo-h563zi` maps the 4 KB OTP/RO area non-cacheable in its board code
     (72e468de8f, from #20122).
   
   Everything else still faults, including the in-tree `stm32_otp_read()`
   (plain 16-bit loads) and any application or out-of-tree board reading these
   areas. Nothing handles a bootloader that hands over with the ICACHE enabled,
   and flash programming runs with the ICACHE enabled.
   
   The series, one commit per step:
   
   1. **Fix unused warnings.** An ICACHE build with no ICACHE region configured
      warns about an unused `stm32_icache_setup_region()` and an unused
      `regval`. Independent of the rest.
   2. **Map `0x08fff000-0x09017fff` non-cacheable.** The OTP, RO and EDATA areas
      are contiguous, so one MPU region (Normal non-cacheable, execute-never)
      covers them. `STM32_ICACHE` now selects `ARM_MPU`, so
      `stm32_mpuinitialize()` has reset and enabled the MPU before the ICACHE is
      enabled. The `nucleo-h563zi` board region is removed in the same commit:
      the Armv8-M MPU faults on an address that matches two regions, so keeping
      both would make OTP and UID reads fault on that board (verified on
      hardware, see Testing 5).
   3. **Drop the per-call-site ICACHE disables.** With the region in place, the
      disable/enable pairs in `stm32_get_uniqueid()` and
      `flash_read_eccsafe16()` are redundant. Each pair invalidated the whole
      ICACHE, in `flash_read_eccsafe16()` with interrupts disabled and twice per
      32-bit OTP read.
   4. **Start the ICACHE from a clean state.** On first use the driver disables
      and invalidates the ICACHE (a bootloader may have left it enabled) before
      touching the associativity and region registers (writable only while
      `EN=0`), waits for a pending invalidate before enabling (RM0481 8.4.5), 
and
      bounds every `BUSYF` wait. `stm32_enable_icache()` returns `OK` or
      `-ETIMEDOUT`; on a timeout the ICACHE is left disabled. When
      `CONFIG_STM32_ICACHE` is not set, `__start` disables an ICACHE left 
enabled
      by a bootloader.
   5. **Disable the ICACHE while program memory is modified.** An enabled ICACHE
      does not manage write transactions and flags cacheable writes as errors
      (`ICACHE_SR.ERRF`); RM0481 8.4.5 recommends modifying the memory with the
      ICACHE disabled. On master, stale ICACHE lines also make
      `up_progmem_write()` and `up_progmem_eraseblock()` fail their read-back
      and erase checks with `-EIO` when the block was read through the ICACHE
      before (Testing 2). `up_progmem_eraseblock()` and
      `up_progmem_write()` now disable the ICACHE, which invalidates it, and
      restore it afterwards, logging if it cannot be re-enabled.
   6. **Documentation.** The STM32H5 peripheral table notes that the ICACHE
      uses the MPU.
   
   The steps are bundled because the ICACHE is only safe to enable with all of
   them in place; each commit builds and runs on its own, and the series is not
   a breaking change (see Impact).
   
   ## Impact
   
   - **New feature:** no. Existing feature changed: yes, the STM32H5 ICACHE
     driver (below).
   - **Config/build:** `STM32_ICACHE` now selects `ARM_MPU`. The option is only
     available on STM32H5 (`STM32_HAVE_ICACHE`), so other families are
     unaffected. The ICACHE uses one additional MPU region, allocated with
     `mpu_configure_region()`. No in-tree defconfig sets
     `CONFIG_STM32_ICACHE=y`.
   - **Boards:** `nucleo-h563zi` loses `stm32_mpu.c` and
     `stm32_mpu_configure_otp()`; the arch region replaces it and also covers
     EDATA. The removed region was read-only (`MPU_RBAR_AP_RORO`); the new one
     is read-write, so the OTP and EDATA write paths keep working.
   - **API:** `stm32_enable_icache()` returns `int` instead of `void`. Existing
     callers ignore the result and keep working. `stm32_disable_icache()` stays
     `void`: it clears `EN` before it waits, so the ICACHE is off even if the
     invalidate times out.
   - **Behavior:**
     - UID, OTP and EDATA reads no longer touch the ICACHE; they bypass it
       through the MPU region.
     - `stm32_disable_icache()` now waits for the invalidate that `EN=0`
       starts, and `stm32_enable_icache()` waits for a pending one. Both waits
       are bounded (`STM32_ICACHE_BUSY_TIMEOUT`, a margin because RM0481 gives
       no invalidate duration). After this series their only in-tree callers
       are `__start` and the flash erase/write paths.
     - `__start` disables an ICACHE left enabled when `CONFIG_STM32_ICACHE` is
       not set.
     - Flash erase/write briefly runs with the ICACHE disabled.
   - **Which writers are covered.** Only `up_progmem_write()` and
     `up_progmem_eraseblock()` write the main flash array, through stores to the
     cacheable `0x08xxxxxx` range, so only they need the ICACHE suspended. The
     OTP writers (`stm32_otp_write()`, `stm32_otp_word_write16/32()`) and the
     EDATA writers (`stm32_flash_edata_write()`, `edata_erase()`) store into
     `0x08fff000-0x09017fff`, which the new region makes non-cacheable, so they
     bypass the ICACHE and need no handling of their own. The option-byte
     functions (`stm32_flash_optmodify()`, `stm32_flash_swapbanks()`,
     `stm32_flash_edata_configure()`) only use control registers.
   - **MPU/HardFault:** `stm32_mpuinitialize()` enables the MPU with
     `HFNMIENA=0`, so the region does not apply inside the HardFault/NMI
     handlers. Before this series `stm32_get_uniqueid()` disabled the ICACHE
     itself, so a UID read from a fault handler worked; now it would fault.
     Nothing in the tree reads the UID from a fault handler.
   - **Docs:** the STM32H5 platform page notes that the ICACHE uses the MPU
     (separate commit).
   - **Security:** no.
   - **Compatibility:** not a breaking change. Callers that ignore the new
     `stm32_enable_icache()` return value build and behave as before. With
     `CONFIG_STM32_ICACHE=y` the MPU is now enabled and one region is used.
     Out-of-tree boards that map the OTP area themselves (as `nucleo-h563zi`
     did) must drop that region, or OTP/UID reads take a MemManage fault.
   
   ## Testing
   
   **Host:** Ubuntu 22.04.5, Intel Core i7-12800H, arm-none-eabi-gcc 10.2.1
   (GNU Arm Embedded 10-2020-q4).
   
   **Builds:** `nucleo-h563zi:nsh` with `CONFIG_STM32_PROGMEM`,
   `CONFIG_STM32H5_OTP_WORD` and `CONFIG_STM32_EDATA` added, since no in-tree
   configuration enables the ICACHE or these options: every commit with
   `CONFIG_STM32_ICACHE=y`, and the tip with the ICACHE off and with
   `CONFIG_STM32_ICACHE_DIRECT`. No warnings in the touched files.
   
   **Hardware:** a custom STM32H563 board (2 MB flash), flashed over SWD. The
   board is out of tree, so it cannot be built from this repository; the
   in-tree `nucleo-h563zi` builds above cover the same code.
   
   **Test setup.** Images were built from master (baseline) and from the tip of
   this series, on top of the board's `nsh` configuration with:
   
   - `CONFIG_STM32_ICACHE` set or not, as each test says;
   - `CONFIG_STM32_PROGMEM=y` and `CONFIG_STM32H5_OTP_WORD=y`, needed by the
     self-tests;
   - the NSH `mh` command enabled (`CONFIG_NSH_DISABLE_MH` not set).
   
   Tests 1 and 4 use only NSH and a debugger. Tests 2, 3 and 5 use two small
   self-tests (not part of this PR) added to the board's `stm32_bringup()`, so
   they run before NSH starts and print to the console through `syslog`:
   
   - **progmem self-test** (test 2): erases the last 8 KB flash block (block
     255 on a 2 MB part), reads its first word, which also loads that line into
     the ICACHE, programs 16 bytes with `up_progmem_write()`, reads the first
     word again, erases the block again and reads it once more. Every step
     prints the result and the expected value, then `PASS` or `FAIL`.
     `up_progmem_write()` reads the data back itself after programming; that
     check is what returns `-EIO` on master.
   - **UID/OTP self-test** (test 3): calls `stm32_get_uniqueid()`, reads the
     first 32 OTP words with `stm32_otp_word_read16()` (ECC-safe), then reads
     the same 64 bytes with `stm32_otp_read()` (plain 16-bit loads) and
     compares the two. The ECC-safe read runs first because a blank OTP word
     raises the flash ECC NMI on a plain load, ICACHE or not.
   - **Overlap variant** (test 5): the UID/OTP self-test, with an
     `mpu_configure_region()` call identical to the removed `nucleo-h563zi`
     one added before the reads.
   
   Registers were read with gdb over SWD after each run: `CFSR` `0xe000ed28`,
   `HFSR` `0xe000ed2c`, `MMFAR` `0xe000ed34`, `BFAR` `0xe000ed38`, `MPU_CTRL`
   `0xe000ed94`, `ICACHE_CR` `0x40030400`, `ICACHE_SR` `0x40030404`.
   
   *1. ICACHE enabled by `CONFIG_STM32_ICACHE=y` — read of the read-only area
   from NSH (`mh 08fff800 8`).*
   
   | | baseline (master) | this PR |
   |---|---|---|
   | console | `Assertion failed panic`, `PC` at the `ldrh` in `cmd_mh`, `R3 = 
08fff800` | four half-words printed |
   | `CFSR` | `0x00008200` (PRECISERR, BFARVALID) | `0x00000000` |
   | `HFSR` | `0x40000000` (FORCED) | `0x00000000` |
   | `BFAR` | `0x08fff800` | not valid |
   | `ICACHE_CR` | `0x5` (enabled) | `0x5` (enabled) |
   | `MPU_CTRL` | `0x0` | `0x5` |
   
   Baseline, same fault signature as #19978:
   
   ```
   nsh> mh 08fff800 8
   dump_assert_info: Assertion failed panic: at file: :0 task: <noname> ...
   up_dump_register: R0: 00000000 R1: 00000010 R2: 00000000  R3: 08fff800
   ```
   
   This PR:
   
   ```
   nsh> mh 08fff800 8
     0x8fff800 = 0x****
     0x8fff802 = 0x****
     0x8fff804 = 0x****
     0x8fff806 = 0x****
   ```
   
   *2. Erase and write program memory with the ICACHE enabled (progmem
   self-test).* The baseline result depends on the build: in one image the
   block read back correctly and only `ERRF` was set; in another (master
   `7e5cf3d5d0`, repeated twice) `up_progmem_write()` failed its read-back
   because it hit the erased data cached by the read just before.
   
   | | baseline, image A | baseline, image B | this PR |
   |---|---|---|---|
   | `up_progmem_write()` | `ret=16` | `ret=-5` (-EIO), `p[0]` still `ffffffff` 
| `ret=16`, `p[0]=11111111` |
   | self-test | PASS | FAIL | PASS |
   | `ICACHE_SR` after the self-test | `0x00000006` (BSYENDF, **ERRF**) | 
`0x00000006` | `0x00000000` |
   
   Baseline image B:
   
   ```
   SELFTEST: ICACHE enabled=1
   SELFTEST: erase #1 ret=8192 (want 8192)
   SELFTEST: after erase p[0]=ffffffff (want ffffffff)
   SELFTEST: write ret=-5 (want 16)
   SELFTEST: after write p[0]=ffffffff (want 11111111)
   ```
   
   To confirm the cause, image B was run under gdb with breakpoints on every
   `-EIO` in `up_progmem_write()`. It stopped at the read-back mismatch
   (`written = -EIO` after the `*fp++ != *rp++` comparison), with `FLASH_NSSR`
   and `FLASH_ECCDETR` both `0`, so programming itself succeeded. A debugger
   read of the block, which does not go through the ICACHE, showed the
   programmed data (`11111111 22222222 33333333 44444444`). After setting
   `ICACHE_CR.CACHEINV` from gdb and continuing, the self-test's next CPU read
   returned `11111111` instead of `ffffffff`. The erase check is affected the
   same way: in that run, the following `up_progmem_eraseblock()` returned
   `-EIO` because its erased-range check read the now cached `11111111`:
   
   ```
   SELFTEST: write ret=-5 (want 16)
   SELFTEST: after write p[0]=11111111 (want 11111111)
   SELFTEST: erase #2 ret=-5 (want 8192)
   SELFTEST: after erase p[0]=11111111 (want ffffffff)
   ```
   
   *3. UID and OTP reads through the driver APIs with the ICACHE enabled
   (UID/OTP self-test).*
   
   | | baseline (master) | this PR |
   |---|---|---|
   | `stm32_get_uniqueid()` | OK (ICACHE disabled around it) | OK (no ICACHE 
handling) |
   | `stm32_otp_word_read16()` x32 | OK (ICACHE disabled around it) | OK (no 
ICACHE handling) |
   | `stm32_otp_read()` | panic, `CFSR 0x8200`, `BFAR 0x08fff000` | `ret=0`, 
same data as `word_read16` |
   | `ICACHE_SR` afterwards | — | `0x00000000` |
   
   Baseline:
   
   ```
   OTPTEST: ICACHE_CR=00000005
   OTPTEST: stm32_get_uniqueid done, nonzero=1
   OTPTEST: word_read16 x32 err=0
   dump_assert_info: Assertion failed panic: at file: :0 task: <noname> ...
   up_dump_register: R0: 00000000 R1: 7ffffffe R2: 200041d8  R3: 08fff000
   ```
   
   This PR:
   
   ```
   OTPTEST: ICACHE_CR=00000005
   OTPTEST: stm32_get_uniqueid done, nonzero=1
   OTPTEST: word_read16 x32 err=0
   OTPTEST: stm32_otp_read ret=0 mismatch=0
   OTPTEST: ICACHE_SR=00000000
   OTPTEST: PASS
   ```
   
   *4. ICACHE left enabled by a bootloader, `CONFIG_STM32_ICACHE` not set.* The
   ICACHE is enabled through the debugger after reset and before the image
   starts, standing in for a bootloader hand-over, once direct-mapped
   (`ICACHE_CR = 0x1`) and once n-way (`0x5`). With OpenOCD and gdb:
   
   ```
   (gdb) monitor reset halt
   (gdb) set {unsigned int}0x40030400 = 1
   (gdb) monitor resume
   ```
   
   Then the self-tests run at boot and `mh 08fff800 8` is entered in NSH.
   
   | | baseline (master) | this PR |
   |---|---|---|
   | progmem self-test `up_progmem_write()` | `ret=-5` (-EIO), `p[0]` still 
`ffffffff` | `ret=16`, PASS |
   | UID/OTP self-test | not run | PASS |
   | `mh 08fff800 8` | panic, `CFSR 0x8200`, `BFAR 0x08fff800` | four 
half-words printed |
   | `ICACHE_CR` after boot | `0x1` (still enabled) | `0x0` (`0x4` for the 
n-way poke): `EN` cleared by `__start` |
   
   *5. Overlapping MPU regions.* To check why the `nucleo-h563zi` region has to
   go, the UID/OTP self-test was run on this PR after first adding a region
   identical to the removed one (`0x08fff000`, 4 KB, `XN | SH_NO | AP_RORO`,
   non-cacheable). The first UID
   read takes a MemManage fault, even though both regions allow reads:
   
   ```
   OTPTEST: adding overlapping OTP region (nucleo)
   OTPTEST: ICACHE_CR=00000005
   dump_assert_info: Assertion failed panic: at file: :0 task: <noname> ...
   up_dump_register: R0: 20004228 R1: 00000008 R2: 00000000  R3: 08fff800
   ```
   
   `CFSR 0x00000082` (DACCVIOL, MMARVALID), `MMFAR 0x08fff800`, `HFSR 0`.
   
   **Revisions:** "this PR" is the tip of this series. The baselines are
   master `7e5cf3d5d0` (table 2 image B, table 3) and `acc7d9bd3c` (tables 1
   and 4, table 2 image A).
   
   **Not tested:** other STM32H5 variants; `nucleo-h563zi` on hardware (built
   only; the overlap behavior is shown in table 5); EDATA reads and writes
   (they use the same helper and the same MPU region as the OTP); OTP writes;
   the `-ETIMEDOUT` paths (no way to make `BUSYF` stick); the invalidate
   latency; the ICACHE interrupt options (`STM32_ICACHE_INV_INT`,
   `STM32_ICACHE_ERR_INT`).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to