xtensa/esp32s3: Report an access through an invalid MMU entry.

The PMS grants and refuses physical addresses, so it never sees an access
that no MMU entry translates.  The cache answered such an access with zeros
and raised nothing, and the task carried on with a value it never should
have had.

Enable EXTMEM_MMU_ENTRY_FAULT and route the Cache Invalid Access interrupt
to the handler that already serves the PMS monitors.  An unprivileged task
that makes the access is terminated with SIGSEGV;  a privileged one still
panics.  The latch is level triggered, so it is cleared with the others.

Read the cause before the clear, so the log tells the two apart:  a PMS
violation is a refused translation, an MMU entry fault is an access that was
never translated.

Give the kernel_oct configuration the addresses that examples/sandbox needs
to name its targets.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-08-11 20:09:28 +02:00 • committed by Alan C. Assis
parent df782bd1e5
commit 1b59698bde
3 changed files with 47 additions and 6 deletions

View file

@ -146,6 +146,16 @@ static void IRAM_ATTR pms_clear_violations(void)
modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_CLR_REG,
EXTMEM_CORE0_IBUS_REJECT_INT_CLR_M |
EXTMEM_CORE0_DBUS_REJECT_INT_CLR_M, 0);
/* The invalid MMU entry monitor. An access that no entry translates never
* reaches the PMS, which checks physical addresses, so without this the
* cache answers it with zeros and nothing is reported.
*/
modifyreg32(EXTMEM_CACHE_ILG_INT_CLR_REG, 0,
EXTMEM_MMU_ENTRY_FAULT_INT_CLR_M);
modifyreg32(EXTMEM_CACHE_ILG_INT_CLR_REG,
EXTMEM_MMU_ENTRY_FAULT_INT_CLR_M, 0);
}
#endif
@ -171,6 +181,16 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg)
{
#ifdef CONFIG_ESP32S3_USERFAULT_ABORT
uint32_t *regs = (uint32_t *)context;
const char *cause;
/* Read why before acknowledging, because the clear below drops the latch.
* The two causes are answered the same way and are worth telling apart in
* the log: a PMS violation is a refused translation, an MMU entry fault
* is an access that was never translated at all.
*/
cause = (getreg32(EXTMEM_CACHE_ILG_INT_ST_REG) &
EXTMEM_MMU_ENTRY_FAULT_ST_M) != 0 ? "MMU entry" : "PMS";
/* Acknowledge and re-arm the monitors first so the level-triggered
* interrupt does not immediately re-fire while we handle it.
@ -190,8 +210,8 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg)
struct tcb_s *tcb = this_task();
siginfo_t info;
_alert("SIGSEGV (PMS) task %s: PC=%08x\n",
get_task_name(tcb), (unsigned)regs[REG_PC]);
_alert("SIGSEGV (%s) task %s: PC=%08x\n",
cause, get_task_name(tcb), (unsigned)regs[REG_PC]);
info.si_signo = SIGSEGV;
info.si_code = SI_USER;
@ -603,7 +623,8 @@ void esp32s3_isolation_permissions(void)
* Name: esp32s3_pmsirqinitialize
*
* Description:
* Install the handler that reports a permission violation.
* Install the handlers that report a permission violation and an access
* that no MMU entry translates.
*
* Returned Value:
* None.
@ -621,8 +642,21 @@ void esp32s3_pmsirqinitialize(void)
VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_PIF_PMS_MONITOR_VIOLATE,
1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
/* Report an access that no MMU entry translates. The PMS grants and
* refuses physical addresses, so an untranslated access is invisible to
* it: the cache returns zeros and the task carries on with a value it
* never should have had. This monitor is what turns that into a fault.
*/
VERIFY(esp_setup_irq(ESP32S3_PERIPH_CACHE_IA,
1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
modifyreg32(EXTMEM_CACHE_ILG_INT_ENA_REG, 0,
EXTMEM_MMU_ENTRY_FAULT_INT_ENA_M);
up_enable_irq(ESP32S3_IRQ_CORE_0_IRAM0_PMS_MONITOR_VIOLATE);
up_enable_irq(ESP32S3_IRQ_CORE_0_DRAM0_PMS_MONITOR_VIOLATE);
up_enable_irq(ESP32S3_IRQ_CACHE_CORE0_ACS);
up_enable_irq(ESP32S3_IRQ_CORE_0_PIF_PMS_MONITOR_VIOLATE);
up_enable_irq(ESP32S3_IRQ_CACHE_IA);
}

View file

@ -86,9 +86,12 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs)
* these to the dispatcher; if serviced, return the register frame so that
* the RFE in the exception vector re-executes the faulting instruction.
*
* Note: ESP32-S3 PMS (World Controller) memory-protection violations are
* NOT delivered as these precise causes; they raise the asynchronous
* DRAM0/IRAM0 PMS-monitor interrupt instead (handled elsewhere).
* A PMS permission violation does not arrive as one of these causes. It
* raises the asynchronous DRAM0/IRAM0 PMS monitor interrupt, which
* pms_violation_isr() in esp32s3_isolation.c serves. So does an access
* that no MMU entry translates, which the cache reports separately. Both
* are installed by esp32s3_pmsirqinitialize(), in a protected build and in
* a kernel build alike.
*/
if (exccause == EXCCAUSE_LOAD_PROHIBITED ||

View file

@ -58,6 +58,10 @@ CONFIG_ESP32S3_SPIRAM_MODE_OCT=y
CONFIG_ESP32S3_UART0=y
CONFIG_ESP32S3_WCL=y
CONFIG_EXAMPLES_PFFAULT=y
CONFIG_EXAMPLES_SANDBOX=y
CONFIG_EXAMPLES_SANDBOX_KERNEL_ADDR=0x3fc98000
CONFIG_EXAMPLES_SANDBOX_PERIPH_ADDR=0x600c5000
CONFIG_EXAMPLES_SANDBOX_UNMAPPED_ADDR=0x3d800000
CONFIG_FS_PROCFS=y
CONFIG_FS_ROMFS=y
CONFIG_HAVE_CXX=y