Ethos-U PMU monitor

Add Python script demonstrating how to download performance data
from device.

Write baremetal PMU events to Event Recorder ring buffer and increase
the systick sample rate.

Change-Id: Ib73c56100a8de2d7b74c455d8f80cda0b59383da
diff --git a/.gitignore b/.gitignore
index 796b96d..f284d11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /build
+__pycache__
diff --git a/applications/baremetal/main.cpp b/applications/baremetal/main.cpp
index ab365c6..6ed7cbe 100644
--- a/applications/baremetal/main.cpp
+++ b/applications/baremetal/main.cpp
@@ -72,20 +72,15 @@
 #endif
 
 #ifdef ETHOSU
-constexpr int32_t EventComponentNo = 0x00;
 namespace {
 std::vector<ethosu_pmu_event_type> pmuEventConfig{ethosu_pmu_event_type(ETHOSU_PMU_EVENT_0),
                                                   ethosu_pmu_event_type(ETHOSU_PMU_EVENT_1),
                                                   ethosu_pmu_event_type(ETHOSU_PMU_EVENT_2),
                                                   ethosu_pmu_event_type(ETHOSU_PMU_EVENT_3)};
-std::vector<int32_t> eventRecMessageIds{EventID(EventLevelDetail, EventComponentNo, ETHOSU_PMU_EVENT_0),
-                                        EventID(EventLevelDetail, EventComponentNo, ETHOSU_PMU_EVENT_1),
-                                        EventID(EventLevelDetail, EventComponentNo, ETHOSU_PMU_EVENT_2),
-                                        EventID(EventLevelDetail, EventComponentNo, ETHOSU_PMU_EVENT_3)};
 
-const uint32_t delayMs = SystemCoreClock / 60ul;
+const uint32_t delayMs = SystemCoreClock / 1000ul;
 struct ethosu_driver *ethosuDrv;
-EthosUMonitor ethosuMonitor(eventRecMessageIds, EthosUMonitor::Backend::PRINTF);
+EthosUMonitor ethosuMonitor(EthosUMonitor::Backend::EVENT_RECORDER);
 } // namespace
 
 extern "C" {
diff --git a/scripts/ethosu_monitor.py b/scripts/ethosu_monitor.py
new file mode 100755
index 0000000..60f0bdf
--- /dev/null
+++ b/scripts/ethosu_monitor.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+
+#
+# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from ethosumonitor.inputs import *
+from ethosumonitor.outputs import *
+from sys import stderr, exit
+
+def eventLoop(input: InputInterface, output: OutputInterface):
+    count = 0
+
+    try:
+        while(True):
+            for record in input.readEventRecord():
+                output.writeEventRecord(record)
+                count = count + 1
+    except KeyboardInterrupt:
+        stderr.write(f'count={count}, input={input}\n')
+        pass
+    except EOFError:
+        pass
+
+    output.flush()
+
+def getDAPLink(args):
+    return InputDAPLink(args.elf)
+
+def getMem(args):
+    return InputMem(args.elf, args.memory_map)
+
+def getFile(args):
+   return InputFile(args.file)
+
+def getOutput(args):
+    if args.output_format == 'binary':
+        return OutputBinary(args.output)
+    else:
+        return OutputJson(args.output)
+
+def addOutputArguments(parser):
+    parser.add_argument('--output-format', choices=['binary', 'json'], default='json', help='Output format.')
+    parser.add_argument('-o', '--output', default='/dev/stdout', help='Output file.')
+
+def main():
+    import argparse
+
+    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
+                                     description='Ethos-U monitor downloading profiling data.',
+                                     epilog='''
+Event Recorder:
+  The Event Recorder library is used to write performance data to a ring buffer
+  in memory. The ring buffer has a limited size and must be continuously
+  streamed to a host machine before it overflows.
+
+  This script demonstrates how to stream performance data using DAPLink or
+  /dev/mem. Support for other technologies can be added implementing the
+  InputInterface class in inputs.py.
+''')
+    subparsers = parser.add_subparsers()
+
+    subparser = subparsers.add_parser('daplink',
+                                      formatter_class=argparse.RawDescriptionHelpFormatter,
+                                      description='Download performance data using DAPLink.',
+                                      epilog='''
+DAPLink:
+  Arm Mbed DAPLink is an open source project that enables programming and
+  debugging application software running on an Arm Cortex CPU. A host machine
+  can connect to the target device using for example USB or JTAG.
+
+  This script demonstrates how DAPLink can be used to stream Event Recorder
+  data from a target device. The ELF file passed to the script must be the
+  same application that is running on the device, and is used to find the
+  location of the Event Recorder ring buffer.
+
+  $ ethosu_monitor.py daplink --target mps3_an540 myapplication.elf
+''')
+    subparser.set_defaults(getInput=getDAPLink)
+    subparser.add_argument('--target', default='mps3_an540', help='DAPLink target platform.')
+    subparser.add_argument('elf', help='Elf file running on the target.')
+    addOutputArguments(subparser)
+
+    subparser = subparsers.add_parser('memory',
+                                      formatter_class=argparse.RawDescriptionHelpFormatter,
+                                      description='Download performance data using /dev/mem.',
+                                      epilog='''
+/dev/mem:
+  For a Linux based system the Event Recorder buffer should be stored in shared
+  memory accessible from Linux. This allows Linux to read device the Event
+  Recorder ring buffer using /dev/mem.
+
+  The address of the Event Recorder ring buffer is found parsing the ELF
+  file. Because the device and Linux do not share the same address space a
+  memory map is required to translate device addresses into host addresses.
+  Please see sample.json for reference.
+
+  $ ethosu_monitor.py memory --memory-map config.json myapplication.elf
+''')
+    subparser.set_defaults(getInput=getMem)
+    subparser.add_argument('--memory-map', required=True, help='JSON file describing physical memory map of target.')
+    subparser.add_argument('elf', help='Elf file running on the target.')
+    addOutputArguments(subparser)
+
+    subparser = subparsers.add_parser('file',
+                                      formatter_class=argparse.RawDescriptionHelpFormatter,
+                                      description='Replay performance data stored in binary file.',
+                                      epilog='''
+file:
+  Event Recorder data can be written in binary format for later processing.
+  This will likely have less latency than the default JSON format, reducing
+  the risk over ring buffer overflows.
+
+  $ ethosu_monitor.py daplink --output-format binary --output samples.bin myapplication.elf
+
+  The binary data can later be unpacked to JSON.
+
+  $ ethosu_monitor.py file samples.bin --output-format json
+''')
+    subparser.set_defaults(getInput=getFile)
+    subparser.add_argument('file', help='Binary file containing recorded performance data.')
+    addOutputArguments(subparser)
+
+    args = parser.parse_args()
+
+    if 'getInput' not in args:
+        parser.print_help()
+        exit(2)
+
+    input = args.getInput(args)
+    output = getOutput(args)
+    eventLoop(input, output)
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/ethosumonitor/elf.py b/scripts/ethosumonitor/elf.py
new file mode 100644
index 0000000..bf9ab39
--- /dev/null
+++ b/scripts/ethosumonitor/elf.py
@@ -0,0 +1,47 @@
+#
+# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+def elfFindSymbol(elf, name):
+    from elftools.elf.sections import SymbolTableSection
+
+    for section in elf.iter_sections():
+        if isinstance(section, SymbolTableSection):
+            symbol = section.get_symbol_by_name(name)
+            if symbol:
+                return symbol[0]
+
+    return None
+
+def elfGetData(elf, address, size):
+    for section in elf.iter_sections():
+        if address >= section.header['sh_addr'] and \
+           (address + size) < (section.header['sh_addr'] + section.header['sh_size']):
+            offset = address - section.header['sh_addr']
+            return bytearray(section.data()[offset:offset+size])
+
+    return None
+
+def elfGetSymbolData(elf, name):
+    from sys import stderr
+
+    symbol = elfFindSymbol(elf, name)
+    if not symbol:
+        stderr.write(f'Failed to find symbol {name}\n')
+        return None
+
+    return elfGetData(elf, symbol.entry.st_value, symbol.entry.st_size)
diff --git a/scripts/ethosumonitor/inputs.py b/scripts/ethosumonitor/inputs.py
new file mode 100644
index 0000000..60fb8ed
--- /dev/null
+++ b/scripts/ethosumonitor/inputs.py
@@ -0,0 +1,173 @@
+#
+# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from .elf import *
+from .types import *
+import json
+import mmap
+import os
+from sys import stderr
+
+class InputInterface:
+    def readEventRecord(self) -> EventRecord_t:
+        ...
+
+class InputFile(InputInterface):
+    def __init__(self, fname):
+        self.file = open(fname, 'rb')
+
+    def readEventRecord(self):
+        data = self.file.read(EventRecord_t.SIZE)
+        if len(data) == 0:
+            raise EOFError
+
+        yield data
+
+class InputRingBuffer(InputInterface):
+    def __init__(self, elfFile):
+        from elftools.elf.elffile import ELFFile
+
+        with open(elfFile, 'rb') as f:
+            elf = ELFFile(f)
+            symbol = elfFindSymbol(elf, 'EventRecorderInfo')
+            elfInfo = EventRecorderInfo_t(elfGetData(elf, symbol.entry.st_value, symbol.entry.st_size))
+            memInfo = EventRecorderInfo_t(self.read(symbol.entry.st_value, symbol.entry.st_size))
+
+        # Validate EventRecorder info
+        if elfInfo.protocolType != memInfo.protocolType or \
+            elfInfo.protocolVersion != memInfo.protocolVersion or \
+            elfInfo.eventBuffer != memInfo.eventBuffer or \
+            elfInfo.eventStatus != memInfo.eventStatus:
+            raise Exception(f'EventRecorder info mismatch. elf={elfInfo}, mem={memInfo}')
+
+        self.info = elfInfo
+        status = EventStatus_t(self.read(self.info.eventStatus, EventStatus_t.SIZE))
+        self.timestamp = status.tsLast
+        self.recordIndex = status.recordIndex
+        self.overflow = 0
+
+    def readEventRecord(self):
+        # Read status and use timestamp to detect if there are new samples
+        status = EventStatus_t(self.read(self.info.eventStatus, EventStatus_t.SIZE))
+        if self.timestamp == status.tsLast:
+            return None
+
+        self.timestamp = status.tsLast
+
+        # Detect firmware reset
+        if self.recordIndex > status.recordIndex:
+            self.recordIndex = 0
+
+        # Detect of recordIndex has overflowed the ring buffer
+        if status.recordIndex - self.recordIndex > self.info.recordCount:
+            stderr.write('Warning: Ring buffer overflow\n')
+            self.overflow = self.overflow + 1
+            self.recordIndex = status.recordIndex
+
+        # Generate data for each event record
+        for i in range(self.recordIndex, status.recordIndex):
+            i = i % self.info.recordCount
+            yield self.read(self.info.eventBuffer + EventRecord_t.SIZE * i, EventRecord_t.SIZE)
+
+        self.recordIndex = status.recordIndex
+
+    def read(self, address, size) -> bytearray:
+        ...
+
+class InputDAPLink(InputRingBuffer):
+    def __init__(self, elfFile):
+        self._open()
+        super().__init__(elfFile)
+        self.target.reset()
+
+    def _open(self):
+        from pyocd.core.helpers import ConnectHelper
+
+        self.session = ConnectHelper.session_with_chosen_probe()
+        self.board = self.session.board
+        self.target = self.board.target
+
+        self.session.open()
+
+    def read(self, address, size):
+        from pyocd.core.exceptions import Error
+
+        for i in range(1000):
+            try:
+                return bytearray(self.target.read_memory_block8(address, size))
+            except Error:
+                pass
+
+class InputMem(InputRingBuffer):
+    def __init__(self, elfFile, jsonFile):
+        with open(jsonFile, 'r') as f:
+            jsonDoc = json.loads(f.read())
+
+        self.memoryMap = []
+        for memoryMap in jsonDoc['memoryMap']:
+            host = int(memoryMap['host'], 16)
+            device = int(memoryMap['device'], 16)
+            size = int(memoryMap['size'], 16)
+            self.memoryMap.append(DevMemDevice(host, device, size))
+
+        super().__init__(elfFile)
+
+    def read(self, device, size):
+        for memoryMap in self.memoryMap:
+            data = memoryMap.read(device, size)
+            if data:
+                return data
+
+        stderr.write(f'Warning: No mapping found for device address {hex(device)} size {size}.\n')
+        return None
+
+class DevMem:
+    def __init__(self, address, size):
+        self.base_address = address & ~(mmap.PAGESIZE - 1)
+        self.offset = address - self.base_address
+        self.size = size + self.offset
+
+        self.fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)
+        self.mem = mmap.mmap(self.fd, self.size, mmap.MAP_SHARED, mmap.PROT_READ,
+                             offset=self.base_address)
+
+    def __del__(self):
+        os.close(self.fd)
+
+    def read(self, offset, size):
+        self.mem.seek(self.offset + offset)
+
+        data = bytearray(size)
+        for i in range(size):
+            data[i] = self.mem.read_byte()
+
+        return data
+
+class DevMemDevice(DevMem):
+    def __init__(self, host, device, size):
+        super().__init__(host, size)
+
+        self.device = device
+        self.size = size
+
+    def read(self, device, size):
+        offset = device - self.device
+        if offset < 0 or (offset + size) > self.size:
+            return None
+
+        return super().read(offset, size)
diff --git a/scripts/ethosumonitor/outputs.py b/scripts/ethosumonitor/outputs.py
new file mode 100644
index 0000000..5a4101c
--- /dev/null
+++ b/scripts/ethosumonitor/outputs.py
@@ -0,0 +1,83 @@
+#
+# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from .types import *
+from sys import stderr
+
+class OutputInterface:
+    def flush(self):
+        ...
+
+    def writeEventRecord(self, data: bytearray):
+        ...
+
+class OutputBinary(OutputInterface):
+    def __init__(self, fname):
+        self.file = open(fname, 'wb')
+
+    def flush(self):
+        self.file.flush()
+
+    def writeEventRecord(self, data: bytearray):
+        self.file.write(data)
+
+class OutputJson(OutputInterface):
+    def __init__(self, fname):
+        self.file = open(fname, 'w')
+
+        self.count = 0
+        self.nextId = 0
+        self.timestamp = 0
+        self.event = []
+
+    def flush(self):
+        self.file.flush()
+
+    def writeEventRecord(self, data: bytearray):
+        record = EventRecord_t(data)
+
+        if record.first():
+            # Drop messages that don't originate from Ethos-U
+            if record.component() != EventRecord_t.ETHOSU_CID:
+                return
+
+            self.nextId = 0
+            self.timestamp = record.timestamp
+            self.eventConfig = []
+            self.eventCount = []
+
+        messageIndex = record.message()
+
+        if self.nextId != messageIndex or self.timestamp != record.timestamp:
+            stderr.write(f'Expected record id {self.nextId} and timestamp {self.timestamp} but got {messageIndex} and {record.timestamp}. count={self.count}, locked={record.locked()}, valid={record.valid()}\n')
+            stderr.write(f'record={record}\n')
+            return
+
+        self.nextId = messageIndex + 1
+
+        if messageIndex == 0:
+            self.cycleCount = record.val2 << 32 | record.val1
+        elif messageIndex == 1:
+            self.qread = record.val1
+            self.status = record.val2
+        else:
+            self.eventConfig.append(record.val1)
+            self.eventCount.append(record.val2)
+
+        if record.last():
+            self.file.write(f'{{ "timestamp": {self.timestamp}, "qread": {self.qread}, "status": {self.status}, "cycleCount": {self.cycleCount}, "eventConfig": [ {", ".join(map(str, self.eventConfig))} ], "eventCount": [ {", ".join(map(str, self.eventCount))} ] }}\n')
diff --git a/scripts/ethosumonitor/sample.json b/scripts/ethosumonitor/sample.json
new file mode 100644
index 0000000..537e95a
--- /dev/null
+++ b/scripts/ethosumonitor/sample.json
@@ -0,0 +1,19 @@
+{
+    "__comment__": [
+        "'host' is the Linux physical address accessible from /dev/mem.",
+        "'device' is the corresponding device address.",
+        "'size' is the size in bytes of the memory region."
+    ],
+    "memoryMap": [
+        {
+            "host": "0x6cf00000",
+            "device": "0x00000000",
+            "size": "0x00400000"
+        },
+        {
+            "host": "0x84000000",
+            "device": "0x64000000",
+            "size": "0x01000000"
+        }
+    ]
+}
diff --git a/scripts/ethosumonitor/types.py b/scripts/ethosumonitor/types.py
new file mode 100644
index 0000000..5116955
--- /dev/null
+++ b/scripts/ethosumonitor/types.py
@@ -0,0 +1,129 @@
+#
+# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import struct
+
+class EventRecord_t:
+    SIZE = 16
+
+    INFO_ID_MASK        = 0x0000FFFF
+    INFO_MESSAGE_MASK   = 0x000000FF
+    INFO_COMPONENT_MASK = 0x0000FF00
+    INFO_COMPONENT_POS  = 8
+    INFO_FIRST          = 0x01000000
+    INFO_LAST           = 0x02000000
+    INFO_LOCKED         = 0x04000000
+    INFO_VALID          = 0x08000000
+    INFO_MSB_TS         = 0x10000000
+    INFO_MSB_VAL1       = 0x20000000
+    INFO_MSB_VAL2       = 0x40000000
+    INFO_TBIT           = 0x80000000
+
+    # Component identifiers
+    EVENT_CID       = 0xFF
+    ETHOSU_CID      = 0x00
+
+    # Message identifiers
+    EVENT_MID_INIT  = 0x00
+    EVENT_MID_START = 0x01
+    EVENT_MID_STOP  = 0x02
+    EVENT_MID_CLOCK = 0x03
+
+    EVENT_ID_INIT   = (EVENT_CID << 8) | EVENT_MID_INIT
+    EVENT_ID_START  = (EVENT_CID << 8) | EVENT_MID_START
+    EVENT_ID_STOP   = (EVENT_CID << 8) | EVENT_MID_STOP
+    EVENT_ID_CLOCK  = (EVENT_CID << 8) | EVENT_MID_CLOCK
+
+    def __init__(self, data):
+        self.data = data
+
+        # Unpack the struct and restore the MSB from info to timestamp, val1 and val2
+        t = struct.unpack('IIII', data)
+        self.timestamp = t[0] & ~EventRecord_t.INFO_TBIT | (t[3] & EventRecord_t.INFO_MSB_TS) << 3
+        self.val1 = t[1] & ~EventRecord_t.INFO_TBIT | (t[3] & EventRecord_t.INFO_MSB_VAL1) << 2
+        self.val2 = t[2] & ~EventRecord_t.INFO_TBIT | (t[3] & EventRecord_t.INFO_MSB_VAL2) << 1
+        self.info = t[3]
+
+    def first(self):
+        return self.info & EventRecord_t.INFO_FIRST != 0
+
+    def last(self):
+        return self.info & EventRecord_t.INFO_LAST != 0
+
+    def component(self):
+        return (self.info & EventRecord_t.INFO_COMPONENT_MASK) >> EventRecord_t.INFO_COMPONENT_POS
+
+    def message(self):
+        return self.info & EventRecord_t.INFO_MESSAGE_MASK
+
+    def id(self):
+        return self.info & EventRecord_t.INFO_ID_MASK
+
+    def locked(self):
+        return self.info & EventRecord_t.INFO_LOCKED != 0
+
+    def valid(self):
+        return self.info & EventRecord_t.INFO_VALID != 0
+
+    def __str__(self):
+        return f'{{ "timestamp": {hex(self.timestamp)}, "val1": {hex(self.val1)}, "val2": {hex(self.val2)}, "info": "{hex(self.info)}" }}'
+
+class EventStatus_t:
+    SIZE = 36
+
+    def __init__(self, data):
+        t = struct.unpack('BBHIIIIIIII', data)
+
+        self.state = t[0]
+        self.context = t[1]
+        self.infoCrc = t[2]
+        self.recordIndex = t[3]
+        self.recordsWritten = t[4]
+        self.recordsDumped = t[5]
+        self.tsOverflow = t[6]
+        self.tsFreq = t[7]
+        self.tsLast = t[8]
+        self.initCount = t[9]
+        self.signature = t[10]
+
+    def __str__(self):
+        return f'{{ state={self.state}, context={self.context}, info_crc={self.infoCrc}, ' \
+               f'record_index={self.recordIndex}, records_written={self.recordsWritten}, records_dumped={self.recordsDumped}, ' \
+               f'ts_overflow={self.tsOverflow}, ts_freq={self.tsFreq}, ts_last={self.tsLast}, ' \
+               f'init_count={self.initCount}, signature={self.signature} }}'
+
+class EventRecorderInfo_t:
+    SIZE = 24
+
+    def __init__(self, data):
+        t = struct.unpack('BBHIIIIBBBB', data)
+
+        self.protocolType = t[0]
+        # self._reserved = t[1]
+        self.protocolVersion = t[2]
+        self.recordCount = t[3]
+        self.eventBuffer = t[4]
+        self.eventFilter = t[5]
+        self.eventStatus = t[6]
+        self.tsSource = t[7]
+        # self._reserved1 = t[8]
+        # self._reserved2 = t[9]
+        # self._reserved3 = t[10]
+
+    def __str__(self):
+        return f'{{ protocolType={hex(self.protocolType)}, protocolVersion={hex(self.protocolVersion)}, recordCount={self.recordCount}, eventBuffer={hex(self.eventBuffer)}, eventFilter={hex(self.eventFilter)}, eventStatus={hex(self.eventStatus)}, tsSource={self.tsSource} }}'
diff --git a/targets/corstone-300/CMakeLists.txt b/targets/corstone-300/CMakeLists.txt
index b129b71..b76c474 100644
--- a/targets/corstone-300/CMakeLists.txt
+++ b/targets/corstone-300/CMakeLists.txt
@@ -132,6 +132,11 @@
         ETHOSU)
 endif()
 
+if (TARGET event_recorder)
+    target_include_directories(event_recorder BEFORE INTERFACE
+        event_recorder)
+endif()
+
 ###############################################################################
 # Applications
 ###############################################################################
diff --git a/targets/corstone-300/event_recorder/EventRecorderConf.h b/targets/corstone-300/event_recorder/EventRecorderConf.h
new file mode 100644
index 0000000..396cfd9
--- /dev/null
+++ b/targets/corstone-300/event_recorder/EventRecorderConf.h
@@ -0,0 +1,47 @@
+/*
+ * SPDX-FileCopyrightText: Copyright 2016-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the License); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Name:    EventRecorderConf.h
+ * Purpose: Event Recorder software component configuration options
+ * Rev.:    V1.1.0
+ */
+
+//-------- <<< Use Configuration Wizard in Context Menu >>> --------------------
+
+// <h>Event Recorder
+
+//   <o>Number of Records
+//     <8=>8 <16=>16 <32=>32 <64=>64 <128=>128 <256=>256 <512=>512 <1024=>1024
+//     <2048=>2048 <4096=>4096 <8192=>8192 <16384=>16384 <32768=>32768
+//     <65536=>65536
+//   <i>Configures size of Event Record Buffer (each record is 16 bytes)
+//   <i>Must be 2^n (min=8, max=65536)
+#define EVENT_RECORD_COUNT 1024U
+
+//   <o>Time Stamp Source
+//      <0=> DWT Cycle Counter  <1=> SysTick  <2=> CMSIS-RTOS2 System Timer
+//      <3=> User Timer (Normal Reset)  <4=> User Timer (Power-On Reset)
+//   <i>Selects source for 32-bit time stamp
+#define EVENT_TIMESTAMP_SOURCE 0
+
+//   <o>Time Stamp Clock Frequency [Hz] <0-1000000000>
+//   <i>Defines initial time stamp clock frequency (0 when not used)
+#define EVENT_TIMESTAMP_FREQ 0U
+
+// </h>
+
+//------------- <<< end of configuration section >>> ---------------------------