From acc66c547999ce444b79efccd67625a1af347d5d Mon Sep 17 00:00:00 2001 From: tangzz98 <54998890+tangzz98@users.noreply.github.com> Date: Mon, 30 Oct 2023 08:24:55 -0400 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0MPU=E6=8A=BD=E8=B1=A1?= =?UTF-8?q?=E5=B1=82=20(#8080)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为RT-Thread设计MPU抽象层,支持ARMV7-M,ARMV8-M架构,让用户使用MPU检测栈溢出等内存问题,实现线程内存隔离 - 在components/mp目录下提供通用的API,libcpu目录下提供各处理器架构的具体实现 - 在STM32U575 NUCLEO, STM32H75 NUCLEO开发板测试通过 --- bsp/stm32/stm32u575-st-nucleo/.config | 100 ++--- bsp/stm32/stm32u575-st-nucleo/board/board.c | 13 + bsp/stm32/stm32u575-st-nucleo/board/board.h | 4 + bsp/stm32/stm32u575-st-nucleo/rtconfig.h | 28 +- components/Kconfig | 1 + components/mprotect/Kconfig | 28 ++ components/mprotect/README.md | 155 ++++++++ components/mprotect/SConscript | 11 + .../mprotect_example_exception_hook.c | 20 + .../mprotect_example_exclusive_region.c | 75 ++++ .../examples/mprotect_example_ro_data.c | 88 +++++ components/mprotect/image/stack_guard.png | Bin 0 -> 53940 bytes components/mprotect/mprotect.c | 201 ++++++++++ components/mprotect/mprotect.h | 41 ++ include/rtdef.h | 8 + include/rthw.h | 4 + libcpu/arm/cortex-m33/SConscript | 3 + libcpu/arm/cortex-m33/context_gcc.S | 10 + libcpu/arm/cortex-m33/cpuport.c | 25 ++ libcpu/arm/cortex-m33/mpu.c | 355 ++++++++++++++++++ libcpu/arm/cortex-m33/mpu.h | 99 +++++ libcpu/arm/cortex-m33/mputype.h | 34 ++ libcpu/arm/cortex-m7/SConscript | 3 + libcpu/arm/cortex-m7/context_gcc.S | 10 + libcpu/arm/cortex-m7/cpuport.c | 25 ++ libcpu/arm/cortex-m7/mpu.c | 299 +++++++++++++++ libcpu/arm/cortex-m7/mpu.h | 104 +++++ libcpu/arm/cortex-m7/mputype.h | 29 ++ src/idle.c | 10 + src/scheduler_mp.c | 10 + src/scheduler_up.c | 10 + src/thread.c | 7 + 32 files changed, 1739 insertions(+), 71 deletions(-) create mode 100644 components/mprotect/Kconfig create mode 100644 components/mprotect/README.md create mode 100644 components/mprotect/SConscript create mode 100644 components/mprotect/examples/mprotect_example_exception_hook.c create mode 100644 components/mprotect/examples/mprotect_example_exclusive_region.c create mode 100644 components/mprotect/examples/mprotect_example_ro_data.c create mode 100644 components/mprotect/image/stack_guard.png create mode 100644 components/mprotect/mprotect.c create mode 100644 components/mprotect/mprotect.h create mode 100644 libcpu/arm/cortex-m33/mpu.c create mode 100644 libcpu/arm/cortex-m33/mpu.h create mode 100644 libcpu/arm/cortex-m33/mputype.h create mode 100644 libcpu/arm/cortex-m7/mpu.c create mode 100644 libcpu/arm/cortex-m7/mpu.h create mode 100644 libcpu/arm/cortex-m7/mputype.h diff --git a/bsp/stm32/stm32u575-st-nucleo/.config b/bsp/stm32/stm32u575-st-nucleo/.config index d4b40c231e..e3233a7f8e 100644 --- a/bsp/stm32/stm32u575-st-nucleo/.config +++ b/bsp/stm32/stm32u575-st-nucleo/.config @@ -11,6 +11,7 @@ CONFIG_RT_NAME_MAX=8 # CONFIG_RT_USING_SMART is not set # CONFIG_RT_USING_AMP is not set # CONFIG_RT_USING_SMP is not set +CONFIG_RT_CPUS_NR=1 CONFIG_RT_ALIGN_SIZE=8 # CONFIG_RT_THREAD_PRIORITY_8 is not set CONFIG_RT_THREAD_PRIORITY_32=y @@ -65,19 +66,15 @@ CONFIG_RT_USING_SMALL_MEM_AS_HEAP=y # CONFIG_RT_USING_MEMTRACE is not set # CONFIG_RT_USING_HEAP_ISR is not set CONFIG_RT_USING_HEAP=y - -# -# Kernel Device Object -# CONFIG_RT_USING_DEVICE=y # CONFIG_RT_USING_DEVICE_OPS is not set -# CONFIG_RT_USING_DM is not set # CONFIG_RT_USING_INTERRUPT_INFO is not set CONFIG_RT_USING_CONSOLE=y CONFIG_RT_CONSOLEBUF_SIZE=256 CONFIG_RT_CONSOLE_DEVICE_NAME="uart1" -CONFIG_RT_VER_NUM=0x50002 +CONFIG_RT_VER_NUM=0x50100 # CONFIG_RT_USING_STDC_ATOMIC is not set +CONFIG_RT_BACKTRACE_LEVEL_MAX_NR=32 # CONFIG_RT_USING_CACHE is not set CONFIG_RT_USING_HW_ATOMIC=y # CONFIG_ARCH_ARM_BOOTWITH_FLUSH_CACHE is not set @@ -121,6 +118,7 @@ CONFIG_FINSH_USING_OPTION_COMPLETION=y # # Device Drivers # +# CONFIG_RT_USING_DM is not set CONFIG_RT_USING_DEVICE_IPC=y CONFIG_RT_UNAMED_PIPE_NUMBER=64 CONFIG_RT_USING_SYSTEM_WORKQUEUE=y @@ -138,6 +136,7 @@ CONFIG_RT_USING_I2C=y # CONFIG_RT_I2C_DEBUG is not set CONFIG_RT_USING_I2C_BITOPS=y # CONFIG_RT_I2C_BITOPS_DEBUG is not set +# CONFIG_RT_USING_SOFT_I2C is not set # CONFIG_RT_USING_PHY is not set CONFIG_RT_USING_PIN=y CONFIG_RT_USING_ADC=y @@ -149,7 +148,6 @@ CONFIG_RT_USING_PWM=y # CONFIG_RT_USING_MTD_NOR is not set # CONFIG_RT_USING_MTD_NAND is not set # CONFIG_RT_USING_PM is not set -# CONFIG_RT_USING_FDT is not set # CONFIG_RT_USING_RTC is not set # CONFIG_RT_USING_SDIO is not set CONFIG_RT_USING_SPI=y @@ -238,6 +236,21 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_RT_USING_RT_LINK is not set # CONFIG_RT_USING_VBUS is not set +# +# Memory management +# +# CONFIG_RT_USING_MEMBLOCK is not set + +# +# Memory protection +# +CONFIG_RT_USING_MEM_PROTECTION=y +CONFIG_RT_USING_HW_STACK_GUARD=y +CONFIG_USE_MEM_PROTECTION_EXAMPLES=y +CONFIG_NUM_MEM_REGIONS=8 +CONFIG_NUM_EXCLUSIVE_REGIONS=2 +CONFIG_NUM_CONFIGURABLE_REGIONS=3 + # # RT-Thread Utestcases # @@ -261,6 +274,7 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_KAWAII_MQTT is not set # CONFIG_PKG_USING_BC28_MQTT is not set # CONFIG_PKG_USING_WEBTERMINAL is not set +# CONFIG_PKG_USING_LIBMODBUS is not set # CONFIG_PKG_USING_FREEMODBUS is not set # CONFIG_PKG_USING_NANOPB is not set @@ -328,8 +342,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_AGILE_FTP is not set # CONFIG_PKG_USING_EMBEDDEDPROTO is not set # CONFIG_PKG_USING_RT_LINK_HW is not set -# CONFIG_PKG_USING_RYANMQTT is not set -# CONFIG_PKG_USING_RYANW5500 is not set # CONFIG_PKG_USING_LORA_PKT_FWD is not set # CONFIG_PKG_USING_LORA_GW_DRIVER_LIB is not set # CONFIG_PKG_USING_LORA_PKT_SNIFFER is not set @@ -337,8 +349,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_SMALL_MODBUS is not set # CONFIG_PKG_USING_NET_SERVER is not set # CONFIG_PKG_USING_ZFTP is not set -# CONFIG_PKG_USING_WOL is not set -# CONFIG_PKG_USING_ZEPHYR_POLLING is not set # # security packages @@ -407,12 +417,17 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_MP3PLAYER is not set # CONFIG_PKG_USING_TINYJPEG is not set # CONFIG_PKG_USING_UGUI is not set + +# +# PainterEngine: A cross-platform graphics application framework written in C language +# +# CONFIG_PKG_USING_PAINTERENGINE is not set +# CONFIG_PKG_USING_PAINTERENGINE_AUX is not set # CONFIG_PKG_USING_MCURSES is not set # CONFIG_PKG_USING_TERMBOX is not set # CONFIG_PKG_USING_VT100 is not set # CONFIG_PKG_USING_QRCODE is not set # CONFIG_PKG_USING_GUIENGINE is not set -# CONFIG_PKG_USING_3GPP_AMRNB is not set # # tools packages @@ -422,7 +437,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_EASYLOGGER is not set # CONFIG_PKG_USING_SYSTEMVIEW is not set # CONFIG_PKG_USING_SEGGER_RTT is not set -# CONFIG_PKG_USING_RTT_AUTO_EXE_CMD is not set # CONFIG_PKG_USING_RDB is not set # CONFIG_PKG_USING_ULOG_EASYFLASH is not set # CONFIG_PKG_USING_LOGMGR is not set @@ -458,6 +472,7 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_CBOX is not set # CONFIG_PKG_USING_SNOWFLAKE is not set # CONFIG_PKG_USING_HASH_MATCH is not set +# CONFIG_PKG_USING_FIRE_PID_CURVE is not set # CONFIG_PKG_USING_ARMV7M_DWT_TOOL is not set # CONFIG_PKG_USING_VOFA_PLUS is not set @@ -530,8 +545,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_TFDB is not set # CONFIG_PKG_USING_QPC is not set # CONFIG_PKG_USING_AGILE_UPGRADE is not set -# CONFIG_PKG_USING_FLASH_BLOB is not set -# CONFIG_PKG_USING_MLIBC is not set # # peripheral libraries and drivers @@ -540,6 +553,7 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # # sensors drivers # +# CONFIG_PKG_USING_FINGERPRINT is not set # CONFIG_PKG_USING_LSM6DSM is not set # CONFIG_PKG_USING_LSM6DSL is not set # CONFIG_PKG_USING_LPS22HB is not set @@ -599,11 +613,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_AD7746 is not set # CONFIG_PKG_USING_ADT74XX is not set # CONFIG_PKG_USING_MAX17048 is not set -# CONFIG_PKG_USING_AS7341 is not set -# CONFIG_PKG_USING_CW2015 is not set -# CONFIG_PKG_USING_ICM20608 is not set -# CONFIG_PKG_USING_PAJ7620 is not set -# CONFIG_PKG_USING_STHS34PF80 is not set # # touch drivers @@ -616,10 +625,11 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_FT5426 is not set # CONFIG_PKG_USING_FT6236 is not set # CONFIG_PKG_USING_XPT2046_TOUCH is not set -# CONFIG_PKG_USING_CST816X is not set # CONFIG_PKG_USING_REALTEK_AMEBA is not set +# CONFIG_PKG_USING_AS7341 is not set # CONFIG_PKG_USING_STM32_SDIO is not set # CONFIG_PKG_USING_ESP_IDF is not set +# CONFIG_PKG_USING_ICM20608 is not set # CONFIG_PKG_USING_BUTTON is not set # CONFIG_PKG_USING_PCF8574 is not set # CONFIG_PKG_USING_SX12XX is not set @@ -629,6 +639,7 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_LKDGUI is not set # CONFIG_PKG_USING_NRF5X_SDK is not set # CONFIG_PKG_USING_NRFX is not set +# CONFIG_PKG_USING_WM_LIBRARIES is not set # # Kendryte SDK @@ -642,7 +653,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_AT24CXX is not set # CONFIG_PKG_USING_MOTIONDRIVER2RTT is not set # CONFIG_PKG_USING_PCA9685 is not set -# CONFIG_PKG_USING_ILI9341 is not set # CONFIG_PKG_USING_I2C_TOOLS is not set # CONFIG_PKG_USING_NRF24L01 is not set # CONFIG_PKG_USING_RPLIDAR is not set @@ -659,6 +669,7 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_CAN_YMODEM is not set # CONFIG_PKG_USING_LORA_RADIO_DRIVER is not set # CONFIG_PKG_USING_QLED is not set +# CONFIG_PKG_USING_PAJ7620 is not set # CONFIG_PKG_USING_AGILE_CONSOLE is not set # CONFIG_PKG_USING_LD3320 is not set # CONFIG_PKG_USING_WK2124 is not set @@ -686,17 +697,14 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_MISAKA_AT24CXX is not set # CONFIG_PKG_USING_MISAKA_RGB_BLING is not set # CONFIG_PKG_USING_LORA_MODEM_DRIVER is not set +# CONFIG_PKG_USING_BL_MCU_SDK is not set # CONFIG_PKG_USING_SOFT_SERIAL is not set # CONFIG_PKG_USING_MB85RS16 is not set +# CONFIG_PKG_USING_CW2015 is not set # CONFIG_PKG_USING_RFM300 is not set # CONFIG_PKG_USING_IO_INPUT_FILTER is not set # CONFIG_PKG_USING_RASPBERRYPI_PICO_SDK is not set # CONFIG_PKG_USING_LRF_NV7LIDAR is not set -# CONFIG_PKG_USING_AIP650 is not set -# CONFIG_PKG_USING_FINGERPRINT is not set -# CONFIG_PKG_USING_BT_ECB02C is not set -# CONFIG_PKG_USING_UAT is not set -# CONFIG_PKG_USING_SPI_TOOLS is not set # # AI packages @@ -711,15 +719,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_QUEST is not set # CONFIG_PKG_USING_NAXOS is not set -# -# Signal Processing and Control Algorithm Packages -# -# CONFIG_PKG_USING_FIRE_PID_CURVE is not set -# CONFIG_PKG_USING_QPID is not set -# CONFIG_PKG_USING_UKAL is not set -# CONFIG_PKG_USING_DIGITALCTRL is not set -# CONFIG_PKG_USING_KISSFFT is not set - # # miscellaneous packages # @@ -749,7 +748,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_TETRIS is not set # CONFIG_PKG_USING_DONUT is not set # CONFIG_PKG_USING_COWSAY is not set -# CONFIG_PKG_USING_MORSE is not set # CONFIG_PKG_USING_LIBCSV is not set # CONFIG_PKG_USING_OPTPARSE is not set # CONFIG_PKG_USING_FASTLZ is not set @@ -765,12 +763,14 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_DSTR is not set # CONFIG_PKG_USING_TINYFRAME is not set # CONFIG_PKG_USING_KENDRYTE_DEMO is not set +# CONFIG_PKG_USING_DIGITALCTRL is not set # CONFIG_PKG_USING_UPACKER is not set # CONFIG_PKG_USING_UPARAM is not set # CONFIG_PKG_USING_HELLO is not set # CONFIG_PKG_USING_VI is not set # CONFIG_PKG_USING_KI is not set # CONFIG_PKG_USING_ARMv7M_DWT is not set +# CONFIG_PKG_USING_UKAL is not set # CONFIG_PKG_USING_CRCLIB is not set # CONFIG_PKG_USING_LWGPS is not set # CONFIG_PKG_USING_STATE_MACHINE is not set @@ -781,8 +781,6 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_SLCAN2RTT is not set # CONFIG_PKG_USING_SOEM is not set # CONFIG_PKG_USING_QPARAM is not set -# CONFIG_PKG_USING_CorevMCU_CLI is not set -# CONFIG_PKG_USING_GET_IRQ_PRIORITY is not set # # Arduino libraries @@ -790,9 +788,8 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_RTDUINO is not set # -# Projects and Demos +# Projects # -# CONFIG_PKG_USING_ARDUINO_MSGQ_C_CPP_DEMO is not set # CONFIG_PKG_USING_ARDUINO_ULTRASOUND_RADAR is not set # CONFIG_PKG_USING_ARDUINO_SENSOR_KIT is not set # CONFIG_PKG_USING_ARDUINO_MATLAB_SUPPORT is not set @@ -800,17 +797,13 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # # Sensors # -# CONFIG_PKG_USING_ARDUINO_SENSOR_DEVICE_DRIVERS is not set # CONFIG_PKG_USING_ARDUINO_CAPACITIVESENSOR is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_ADXL375 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_VL53L0X is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_VL53L1X is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_SENSOR is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_VL6180X is not set # CONFIG_PKG_USING_ADAFRUIT_MAX31855 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_MAX31865 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_MAX31856 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_MAX6675 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_MLX90614 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_LSM9DS1 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_AHTX0 is not set @@ -931,27 +924,17 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_ARDUINO_SEEED_AT42QT1070 is not set # CONFIG_PKG_USING_ARDUINO_SEEED_LSM6DS3 is not set # CONFIG_PKG_USING_ARDUINO_SEEED_HDC1000 is not set -# CONFIG_PKG_USING_ARDUINO_SEEED_HM3301 is not set -# CONFIG_PKG_USING_ARDUINO_SEEED_MCP9600 is not set -# CONFIG_PKG_USING_ARDUINO_SEEED_LTC2941 is not set -# CONFIG_PKG_USING_ARDUINO_SEEED_LDC1612 is not set # # Display # -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_GFX_LIBRARY is not set # CONFIG_PKG_USING_ARDUINO_U8G2 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_ST7735 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_SSD1306 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_ILI9341 is not set # CONFIG_PKG_USING_SEEED_TM1637 is not set # # Timing # # CONFIG_PKG_USING_ARDUINO_MSTIMER2 is not set -# CONFIG_PKG_USING_ARDUINO_TICKER is not set -# CONFIG_PKG_USING_ARDUINO_TASKSCHEDULER is not set # # Data Processing @@ -975,17 +958,10 @@ CONFIG_RT_LIBC_TZ_DEFAULT_SEC=0 # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_PCF8574 is not set # CONFIG_PKG_USING_ARDUINO_ADAFRUIT_PCA9685 is not set # CONFIG_PKG_USING_ARDUINO_SEEED_PCF85063TP is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_TPA2016 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_DRV2605 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_DS1841 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_DS3502 is not set # # Other # -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_MFRC630 is not set -# CONFIG_PKG_USING_ARDUINO_ADAFRUIT_SI5351 is not set -# CONFIG_PKG_USING_ARDUINO_RTCLIB is not set # # Signal IO diff --git a/bsp/stm32/stm32u575-st-nucleo/board/board.c b/bsp/stm32/stm32u575-st-nucleo/board/board.c index 0ec6996618..69143bbb3b 100644 --- a/bsp/stm32/stm32u575-st-nucleo/board/board.c +++ b/bsp/stm32/stm32u575-st-nucleo/board/board.c @@ -10,6 +10,19 @@ #include "board.h" +#ifdef RT_USING_MEM_PROTECTION +#include "mprotect.h" + +rt_mem_region_t static_regions[NUM_STATIC_REGIONS] = { + /* Flash region, read only */ + { + .start = (void *)STM32_FLASH_START_ADRESS, + .size = (rt_size_t)STM32_FLASH_SIZE, + .attr = RT_MEM_REGION_P_RX_U_RX, + }, +}; +#endif + void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; diff --git a/bsp/stm32/stm32u575-st-nucleo/board/board.h b/bsp/stm32/stm32u575-st-nucleo/board/board.h index f1d911972c..50edb54364 100644 --- a/bsp/stm32/stm32u575-st-nucleo/board/board.h +++ b/bsp/stm32/stm32u575-st-nucleo/board/board.h @@ -42,6 +42,10 @@ extern int __bss_end; #define HEAP_END STM32_SRAM1_END +#ifdef RT_USING_MEM_PROTECTION +#define NUM_STATIC_REGIONS 1 +#endif + void SystemClock_Config(void); #ifdef __cplusplus diff --git a/bsp/stm32/stm32u575-st-nucleo/rtconfig.h b/bsp/stm32/stm32u575-st-nucleo/rtconfig.h index 2ef9bcacc3..33390f1d3e 100644 --- a/bsp/stm32/stm32u575-st-nucleo/rtconfig.h +++ b/bsp/stm32/stm32u575-st-nucleo/rtconfig.h @@ -7,6 +7,7 @@ /* RT-Thread Kernel */ #define RT_NAME_MAX 8 +#define RT_CPUS_NR 1 #define RT_ALIGN_SIZE 8 #define RT_THREAD_PRIORITY_32 #define RT_THREAD_PRIORITY_MAX 32 @@ -42,14 +43,12 @@ #define RT_USING_SMALL_MEM #define RT_USING_SMALL_MEM_AS_HEAP #define RT_USING_HEAP - -/* Kernel Device Object */ - #define RT_USING_DEVICE #define RT_USING_CONSOLE #define RT_CONSOLEBUF_SIZE 256 #define RT_CONSOLE_DEVICE_NAME "uart1" -#define RT_VER_NUM 0x50002 +#define RT_VER_NUM 0x50100 +#define RT_BACKTRACE_LEVEL_MAX_NR 32 #define RT_USING_HW_ATOMIC #define RT_USING_CPU_FFS #define ARCH_ARM @@ -126,6 +125,18 @@ /* Utilities */ +/* Memory management */ + + +/* Memory protection */ + +#define RT_USING_MEM_PROTECTION +#define RT_USING_HW_STACK_GUARD +#define USE_MEM_PROTECTION_EXAMPLES +#define NUM_MEM_REGIONS 8 +#define NUM_EXCLUSIVE_REGIONS 2 +#define NUM_CONFIGURABLE_REGIONS 3 + /* RT-Thread Utestcases */ @@ -164,6 +175,9 @@ /* u8g2: a monochrome graphic library */ +/* PainterEngine: A cross-platform graphics application framework written in C language */ + + /* tools packages */ @@ -195,9 +209,6 @@ /* AI packages */ -/* Signal Processing and Control Algorithm Packages */ - - /* miscellaneous packages */ /* project laboratory */ @@ -211,7 +222,7 @@ /* Arduino libraries */ -/* Projects and Demos */ +/* Projects */ /* Sensors */ @@ -236,7 +247,6 @@ /* Other */ - /* Signal IO */ diff --git a/components/Kconfig b/components/Kconfig index 45cd7e1f7c..21fc6e3ebc 100644 --- a/components/Kconfig +++ b/components/Kconfig @@ -35,5 +35,6 @@ source "$RTT_DIR/components/net/Kconfig" source "$RTT_DIR/components/utilities/Kconfig" source "$RTT_DIR/components/vbus/Kconfig" source "$RTT_DIR/components/mm/Kconfig" +source "$RTT_DIR/components/mprotect/Kconfig" endmenu diff --git a/components/mprotect/Kconfig b/components/mprotect/Kconfig new file mode 100644 index 0000000000..82348cbd9b --- /dev/null +++ b/components/mprotect/Kconfig @@ -0,0 +1,28 @@ +menu "Memory protection" + +config RT_USING_MEM_PROTECTION + bool "Enable memory protection" + default n + select RT_USING_HEAP + +config RT_USING_HW_STACK_GUARD + bool "Enable hardware stack guard" + default n + select RT_USING_MEM_PROTECTION + +if RT_USING_MEM_PROTECTION + config USE_MEM_PROTECTION_EXAMPLES + bool "Use memory protection examples" + default y + + config NUM_MEM_REGIONS + int "Total number of memory protection regions supported by hardware" + + config NUM_EXCLUSIVE_REGIONS + int "Total number of exclusive memory regions added using rt_mprotect_add_exclusive_region API" + + config NUM_CONFIGURABLE_REGIONS + int "Maximum number of configurable memory regions for each thread, excluding stack guard and exclusive regions added using rt_mprotect_add_exclusive_region API" +endif + +endmenu diff --git a/components/mprotect/README.md b/components/mprotect/README.md new file mode 100644 index 0000000000..73c2671b10 --- /dev/null +++ b/components/mprotect/README.md @@ -0,0 +1,155 @@ +# RT-Thread MPU抽象层 +Mprotect(Memory Protection)组件是为不同处理器架构的内存保护单元提供的一套通用框架,让用户能使用这套框架解决一些常见的内存问题。 + +# 内存保护单元 +内存保护单元是一个可编程的设备,用来指定一块特定内存区域的访问权限,比如读,写,和从该区域执行代码的权限。内存保护单元可以增加系统的健壮性,预防一些黑客的攻击。ARMV7-M和ARMV8-M都提供了内存保护单元,简称MPU(Memory Protection Unit)。[论坛里的这篇文章](https://club.rt-thread.org/ask/article/610305c1379b9e5e.html)提供了ARM MPU更详细的介绍。RISC-V也提供了相似的功能,简称PMP(Physical Memory Protection),具体可参考[RISC-V架构手册](https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf)。 + +# 硬件支持 +目前支持ARMV7-M和ARMV8-M架构。本目录下存放框架的通用代码和两个简单的例程。硬件相关的代码存放在`libcpu`目录。 + +# 功能简介 +RT-Thread操作系统的任务和内核使用同一个地址空间,全部运行在特权级。所有代码默认对任何内存都有读,写,和执行的权限。使用MPU框架可以给特定的内存区域设置更低的权限,如只读权限。MPU框架可以被用来实现以下的功能: + +- 把关键数据或代码设置成只读,防止它们被破坏 +- 任务隔离,设定特定地址只能由特定的任务访问 +- 检测栈溢出 +- 把数据区域设置为不可执行,防止栈溢出攻击 + +# 使用方法 +## Menuconfig配置 +通过`menuconfig`进入`RT-Thread Components->Memory Protection`配置相关选项 + +- `RT_USING_MEM_PROTECTION`:开启MPU抽象层 +- `RT_USING_HW_STACK_GUARD`:使用MPU检测栈溢出。具体实现原理是在任务栈顶和栈底各设置一个MPU区域,权限设置为不可访问。如果发生栈溢出,代码访问了MPU保护的地址,会触发异常 +- `NUM_MEM_REGIONS`:硬件支持的MPU区域数量 +- `NUM_EXCLUSIVE_REGIONS`:使用`rt_mprotect_add_exclusive_region`函数配置的内存区域数量 +- `NUM_CONFIGURABLE_REGIONS`:各任务可以通过`rt_mprotect_add_region`函数配置的内存区域数量 + +## 内存区域配置 +MPU抽象层提供了以下的API来配置任务对内存区域的权限: + +- `rt_err_t rt_mprotect_add_region(rt_thread_t thread, rt_mem_region_t *region)`:添加内存区域 +- `rt_err_t rt_mprotect_delete_region(rt_thread_t thread, rt_mem_region_t *region)`:删除内存区域 +- `rt_err_t rt_mprotect_update_region(rt_thread_t thread, rt_mem_region_t *region)`:更新内存区域配置 + +内存区域的特性由`rt_mem_region_t`结构体定义: +``` +typedef struct { + void *start; /* 起始地址 */ + rt_size_t size; /* 区域大小 */ + rt_mem_attr_t attr; /* 区域特性 */ +} rt_mem_region_t; +``` +其中`attr`可通过以下宏来定义,使用这样定义的代码在任何处理器架构下都是通用的: + +- `RT_MEM_REGION_P_RW_U_RW`:可读写 +- `RT_MEM_REGION_P_RO_U_RO`: 只读 +- `RT_MEM_REGION_P_NA_U_NA`:不可访问 +- `RT_MEM_REGION_P_RWX_U_RWX`:可读写,执行 +- `RT_MEM_REGION_P_RX_U_RX`:只读,可执行 + +通常程序需要定义一块内存区域只能由一个特定的任务访问。允许访问该内存区域的任务可以调用以下函数实现这个功能: + +- `rt_err_t rt_mprotect_add_exclusive_region(void *start, rt_size_t size)`:添加内存区域 +- `rt_err_t rt_mprotect_delete_exclusive_region(void *start, rt_size_t size)`:删除内存区域 + +调用了`rt_mprotect_add_exclusive_region`的任务在退出前必须调用`rt_mprotect_delete_exclusive_region`删除内存区域。 + +## 初始化 +使用MPU抽象层之前需要在`board.h`文件定义固定的MPU区域数量: +``` +#define NUM_STATIC_REGIONS 2 +``` +在`board.c`文件定义固定的MPU区域特性: +``` +rt_mem_region_t static_regions[NUM_STATIC_REGIONS] = { + /* Flash region, read only */ + { + .start = (void *)STM32_FLASH_START_ADRESS, + .size = (rt_size_t)STM32_FLASH_SIZE, + .attr = RT_MEM_REGION_P_RX_U_RX, + }, + /* SRAM regin, no execute */ + { + .start = (void *)STM32_SRAM_START_ADDRESS, + .size = (rt_size_t)STM32_SRAM_SIZE, + .attr = RT_MEM_REGION_P_RW_U_RW, + }, +}; +``` +任何代码进行内存访问,都要遵守这些区域的配置。可以用固定的MPU区域,把代码段配置为只读,可执行,把数据段配置成可读写,不可执行。 + +另外必须确保配置的MPU区域数量满足以下的关系: +- 如果开启了`RT_USING_HW_STACK_GUARD`:`NUM_STATIC_REGIONS` + `NUM_CONFIGURABLE_REGIONS` + `NUM_EXCLUSIVE_REGIONS` + 2 <= `NUM_MEM_REGIONS` +- 如果没有开启`RT_USING_HW_STACK_GUARD`:`NUM_STATIC_REGIONS` + `NUM_CONFIGURABLE_REGIONS` + `NUM_EXCLUSIVE_REGIONS` <= `NUM_MEM_REGIONS` + +## 异常检测 +程序可以注册钩子函数,用来检测内存异常: +``` +rt_err_t rt_hw_mpu_exception_set_hook(rt_hw_mpu_exception_hook_t hook) +``` +`hook`函数会在发生内存异常时被调用。函数声明如下: +``` +typedef void (*rt_hw_mpu_exception_hook_t)(rt_mem_exception_info_t *) +``` +`rt_mem_exception_info_t`结构体根据处理器机构定义,对于ARM架构,提供以下用来诊断内存异常的信息: +``` +typedef struct { + rt_thread_t thread; /* 触发异常的线程 */ + void *addr; /* 发生异常的地址 */ + rt_mem_region_t region; /* 地址对应的内存区域 */ + rt_uint8_t mmfsr; /* MemManage Status寄存器的值 */ +} rt_mem_exception_info_t; +``` + +# 对RT-Thread内核的影响 +## 线程内存区域的保存 +Mprotect组件在`rt_thread_t`结构体添加了`mem_regions`成员变量,用于保存线程内存区域的配置。 +``` +struct rt_thread +{ + ...... +#ifdef RT_USING_MEM_PROTECTION + void *mem_regions; +#endif + ...... +} +``` +`mem_regions`的内存采用动态分配,并在删除线程时释放。 +在切换线程时调用`rt_hw_mpu_table_switch`,切换线程的内存区域配置。 +``` +#if defined (RT_USING_MEM_PROTECTION) + PUSH {r0-r3, r12, lr} + LDR r1, =rt_current_thread + LDR r0, [r1] + BL rt_hw_mpu_table_switch + POP {r0-r3, r12, lr} +#endif +``` + +## 栈溢出检测的实现原理 +线程创建时内核会根据用户指定的参数为栈分配内存,之后调用`rt_hw_stack_guard_init`配置栈溢出检测。栈溢出检测的实现原理是在线程栈底和栈顶分配两块不可读写的内存区域,如果代码访问这块内存,就会触发异常。 +![stack guard](image/stack_guard.png) +这种方法会改变内核代码可以操作的栈的起始地址和大小。因此`rt_hw_stack_guard_init`会调整`rt_thread_t->stack_addr`,指向允许访问的栈内存的起始地址,调整`rt_thread_t->stack_size`反映允许操作的内存大小,并在`rt_thread_t`添加成员变量`stack_buf`,指向原本为栈分配的内存的起始地址。这样,内核代码可以对栈进行正常操作,无需改动。 + +应用程序需要注意,如果开启了栈溢出检测,线程实际可以使用的栈空间会比分配的内存更小。因此在创建线程时,需要考虑增加`stack_size`参数。 + +在删除线程时要使用`stack_buf`变量,正确释放为栈分配的内存。 +``` +static void rt_defunct_execute(void) +{ + ...... + if (object_is_systemobject == RT_FALSE) + { + /* release thread's stack */ +#ifdef RT_USING_HW_STACK_GUARD + RT_KERNEL_FREE(thread->stack_buf); +#else + RT_KERNEL_FREE(thread->stack_addr); +#endif + /* delete thread object */ + rt_object_delete((rt_object_t)thread); + } + ...... +} +``` \ No newline at end of file diff --git a/components/mprotect/SConscript b/components/mprotect/SConscript new file mode 100644 index 0000000000..89d224a27f --- /dev/null +++ b/components/mprotect/SConscript @@ -0,0 +1,11 @@ +from building import * + +cwd = GetCurrentDir() +src = Glob('*.c') +if GetDepend("USE_MEM_PROTECTION_EXAMPLES"): + src += Glob('examples/*.c') +CPPPATH = [cwd] + +group = DefineGroup('mprotect', src, depend = ['RT_USING_MEM_PROTECTION'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/mprotect/examples/mprotect_example_exception_hook.c b/components/mprotect/examples/mprotect_example_exception_hook.c new file mode 100644 index 0000000000..7a8622205e --- /dev/null +++ b/components/mprotect/examples/mprotect_example_exception_hook.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-9-25 tangzz98 the first version + */ + +#include +#include + +void mprotect_example_exception_hook(rt_mem_exception_info_t *info) +{ + rt_kprintf("Memory manage exception\n"); + rt_kprintf("Faulting thread: %s\n", info->thread->parent.name); + rt_kprintf("Faulting address: %p\n", info->addr); + rt_kprintf("Faulting region: %p - %p", info->region.start, (void *)((rt_size_t)info->region.start + info->region.size)); +} diff --git a/components/mprotect/examples/mprotect_example_exclusive_region.c b/components/mprotect/examples/mprotect_example_exclusive_region.c new file mode 100644 index 0000000000..f1d446cca8 --- /dev/null +++ b/components/mprotect/examples/mprotect_example_exclusive_region.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#include +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +rt_align(MPU_MIN_REGION_SIZE) rt_uint8_t thread1_private_data[MPU_MIN_REGION_SIZE]; + +static void thread1_entry(void *parameter) +{ + (void)parameter; + /* Thread 1 configures thread1_private_data for exclusive access */ + rt_kprintf("Thread 1 configures private data\n"); + rt_mprotect_add_exclusive_region((void *)thread1_private_data, MPU_MIN_REGION_SIZE); + rt_kprintf("Thread 1 private data address: %p - %p\n", &thread1_private_data[0], &thread1_private_data[MPU_MIN_REGION_SIZE]); + rt_kprintf("Thread 1 reads and writes to its private data\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + /* Thread 1 has access to its private data */ + thread1_private_data[i] = i; + rt_kprintf("thread1_private_data[%d] = %d\n", i, thread1_private_data[i]); + } +} + +static void thread2_entry(void *parameter) +{ + (void)parameter; + rt_kprintf("Thread 2 writes to thread 1's private data\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + /* + * Thread 2 does not have access to thread 1's private data. + * Access generates an exception. + */ + thread1_private_data[i] = i; + } +} + +int mprotect_example_exclusive_region() +{ + extern void mprotect_example_exception_hook(rt_mem_exception_info_t *info); + rt_hw_mpu_exception_set_hook(mprotect_example_exception_hook); + rt_thread_t tid1 = RT_NULL; + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, + THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + rt_thread_t tid2 = RT_NULL; + tid2 = rt_thread_create("thread2", + thread2_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + return 0; +} + +MSH_CMD_EXPORT(mprotect_example_exclusive_region, Memory protection example (exclusive_region)); diff --git a/components/mprotect/examples/mprotect_example_ro_data.c b/components/mprotect/examples/mprotect_example_ro_data.c new file mode 100644 index 0000000000..97fff6da67 --- /dev/null +++ b/components/mprotect/examples/mprotect_example_ro_data.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#include +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +rt_align(MPU_MIN_REGION_SIZE) rt_uint8_t ro_data[MPU_MIN_REGION_SIZE]; + +static void thread1_entry(void *parameter) +{ + (void)parameter; + rt_kprintf("ro_data address: %p - %p\n", &ro_data[0], &ro_data[MPU_MIN_REGION_SIZE]); + /* Thread 1 can write ro_data before configuring memory protection. */ + rt_kprintf("Thread 1 writes to ro_data before configuring memory protection\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + ro_data[i] = i; + rt_kprintf("ro_data[%d] = %d\n", i, ro_data[i]); + } + rt_mem_region_t ro_region = + { + .start = (void *)ro_data, + .size = MPU_MIN_REGION_SIZE, + .attr = RT_MEM_REGION_P_RO_U_RO, + }; + /* Thread 1 configures ro_data as read only. */ + rt_kprintf("Thread 1 configures ro_data for read-only access for thread 1\n"); + rt_mprotect_add_region(RT_NULL, &ro_region); + rt_kprintf("Thread 1 reads ro_data\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + rt_kprintf("ro_data[%d] = %d\n", i, ro_data[i]); + } + rt_thread_delay(RT_TICK_PER_SECOND * 1); + /* Thread 1 cannot write ro_data. */ + rt_kprintf("Thread 1 writes to ro_data\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + ro_data[i] = i; + } +} + +static void thread2_entry(void *parameter) +{ + (void)parameter; + rt_kprintf("Thread 2 writes to ro_data\n"); + for (int i = 0; i < MPU_MIN_REGION_SIZE; i++) + { + /* Thread 2 can write ro_data. */ + ro_data[i] = i; + rt_kprintf("ro_data[%d] = %d\n", i, ro_data[i]); + } +} + +int mprotect_example_ro_data() +{ + extern void mprotect_example_exception_hook(rt_mem_exception_info_t *info); + rt_hw_mpu_exception_set_hook(mprotect_example_exception_hook); + rt_thread_t tid1 = RT_NULL; + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, + THREAD_TIMESLICE); + rt_thread_startup(tid1); + rt_thread_t tid2 = RT_NULL; + tid2 = rt_thread_create("thread2", + thread2_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + rt_thread_startup(tid2); + + return 0; +} + +MSH_CMD_EXPORT(mprotect_example_ro_data, Memory protection example (read-only data)); diff --git a/components/mprotect/image/stack_guard.png b/components/mprotect/image/stack_guard.png new file mode 100644 index 0000000000000000000000000000000000000000..5f05e765b7678834cb4b168239b40d21f0162772 GIT binary patch literal 53940 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 b>+Sb{$1qNnOcPG(4i@6;lSga6jJok(cuEmY literal 0 HcmV?d00001 diff --git a/components/mprotect/mprotect.c b/components/mprotect/mprotect.c new file mode 100644 index 0000000000..90f8f52b0f --- /dev/null +++ b/components/mprotect/mprotect.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#include "mprotect.h" + +#define DBG_ENABLE +#define DBG_SECTION_NAME "MEMORY PROTECTION" +#define DBG_LEVEL DBG_ERROR +#include + +rt_mem_exclusive_region_t exclusive_regions[NUM_EXCLUSIVE_REGIONS] = {}; + +rt_mem_region_t *rt_mprotect_find_free_region(rt_thread_t thread) +{ + rt_uint8_t i; + rt_mem_region_t *free_region = RT_NULL; + if (thread->mem_regions != RT_NULL) + { + for (i = 0U; i < NUM_DYNAMIC_REGIONS; i++) + { + if (((rt_mem_region_t *)thread->mem_regions)[i].size == 0) + { + free_region = &(((rt_mem_region_t *)thread->mem_regions)[i]); + break; + } + } + } + + return free_region; +} + +rt_mem_region_t *rt_mprotect_find_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t i; + rt_mem_region_t *found_region = RT_NULL; + if (thread->mem_regions != RT_NULL) + { + for (i = 0U; i < NUM_DYNAMIC_REGIONS; i++) + { + if ((((rt_mem_region_t *)thread->mem_regions)[i].start == region->start) && (((rt_mem_region_t *)thread->mem_regions)[i].size == region->size)) + { + found_region = &(((rt_mem_region_t *)thread->mem_regions)[i]); + break; + } + } + } + + return found_region; +} + +/** + * @brief This function will initialize memory protection. + * + * @return Return the operation status. When the return value is RT_EOK, the initialization is successful. + * When the return value is any other values, it means the initialization failed. + */ +int rt_mprotect_init(void) +{ + return (int)rt_hw_mpu_init(); +} + +/** + * @brief The function will add a memory region configuraiton for a thread. + * + * @param thread is the thread that the memory region configuration will apply to. + * + * @param region is the configuration for the memory region to add. + * + * @return Return the operation status. When the return value is RT_EOK, the operation is successful. + * If the return value is any other values, it represents the operation failed. + */ +rt_err_t rt_mprotect_add_region(rt_thread_t thread, rt_mem_region_t *region) +{ + if (thread == RT_NULL) + { + thread = rt_thread_self(); + } + if (thread->mem_regions == RT_NULL) + { + thread->mem_regions = RT_KERNEL_MALLOC(NUM_DYNAMIC_REGIONS * sizeof(rt_mem_region_t)); + if (thread->mem_regions == RT_NULL) + { + return RT_ERROR; + } + rt_memset(thread->mem_regions, 0U, sizeof(rt_mem_region_t ) * NUM_DYNAMIC_REGIONS); + } + return rt_hw_mpu_add_region(thread, region); +} + +/** + * @brief The function will delete an existing memory region configuraiton for a thread. + * + * @param thread is the thread that the memory region configuration will apply to. + * + * @param region is the configuration for the memory region to delete. + * + * @return Return the operation status. When the return value is RT_EOK, the operation is successful. + * If the return value is any other values, it represents the operation failed. + */ +rt_err_t rt_mprotect_delete_region(rt_thread_t thread, rt_mem_region_t *region) +{ + if (thread == RT_NULL) + { + thread = rt_thread_self(); + } + return rt_hw_mpu_delete_region(thread, region); +} + +/** + * @brief The function will update an existing memory region configuraiton for a thread. + * + * @param thread is the thread that the memory region configuration will apply to. + * + * @param region is the new configuration for the memory region. + * + * @return Return the operation status. When the return value is RT_EOK, the operation is successful. + * If the return value is any other values, it represents the operation failed. + */ +rt_err_t rt_mprotect_update_region(rt_thread_t thread, rt_mem_region_t *region) +{ + if (thread == RT_NULL) + { + thread = rt_thread_self(); + } + return rt_hw_mpu_update_region(thread, region); +} + +/** + * @brief The function will add a memory region that is only accessible by the calling thread. + * + * @param start is the start address of the memory region. + * + * @param size is the size of the memory region. + * + * @return Return the operation status. When the return value is RT_EOK, the operation is successful. + * If the return value is any other values, it represents the operation failed. + */ +rt_err_t rt_mprotect_add_exclusive_region(void *start, rt_size_t size) +{ + rt_uint8_t i; + rt_mem_exclusive_region_t region; + region.owner = rt_thread_self(); + region.region.start = start; + region.region.size = size; + region.region.attr = RT_MEM_REGION_P_NA_U_NA; + if (rt_hw_mpu_add_region(RT_NULL, (rt_mem_region_t *)(&(region.region))) != RT_EOK) + { + return RT_ERROR; + } + rt_enter_critical(); + for (i = 0; i < NUM_EXCLUSIVE_REGIONS; i++) + { + if (exclusive_regions[i].owner == RT_NULL) + { + rt_memcpy(&(exclusive_regions[i]), ®ion, sizeof(rt_mem_exclusive_region_t)); + rt_exit_critical(); + return RT_EOK; + } + } + rt_exit_critical(); + LOG_E("Insufficient regions"); + return RT_ERROR; +} + +/** + * @brief The function will delete a memory region that is only accessible by the calling thread. + * The deleted region will be accessible by other threads. + * + * @param start is the start address of the memory region. + * + * @param size is the size of the memory region. + * + * @return Return the operation status. When the return value is RT_EOK, the operation is successful. + * If the return value is any other values, it represents the operation failed. + */ +rt_err_t rt_mprotect_delete_exclusive_region(void *start, rt_size_t size) +{ + rt_uint8_t i; + rt_enter_critical(); + for (i = 0; i < NUM_EXCLUSIVE_REGIONS; i++) + { + if (exclusive_regions[i].owner == rt_thread_self() && exclusive_regions[i].region.start == start && exclusive_regions[i].region.size == size) + { + exclusive_regions[i].owner = RT_NULL; + rt_exit_critical(); + return RT_EOK; + } + } + rt_exit_critical(); + LOG_E("Region not found"); + return RT_ERROR; +} + +INIT_BOARD_EXPORT(rt_mprotect_init); diff --git a/components/mprotect/mprotect.h b/components/mprotect/mprotect.h new file mode 100644 index 0000000000..c350467b4f --- /dev/null +++ b/components/mprotect/mprotect.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#ifndef __MPROTECT_H__ +#define __MPROTECT_H__ + +#include +#include + +#define ADDR_IN_REGION(addr, region) (((rt_size_t)(addr) >= (rt_size_t)((region)->start)) && ((rt_size_t)(addr) < (rt_size_t)((region)->start) + (region)->size)) + +typedef struct +{ + void *start; + rt_size_t size; + rt_mem_attr_t attr; +} rt_mem_region_t; + +#include + +typedef struct +{ + rt_mem_region_t region; + rt_thread_t owner; +} rt_mem_exclusive_region_t; + +int rt_mprotect_init(void); +rt_err_t rt_mprotect_add_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_mprotect_delete_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_mprotect_update_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_mprotect_add_exclusive_region(void *start, rt_size_t size); +rt_err_t rt_mprotect_delete_exclusive_region(void *start, rt_size_t size); + +#endif /* __MPROTECT_H__ */ diff --git a/include/rtdef.h b/include/rtdef.h index c62167f73f..10edc25df1 100644 --- a/include/rtdef.h +++ b/include/rtdef.h @@ -997,6 +997,14 @@ struct rt_thread int *clear_child_tid; #endif /* ARCH_MM_MMU */ #endif /* RT_USING_SMART */ + +#ifdef RT_USING_MEM_PROTECTION + void *mem_regions; +#ifdef RT_USING_HW_STACK_GUARD + void *stack_buf; +#endif +#endif + rt_atomic_t ref_count; struct rt_spinlock spinlock; rt_ubase_t user_data; /**< private user data beyond this thread */ diff --git a/include/rthw.h b/include/rthw.h index 6e7773805a..ba3bb14cbd 100644 --- a/include/rthw.h +++ b/include/rthw.h @@ -99,6 +99,10 @@ rt_uint8_t *rt_hw_stack_init(void *entry, rt_uint8_t *stack_addr, void *exit); +#ifdef RT_USING_HW_STACK_GUARD +void rt_hw_stack_guard_init(rt_thread_t thread); +#endif + /* * Interrupt handler definition */ diff --git a/libcpu/arm/cortex-m33/SConscript b/libcpu/arm/cortex-m33/SConscript index b259a94c94..8e6881ce06 100644 --- a/libcpu/arm/cortex-m33/SConscript +++ b/libcpu/arm/cortex-m33/SConscript @@ -18,6 +18,9 @@ if rtconfig.PLATFORM in ['gcc']: if rtconfig.PLATFORM in ['iccarm']: src += Glob('*_iar.S') +if not GetDepend('RT_USING_MEM_PROTECTION') and not GetDepend('RT_USING_HW_STACK_GUARD'): + SrcRemove(src, 'mpu.c') + group = DefineGroup('CPU', src, depend = [''], CPPPATH = CPPPATH) Return('group') diff --git a/libcpu/arm/cortex-m33/context_gcc.S b/libcpu/arm/cortex-m33/context_gcc.S index d1a4921a9d..c0bc32222e 100644 --- a/libcpu/arm/cortex-m33/context_gcc.S +++ b/libcpu/arm/cortex-m33/context_gcc.S @@ -17,6 +17,8 @@ */ /*@{*/ +#include + .cpu cortex-m4 .syntax unified .thumb @@ -192,6 +194,14 @@ contex_ns_load: VLDMIAEQ r1!, {d8 - d15} /* pop FPU register s16~s31 */ #endif +#if defined (RT_USING_MEM_PROTECTION) + PUSH {r0-r3, r12, lr} + LDR r1, =rt_current_thread + LDR r0, [r1] + BL rt_hw_mpu_table_switch + POP {r0-r3, r12, lr} +#endif + pendsv_exit: MSR psp, r1 /* update stack pointer */ /* restore interrupt */ diff --git a/libcpu/arm/cortex-m33/cpuport.c b/libcpu/arm/cortex-m33/cpuport.c index 749932c609..c7cc7e4ada 100644 --- a/libcpu/arm/cortex-m33/cpuport.c +++ b/libcpu/arm/cortex-m33/cpuport.c @@ -18,6 +18,9 @@ */ #include +#ifdef RT_USING_HW_STACK_GUARD +#include +#endif #if /* ARMCC */ ( (defined ( __CC_ARM ) && defined ( __TARGET_FPU_VFP )) \ /* Clang */ || (defined ( __clang__ ) && defined ( __VFP_FP__ ) && !defined(__SOFTFP__)) \ @@ -233,6 +236,28 @@ rt_uint8_t *rt_hw_stack_init(void *tentry, return stk; } +#ifdef RT_USING_HW_STACK_GUARD +void rt_hw_stack_guard_init(rt_thread_t thread) +{ + rt_mem_region_t stack_top_region, stack_bottom_region; + rt_ubase_t stack_bottom = (rt_ubase_t)thread->stack_addr; + rt_ubase_t stack_top = (rt_ubase_t)((rt_uint8_t *)thread->stack_addr + thread->stack_size); + rt_ubase_t stack_bottom_region_start = RT_ALIGN(stack_bottom, MPU_MIN_REGION_SIZE); + rt_ubase_t stack_top_region_start = RT_ALIGN_DOWN(stack_top - MPU_MIN_REGION_SIZE, MPU_MIN_REGION_SIZE); + stack_top_region.start = (void *)stack_top_region_start; + stack_top_region.size = MPU_MIN_REGION_SIZE; + stack_top_region.attr = RT_MEM_REGION_P_RO_U_NA; + stack_bottom_region.start = (void *)stack_bottom_region_start; + stack_bottom_region.size = MPU_MIN_REGION_SIZE; + stack_bottom_region.attr = RT_MEM_REGION_P_RO_U_NA; + rt_mprotect_add_region(thread, &stack_top_region); + rt_mprotect_add_region(thread, &stack_bottom_region); + thread->stack_buf = thread->stack_addr; + thread->stack_addr = (void *)(stack_bottom_region_start + MPU_MIN_REGION_SIZE); + thread->stack_size = (rt_uint32_t)(stack_top_region_start - (rt_ubase_t)thread->stack_addr); +} +#endif + /** * This function set the hook, which is invoked on fault exception handling. * diff --git a/libcpu/arm/cortex-m33/mpu.c b/libcpu/arm/cortex-m33/mpu.c new file mode 100644 index 0000000000..bddd60022c --- /dev/null +++ b/libcpu/arm/cortex-m33/mpu.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#include +#include + +#define DBG_ENABLE +#define DBG_SECTION_NAME "MEMORY PROTECTION" +#define DBG_LEVEL DBG_ERROR +#include + +#define MEM_REGION_TO_MPU_INDEX(thread, region) ((((rt_size_t)region - (rt_size_t)(thread->mem_regions)) / sizeof(rt_mem_region_t)) + NUM_STATIC_REGIONS) + +extern rt_mem_region_t *rt_mprotect_find_free_region(rt_thread_t thread); +extern rt_mem_region_t *rt_mprotect_find_region(rt_thread_t thread, rt_mem_region_t *region); + +static rt_hw_mpu_exception_hook_t mem_manage_hook = RT_NULL; +static rt_uint8_t mpu_mair[8U]; + +rt_weak rt_uint8_t rt_hw_mpu_region_default_attr(rt_mem_region_t *region) +{ + static rt_uint8_t default_mem_attr[] = + { + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1U, 0U, 1U, 0U), ARM_MPU_ATTR_MEMORY_(1U, 0U, 1U, 0U)), + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1U, 1U, 1U, 1U), ARM_MPU_ATTR_MEMORY_(1U, 1U, 1U, 1U)), + ARM_MPU_ATTR_DEVICE_nGnRE, + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1U, 1U, 1U, 1U), ARM_MPU_ATTR_MEMORY_(1U, 1U, 1U, 1U)), + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1U, 0U, 1U, 0U), ARM_MPU_ATTR_MEMORY_(1U, 0U, 1U, 0U)), + ARM_MPU_ATTR_DEVICE_nGnRE, + ARM_MPU_ATTR_DEVICE_nGnRE + }; + rt_uint8_t attr = 0U; + if ((rt_uint32_t)region->start >= 0xE0000000U) + { + attr = ((rt_uint32_t)region->start >= 0xE0100000U) ? ARM_MPU_ATTR_DEVICE_nGnRE : ARM_MPU_ATTR_DEVICE_nGnRnE; + } + else + { + attr = default_mem_attr[((rt_uint32_t)region->start & ~0xFFFFFFFU) >> 29U]; + } + return attr; +} + +static rt_err_t _mpu_rbar_rlar(rt_mem_region_t *region) +{ + rt_uint32_t rlar = 0U; + rt_uint8_t mair_attr; + rt_uint8_t index; + rt_uint8_t attr_indx = 0xFFU; + region->attr.rbar = (rt_uint32_t)region->start | (region->attr.rbar & (~MPU_RBAR_BASE_Msk)); + rlar |= ((rt_uint32_t)region->start + region->size - 1U) & MPU_RLAR_LIMIT_Msk; + if (region->attr.mair_attr == RT_ARM_DEFAULT_MAIR_ATTR) + { + mair_attr = rt_hw_mpu_region_default_attr(region); + } + else + { + mair_attr = (rt_uint8_t)region->attr.mair_attr; + } + for (index = 0U; index < 8U; index++) + { + if (mpu_mair[index] == RT_ARM_DEFAULT_MAIR_ATTR) + { + break; + } + else if (mpu_mair[index] == mair_attr) + { + attr_indx = index; + break; + } + } + /* + * Current region's mair_attr does not match any existing region. + * All entries in MPU_MAIR are configured. + */ + if (index == 8U) + { + return RT_ERROR; + } + /* An existing region has the same mair_attr. */ + if (attr_indx != 0xFFU) + { + rlar |= attr_indx & MPU_RLAR_AttrIndx_Msk; + } + /* Current region's mair_attr does not match any existing region. */ + else + { + ARM_MPU_SetMemAttr(index, mair_attr); + rlar |= index & MPU_RLAR_AttrIndx_Msk; + } + rlar |= MPU_RLAR_EN_Msk; + region->attr.rlar = rlar; + + return RT_EOK; +} + +rt_bool_t rt_hw_mpu_region_valid(rt_mem_region_t *region) +{ + if (region->size < MPU_MIN_REGION_SIZE) + { + LOG_E("Region size is too small"); + return RT_FALSE; + } + if (region->size & (~(MPU_MIN_REGION_SIZE - 1U)) != region->size) + { + LOG_E("Region size is not a multiple of 32 bytes"); + return RT_FALSE; + } + if ((rt_uint32_t)region->start & (MPU_MIN_REGION_SIZE - 1U) != 0U) + { + LOG_E("Region is not aligned by 32 bytes"); + return RT_FALSE; + } + return RT_TRUE; +} + +rt_err_t rt_hw_mpu_init(void) +{ + extern rt_mem_region_t static_regions[NUM_STATIC_REGIONS]; + rt_uint8_t num_mpu_regions; + rt_uint8_t num_dynamic_regions; + rt_uint8_t index; + num_mpu_regions = (rt_uint8_t)((MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos); + if (num_mpu_regions == 0U) + { + LOG_E("Hardware does not support MPU"); + return RT_ERROR; + } + if (num_mpu_regions != NUM_MEM_REGIONS) + { + LOG_E("Incorrect setting of NUM_MEM_REGIONS"); + LOG_E("NUM_MEM_REGIONS = %d, hardware support %d MPU regions", NUM_MEM_REGIONS, num_mpu_regions); + return RT_ERROR; + } + + num_dynamic_regions = NUM_DYNAMIC_REGIONS + NUM_EXCLUSIVE_REGIONS; + if (num_dynamic_regions + NUM_STATIC_REGIONS > num_mpu_regions) + { + LOG_E("Insufficient MPU regions: %d hardware MPU regions", num_mpu_regions); +#ifdef RT_USING_HW_STACK_GUARD + LOG_E("Current configuration requires %d static regions + %d configurable regions + %d exclusive regions + %d stack guard regions", NUM_STATIC_REGIONS, NUM_CONFIGURABLE_REGIONS, NUM_EXCLUSIVE_REGIONS, 1); +#else + LOG_E("Current configuration requires %d static regions + %d configurable regions + %d exclusive regions", NUM_STATIC_REGIONS, NUM_CONFIGURABLE_REGIONS, NUM_EXCLUSIVE_REGIONS); +#endif + return RT_ERROR; + } + for (index = 0U; index < 8U; index++) + { + mpu_mair[index] = RT_ARM_DEFAULT_MAIR_ATTR; + } + + ARM_MPU_Disable(); + for (index = 0U; index < NUM_STATIC_REGIONS; index++) + { + if (rt_hw_mpu_region_valid(&(static_regions[index])) == RT_FALSE) + { + return RT_ERROR; + } + if (_mpu_rbar_rlar(&(static_regions[index])) == RT_ERROR) + { + LOG_E("Number of different mair_attr configurations exceeds 8"); + return RT_ERROR; + } + ARM_MPU_SetRegion(index, static_regions[index].attr.rbar, static_regions[index].attr.rlar); + } + /* Enable background region. */ + ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk); + + return RT_EOK; +} + +rt_err_t rt_hw_mpu_add_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + rt_mem_region_t *free_region; + if (rt_hw_mpu_region_valid(region) == RT_FALSE) + { + return RT_ERROR; + } + rt_enter_critical(); + if (_mpu_rbar_rlar(region) == RT_ERROR) + { + rt_exit_critical(); + LOG_E("Number of different mair_attr configurations exceeds 8"); + return RT_ERROR; + } + if (thread == RT_NULL) + { + rt_exit_critical(); + return RT_EOK; + } + free_region = rt_mprotect_find_free_region(thread); + if (free_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Insufficient regions"); + return RT_ERROR; + } + rt_memcpy(free_region, region, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, free_region); + ARM_MPU_SetRegion(index, region->attr.rbar, region->attr.rlar); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_delete_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + rt_enter_critical(); + rt_mem_region_t *found_region = rt_mprotect_find_region(thread, region); + if (found_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Region not found"); + return RT_ERROR; + } + rt_memset(found_region, 0, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, found_region); + ARM_MPU_ClrRegion(index); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_update_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + if (rt_hw_mpu_region_valid(region) == RT_FALSE) + { + return RT_ERROR; + } + rt_enter_critical(); + if (_mpu_rbar_rlar(region) == RT_ERROR) + { + rt_exit_critical(); + LOG_E("Number of different mair_attr configurations exceeds 8"); + return RT_ERROR; + } + rt_mem_region_t *old_region = rt_mprotect_find_region(thread, region); + if (old_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Region not found"); + return RT_ERROR; + } + rt_memcpy(old_region, region, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, old_region); + ARM_MPU_SetRegion(index, region->attr.rbar, region->attr.rlar); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_exception_set_hook(rt_hw_mpu_exception_hook_t hook) +{ + mem_manage_hook = hook; + return RT_EOK; +} + +void rt_hw_mpu_table_switch(rt_thread_t thread) +{ + extern rt_mem_exclusive_region_t exclusive_regions[NUM_EXCLUSIVE_REGIONS]; + rt_uint8_t i; + rt_uint8_t index = NUM_STATIC_REGIONS; + if (thread->mem_regions != RT_NULL) + { + for (i = 0U; i < NUM_DYNAMIC_REGIONS; i++) + { + if (((rt_mem_region_t *)thread->mem_regions)[i].size != 0U) + { + ARM_MPU_SetRegion(index, ((rt_mem_region_t *)thread->mem_regions)[i].attr.rbar, ((rt_mem_region_t *)thread->mem_regions)[i].attr.rlar); + index += 1U; + } + } + } + for (i = 0U; i < NUM_EXCLUSIVE_REGIONS; i++) + { + if ((exclusive_regions[i].owner != RT_NULL) && (exclusive_regions[i].owner != thread)) + { + ARM_MPU_SetRegion(index, exclusive_regions[i].region.attr.rbar, exclusive_regions[i].region.attr.rlar); + index += 1U; + } + } + for ( ; index < NUM_MEM_REGIONS; index++) + { + ARM_MPU_ClrRegion(index); + } +} + +void MemManage_Handler(void) +{ + extern rt_mem_region_t static_regions[NUM_STATIC_REGIONS]; + extern rt_mem_exclusive_region_t exclusive_regions[NUM_EXCLUSIVE_REGIONS]; + rt_mem_exception_info_t info; + rt_int8_t i; + rt_memset(&info, 0, sizeof(rt_mem_exception_info_t)); + info.thread = rt_thread_self(); + if (SCB->CFSR & SCB_CFSR_MMARVALID_Msk) + { + info.addr = (void *)(SCB->MMFAR); + for (i = NUM_EXCLUSIVE_REGIONS - 1; i >= 0; i--) + { + if ((exclusive_regions[i].owner != RT_NULL) && ((exclusive_regions[i].owner != rt_thread_self())) && ADDR_IN_REGION(info.addr, (rt_mem_region_t *)&(exclusive_regions[i]))) + { + rt_memcpy(&(info.region), &(exclusive_regions[i]), sizeof(rt_mem_region_t)); + break; + } + } + if (info.region.size == 0U) + { + if (info.thread->mem_regions != RT_NULL) + { + for (i = NUM_DYNAMIC_REGIONS - 1; i >= 0; i--) + { + if ((((rt_mem_region_t *)info.thread->mem_regions)[i].size != 0U) && ADDR_IN_REGION(info.addr, &(((rt_mem_region_t *)info.thread->mem_regions)[i]))) + { + rt_memcpy(&(info.region), &(((rt_mem_region_t *)info.thread->mem_regions)[i]), sizeof(rt_mem_region_t)); + break; + } + } + } + if (info.region.size == 0U) + { + for (i = NUM_STATIC_REGIONS - 1; i >= 0; i--) + { + if (ADDR_IN_REGION(info.addr, &(static_regions[i]))) + { + rt_memcpy(&(info.region), &(static_regions[i]), sizeof(rt_mem_region_t)); + break; + } + } + } + } + } + info.mmfsr = (SCB->CFSR & SCB_CFSR_MEMFAULTSR_Msk) >> SCB_CFSR_MEMFAULTSR_Pos; + if (mem_manage_hook != RT_NULL) + { + mem_manage_hook(&info); + } + while (1); +} diff --git a/libcpu/arm/cortex-m33/mpu.h b/libcpu/arm/cortex-m33/mpu.h new file mode 100644 index 0000000000..896d0df596 --- /dev/null +++ b/libcpu/arm/cortex-m33/mpu.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#ifndef __MPU_H__ +#define __MPU_H__ + +#ifdef RT_USING_MEM_PROTECTION + +#include +#include + +#define MPU_MIN_REGION_SIZE 32U + +#define RT_ARM_DEFAULT_MAIR_ATTR (0xF0U) + +/* MPU attributes for configuring data region permission*/ +/* Privileged Read Write, Unprivileged No Access */ +#define P_RW_U_NA_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RW_U_NA_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RW_U_NA_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +/* Privileged Read Write, Unprivileged Read Write */ +#define P_RW_U_RW_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RW_U_RW_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RW_U_RW_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +/* Privileged Read Only, Unprivileged No Access */ +#define P_RO_U_NA_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RO_U_NA_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RO_U_NA_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +/* Privileged Read Only, Unprivileged Read Only */ +#define P_RO_U_RO_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RO_U_RO_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) +#define P_RO_U_RO_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk) | MPU_RBAR_XN_Msk) + +/* MPU attributes for configuring code region permission */ +#define P_RWX_U_NA_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RWX_U_NA_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RWX_U_NA_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x0 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) + +/* Privileged Read Write Execute, Unprivileged Read Write Execute */ +#define P_RWX_U_RWX_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RWX_U_RWX_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RWX_U_RWX_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x1 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) + +/* Privileged Read Execute, Unprivileged No Access */ +#define P_RX_U_NA_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RX_U_NA_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RX_U_NA_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x2 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) + +/* Privileged Read Execute, Unprivileged Read Execute */ +#define P_RX_U_RX_NON_SHAREABLE (((0x0 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RX_U_RX_OUTER_SHAREABLE (((0x2 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) +#define P_RX_U_RX_INNER_SHAREABLE (((0x3 << MPU_RBAR_SH_Pos) & MPU_RBAR_SH_Msk) | ((0x3 << MPU_RBAR_AP_Pos) & MPU_RBAR_AP_Msk)) + +typedef struct +{ + rt_thread_t thread; /* Thread that triggered exception */ + void *addr; /* Address of faulting memory access */ + rt_mem_region_t region; /* Configurations of the memory region containing the address */ + rt_uint8_t mmfsr; /* Content of MemManage Status Register */ +} rt_mem_exception_info_t; + +typedef void (*rt_hw_mpu_exception_hook_t)(rt_mem_exception_info_t *); + +#define RT_ARM_MEM_ATTR(perm, type) ((rt_mem_attr_t){ .rbar = (perm), .mair_attr = (type) }) + +/* Convenient macros for configuring data region attributes with default memory type */ +#define RT_MEM_REGION_P_RW_U_NA RT_ARM_MEM_ATTR(P_RW_U_NA_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RW_U_RW RT_ARM_MEM_ATTR(P_RW_U_RW_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RO_U_NA RT_ARM_MEM_ATTR(P_RO_U_NA_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RO_U_RO RT_ARM_MEM_ATTR(P_RO_U_RO_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +/* ARM-V8M does not support P_NA_U_NA. + For compatibility with rt_mprotect_add_exclusive_region, + define RT_MEM_REGION_P_NA_U_NA as the lowest privilege supported. +*/ +#define RT_MEM_REGION_P_NA_U_NA RT_MEM_REGION_P_RO_U_NA + +/* Convenient macros for configuring code region attributes with default memory type and shareability */ +#define RT_MEM_REGION_P_RWX_U_NA RT_ARM_MEM_ATTR(P_RWX_U_NA_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RWX_U_RWX RT_ARM_MEM_ATTR(P_RWX_U_RWX_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RX_U_NA RT_ARM_MEM_ATTR(P_RX_U_NA_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) +#define RT_MEM_REGION_P_RX_U_RX RT_ARM_MEM_ATTR(P_RX_U_RX_NON_SHAREABLE, RT_ARM_DEFAULT_MAIR_ATTR) + +rt_bool_t rt_hw_mpu_region_valid(rt_mem_region_t *region); +rt_err_t rt_hw_mpu_init(void); +rt_err_t rt_hw_mpu_add_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_delete_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_update_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_exception_set_hook(rt_hw_mpu_exception_hook_t hook); + +#endif /* RT_USING_MEM_PROTECTION */ + +#endif /* __MPU_H__ */ diff --git a/libcpu/arm/cortex-m33/mputype.h b/libcpu/arm/cortex-m33/mputype.h new file mode 100644 index 0000000000..728aaa1d78 --- /dev/null +++ b/libcpu/arm/cortex-m33/mputype.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#ifndef __MPUTYPE_H__ +#define __MPUTYPE_H__ + +#ifdef RT_USING_MEM_PROTECTION + +#ifdef RT_USING_HW_STACK_GUARD +#define NUM_DYNAMIC_REGIONS (2 + NUM_CONFIGURABLE_REGIONS) +#else +#define NUM_DYNAMIC_REGIONS (NUM_CONFIGURABLE_REGIONS) +#endif + +typedef struct +{ + rt_uint32_t rbar; + union + { + rt_uint32_t mair_attr; + rt_uint32_t rlar; + }; +} rt_mem_attr_t; + +#endif /* RT_USING_MEM_PROTECTION */ + +#endif /* __MPUTYPE_H__ */ diff --git a/libcpu/arm/cortex-m7/SConscript b/libcpu/arm/cortex-m7/SConscript index b259a94c94..8e6881ce06 100644 --- a/libcpu/arm/cortex-m7/SConscript +++ b/libcpu/arm/cortex-m7/SConscript @@ -18,6 +18,9 @@ if rtconfig.PLATFORM in ['gcc']: if rtconfig.PLATFORM in ['iccarm']: src += Glob('*_iar.S') +if not GetDepend('RT_USING_MEM_PROTECTION') and not GetDepend('RT_USING_HW_STACK_GUARD'): + SrcRemove(src, 'mpu.c') + group = DefineGroup('CPU', src, depend = [''], CPPPATH = CPPPATH) Return('group') diff --git a/libcpu/arm/cortex-m7/context_gcc.S b/libcpu/arm/cortex-m7/context_gcc.S index 8475d33f5f..14fd3711b3 100644 --- a/libcpu/arm/cortex-m7/context_gcc.S +++ b/libcpu/arm/cortex-m7/context_gcc.S @@ -17,6 +17,8 @@ */ /*@{*/ +#include + .cpu cortex-m4 .syntax unified .thumb @@ -152,6 +154,14 @@ switch_to_thread: BICNE lr, lr, #0x10 /* lr &= ~(1 << 4), set FPCA. */ #endif +#if defined (RT_USING_MEM_PROTECTION) + PUSH {r0-r3, r12, lr} + LDR r1, =rt_current_thread + LDR r0, [r1] + BL rt_hw_mpu_table_switch + POP {r0-r3, r12, lr} +#endif + pendsv_exit: /* restore interrupt */ MSR PRIMASK, r2 diff --git a/libcpu/arm/cortex-m7/cpuport.c b/libcpu/arm/cortex-m7/cpuport.c index d4a14decc9..bcae6b1558 100644 --- a/libcpu/arm/cortex-m7/cpuport.c +++ b/libcpu/arm/cortex-m7/cpuport.c @@ -18,6 +18,9 @@ */ #include +#ifdef RT_USING_HW_STACK_GUARD +#include +#endif #if /* ARMCC */ ( (defined ( __CC_ARM ) && defined ( __TARGET_FPU_VFP )) \ /* Clang */ || (defined ( __clang__ ) && defined ( __VFP_FP__ ) && !defined(__SOFTFP__)) \ @@ -175,6 +178,28 @@ rt_uint8_t *rt_hw_stack_init(void *tentry, return stk; } +#ifdef RT_USING_HW_STACK_GUARD +void rt_hw_stack_guard_init(rt_thread_t thread) +{ + rt_mem_region_t stack_top_region, stack_bottom_region; + rt_ubase_t stack_bottom = (rt_ubase_t)thread->stack_addr; + rt_ubase_t stack_top = (rt_ubase_t)((rt_uint8_t *)thread->stack_addr + thread->stack_size); + rt_ubase_t stack_bottom_region_start = RT_ALIGN(stack_bottom, MPU_MIN_REGION_SIZE); + rt_ubase_t stack_top_region_start = RT_ALIGN_DOWN(stack_top - MPU_MIN_REGION_SIZE, MPU_MIN_REGION_SIZE); + stack_top_region.start = (void *)stack_top_region_start; + stack_top_region.size = MPU_MIN_REGION_SIZE; + stack_top_region.attr = RT_MEM_REGION_P_NA_U_NA; + stack_bottom_region.start = (void *)stack_bottom_region_start; + stack_bottom_region.size = MPU_MIN_REGION_SIZE; + stack_bottom_region.attr = RT_MEM_REGION_P_NA_U_NA; + rt_mprotect_add_region(thread, &stack_top_region); + rt_mprotect_add_region(thread, &stack_bottom_region); + thread->stack_buf = thread->stack_addr; + thread->stack_addr = (void *)(stack_bottom_region_start + MPU_MIN_REGION_SIZE); + thread->stack_size = (rt_uint32_t)(stack_top_region_start - stack_bottom_region_start - MPU_MIN_REGION_SIZE); +} +#endif + /** * This function set the hook, which is invoked on fault exception handling. * diff --git a/libcpu/arm/cortex-m7/mpu.c b/libcpu/arm/cortex-m7/mpu.c new file mode 100644 index 0000000000..63fde3d352 --- /dev/null +++ b/libcpu/arm/cortex-m7/mpu.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#include +#include + +#define DBG_ENABLE +#define DBG_SECTION_NAME "MEMORY PROTECTION" +#define DBG_LEVEL DBG_ERROR +#include + +#define MEM_REGION_TO_MPU_INDEX(thread, region) ((((rt_size_t)region - (rt_size_t)(thread->mem_regions)) / sizeof(rt_mem_region_t)) + NUM_STATIC_REGIONS) + +extern rt_mem_region_t *rt_mprotect_find_free_region(rt_thread_t thread); +extern rt_mem_region_t *rt_mprotect_find_region(rt_thread_t thread, rt_mem_region_t *region); + +static rt_hw_mpu_exception_hook_t mem_manage_hook = RT_NULL; + +rt_weak rt_uint32_t rt_hw_mpu_region_default_attr(rt_mem_region_t *region) +{ + static rt_uint32_t default_mem_attr[] = + { + NORMAL_OUTER_INNER_WRITE_THROUGH_NON_SHAREABLE, + NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE, + DEVICE_NON_SHAREABLE, + NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE, + NORMAL_OUTER_INNER_WRITE_THROUGH_NON_SHAREABLE, + DEVICE_SHAREABLE, + DEVICE_NON_SHAREABLE + }; + rt_uint32_t attr = 0U; + if ((rt_uint32_t)region->start >= 0xE0000000U) + { + attr = ((rt_uint32_t)region->start >= 0xE0100000U) ? STRONGLY_ORDERED_SHAREABLE : DEVICE_SHAREABLE; + } + else + { + attr = default_mem_attr[((rt_uint32_t)region->start & ~0xFFFFFFFU) >> 29U]; + } + return attr; +} + +static rt_uint32_t _mpu_rasr(rt_mem_region_t *region) +{ + rt_uint32_t rasr = 0U; + if ((region->attr.rasr & RESERVED) == RESERVED) + { + rasr |= rt_hw_mpu_region_default_attr(region); + rasr |= region->attr.rasr & (MPU_RASR_XN_Msk | MPU_RASR_AP_Msk); + } + else + { + rasr |= region->attr.rasr & MPU_RASR_ATTRS_Msk; + } + rasr |= ((32U - __builtin_clz(region->size - 1U) - 2U + 1U) << MPU_RASR_SIZE_Pos) & MPU_RASR_SIZE_Msk; + rasr |= MPU_RASR_ENABLE_Msk; + return rasr; +} + +rt_bool_t rt_hw_mpu_region_valid(rt_mem_region_t *region) +{ + if (region->size < MPU_MIN_REGION_SIZE) + { + LOG_E("Region size is too small"); + return RT_FALSE; + } + if (region->size & (region->size - 1U) != 0U) + { + LOG_E("Region size is not power of 2"); + return RT_FALSE; + } + if ((rt_uint32_t)region->start & (region->size - 1U) != 0U) + { + LOG_E("Region is not naturally aligned"); + return RT_FALSE; + } + return RT_TRUE; +} + +rt_err_t rt_hw_mpu_init(void) +{ + extern rt_mem_region_t static_regions[NUM_STATIC_REGIONS]; + rt_uint8_t num_mpu_regions; + rt_uint8_t num_dynamic_regions; + rt_uint8_t index; + num_mpu_regions = (rt_uint8_t)((MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos); + if (num_mpu_regions == 0U) + { + LOG_E("Hardware does not support MPU"); + return RT_ERROR; + } + if (num_mpu_regions != NUM_MEM_REGIONS) + { + LOG_E("Incorrect setting of NUM_MEM_REGIONS"); + LOG_E("NUM_MEM_REGIONS = %d, hardware support %d MPU regions", NUM_MEM_REGIONS, num_mpu_regions); + return RT_ERROR; + } + + num_dynamic_regions = NUM_DYNAMIC_REGIONS + NUM_EXCLUSIVE_REGIONS; + if (num_dynamic_regions + NUM_STATIC_REGIONS > num_mpu_regions) + { + LOG_E("Insufficient MPU regions: %d hardware MPU regions", num_mpu_regions); +#ifdef RT_USING_HW_STACK_GUARD + LOG_E("Current configuration requires %d static regions + %d configurable regions + %d exclusive regions + %d stack guard regions", NUM_STATIC_REGIONS, NUM_CONFIGURABLE_REGIONS, NUM_EXCLUSIVE_REGIONS, 2); +#else + LOG_E("Current configuration requires %d static regions + %d configurable regions + %d exclusive regions", NUM_STATIC_REGIONS, NUM_CONFIGURABLE_REGIONS, NUM_EXCLUSIVE_REGIONS); +#endif + return RT_ERROR; + } + + ARM_MPU_Disable(); + for (index = 0U; index < NUM_STATIC_REGIONS; index++) + { + if (rt_hw_mpu_region_valid(&(static_regions[index])) == RT_FALSE) + { + return RT_ERROR; + } + static_regions[index].attr.rasr = _mpu_rasr(&(static_regions[index])); + ARM_MPU_SetRegion(ARM_MPU_RBAR(index, (rt_uint32_t)static_regions[index].start), static_regions[index].attr.rasr); + } + /* Enable background region. */ + ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk); + + return RT_EOK; +} + +rt_err_t rt_hw_mpu_add_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + rt_mem_region_t *free_region; + if (rt_hw_mpu_region_valid(region) == RT_FALSE) + { + return RT_ERROR; + } + region->attr.rasr = _mpu_rasr(region); + if (thread == RT_NULL) + { + return RT_EOK; + } + rt_enter_critical(); + free_region = rt_mprotect_find_free_region(thread); + if (free_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Insufficient regions"); + return RT_ERROR; + } + rt_memcpy(free_region, region, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, free_region); + ARM_MPU_SetRegion(ARM_MPU_RBAR(index, (rt_uint32_t)region->start), region->attr.rasr); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_delete_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + rt_enter_critical(); + rt_mem_region_t *found_region = rt_mprotect_find_region(thread, region); + if (found_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Region not found"); + return RT_ERROR; + } + rt_memset(found_region, 0, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, found_region); + ARM_MPU_ClrRegion(index); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_update_region(rt_thread_t thread, rt_mem_region_t *region) +{ + rt_uint8_t index; + if (rt_hw_mpu_region_valid(region) == RT_FALSE) + { + return RT_ERROR; + } + region->attr.rasr = _mpu_rasr(region); + rt_enter_critical(); + rt_mem_region_t *old_region = rt_mprotect_find_region(thread, region); + if (old_region == RT_NULL) + { + rt_exit_critical(); + LOG_E("Region not found"); + return RT_ERROR; + } + rt_memcpy(old_region, region, sizeof(rt_mem_region_t)); + if (thread == rt_thread_self()) + { + index = MEM_REGION_TO_MPU_INDEX(thread, old_region); + ARM_MPU_SetRegion(ARM_MPU_RBAR(index, (rt_uint32_t)region->start), region->attr.rasr); + } + rt_exit_critical(); + return RT_EOK; +} + +rt_err_t rt_hw_mpu_exception_set_hook(rt_hw_mpu_exception_hook_t hook) +{ + mem_manage_hook = hook; + return RT_EOK; +} + +void rt_hw_mpu_table_switch(rt_thread_t thread) +{ + extern rt_mem_exclusive_region_t exclusive_regions[NUM_EXCLUSIVE_REGIONS]; + rt_uint8_t i; + rt_uint8_t index = NUM_STATIC_REGIONS; + if (thread->mem_regions != RT_NULL) + { + for (i = 0U; i < NUM_DYNAMIC_REGIONS; i++) + { + if (((rt_mem_region_t *)thread->mem_regions)[i].size != 0U) + { + ARM_MPU_SetRegion(ARM_MPU_RBAR(index, (rt_uint32_t)(((rt_mem_region_t *)thread->mem_regions)[i].start)), ((rt_mem_region_t *)thread->mem_regions)[i].attr.rasr); + index += 1U; + } + } + } + for (i = 0U; i < NUM_EXCLUSIVE_REGIONS; i++) + { + if ((exclusive_regions[i].owner != RT_NULL) && (exclusive_regions[i].owner != thread)) + { + ARM_MPU_SetRegion(ARM_MPU_RBAR(index, (rt_uint32_t)(exclusive_regions[i].region.start)), exclusive_regions[i].region.attr.rasr); + index += 1U; + } + } + for ( ; index < NUM_MEM_REGIONS; index++) + { + ARM_MPU_ClrRegion(index); + } +} + +void MemManage_Handler(void) +{ + extern rt_mem_region_t static_regions[NUM_STATIC_REGIONS]; + extern rt_mem_exclusive_region_t exclusive_regions[NUM_EXCLUSIVE_REGIONS]; + rt_mem_exception_info_t info; + rt_int8_t i; + rt_memset(&info, 0, sizeof(rt_mem_exception_info_t)); + info.thread = rt_thread_self(); + if (SCB->CFSR & SCB_CFSR_MMARVALID_Msk) + { + info.addr = (void *)(SCB->MMFAR); + for (i = NUM_EXCLUSIVE_REGIONS - 1; i >= 0; i--) + { + if ((exclusive_regions[i].owner != RT_NULL) && ((exclusive_regions[i].owner != rt_thread_self())) && ADDR_IN_REGION(info.addr, (rt_mem_region_t *)&(exclusive_regions[i]))) + { + rt_memcpy(&(info.region), &(exclusive_regions[i]), sizeof(rt_mem_region_t)); + break; + } + } + if (info.region.size == 0U) + { + if (info.thread->mem_regions != RT_NULL) + { + for (i = NUM_DYNAMIC_REGIONS - 1; i >= 0; i--) + { + if ((((rt_mem_region_t *)info.thread->mem_regions)[i].size != 0U) && ADDR_IN_REGION(info.addr, &(((rt_mem_region_t *)info.thread->mem_regions)[i]))) + { + rt_memcpy(&(info.region), &(((rt_mem_region_t *)info.thread->mem_regions)[i]), sizeof(rt_mem_region_t)); + break; + } + } + } + if (info.region.size == 0U) + { + for (i = NUM_STATIC_REGIONS - 1; i >= 0; i--) + { + if (ADDR_IN_REGION(info.addr, &(static_regions[i]))) + { + rt_memcpy(&(info.region), &(static_regions[i]), sizeof(rt_mem_region_t)); + break; + } + } + } + } + } + info.mmfsr = (SCB->CFSR & SCB_CFSR_MEMFAULTSR_Msk) >> SCB_CFSR_MEMFAULTSR_Pos; + if (mem_manage_hook != RT_NULL) + { + mem_manage_hook(&info); + } + while (1); +} diff --git a/libcpu/arm/cortex-m7/mpu.h b/libcpu/arm/cortex-m7/mpu.h new file mode 100644 index 0000000000..e03d9f32ec --- /dev/null +++ b/libcpu/arm/cortex-m7/mpu.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#ifndef __MPU_H__ +#define __MPU_H__ + +#ifdef RT_USING_MEM_PROTECTION + +#include + +#define MPU_MIN_REGION_SIZE 32U + +/* MPU attributes for configuring data region permission */ +/* Privileged No Access, Unprivileged No Access */ +#define P_NA_U_NA ((0x0 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) +/* Privileged Read Write, Unprivileged No Access */ +#define P_RW_U_NA ((0x1 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) +/* Privileged Read Write, Unprivileged Read Only */ +#define P_RW_U_RO ((0x2 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) +/* Privileged Read Write, Unprivileged Read Write */ +#define P_RW_U_RW ((0x3 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) +/* Privileged Read Only, Unprivileged No Access */ +#define P_RO_U_NA ((0x5 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) +/* Privileged Read Only, Unprivileged Read Only */ +#define P_RO_U_RO ((0x6 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk | MPU_RASR_XN_Msk) + +/* MPU attributes for configuring code region permission */ +/* Privileged Read Write Execute, Unprivileged Read Write Execute */ +#define P_RWX_U_RWX ((0x3 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) +/* Privileged Read Write Execute, Unprivileged Read Execute */ +#define P_RWX_U_RX ((0x2 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) +/* Privileged Read Write Execute, Unprivileged No Access */ +#define P_RWX_U_NA ((0x1 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) +/* Privileged Read Execute, Unprivileged Read Execute */ +#define P_RX_U_RX ((0x6 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) +/* Privileged Read Execute, Unprivileged No Access */ +#define P_RX_U_NA ((0x5 << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) + +/* MPU attributes for configuring memory type, cacheability and shareability */ +#define STRONGLY_ORDERED_SHAREABLE MPU_RASR_S_Msk +#define DEVICE_SHAREABLE (MPU_RASR_B_Msk | MPU_RASR_S_Msk) +#define NORMAL_OUTER_INNER_WRITE_THROUGH_SHAREABLE \ + (MPU_RASR_C_Msk | MPU_RASR_S_Msk) +#define NORMAL_OUTER_INNER_WRITE_THROUGH_NON_SHAREABLE MPU_RASR_C_Msk +#define NORMAL_OUTER_INNER_WRITE_BACK_SHAREABLE \ + (MPU_RASR_C_Msk | MPU_RASR_B_Msk | MPU_RASR_S_Msk) +#define NORMAL_OUTER_INNER_WRITE_BACK_NON_SHAREABLE \ + (MPU_RASR_C_Msk | MPU_RASR_B_Msk) +#define NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE \ + ((1 << MPU_RASR_TEX_Pos) | MPU_RASR_S_Msk) +#define NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE \ + (1 << MPU_RASR_TEX_Pos) +#define NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_SHAREABLE \ + ((1 << MPU_RASR_TEX_Pos) |\ + MPU_RASR_C_Msk | MPU_RASR_B_Msk | MPU_RASR_S_Msk) +#define NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE \ + ((1 << MPU_RASR_TEX_Pos) | MPU_RASR_C_Msk | MPU_RASR_B_Msk) +#define DEVICE_NON_SHAREABLE (2 << MPU_RASR_TEX_Pos) +#define RESERVED ((2 << MPU_RASR_TEX_Pos) | MPU_RASR_B_Msk) + +typedef struct +{ + rt_thread_t thread; /* Thread that triggered exception */ + void *addr; /* Address of faulting memory access */ + rt_mem_region_t region; /* Configurations of the memory region containing the address */ + rt_uint8_t mmfsr; /* Content of MemManage Status Register */ +} rt_mem_exception_info_t; + +typedef void (*rt_hw_mpu_exception_hook_t)(rt_mem_exception_info_t *); + +#define RT_ARM_MEM_ATTR(perm, type) ((rt_mem_attr_t){ (perm) | (type)}) + +/* Convenient macros for configuring data region attributes with default memory type */ +#define RT_MEM_REGION_P_NA_U_NA RT_ARM_MEM_ATTR(P_NA_U_NA, RESERVED) +#define RT_MEM_REGION_P_RW_U_RW RT_ARM_MEM_ATTR(P_RW_U_RW, RESERVED) +#define RT_MEM_REGION_P_RW_U_RO RT_ARM_MEM_ATTR(P_RW_U_RO, RESERVED) +#define RT_MEM_REGION_P_RW_U_NA RT_ARM_MEM_ATTR(P_RW_U_NA, RESERVED) +#define RT_MEM_REGION_P_RO_U_RO RT_ARM_MEM_ATTR(P_RO_U_RO, RESERVED) +#define RT_MEM_REGION_P_RO_U_NA RT_ARM_MEM_ATTR(P_RO_U_NA, RESERVED) + +/* Convenient macros for configuring code region attributes with default memory type */ +#define RT_MEM_REGION_P_RWX_U_RWX RT_ARM_MEM_ATTR(P_RWX_U_RWX, RESERVED) +#define RT_MEM_REGION_P_RWX_U_RX RT_ARM_MEM_ATTR(P_RWX_U_RX, RESERVED) +#define RT_MEM_REGION_P_RWX_U_NA RT_ARM_MEM_ATTR(P_RWX_U_NA, RESERVED) +#define RT_MEM_REGION_P_RX_U_RX RT_ARM_MEM_ATTR(P_RX_U_RX, RESERVED) +#define RT_MEM_REGION_P_RX_U_NA RT_ARM_MEM_ATTR(P_RX_U_NA, RESERVED) + +rt_bool_t rt_hw_mpu_region_valid(rt_mem_region_t *region); +rt_err_t rt_hw_mpu_init(void); +rt_err_t rt_hw_mpu_add_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_delete_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_update_region(rt_thread_t thread, rt_mem_region_t *region); +rt_err_t rt_hw_mpu_exception_set_hook(rt_hw_mpu_exception_hook_t hook); + +#endif /* RT_USING_MEM_PROTECTION */ + +#endif /* __MPU_H__ */ diff --git a/libcpu/arm/cortex-m7/mputype.h b/libcpu/arm/cortex-m7/mputype.h new file mode 100644 index 0000000000..bc34b32673 --- /dev/null +++ b/libcpu/arm/cortex-m7/mputype.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-25 tangzz98 the first version + */ + +#ifndef __MPUTYPE_H__ +#define __MPUTYPE_H__ + +#ifdef RT_USING_MEM_PROTECTION + +#ifdef RT_USING_HW_STACK_GUARD +#define NUM_DYNAMIC_REGIONS (2 + NUM_CONFIGURABLE_REGIONS) +#else +#define NUM_DYNAMIC_REGIONS (NUM_CONFIGURABLE_REGIONS) +#endif + +typedef struct +{ + rt_uint32_t rasr; +} rt_mem_attr_t; + +#endif /* RT_USING_MEM_PROTECTION */ + +#endif /* __MPUTYPE_H__ */ diff --git a/src/idle.c b/src/idle.c index ca5064e346..524b45d3b3 100644 --- a/src/idle.c +++ b/src/idle.c @@ -233,11 +233,21 @@ static void rt_defunct_execute(void) } #ifdef RT_USING_HEAP +#ifdef RT_USING_MEM_PROTECTION + if (thread->mem_regions != RT_NULL) + { + RT_KERNEL_FREE(thread->mem_regions); + } +#endif /* if need free, delete it */ if (object_is_systemobject == RT_FALSE) { /* release thread's stack */ +#ifdef RT_USING_HW_STACK_GUARD + RT_KERNEL_FREE(thread->stack_buf); +#else RT_KERNEL_FREE(thread->stack_addr); +#endif /* delete thread object */ rt_object_delete((rt_object_t)thread); } diff --git a/src/scheduler_mp.c b/src/scheduler_mp.c index 656457aad6..ff3a4f7a11 100644 --- a/src/scheduler_mp.c +++ b/src/scheduler_mp.c @@ -107,6 +107,7 @@ static void _scheduler_stack_check(struct rt_thread *thread) #endif /* not defined ARCH_MM_MMU */ #endif /* RT_USING_SMART */ +#ifndef RT_USING_HW_STACK_GUARD #ifdef ARCH_CPU_STACK_GROWS_UPWARD if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' || #else @@ -124,14 +125,23 @@ static void _scheduler_stack_check(struct rt_thread *thread) rt_spin_lock(&_spinlock); while (level); } +#endif #ifdef ARCH_CPU_STACK_GROWS_UPWARD +#ifndef RT_USING_HW_STACK_GUARD else if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size)) +#else + if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size)) +#endif { rt_kprintf("warning: %s stack is close to the top of stack address.\n", thread->parent.name); } #else +#ifndef RT_USING_HW_STACK_GUARD else if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32)) +#else + if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32)) +#endif { rt_kprintf("warning: %s stack is close to end of stack address.\n", thread->parent.name); diff --git a/src/scheduler_up.c b/src/scheduler_up.c index ef8c36dc88..d8b13a5620 100644 --- a/src/scheduler_up.c +++ b/src/scheduler_up.c @@ -110,6 +110,7 @@ static void _scheduler_stack_check(struct rt_thread *thread) #endif /* not defined ARCH_MM_MMU */ #endif /* RT_USING_SMART */ +#ifndef RT_USING_HW_STACK_GUARD #ifdef ARCH_CPU_STACK_GROWS_UPWARD if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' || #else @@ -126,14 +127,23 @@ static void _scheduler_stack_check(struct rt_thread *thread) level = rt_hw_interrupt_disable(); while (level); } +#endif #ifdef ARCH_CPU_STACK_GROWS_UPWARD +#ifndef RT_USING_HW_STACK_GUARD else if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size)) +#else + if ((rt_ubase_t)thread->sp > ((rt_ubase_t)thread->stack_addr + thread->stack_size)) +#endif { rt_kprintf("warning: %s stack is close to the top of stack address.\n", thread->parent.name); } #else +#ifndef RT_USING_HW_STACK_GUARD else if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32)) +#else + if ((rt_ubase_t)thread->sp <= ((rt_ubase_t)thread->stack_addr + 32)) +#endif { rt_kprintf("warning: %s stack is close to end of stack address.\n", thread->parent.name); diff --git a/src/thread.c b/src/thread.c index 21c6230192..cd14a15d21 100644 --- a/src/thread.c +++ b/src/thread.c @@ -157,6 +157,10 @@ static rt_err_t _thread_init(struct rt_thread *thread, rt_list_init(&(thread->tlist)); rt_list_init(&(thread->tlist_schedule)); +#ifdef RT_USING_MEM_PROTECTION + thread->mem_regions = RT_NULL; +#endif + #ifdef RT_USING_SMART thread->wakeup.func = RT_NULL; #endif @@ -170,6 +174,9 @@ static rt_err_t _thread_init(struct rt_thread *thread, /* init thread stack */ rt_memset(thread->stack_addr, '#', thread->stack_size); +#ifdef RT_USING_HW_STACK_GUARD + rt_hw_stack_guard_init(thread); +#endif #ifdef ARCH_CPU_STACK_GROWS_UPWARD thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter, (void *)((char *)thread->stack_addr),