1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
19712: cpu/riscv: Add PMP driver r=MrKevinWeiss a=Teufelchen1

### Contribution description

Hi! 🐘 

this adds a basic RISC-V physical memory protection (PMP) driver to RIOT. Well, 'driver' might be a stretched, feels more like a little utility :)

EDIT: Also added  a no-execute RAM option for the hifive & a corresponding test

Since I only have an Hifive rev b, it's only enabled on this board / cpu. I also tested the code on an ESP32-C but the feature can't be enabled there, as `cpu/riscv_common/` is not used by the ESP32...

### Testing procedure

* Grab a hifive rev b
* go to `examples/hello-world`
* Add `USEMODULES += periph_pmp` to the `Makefile`
* Include `pmp.h` in `main.c`
* Add code e.g. `print_pmpcfg(0);`
* compile & flash & term 

You should see something like this:
```
# Hello World!
# You are running RIOT on a(n) hifive1b board.
# This board features a(n) fe310 MCU.
# pmp00cfg: - R-X OFF   0x00000000 - 0x00000000
```



Co-authored-by: Teufelchen1 <bennet.blischke@outlook.com>
This commit is contained in:
bors[bot] 2023-06-29 09:57:31 +00:00 committed by GitHub
commit 755442fe27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 561 additions and 1 deletions

View File

@ -33,6 +33,10 @@ ifneq (,$(filter mpu_noexec_ram,$(USEMODULE)))
FEATURES_REQUIRED += cortexm_mpu
endif
ifneq (,$(filter pmp_noexec_ram,$(USEMODULE)))
FEATURES_REQUIRED += periph_pmp
endif
ifneq (,$(filter lwip_%,$(USEMODULE)))
USEPKG += lwip
endif

View File

@ -37,13 +37,13 @@ typedef enum {
PANIC_HARD_REBOOT,
PANIC_ASSERT_FAIL,
PANIC_EXPECT_FAIL,
PANIC_MEM_MANAGE, /**< memory management fault */
#ifdef MODULE_CORTEXM_COMMON
PANIC_NMI_HANDLER, /**< non maskable interrupt */
PANIC_HARD_FAULT, /**< hard fault */
#if defined(CPU_CORE_CORTEX_M3) || defined(CPU_CORE_CORTEX_M33) || \
defined(CPU_CORE_CORTEX_M4) || defined(CPU_CORE_CORTEX_M4F) || \
defined(CPU_CORE_CORTEX_M7)
PANIC_MEM_MANAGE, /**< memory controller interrupt */
PANIC_BUS_FAULT, /**< bus fault */
PANIC_USAGE_FAULT, /**< undefined instruction or unaligned access */
PANIC_DEBUG_MON, /**< debug interrupt */

View File

@ -13,6 +13,7 @@ config CPU_FAM_FE310
select HAS_PERIPH_GPIO
select HAS_PERIPH_GPIO_IRQ
select HAS_PERIPH_PM
select HAS_PERIPH_PMP
select HAS_PERIPH_PLIC
select HAS_PERIPH_RTT_OVERFLOW
select HAS_PERIPH_RTT_SET_COUNTER

View File

@ -3,6 +3,7 @@ CPU_CORE := rv32imac
FEATURES_PROVIDED += periph_cpuid
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_pm
FEATURES_PROVIDED += periph_pmp
FEATURES_PROVIDED += periph_plic
FEATURES_PROVIDED += periph_rtt_overflow
FEATURES_PROVIDED += periph_rtt_set_counter

View File

@ -36,6 +36,12 @@ extern "C" {
*/
#define PLIC_BASE_ADDR (PLIC_CTRL_ADDR)
/**
* @brief Number of available PMP regions
* Note, the upper 8 regions are hardwired to zero!
*/
#define NUM_PMP_ENTRIES 16
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2023 Bennet Blischke <bennet.blischke@haw-hamburg.de>
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_riscv_common
* @{
*
* @file
* @brief RISC-V PMP configuration options
*
* RISCV implementations using this peripheral must define the `NUM_PMP_ENTRIES`
* `NUM_PMP_ENTRIES` must be 16 or 64.
*
* @author Bennet Blischke
*/
#ifndef PMP_H
#define PMP_H
#include <assert.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Bit masks for the PMP configuration register
* @{
*/
#define PMP_NONE 0x00 /**< No access allowed at all */
#define PMP_R 0x01 /**< Allow read access */
#define PMP_W 0x02 /**< Allow write access */
#define PMP_X 0x04 /**< Allow execution */
#define PMP_A 0x18 /**< Addressing mode mask */
#define PMP_OFF 0x00 /**< Disable this pmp entry */
#define PMP_TOR 0x08 /**< Top-of-range addressing mode */
#define PMP_NA4 0x10 /**< Naturally aligned four-byte region */
#define PMP_NAPOT 0x18 /**< Naturally aligned power-of-two region, ≥8 bytes */
#define PMP_L 0x80 /**< Lock; read-only config & applies to machine-mode */
/** @} */
/**
* @brief Create a NAPOT formatted address
*
* @param addr Base address, must be aligned to the size of the region
* @param size Size of the region in bytes
*/
static inline uint32_t make_napot(uint32_t addr, uint32_t size)
{
assert(addr % size == 0);
return addr | ((size - 1) >> 1);
}
/**
* @brief Writes a complete pmpcfg register
*
* @param reg_num Register number
* @param value Value to write
*/
void write_pmpcfg(uint8_t reg_num, uint32_t value);
/**
* @brief Read a complete pmpcfg register
*
* @param[in] reg_num Register number
*
* @return Contents of the specified register
*/
uint32_t read_pmpcfg(uint8_t reg_num);
/**
* @brief Writes a complete pmpaddr register
*
* @param reg_num Register number
* @param value Value to write
*/
void write_pmpaddr(uint8_t reg_num, uint32_t value);
/**
* @brief Read a complete pmpaddr register
*
* @param[in] reg_num Register number
*
* @return Contents of the specified register
*/
uint32_t read_pmpaddr(uint8_t reg_num);
/**
* @brief Read a single pmpcfg sub-register
*
* @param[in] entry Sub-register number
*
* @return Contents of the specified sub-register
*/
uint8_t get_pmpcfg(uint8_t entry);
/**
* @brief Set's a single pmpcfg sub-register
*
* @param entry Sub-register number
* @param value Value to write
*/
void set_pmpcfg(uint8_t entry, uint8_t value);
/**
* @brief Prints a single pmpcfg sub-register human readable
*
* @param entry Register number to print
*/
void print_pmpcfg(uint8_t entry);
#ifdef __cplusplus
}
#endif
#endif /* PMP_H */
/** @} */

View File

@ -171,6 +171,9 @@
#define PTE_TABLE(PTE) (((PTE) & (PTE_V | PTE_R | PTE_W | PTE_X)) == PTE_V)
#define CSR_PMPCFG0 0x3a0 // PMP configuration base register
#define CSR_PMPADDR0 0x3b0 // PMP address base register
#ifdef __riscv
#ifdef __riscv64

View File

@ -134,6 +134,14 @@ static void handle_trap(uword_t mcause)
write_csr(mepc, return_pc + 4);
break;
}
#ifdef MODULE_PERIPH_PMP
case CAUSE_FAULT_FETCH:
core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (fetch)");
case CAUSE_FAULT_LOAD:
core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (load)");
case CAUSE_FAULT_STORE:
core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (store)");
#endif
default:
#ifdef DEVELHELP
printf("Unhandled trap:\n");

View File

@ -32,4 +32,10 @@ config MODULE_PERIPH_PLIC
help
Platform-Level interrupt controller driver.
config MODULE_PERIPH_PMP
bool
depends on HAS_PERIPH_PMP
help
Physical memory protection driver.
endif # MODULE_RISCV_COMMON_PERIPH

View File

@ -0,0 +1,300 @@
/*
* Copyright (C) 2023 Bennet Blischke <bennet.blischke@haw-hamburg.de>
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_riscv_common
* @{
*
* @file
* @brief RISCV PMP implementation
*
* @author Bennet Blischke
*/
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include "cpu_conf.h"
#include "vendor/riscv_csr.h"
#include "pmp.h"
#if NUM_PMP_ENTRIES != 16 && NUM_PMP_ENTRIES != 64
#error "Only 16 or 64 PMP entries allowed."
#endif
#define _WRITE_PMPCFG(REG, VALUE) write_csr(REG, VALUE)
#define _READ_PMPCFG(REG) read_csr(REG)
#define WRITE_PMPCFG(REG, VALUE) _WRITE_PMPCFG(CSR_PMPCFG0 + REG, VALUE)
#define READ_PMPCFG(REG) _READ_PMPCFG(CSR_PMPCFG0 + REG)
/* These shifts are needed as the PMP Address registers stores
* 34 Bit addresses with 4 byte alignment in a 32 bit register
*/
#define _WRITE_PMPADDR(REG, VALUE) write_csr(REG, VALUE >> 2)
#define _READ_PMPADDR(REG) (read_csr(REG) << 2)
#define WRITE_PMPADDR(REG, VALUE) _WRITE_PMPADDR(CSR_PMPADDR0 + REG, VALUE)
#define READ_PMPADDR(REG) _READ_PMPADDR(CSR_PMPADDR0 + REG)
#define _NAPOT_base(addr) (addr & (addr + 1))
#define _NAPOT_end(addr) (addr | (addr + 1))
/* The assembly instructions for reading riscv CSRs, encode the CSR-address
* in the immediate field which is not changeable at run-time.
* Hence it is needed to generate all possible register accesses at build-time.
* Settled for switch-case as they are easiest to read, understand and maintain.
*/
void write_pmpcfg(uint8_t reg_num, uint32_t value)
{
assert(reg_num < NUM_PMP_ENTRIES / 4);
switch (reg_num) {
case 0: WRITE_PMPCFG(0, value); break;
case 1: WRITE_PMPCFG(1, value); break;
case 2: WRITE_PMPCFG(2, value); break;
case 3: WRITE_PMPCFG(3, value); break;
#if NUM_PMP_ENTRIES == 64
case 4: WRITE_PMPCFG(4, value); break;
case 5: WRITE_PMPCFG(5, value); break;
case 6: WRITE_PMPCFG(6, value); break;
case 7: WRITE_PMPCFG(7, value); break;
case 8: WRITE_PMPCFG(8, value); break;
case 9: WRITE_PMPCFG(9, value); break;
case 10: WRITE_PMPCFG(10, value); break;
case 11: WRITE_PMPCFG(11, value); break;
case 12: WRITE_PMPCFG(12, value); break;
case 13: WRITE_PMPCFG(13, value); break;
case 14: WRITE_PMPCFG(14, value); break;
case 15: WRITE_PMPCFG(15, value); break;
#endif
}
}
uint32_t read_pmpcfg(uint8_t reg_num)
{
assert(reg_num < NUM_PMP_ENTRIES / 4);
switch (reg_num) {
case 0: return READ_PMPCFG(0);
case 1: return READ_PMPCFG(1);
case 2: return READ_PMPCFG(2);
case 3: return READ_PMPCFG(3);
#if NUM_PMP_ENTRIES == 64
case 4: return READ_PMPCFG(4);
case 5: return READ_PMPCFG(5);
case 6: return READ_PMPCFG(6);
case 7: return READ_PMPCFG(7);
case 8: return READ_PMPCFG(8);
case 9: return READ_PMPCFG(9);
case 10: return READ_PMPCFG(10);
case 11: return READ_PMPCFG(11);
case 12: return READ_PMPCFG(12);
case 13: return READ_PMPCFG(13);
case 14: return READ_PMPCFG(14);
case 15: return READ_PMPCFG(15);
#endif
}
return 0;
}
void write_pmpaddr(uint8_t reg_num, uint32_t value)
{
assert(reg_num < NUM_PMP_ENTRIES);
switch (reg_num) {
case 0: WRITE_PMPADDR(0, value); break;
case 1: WRITE_PMPADDR(1, value); break;
case 2: WRITE_PMPADDR(2, value); break;
case 3: WRITE_PMPADDR(3, value); break;
case 4: WRITE_PMPADDR(4, value); break;
case 5: WRITE_PMPADDR(5, value); break;
case 6: WRITE_PMPADDR(6, value); break;
case 7: WRITE_PMPADDR(7, value); break;
case 8: WRITE_PMPADDR(8, value); break;
case 9: WRITE_PMPADDR(9, value); break;
case 10: WRITE_PMPADDR(10, value); break;
case 11: WRITE_PMPADDR(11, value); break;
case 12: WRITE_PMPADDR(12, value); break;
case 13: WRITE_PMPADDR(13, value); break;
case 14: WRITE_PMPADDR(14, value); break;
case 15: WRITE_PMPADDR(15, value); break;
#if NUM_PMP_ENTRIES == 64
case 16: WRITE_PMPADDR(16, value); break;
case 17: WRITE_PMPADDR(17, value); break;
case 18: WRITE_PMPADDR(18, value); break;
case 19: WRITE_PMPADDR(19, value); break;
case 20: WRITE_PMPADDR(20, value); break;
case 21: WRITE_PMPADDR(21, value); break;
case 22: WRITE_PMPADDR(22, value); break;
case 23: WRITE_PMPADDR(23, value); break;
case 24: WRITE_PMPADDR(24, value); break;
case 25: WRITE_PMPADDR(25, value); break;
case 26: WRITE_PMPADDR(26, value); break;
case 27: WRITE_PMPADDR(27, value); break;
case 28: WRITE_PMPADDR(28, value); break;
case 29: WRITE_PMPADDR(29, value); break;
case 30: WRITE_PMPADDR(30, value); break;
case 31: WRITE_PMPADDR(31, value); break;
case 32: WRITE_PMPADDR(32, value); break;
case 33: WRITE_PMPADDR(33, value); break;
case 34: WRITE_PMPADDR(34, value); break;
case 35: WRITE_PMPADDR(35, value); break;
case 36: WRITE_PMPADDR(36, value); break;
case 37: WRITE_PMPADDR(37, value); break;
case 38: WRITE_PMPADDR(38, value); break;
case 39: WRITE_PMPADDR(39, value); break;
case 40: WRITE_PMPADDR(40, value); break;
case 41: WRITE_PMPADDR(41, value); break;
case 42: WRITE_PMPADDR(42, value); break;
case 43: WRITE_PMPADDR(43, value); break;
case 44: WRITE_PMPADDR(44, value); break;
case 45: WRITE_PMPADDR(45, value); break;
case 46: WRITE_PMPADDR(46, value); break;
case 47: WRITE_PMPADDR(47, value); break;
case 48: WRITE_PMPADDR(48, value); break;
case 49: WRITE_PMPADDR(49, value); break;
case 50: WRITE_PMPADDR(50, value); break;
case 51: WRITE_PMPADDR(51, value); break;
case 52: WRITE_PMPADDR(52, value); break;
case 53: WRITE_PMPADDR(53, value); break;
case 54: WRITE_PMPADDR(54, value); break;
case 55: WRITE_PMPADDR(55, value); break;
case 56: WRITE_PMPADDR(56, value); break;
case 57: WRITE_PMPADDR(57, value); break;
case 58: WRITE_PMPADDR(58, value); break;
case 59: WRITE_PMPADDR(59, value); break;
case 60: WRITE_PMPADDR(60, value); break;
case 61: WRITE_PMPADDR(61, value); break;
case 62: WRITE_PMPADDR(62, value); break;
case 63: WRITE_PMPADDR(63, value); break;
#endif
}
}
uint32_t read_pmpaddr(uint8_t reg_num)
{
assert(reg_num < NUM_PMP_ENTRIES);
switch (reg_num) {
case 0: return READ_PMPADDR(0);
case 1: return READ_PMPADDR(1);
case 2: return READ_PMPADDR(2);
case 3: return READ_PMPADDR(3);
case 4: return READ_PMPADDR(4);
case 5: return READ_PMPADDR(5);
case 6: return READ_PMPADDR(6);
case 7: return READ_PMPADDR(7);
case 8: return READ_PMPADDR(8);
case 9: return READ_PMPADDR(9);
case 10: return READ_PMPADDR(10);
case 11: return READ_PMPADDR(11);
case 12: return READ_PMPADDR(12);
case 13: return READ_PMPADDR(13);
case 14: return READ_PMPADDR(14);
case 15: return READ_PMPADDR(15);
#if NUM_PMP_ENTRIES == 64
case 16: return READ_PMPADDR(16);
case 17: return READ_PMPADDR(17);
case 18: return READ_PMPADDR(18);
case 19: return READ_PMPADDR(19);
case 20: return READ_PMPADDR(20);
case 21: return READ_PMPADDR(21);
case 22: return READ_PMPADDR(22);
case 23: return READ_PMPADDR(23);
case 24: return READ_PMPADDR(24);
case 25: return READ_PMPADDR(25);
case 26: return READ_PMPADDR(26);
case 27: return READ_PMPADDR(27);
case 28: return READ_PMPADDR(28);
case 29: return READ_PMPADDR(29);
case 30: return READ_PMPADDR(30);
case 31: return READ_PMPADDR(31);
case 32: return READ_PMPADDR(32);
case 33: return READ_PMPADDR(33);
case 34: return READ_PMPADDR(34);
case 35: return READ_PMPADDR(35);
case 36: return READ_PMPADDR(36);
case 37: return READ_PMPADDR(37);
case 38: return READ_PMPADDR(38);
case 39: return READ_PMPADDR(39);
case 40: return READ_PMPADDR(40);
case 41: return READ_PMPADDR(41);
case 42: return READ_PMPADDR(42);
case 43: return READ_PMPADDR(43);
case 44: return READ_PMPADDR(44);
case 45: return READ_PMPADDR(45);
case 46: return READ_PMPADDR(46);
case 47: return READ_PMPADDR(47);
case 48: return READ_PMPADDR(48);
case 49: return READ_PMPADDR(49);
case 50: return READ_PMPADDR(50);
case 51: return READ_PMPADDR(51);
case 52: return READ_PMPADDR(52);
case 53: return READ_PMPADDR(53);
case 54: return READ_PMPADDR(54);
case 55: return READ_PMPADDR(55);
case 56: return READ_PMPADDR(56);
case 57: return READ_PMPADDR(57);
case 58: return READ_PMPADDR(58);
case 59: return READ_PMPADDR(59);
case 60: return READ_PMPADDR(60);
case 61: return READ_PMPADDR(61);
case 62: return READ_PMPADDR(62);
case 63: return READ_PMPADDR(63);
#endif
}
return 0;
}
uint8_t get_pmpcfg(uint8_t entry)
{
return (read_pmpcfg(entry / 4) >> (entry % 4) * 8) & 0xFF;
}
void set_pmpcfg(uint8_t entry, uint8_t value)
{
uint32_t cur_value = read_pmpcfg(entry / 4);
uint32_t mask = 0xff << (entry % 4) * 8;
cur_value &= ~mask;
cur_value |= value << (entry % 4) * 8;
write_pmpcfg(entry / 4, cur_value);
}
void print_pmpcfg(uint8_t entry)
{
uint8_t cfg = get_pmpcfg(entry);
uint32_t start = 0;
uint32_t stop = 0;
char *mode = "OFF ";
switch (cfg & PMP_A) {
case PMP_TOR:
mode = "TOR ";
start = (entry > 0) ? read_pmpaddr(entry - 1) : 0;
stop = read_pmpaddr(entry);
break;
case PMP_NA4:
mode = "NA4 ";
start = read_pmpaddr(entry);
stop = start + 3;
break;
case PMP_NAPOT:
mode = "NAPOT";
/* Flipp last two bits in NAPOT mode */
uint32_t _tmp = read_pmpaddr(entry) | 0x03;
start = _NAPOT_base(_tmp);
stop = _NAPOT_end(_tmp);
break;
}
printf("pmp%02dcfg: %c %c%c%c %s 0x%08x - 0x%08x\n",
entry,
(cfg & PMP_L) ? 'L' : '-',
(cfg & PMP_R) ? 'R' : '-',
(cfg & PMP_W) ? 'W' : '-',
(cfg & PMP_X) ? 'X' : '-',
mode,
(unsigned int)start,
(unsigned int)stop
);
}

View File

@ -21,6 +21,10 @@
#include "cpu_conf_common.h"
#include "periph_cpu_common.h"
#ifdef MODULE_PMP_NOEXEC_RAM
#include "pmp.h"
#endif
#ifdef MODULE_PUF_SRAM
#include "puf_sram.h"
@ -45,4 +49,13 @@ void riscv_init(void)
{
riscv_fpu_init();
riscv_irq_init();
#ifdef MODULE_PMP_NOEXEC_RAM
/* This marks the (main) RAM region as non
* executable. Using PMP entry 0.
*/
write_pmpaddr(0, make_napot(CPU_RAM_BASE, CPU_RAM_SIZE));
/* Lock & select NAPOT, only allow write and read */
set_pmpcfg(0, PMP_L | PMP_NAPOT | PMP_W | PMP_R);
#endif
}

View File

@ -344,6 +344,11 @@ config HAS_PERIPH_PLIC
help
Indicates that a RISC-V Platform-local Interrupt Controller (PLIC) peripheral is present.
config HAS_PERIPH_PMP
bool
help
Indicates that a RISC-V physical memory protection (PMP) peripheral is present.
config HAS_PERIPH_PM
bool
help

View File

@ -322,6 +322,15 @@ PSEUDOMODULES += mpu_stack_guard
PSEUDOMODULES += mpu_noexec_ram
## @}
## @defgroup pseudomodule_pmp_noexec_ram pmp_noexec_ram
## @{
## @brief Mark RAM as non-executable using the PMP
##
## Mark the RAM non executable.
## This is a protection mechanism which makes exploitation of buffer overflows significantly harder.
PSEUDOMODULES += pmp_noexec_ram
## @}
## @defgroup pseudomodule_md5sum md5sum
## @ingroup sys_shell_commands
## @{

View File

@ -0,0 +1,7 @@
BOARD ?= hifive1b
include ../Makefile.cpu_common
USEMODULE += pmp_noexec_ram
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,11 @@
# mpu_noexec_ram
Tests for the `pmp_noexec_ram` pseudomodule.
Only supported on RISC-V devices with PMP.
## Output
With `USEMODULE += pmp_noexec_ram` in `Makefile` this application should
execute a kernel panic, stating `Instruction access fault` (0x01) in the
`mcause` register. Without this pseudomodule activated, the hard fault
will be triggered by `Illegal instruction` (0x02) instead.

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Bennet Blischke <bennet.blischke@haw-hamburg.de>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for the pmp_noexec_ram pseudo-module
*
* @author Sören Tempel <tempel@uni-bremen.de>
* @author Bennet Blischke <bennet.blischke@haw-hamburg.de>
*
* @}
*/
#include <stdio.h>
#include <stdint.h>
#include "cpu.h"
#include "pmp.h"
#define JMPBUF_SIZE 3
int main(void)
{
uint32_t buf[JMPBUF_SIZE];
/* Fill the buffer with invalid instructions */
for (unsigned i = 0; i < JMPBUF_SIZE; i++) {
buf[i] = UINT32_MAX;
}
puts("Attempting to jump to stack buffer ...\n");
__asm__ volatile ("jr %0" :: "r" ((uint8_t*)&buf));
return 0;
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# Copyright (C) 2020 Sören Tempel <tempel@uni-bremen.de>
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
from testrunner import run
def testfunc(child):
child.expect_exact("MEM MANAGE HANDLER (fetch)\r\n")
if __name__ == "__main__":
sys.exit(run(testfunc, timeout=10))