Initial CHERIoT Soundstream demo
Converts the bare-metal soundstream demo app to a hypothetical sencha
platform (derived from matcha by replacing the SMC Ibex core by a RISC-V
CHERIoT core). This depends on the SEC to boot the SMC from flash and
uses DIF support where possible for peripherals.
Specific changes:
- stick drivers in compartments
- use cheriot-rtos interrupts & threads
- use capabilities for mmio + shared memory refs
- allocate app data from the heap instead of using global vars (temp
until toolchain is updated)
- hide hard-to-use-directly bits in a compat.h
- use cheriot api's where appropriate (e.g. __builtin_unreachable -> panic)
- band-aid dif include files to workaround c++ usage
NB: interrupt thread stacks are super-sized to support logging
Bug: 330741645
Bypass-Presubmit-Reason: no sencha presubmit tests
Change-Id: Iea2122422231571eb0a477b31b47eed0cb0c18f7
diff --git a/sw/device/cheriot/soundstream/.gitignore b/sw/device/cheriot/soundstream/.gitignore
new file mode 100644
index 0000000..b39c548
--- /dev/null
+++ b/sw/device/cheriot/soundstream/.gitignore
@@ -0,0 +1 @@
+*.proto
diff --git a/sw/device/cheriot/soundstream/README.md b/sw/device/cheriot/soundstream/README.md
new file mode 100644
index 0000000..12f2f32
--- /dev/null
+++ b/sw/device/cheriot/soundstream/README.md
@@ -0,0 +1,144 @@
+Soundstream bare-metal demo example
+===================================
+
+Cheriot port of the shodan soundstream bare-metal demo.
+
+If you do not have a shodan repo setup, follow the instructions at
+https://spacebeaker.googlesource.com/shodan/docs/+/refs/heads/master/GettingStarted.md.
+
+Be sure ROOTDIR is set in the environment pointing to a current
+shodan repo checkout and the target platform is "sencha"; e.g.
+```shell
+cd ~/shodan
+source build/setup.sh
+set-platform sencha
+printenv ROOTDIR
+/usr/local/google/home/sleffler/shodan
+```
+
+The first time you setup a "sencha" platform you need to install the
+necessary tools:
+```shell
+set-platform sencha
+m tools
+```
+(note the tools are platform-dependent and only installed when
+the current platform is set to "sencha").
+
+You also need a current `xmake` to build cheriot firmware. Note the most
+recent prebuilt package is too old so you need to do something like:
+```shell
+$ sudo apt-get install xmake
+Reading package lists... Done
+Building dependency tree... Done
+Reading state information... Done
+The following NEW packages will be installed:
+ xmake
+0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
+...
+Setting up xmake (2.8.6+ds-3) ...
+...
+$ which xmake
+/usr/bin/xmake
+$ xmake update
+update version v2.9.1 from official source ..
+ => download https://gitlab.com/tboox/xmake.git .. ok
+ => install to ~/.local/bin .. ok
+```
+(if you use an old xmake you will see this failure:
+```shell
+ xmake build
+checking for platform ... cheriot
+checking for architecture ... cheriot
+error: decode json failed, @programdir/core/base/json.lua:223: invalid json syntax starting at position 63: x2000000,
+```
+)
+
+Build a sencha platform image with the soundstream firmware for the SMC
+and run the simulator:
+
+```shell
+$ m simulate
+...
+export XMAKE_CONFIGDIR=/usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release; \
+cd/usr/local/google/home/sleffler/shodan/hw/matcha/sw/device/cheriot/soundstream && \
+ xmake config \
+ -o /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release \
+ --sdk=/usr/local/google/home/sleffler/shodan/cache/cheriot-tools \
+ --board=sencha \
+ --debug-scheduler=true --debug-allocator=true && \
+xmake build
+checking for platform ... cheriot
+checking for architecture ... cheriot
+generating /usr/local/google/home/sleffler/shodan/sw/cheriot-rtos/sdk/firmware.ldscript.in ... ok
+[ 31%]: cache compiling.release i2s.cc
+[ 31%]: cache compiling.release ../../lib/dif/dif_i2s.c
+[ 32%]: cache compiling.release soundstream.cc
+[ 32%]: cache compiling.release ../../lib/dif/autogen/dif_i2s_autogen.c
+[ 32%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/crt/cz.c
+[ 32%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/crt/arith64.c
+[ 32%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/scheduler/main.cc
+[ 32%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/atomic/atomic1.cc
+[ 33%]: cache compiling.release encode.cc
+[ 34%]: cache compiling.release ../../../../hw/top_matcha/sw/autogen/top_matcha.c
+[ 37%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/freestanding/memcmp.c
+[ 38%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/freestanding/memcpy.c
+[ 38%]: compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/token_library/token_unseal.S
+[ 39%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/freestanding/memset.c
+[ 40%]: cache compiling.release mailbox.cc
+[ 42%]: cache compiling.release ../../lib/dif/dif_tlul_mailbox.c
+[ 43%]: cache compiling.release ../../lib/dif/autogen/dif_tlul_mailbox_autogen.c
+[ 44%]: compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/switcher/entry.S
+[ 45%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/loader/boot.cc
+[ 46%]: compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/loader/boot.S
+[ 46%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/software_revoker/revoker.cc
+[ 48%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/debug/debug.cc
+[ 49%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/core/allocator/main.cc
+[ 50%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/compartment_helpers/claim_fast.cc
+[ 51%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/compartment_helpers/check_pointer.cc
+[ 53%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/locks/locks.cc
+[ 54%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/locks/semaphore.cc
+[ 55%]: cache compiling.release ../../../../../../sw/cheriot-rtos/sdk/lib/atomic/atomic4.cc
+[ 56%]: cache compiling.release ml_top.cc
+[ 57%]: cache compiling.release ../../lib/dif/dif_ml_top.c
+[ 59%]: cache compiling.release ../../lib/dif/autogen/dif_ml_top_autogen.c
+[ 60%]: linking library crt.library
+[ 61%]: linking privileged library cheriot.token_library.library
+[ 62%]: linking library freestanding.library
+[ 65%]: linking compartment i2s.compartment
+[ 66%]: linking compartment soundstream.compartment
+[ 67%]: linking library atomic1.library
+[ 68%]: linking compartment mailbox.compartment
+[ 69%]: linking library debug.library
+[ 71%]: linking privileged compartment cheriot.software_revoker.compartment
+[ 83%]: linking library atomic4.library
+[ 85%]: linking library compartment_helpers.library
+[ 89%]: linking library locks.library
+[ 91%]: linking privileged compartment soundstream-firmware.scheduler.compartment
+[ 93%]: linking compartment ml_top.compartment
+[ 96%]: linking privileged compartment cheriot.allocator.compartment
+[ 98%]: linking firmware ../../../../../../out/cheriot/sencha/release/cheriot/cheriot/release/soundstream-firmware
+[ 98%]: Creating firmware report ../../../../../../out/cheriot/sencha/release/cheriot/cheriot/release/soundstream-firmware.json
+[ 98%]: Creating firmware dump ../../../../../../out/cheriot/sencha/release/cheriot/cheriot/release/soundstream-firmware.dump
+...
+mkdir /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp
+cp -f /usr/local/google/home/sleffler/shodan/out/matcha-bundle-release.elf /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/matcha-tock-bundle
+riscv32-unknown-elf-strip /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/matcha-tock-bundle
+riscv32-unknown-elf-objcopy -O binary -g /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/matcha-tock-bundle /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/matcha-tock-bundle.bin
+ln -sf /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/cheriot/cheriot/release/soundstream-firmware /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/kernel
+tar -C /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp -cvhf /usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/ext_flash.tar matcha-tock-bundle.bin kernel
+matcha-tock-bundle.bin
+kernel
+cd /usr/local/google/home/sleffler/shodan && /usr/local/google/home/sleffler/shodan/cache/renode/renode --disable-xwt --port 1234 -e "\
+ \$repl_file = @sim/config/platforms/sencha.repl; \
+ \$tar = @/usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/ext_flash.tar; \
+ \$sc_bin =@/usr/local/google/home/sleffler/shodan/out/cheriot/sencha/release/tmp/matcha-tock-bundle.bin; \
+ \$term_port = 3456; \$gdb_port = 3333; i @sim/config/sencha.resc; \
+ pause; cpu0 IsHalted false; start"
+16:20:42.1702 [INFO] Loaded monitor commands from: /usr/local/google/home/sleffler/shodan/cache/renode/scripts/monitor.py
+16:20:42.1895 [INFO] Monitor available in telnet mode on port 1234
+16:20:42.4740 [INFO] Including script: /usr/local/google/home/sleffler/shodan/sim/config/sencha.resc
+16:20:42.4910 [INFO] System bus created.
+16:20:45.5498 [INFO] Including script: /usr/local/google/home/sleffler/shodan/sim/config/sencha.resc
+...
+```
diff --git a/sw/device/cheriot/soundstream/compat.h b/sw/device/cheriot/soundstream/compat.h
new file mode 100644
index 0000000..70b1193
--- /dev/null
+++ b/sw/device/cheriot/soundstream/compat.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_COMPAT_H_
+#define EXAMPLES_SOUNDSTREAM_COMPAT_H_
+
+// XXX maybe uart_write?
+// XXX fix var args
+#define LOG_ERROR(msg, ...) Debug::log("{}", msg)
+#define LOG_INFO(msg, ...) Debug::log("{}", msg)
+#define LOG_FATAL(msg, ...) Debug::Assert(false, "{}", msg)
+
+/**
+ * Checks that the given condition is true. If the condition is false, this
+ * function logs and then aborts.
+ *
+ * @param condition An expression to check.
+ * @param ... Arguments to a LOG_* macro, which are evaluated if the check
+ * fails.
+ */
+#define CHECK(condition, ...) \
+ do { \
+ if (!(condition)) { \
+ /* NOTE: because the condition in this if \
+ statement can be statically determined, \
+ only one of the below string constants \
+ will be included in the final binary.*/ \
+ if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \
+ LOG_ERROR("CHECK-fail: " #condition); \
+ } else { \
+ LOG_ERROR("CHECK-fail: " __VA_ARGS__); \
+ } \
+ /* Currently, this macro will call into \
+ the test failure code, which logs \
+ "FAIL" and aborts. In the future, \
+ we will try to condition on whether \
+ or not this is a test.*/ \
+ /* XXX fill me in */ \
+ } \
+ } while (false)
+
+/**
+ * Checks that the given DIF call returns kDifOk. If the DIF call returns a
+ * different dif_result_t value (defined in sw/device/lib/dif/dif_base.h), this
+ * function logs and then aborts.
+ *
+ * @param dif_call DIF call to invoke and check its return value.
+ * @param ... Arguments to a LOG_* macro, which are evaluated if the check
+ * fails.
+ */
+#define CHECK_DIF_OK(dif_call, ...) \
+ do { \
+ dif_result_t dif_result = dif_call; \
+ if (dif_result != kDifOk) { \
+ /* NOTE: because the condition in this if \
+ statement can be statically determined, \
+ only one of the below string constants \
+ will be included in the final binary.*/ \
+ if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \
+ LOG_ERROR("DIF-fail: " #dif_call " returns %d", dif_result); \
+ } else { \
+ LOG_ERROR("DIF-fail: " __VA_ARGS__); \
+ } \
+ /* Currently, this macro will call into \
+ the test failure code, which logs \
+ "FAIL" and aborts. In the future, \
+ we will try to condition on whether \
+ or not this is a test.*/ \
+ /* XXX fill me in */ \
+ } \
+ } while (false)
+
+// TODO(sleffler): need a scheduler yield kinda call
+inline void wait_for_interrupt(void) { asm volatile("wfi"); }
+
+#endif // EXAMPLES_SOUNDSTREAM_COMPAT_H_
diff --git a/sw/device/cheriot/soundstream/encode.cc b/sw/device/cheriot/soundstream/encode.cc
new file mode 100644
index 0000000..da826d1
--- /dev/null
+++ b/sw/device/cheriot/soundstream/encode.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+#include "encode.h"
+
+const char base64_alphabet[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+void encode(const uint8_t* const in, size_t in_len, char* out) {
+ size_t rem = in_len % 3;
+ size_t out_idx = 0;
+ for (int i = 0; i < (in_len - rem); i += 3) {
+ out[out_idx++] = base64_alphabet[(in[0 + i] >> 2)];
+ out[out_idx++] =
+ base64_alphabet[((in[0 + i] & 0x3) << 4) | (in[1 + i] >> 4)];
+ out[out_idx++] =
+ base64_alphabet[((in[1 + i] & 0xf) << 2) | (in[2 + i] >> 6)];
+ out[out_idx++] = base64_alphabet[(in[2 + i] & 0x3f)];
+ }
+
+ if (rem == 2) {
+ out[out_idx++] = base64_alphabet[in[in_len - rem] >> 2];
+ out[out_idx++] = base64_alphabet[((in[in_len - rem] & 0x3) << 4) |
+ (in[in_len - rem + 1] >> 4)];
+ out[out_idx++] = base64_alphabet[((in[in_len - rem + 1] & 0xf) << 2)];
+ out[out_idx++] = '=';
+ } else if (rem == 1) {
+ out[out_idx++] = base64_alphabet[in[in_len - rem] >> 2];
+ out[out_idx++] = base64_alphabet[((in[in_len - rem] & 0x3) << 4) |
+ (in[in_len - rem + 1] >> 4)];
+ out[out_idx++] = '=';
+ out[out_idx++] = '=';
+ }
+ // NULL-terminate.
+ out[out_idx] = 0;
+}
diff --git a/sw/device/cheriot/soundstream/encode.h b/sw/device/cheriot/soundstream/encode.h
new file mode 100644
index 0000000..01f9b3e
--- /dev/null
+++ b/sw/device/cheriot/soundstream/encode.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_ENCODE_H_
+#define EXAMPLES_SOUNDSTREAM_ENCODE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define ENCODE_OUT_SIZE(s) ((size_t)((((s) + 2) / 3) * 4 + 1))
+
+void encode(const uint8_t* in, size_t in_len, char* out);
+
+#endif // EXAMPLES_SOUNDSTREAM_ENCODE_H_
diff --git a/sw/device/cheriot/soundstream/i2s.cc b/sw/device/cheriot/soundstream/i2s.cc
new file mode 100644
index 0000000..90a04bf
--- /dev/null
+++ b/sw/device/cheriot/soundstream/i2s.cc
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+/*
+ * Barebones i2s for soundstream. Only supports recording and
+ * without any buffering. The caller must manage the rx fifo
+ * carefully to avoid data loss.
+ */
+#include "i2s.h"
+
+#include <fail-simulator-on-error.h>
+#include <futex.h>
+#include <interrupt.h>
+#include <locks.h>
+#include <thread.h>
+
+#include <debug.hh>
+#include <platform/sencha/platform-i2s.hh>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "i2s_regs.h"
+
+/// Expose debugging features unconditionally for this compartment.
+using Debug = ConditionalDebug<false, "I2S">;
+
+#include "compat.h"
+
+typedef uint32_t i2s_mmio_t[TOP_MATCHA_I2S0_SIZE_BYTES / sizeof(uint32_t)];
+
+static dif_i2s_t i2s;
+static void i2s_irq_acknowledge(i2s_irq_t irq_id);
+
+#define CHECK_INIT() CHECK(i2s.base_addr.base != 0)
+
+static CountingSemaphoreState startup = { 0, 1 };
+
+void i2s_init(void) {
+ CHECK_DIF_OK(dif_i2s_init(
+ mmio_region_from_addr((uintptr_t)MMIO_CAPABILITY(i2s_mmio_t, i2s)),
+ &i2s));
+ semaphore_put(&startup);
+}
+
+// NB: could be atomic but not needed for our ussage.
+volatile bool rx_watermark_seen = false;
+
+void i2s_isr(void) {
+ Timeout t = {0, UnlimitedTimeout};
+ semaphore_get(&t, &startup);
+ Debug::log("i2s_isr: i2s {} (Thread {})", i2s.base_addr.base, thread_id_get());
+
+ const uint32_t *rxWatermarkFutex = interrupt_futex_get(
+ STATIC_SEALED_VALUE(i2sRxWatermarkInterruptCapability));
+ uint32_t last = *rxWatermarkFutex;
+ for (;;) {
+ Debug::Assert(futex_wait(rxWatermarkFutex, last) == 0, "futex_wait");
+ last = *rxWatermarkFutex;
+ Debug::log("i2s_isr: i2s {} last {})", i2s.base_addr.base, last);
+ rx_watermark_seen = true;
+ // Acknowledge and disable the interrupt; it will be
+ // re-enabled after the RX FIFO is drained.
+ i2s_irq_acknowledge(kI2sIrqRxWatermark);
+ i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/false);
+ CHECK(interrupt_complete(
+ STATIC_SEALED_VALUE(i2sRxWatermarkInterruptCapability)) == 0);
+ }
+}
+bool i2s_rx_watermark_seen(void) {
+ // return atomic_swap(&rx_watermark_seen, false);
+ bool was_seen = rx_watermark_seen;
+ if (was_seen) {
+ rx_watermark_seen = false;
+ }
+ return was_seen;
+}
+
+static void i2s_irq_acknowledge(i2s_irq_t irq_id) {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_i2s_irq_acknowledge(&i2s, irq_id));
+}
+
+void i2s_irq_acknowledge_all() {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_i2s_irq_acknowledge_all(&i2s));
+}
+
+void i2s_irq_set_enabled(i2s_irq_t irq_id, bool enabled) {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_i2s_irq_set_enabled(
+ &i2s, irq_id, enabled ? kDifToggleEnabled : kDifToggleDisabled));
+}
+
+// Clear RX FIFO
+void i2s_rxfifo_clear(void) {
+ CHECK_INIT();
+ uint32_t reg_val;
+ reg_val = mmio_region_read32(i2s.base_addr, I2S_FIFO_CTRL_REG_OFFSET);
+ reg_val = bitfield_bit32_write(reg_val, I2S_FIFO_CTRL_RXRST_BIT, 1);
+ mmio_region_write32(i2s.base_addr, I2S_FIFO_CTRL_REG_OFFSET, reg_val);
+}
+
+bool i2s_rxfifo_is_empty(void) {
+ CHECK_INIT();
+ bool empty;
+ CHECK_DIF_OK(dif_i2s_rxfifo_empty(&i2s, &empty));
+ return empty;
+}
+
+// Configure and enable recording
+void i2s_record_begin(void) {
+ CHECK_INIT();
+ uint32_t reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET);
+ reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_RX_BIT, 1);
+ reg_val = bitfield_field32_write(reg_val, I2S_CTRL_NCO_RX_FIELD,
+ 24); // divide by 24
+ mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val);
+}
+
+uint32_t i2s_get_rdata(void) {
+ CHECK_INIT();
+ return mmio_region_read32(i2s.base_addr, I2S_RDATA_REG_OFFSET);
+}
+
+// Disable recording
+void i2s_record_end(void) {
+ CHECK_INIT();
+ uint32_t reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET);
+ reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_RX_BIT, 0);
+ mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val);
+}
diff --git a/sw/device/cheriot/soundstream/i2s.h b/sw/device/cheriot/soundstream/i2s.h
new file mode 100644
index 0000000..08f6565
--- /dev/null
+++ b/sw/device/cheriot/soundstream/i2s.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_I2S_H_
+#define EXAMPLES_SOUNDSTREAM_I2S_H_
+
+#include <compartment.h>
+
+#include "sw/device/lib/dif/dif_i2s.h"
+
+typedef dif_i2s_irq_t i2s_irq_t;
+const i2s_irq_t kI2sIrqRxWatermark = kDifI2sIrqRxWatermark;
+const i2s_irq_t kI2sIrqTxWatermark = kDifI2sIrqTxWatermark;
+const i2s_irq_t kI2sIrqTxEmpty = kDifI2sIrqTxEmpty;
+
+void __cheri_compartment("i2s") i2s_init(void);
+void __cheri_compartment("i2s") i2s_isr(void);
+void __cheri_compartment("i2s") i2s_irq_acknowledge_all(void);
+void __cheri_compartment("i2s")
+ i2s_irq_set_enabled(i2s_irq_t irq_id, bool enabled);
+bool __cheri_compartment("i2s") i2s_rx_watermark_seen(void);
+void __cheri_compartment("i2s") i2s_rxfifo_clear(void);
+bool __cheri_compartment("i2s") i2s_rxfifo_is_empty(void);
+void __cheri_compartment("i2s") i2s_record_begin(void);
+void __cheri_compartment("i2s") i2s_record_end(void);
+uint32_t __cheri_compartment("i2s") i2s_get_rdata(void);
+
+#endif // EXAMPLES_SOUNDSTREAM_I2S_H_
diff --git a/sw/device/cheriot/soundstream/mailbox.cc b/sw/device/cheriot/soundstream/mailbox.cc
new file mode 100644
index 0000000..174c17f
--- /dev/null
+++ b/sw/device/cheriot/soundstream/mailbox.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+/*
+ * Special-purpose mailbox support for soundstream. Assumes a
+ * companion implementation is running on the security core.
+ */
+#include "mailbox.h"
+
+#include <fail-simulator-on-error.h>
+#include <futex.h>
+#include <interrupt.h>
+#include <locks.h>
+#include <thread.h>
+
+#include <debug.hh>
+#include <platform/sencha/platform-mailbox.hh>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+
+/// Expose debugging features unconditionally for this compartment.
+using Debug = ConditionalDebug<false, "Mailbox">;
+
+#include "compat.h"
+
+const uint32_t kMessageGpioPressed = 0xdeadbeef;
+const uint32_t kMessageGpioReleased = 0x21524110;
+const uint32_t kMessageEnableLed = 0xcafeb0ba;
+const uint32_t kMessageDisableLed = 0x35014f45;
+
+typedef uint32_t
+ mailbox_mmio_t[TOP_MATCHA_TLUL_MAILBOX_SMC_SIZE_BYTES / sizeof(uint32_t)];
+
+static dif_tlul_mailbox_t mailbox;
+#define CHECK_INIT() CHECK(mailbox.base_addr.base != 0)
+
+static CountingSemaphoreState startup = { 0, 1 };
+
+void mailbox_init(void) {
+ CHECK_DIF_OK(
+ dif_tlul_mailbox_init(mmio_region_from_addr((uintptr_t)MMIO_CAPABILITY(
+ mailbox_mmio_t, mailbox_smc)),
+ &mailbox));
+ semaphore_put(&startup);
+}
+
+// NB: could be atomic but not needed for our ussage.
+volatile bool button_pressed = false;
+
+void mailbox_isr(void) {
+ Timeout t = {0, UnlimitedTimeout};
+ semaphore_get(&t, &startup);
+ Debug::log("mailbox_isr: mailbox {} (Thread {})", mailbox.base_addr.base, thread_id_get());
+
+ const uint32_t *mailboxRtFutex =
+ interrupt_futex_get(STATIC_SEALED_VALUE(mailboxRtInterruptCapability));
+ uint32_t last = *mailboxRtFutex;
+ for (;;) {
+ Debug::Assert(futex_wait(mailboxRtFutex, last) == 0, "futex_wait");
+ last = *mailboxRtFutex;
+ Debug::log("mailbox_isr: mailbox {} last {}", mailbox.base_addr.base, last);
+ interrupt_complete(STATIC_SEALED_VALUE(mailboxRtInterruptCapability));
+
+ // NB: this is NOT the TockOS mailbox protocol that exchanges
+ // variable length packets that are encoded by Rust's postcard
+ // crate.
+ uint32_t message;
+ CHECK_DIF_OK(dif_tlul_mailbox_read_message(&mailbox, &message));
+ if (message == kMessageGpioPressed) {
+ button_pressed = true;
+ } else if (message == kMessageGpioReleased) {
+ button_pressed = false;
+ } else {
+ CHECK(false, "Unknown message");
+ }
+ }
+}
+bool mailbox_button_pressed(void) { return button_pressed; }
+
+// XXX temp until gpio's on the SEC can be used (either in simulation or for real)
+void mailbox_set_button_pressed(bool pressed) { button_pressed = pressed; }
+
+void mailbox_set_led(bool enabled) {
+ CHECK_INIT();
+ // NB: this is not the TockOS mailbox protocol.
+ uint32_t message_enable_led =
+ enabled ? kMessageEnableLed : kMessageDisableLed;
+ CHECK_DIF_OK(dif_tlul_mailbox_send_message(&mailbox, &message_enable_led));
+}
diff --git a/sw/device/cheriot/soundstream/mailbox.h b/sw/device/cheriot/soundstream/mailbox.h
new file mode 100644
index 0000000..6663fca
--- /dev/null
+++ b/sw/device/cheriot/soundstream/mailbox.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_MBOX_H_
+#define EXAMPLES_SOUNDSTREAM_MBOX_H_
+
+#include <compartment.h>
+
+#include "sw/device/lib/dif/dif_tlul_mailbox.h"
+
+typedef dif_tlul_mailbox_irq_t mailbox_irq_t;
+const mailbox_irq_t kMboxIrqRtIrq = kDifTlulMailboxIrqRtirq;
+
+void __cheri_compartment("mailbox") mailbox_init(void);
+void __cheri_compartment("mailbox") mailbox_isr(void);
+bool __cheri_compartment("mailbox") mailbox_button_pressed(void);
+// XXX temp
+void __cheri_compartment("mailbox") mailbox_set_button_pressed(bool);
+void __cheri_compartment("mailbox") mailbox_set_led(bool enabled);
+
+#endif // EXAMPLES_SOUNDSTREAM_MBOX_H_
diff --git a/sw/device/cheriot/soundstream/ml_top.cc b/sw/device/cheriot/soundstream/ml_top.cc
new file mode 100644
index 0000000..dc931ce
--- /dev/null
+++ b/sw/device/cheriot/soundstream/ml_top.cc
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+// NB: override default setup done for DIF code linked into this compartment
+#ifdef CHERIOT_NO_AMBIENT_MALLOC
+#undef CHERIOT_NO_AMBIENT_MALLOC
+#endif
+
+/*
+ * ML accelerator support for soundstream. There is no support for
+ * loading a model; it's assumed done by the security core which
+ * has access to the flash where model files are placed.
+ */
+#include "ml_top.h"
+
+#include <fail-simulator-on-error.h>
+#include <futex.h>
+#include <locks.h>
+#include <multiwaiter.h>
+#include <thread.h>
+
+#include <debug.hh>
+#include <platform/sencha/platform-ml_top.hh>
+
+#include "compat.h"
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "ml_top_regs.h"
+
+typedef uint8_t ml_top_dmem_t[TOP_MATCHA_RAM_ML_DMEM_SIZE_BYTES];
+typedef uint32_t
+ ml_top_mmio_t[TOP_MATCHA_ML_TOP_CORE_SIZE_BYTES / sizeof(uint32_t)];
+
+/// Expose debugging features unconditionally for this compartment.
+using Debug = ConditionalDebug<false, "ML_TOP">;
+
+static dif_ml_top_t ml_top;
+#define CHECK_INIT() CHECK(ml_top.base_addr.base != 0)
+
+static CountingSemaphoreState startup = { 0, 1 };
+
+void ml_top_init(void) {
+ CHECK_DIF_OK(dif_ml_top_init(mmio_region_from_addr((uintptr_t)MMIO_CAPABILITY(
+ ml_top_mmio_t, ml_top_core)),
+ &ml_top));
+ semaphore_put(&startup);
+}
+
+// NB: could be atomic but not needed for our ussage.
+volatile bool finish_done = false;
+
+void ml_top_isr(void) {
+ Timeout t = {0, UnlimitedTimeout};
+ semaphore_get(&t, &startup);
+ Debug::log("ml_top_isr: ml_top {} (Thread {})",
+ ml_top.base_addr.base, thread_id_get());
+
+ MultiWaiter* mw;
+ Timeout unlimited{0, UnlimitedTimeout};
+ int error = multiwaiter_create(&unlimited, MALLOC_CAPABILITY, &mw, 2);
+ Debug::Assert(error == 0 && mw != nullptr,
+ "multiwaiter_create failed: {}", error);
+
+ EventWaiterSource events[2];
+ const uint32_t* mlTopFinishFutex =
+ interrupt_futex_get(STATIC_SEALED_VALUE(mlTopFinishInterruptCapability));
+ events[0] = {(void*)mlTopFinishFutex, EventWaiterFutex, *mlTopFinishFutex};
+ const uint32_t* mlTopFaultFutex =
+ interrupt_futex_get(STATIC_SEALED_VALUE(mlTopFaultInterruptCapability));
+ events[1] = {(void*)mlTopFaultFutex, EventWaiterFutex, *mlTopFaultFutex};
+
+ for (;;) {
+ Debug::Assert(multiwaiter_wait(&unlimited, mw, events, 2) == 0, "multiwaiter_wait");
+ Debug::log("ml_top_isr: Finish:{} Fault:{}", events[0].value, events[1].value);
+ if (events[1].value == 1) { // Fault signaled
+ LOG_FATAL("ML_TOP fault!");
+ abort();
+ }
+ if (events[0].value == 1) { // Finish signaled
+ finish_done = true;
+ interrupt_complete(STATIC_SEALED_VALUE(mlTopFinishInterruptCapability));
+ CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+ }
+ }
+}
+bool ml_top_finish_done(void) {
+ // return atomic_swap(&finish_done, false);
+ bool was_done = finish_done;
+ if (was_done) {
+ finish_done = false;
+ }
+ return was_done;
+}
+
+void ml_top_irq_acknowledge(ml_top_irq_t irq_id) {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_ml_top_irq_acknowledge(&ml_top, irq_id));
+}
+
+void ml_top_irq_acknowledge_all() {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ml_top_irq_set_enabled(ml_top_irq_t irq_id, bool enabled) {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_ml_top_irq_set_enabled(
+ &ml_top, irq_id, enabled ? kDifToggleEnabled : kDifToggleDisabled));
+}
+
+void ml_top_resume_ctrl_en(uint32_t resume_pc) {
+ CHECK_INIT();
+ CHECK_DIF_OK(dif_ml_top_resume_ctrl_en(&ml_top, resume_pc));
+}
+
+void ml_top_set_input(void* const data, size_t data_len_bytes) {
+ void* input_ptr = (void*)(MMIO_CAPABILITY(ml_top_dmem_t, ml_top_dmem) +
+ TOP_MATCHA_RAM_ML_DMEM_SIZE_BYTES - 4096);
+ memcpy(input_ptr, data, data_len_bytes);
+}
+
+void ml_top_get_output_header(struct output_header* header) {
+ uint8_t* ml_top_dmem_base = (uint8_t*) MMIO_CAPABILITY(ml_top_dmem_t, ml_top_dmem);
+ struct output_header* output_header_ptr = (struct output_header*)
+ (ml_top_dmem_base + (TOP_MATCHA_RAM_ML_DMEM_SIZE_BYTES - 0x40));
+ header->return_code = output_header_ptr->return_code;
+ header->output_ptr = output_header_ptr->output_ptr;
+ header->length = output_header_ptr->length;
+ header->resume_pc = output_header_ptr->resume_pc;
+#if 0
+ Debug::log("return_code: {}", header.return_code);
+ Debug::log("output_ptr: {}", header.output_ptr);
+ Debug::log("length: {}", header.length);
+ Debug::log("resume_pc: {}", header.resume_pc);
+#endif
+}
+
+void ml_top_get_output_data(struct output_header* const header, void* buffer) {
+ const void* src = (const void*)(MMIO_CAPABILITY(ml_top_dmem_t, ml_top_dmem) +
+ header->output_ptr);
+ memcpy(buffer, src, header->length);
+}
diff --git a/sw/device/cheriot/soundstream/ml_top.h b/sw/device/cheriot/soundstream/ml_top.h
new file mode 100644
index 0000000..5555576
--- /dev/null
+++ b/sw/device/cheriot/soundstream/ml_top.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_ML_TOP_H_
+#define EXAMPLES_SOUNDSTREAM_ML_TOP_H_
+
+#include <compartment.h>
+
+#include "sw/device/lib/dif/dif_ml_top.h"
+
+typedef dif_ml_top_irq_t ml_top_irq_t;
+const uint32_t kMlTopIrqFinish = kDifMlTopIrqFinish;
+
+struct output_header {
+ uint32_t return_code; // Populated in kelvin_start
+ uint32_t output_ptr;
+ uint32_t length;
+ uint32_t resume_pc; // PC at which to resume for another invocation.
+};
+
+void __cheri_compartment("ml_top") ml_top_init(void);
+void __cheri_compartment("ml_top") ml_top_isr(void);
+void __cheri_compartment("ml_top") ml_top_irq_acknowledge_all(void);
+void __cheri_compartment("ml_top")
+ ml_top_irq_set_enabled(ml_top_irq_t irq_id, bool enabled);
+bool __cheri_compartment("ml_top") ml_top_finish_done(void);
+void __cheri_compartment("ml_top") ml_top_resume_ctrl_en(uint32_t resume_pc);
+void __cheri_compartment("ml_top")
+ ml_top_set_input(void* const data, size_t data_len_bytes);
+void __cheri_compartment("ml_top")
+ ml_top_get_output_header(struct output_header* header);
+void __cheri_compartment("ml_top")
+ ml_top_get_output_data(struct output_header* const header, void* buffer);
+
+#endif // EXAMPLES_SOUNDSTREAM_ML_TOP_H_
diff --git a/sw/device/cheriot/soundstream/soundstream.cc b/sw/device/cheriot/soundstream/soundstream.cc
new file mode 100644
index 0000000..897a921
--- /dev/null
+++ b/sw/device/cheriot/soundstream/soundstream.cc
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+// Data structures that hold audio samples are allocated on the heap
+// because they are too big (for now) for .data/.bss. We use calloc
+// for this and need to override the default quota (4K) to satisfy our
+// large'ish requests.
+//#define kSamples (5 * 16000)
+#define kSamples (5000) // NB: reduced sample count for slow renode
+#define kFilterSamples (256)
+#define roundup(a, b) (((a) + (b) - 1) / (b)) * (b)
+#define MALLOC_QUOTA \
+ roundup( \
+ (kSamples * sizeof(int32_t)) + 2 * (kFilterSamples * sizeof(int16_t)), \
+ 4096)
+
+#include "soundstream.h"
+
+#include <fail-simulator-on-error.h>
+#include <thread.h>
+
+#include <debug.hh>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+
+/// Expose debugging features unconditionally for this compartment.
+using Debug = ConditionalDebug<true, "SOUNDSTREAM">;
+
+#include "compat.h"
+
+#define ABS(x) (x > 0 ? x : -x)
+
+void __cheri_compartment("soundstream") entry(void) {
+ Debug::log("soundstream (Thread {})", thread_id_get());
+
+ i2s_init();
+ ml_top_init();
+ mailbox_init();
+
+ i2s_rxfifo_clear();
+ i2s_irq_acknowledge_all();
+ i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/true);
+
+ // NB: sample buffers are temporarily allocated from the heap because
+ // the toolchain does not support large'ish data structures. Support
+ // has been committed upstream but not yet brought in.
+
+ // XXX calloc returns 0xffffffff (v:0 0xfffffe00-0xfffffe00 l:0x0 o:0x0 p: -
+ // ------ -- ---) on failure so cannot check against NULL
+ // int16_t samples_left[kFilterSamples] = {0};
+ // int16_t samples_right[kFilterSamples] = {0};
+ int16_t* samples_left = (int16_t*)calloc(sizeof(int16_t), kFilterSamples);
+ Debug::Assert(__builtin_cheri_length_get(samples_left) > 0,
+ "samples_left allocation failed {}", samples_left);
+ int16_t* samples_right = (int16_t*)calloc(sizeof(int16_t), kFilterSamples);
+ Debug::Assert(__builtin_cheri_length_get(samples_right) > 0,
+ "samples_right allocation failed {}", samples_right);
+ size_t index_left = 0;
+ size_t index_right = 0;
+ int32_t total_left = 0;
+ int32_t total_right = 0;
+
+ // static int32_t samples[kSamples];
+ int32_t* samples = (int32_t*)calloc(sizeof(int32_t), kSamples);
+ Debug::Assert(__builtin_cheri_length_get(samples) > 0,
+ "Sample allocation failed {}", samples);
+ memset(samples, 0xa5, sizeof(int32_t) * kSamples);
+
+ Debug::log("Setup complete");
+
+ while (true) {
+ // TODO(sleffler): need custom security core code running and
+ // a way to toggle the gpio associated with the button; for now
+ // just force it to appear as though the button has been pressed.
+ mailbox_set_button_pressed(true);
+
+ // Wait until the record switch is pushed
+ Debug::log("Wait for button press...");
+ while (!mailbox_button_pressed()) {
+ wait_for_interrupt();
+ }
+
+ mailbox_set_led(/*enabled=*/true);
+ Debug::log("Start recording (max {} samples)...", kSamples);
+ i2s_record_begin();
+
+ // Record until our buffer is full or the switch is released.
+ int sample = 0;
+ while (sample < kSamples && mailbox_button_pressed()) {
+ i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/true);
+ while (!i2s_rx_watermark_seen()) {
+ wait_for_interrupt();
+ }
+
+ while (!i2s_rxfifo_is_empty()) {
+ uint32_t reg_val = i2s_get_rdata();
+ // For each sample, split into left and right channel values,
+ // and update the moving average.
+ int16_t left = reg_val >> 16;
+ int16_t right = reg_val & 0xFFFF;
+ total_left -= samples_left[index_left];
+ total_right -= samples_right[index_right];
+ total_left += left;
+ samples_left[index_left] = left;
+ total_right += right;
+ samples_right[index_right] = right;
+ index_left = (index_left + 1) % kFilterSamples;
+ index_right = (index_right + 1) % kFilterSamples;
+ int16_t mean_left = total_left / kFilterSamples;
+ int16_t mean_right = total_right / kFilterSamples;
+
+ // Subtract the moving average from each channel, and repack into the
+ // sample buffer.
+ uint32_t offset_reg_val = (((left - mean_left) & 0xFFFF) << 16) |
+ ((right - mean_right) & 0xFFFF);
+ samples[sample++] = offset_reg_val;
+ if (sample == kSamples || !mailbox_button_pressed()) {
+ break;
+ }
+ }
+ }
+
+ i2s_record_end();
+ mailbox_set_led(/*enabled=*/false);
+
+ Debug::log("Done recording {} samples", sample);
+ int samples_captured = sample;
+
+ // Calculate the min/max of the audio, after correcting DC offsets
+ int32_t max = INT16_MIN;
+ int32_t min = INT16_MAX;
+ for (int i = 1; i < (samples_captured * 2); i += 2) {
+ int16_t* samples_s16 = (int16_t*)samples;
+ int32_t sample = samples_s16[i];
+ if (sample < min) {
+ min = sample;
+ }
+ if (sample > max) {
+ max = sample;
+ }
+ }
+
+ // Calculate a scaling factor, and use this to scale the waveform
+ // to a peak of 75% amplitude.
+ int32_t scale_max = ((int32_t)max * 100) / ((int32_t)INT16_MAX);
+ int32_t scale_min = ABS(((int32_t)min * 100) / ((int32_t)INT16_MIN));
+ int32_t scale = scale_max > scale_min ? scale_max : scale_min;
+ for (int i = 1; i < (samples_captured * 2); i += 2) {
+ int16_t* samples_s16 = (int16_t*)samples;
+ int16_t sample = samples_s16[i];
+ int16_t scaled_sample = (int16_t)(((int32_t)sample * 100) / scale);
+ scaled_sample = (((int32_t)scaled_sample * 75) / 100);
+ samples_s16[i] = scaled_sample;
+ }
+
+ Debug::log("Processing recorded audio...");
+
+ // 320 x int16
+ int iterations_to_process = samples_captured / 320;
+ int16_t process_buffer[320];
+ int16_t result_buffer[64];
+ char result_buffer_encoded[ENCODE_OUT_SIZE(sizeof(result_buffer))];
+
+ struct output_header header;
+ memset(&header, 0, sizeof(header)); // NB: resume_pc = 0
+
+ for (int i = 0; i < iterations_to_process; ++i) {
+ Debug::log("Iteration {}", i);
+ int16_t* samples_s16 = (int16_t*)samples;
+ // Extract left channel audio
+ for (int j = 0; j < 320; ++j) {
+ process_buffer[j] = samples_s16[(i * 320 * 2) + (j * 2) + 1];
+ }
+ ml_top_set_input(process_buffer, sizeof(process_buffer));
+
+ // Start/resume kelvin
+ (void) ml_top_finish_done(); // NB: reset state
+ ml_top_resume_ctrl_en(header.resume_pc);
+
+ // wfi
+ while (!ml_top_finish_done()) {
+ wait_for_interrupt();
+ }
+
+ ml_top_get_output_header(&header);
+ CHECK(header.length == sizeof(result_buffer), "Unexpected ML result size");
+ ml_top_get_output_data(&header, result_buffer);
+
+ encode((const unsigned char*)result_buffer, sizeof(result_buffer),
+ result_buffer_encoded);
+ Debug::log("[sound]::ENCODER:{}",
+ std::string_view(result_buffer_encoded, sizeof(result_buffer_encoded)));
+ }
+
+ Debug::log("[sound]::ENCODER: done");
+ Debug::log("Done with processing.");
+ }
+ panic();
+}
diff --git a/sw/device/cheriot/soundstream/soundstream.h b/sw/device/cheriot/soundstream/soundstream.h
new file mode 100644
index 0000000..640834c
--- /dev/null
+++ b/sw/device/cheriot/soundstream/soundstream.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef EXAMPLES_SOUNDSTREAM_SOUNDSTREAM_H_
+#define EXAMPLES_SOUNDSTREAM_SOUNDSTREAM_H_
+
+#include <stdint.h>
+
+#include "encode.h"
+#include "i2s.h"
+#include "mailbox.h"
+#include "ml_top.h"
+
+#endif // EXAMPLES_SOUNDSTREAM_SOUNDSTREAM_H_
diff --git a/sw/device/cheriot/soundstream/xmake.lua b/sw/device/cheriot/soundstream/xmake.lua
new file mode 100644
index 0000000..3d2d1da
--- /dev/null
+++ b/sw/device/cheriot/soundstream/xmake.lua
@@ -0,0 +1,111 @@
+--
+-- Copyright 2023 Google LLC
+--
+-- 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
+--
+-- http://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.
+
+set_project("CHERIoT soundstream demo")
+
+local shodan_dir = os.getenv("ROOTDIR")
+if shodan_dir == nil or shodan_dir == '' then
+ raise("ROOTDIR not set")
+end
+local cheriot_out_dir = os.getenv("CHERIOT_OUT_DIR")
+if cheriot_out_dir == nil or cheriot_out_dir == '' then
+ raise("CHERIOT_OUT_DIR not set")
+end
+
+local sdkdir = path.join(shodan_dir, "sw/cheriot-rtos/sdk")
+includes(sdkdir)
+set_toolchains("cheriot-clang")
+
+option("board")
+ set_default("sencha")
+
+local matcha_dir = path.join(shodan_dir, "hw/matcha")
+local dif_dir = path.join(matcha_dir, "sw/device/lib/dif")
+local dif_autogen_dir = path.join(dif_dir, "autogen")
+local opentitan_dir = path.join(shodan_dir, "hw/opentitan-upstream")
+local matcha_gen_dir = path.join(cheriot_out_dir, "opentitan-gen/include/opentitan")
+
+-- Support libraries
+includes(path.join(sdkdir, "lib"))
+includes(path.join(sdkdir, "lib/freestanding"))
+
+-- Each driver operates in a compartment.
+compartment("i2s")
+ add_files("i2s.cc")
+ add_files(path.join(dif_dir, "dif_i2s.c"),
+ path.join(dif_autogen_dir, "dif_i2s_autogen.c"))
+ add_includedirs(matcha_dir, matcha_gen_dir, opentitan_dir)
+ add_defines("CHERIOT_NO_AMBIENT_MALLOC")
+
+compartment("mailbox")
+ add_files("mailbox.cc")
+ add_files(path.join(dif_dir, "dif_tlul_mailbox.c"),
+ path.join(dif_autogen_dir, "dif_tlul_mailbox_autogen.c"))
+ add_includedirs(matcha_dir, matcha_gen_dir, opentitan_dir)
+ add_defines("CHERIOT_NO_AMBIENT_MALLOC")
+
+compartment("ml_top")
+ add_files("ml_top.cc")
+ add_files(path.join(dif_dir, "dif_ml_top.c"),
+ path.join(dif_autogen_dir, "dif_ml_top_autogen.c"))
+ add_includedirs(matcha_dir, matcha_gen_dir, opentitan_dir)
+ add_defines("CHERIOT_NO_AMBIENT_MALLOC")
+
+-- Soundstream application.
+compartment("soundstream")
+ add_files("soundstream.cc", "encode.cc")
+ add_files(path.join(matcha_dir, "hw/top_matcha/sw/autogen/top_matcha.c"));
+ add_includedirs(matcha_dir, matcha_gen_dir, opentitan_dir)
+
+-- Firmware image.
+firmware("soundstream-firmware")
+ add_deps("freestanding")
+ add_deps("debug", "i2s", "ml_top", "mailbox", "soundstream")
+ on_load(function(target)
+ target:values_set("board", "$(board)")
+ -- NB: trusted_stack_frames is a guess; +1'd for any
+ -- compartment error handler usage?
+ target:values_set("threads", {
+ {
+ compartment = "soundstream",
+ priority = 1,
+ entry_point = "entry",
+ stack_size = 0x1000, -- 4KB
+ trusted_stack_frames = 5
+ },
+ -- NB: stack sizes bumped for logging
+ {
+ compartment = "i2s",
+ priority = 1,
+ entry_point = "i2s_isr",
+ stack_size = 0x400, -- 512B
+ trusted_stack_frames = 3
+ },
+ {
+ compartment = "ml_top",
+ priority = 1,
+ entry_point = "ml_top_isr",
+ stack_size = 0x400, -- 512B
+ trusted_stack_frames = 3
+ },
+ {
+ compartment = "mailbox",
+ priority = 1,
+ entry_point = "mailbox_isr",
+ stack_size = 0x400, -- 512B
+ trusted_stack_frames = 3
+ }
+ }, {expand = false})
+ end)
diff --git a/sw/device/lib/dif/dif_i2s.h b/sw/device/lib/dif/dif_i2s.h
index 190c454..7d01197 100644
--- a/sw/device/lib/dif/dif_i2s.h
+++ b/sw/device/lib/dif/dif_i2s.h
@@ -16,8 +16,6 @@
*/
-
-
#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2S_H_
#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2S_H_
@@ -36,6 +34,8 @@
extern "C" {
#endif // __cplusplus
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextern-c-compat"
/**
* Runtime configuration for I2S Audio.
*
@@ -60,6 +60,7 @@
typedef struct dif_i2s_output {
// Your fields here.
} dif_i2s_output_t;
+#pragma clang diagnostic pop
/**
* Configures I2S Audio with runtime information.
diff --git a/sw/device/lib/dif/dif_ml_top.h b/sw/device/lib/dif/dif_ml_top.h
index 57ed19e..684c461 100644
--- a/sw/device/lib/dif/dif_ml_top.h
+++ b/sw/device/lib/dif/dif_ml_top.h
@@ -48,6 +48,8 @@
*/
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextern-c-compat"
/**
* Runtime configuration for ML_Top.
*
@@ -72,6 +74,7 @@
typedef struct dif_ml_top_output {
// Your fields here.
} dif_ml_top_output_t;
+#pragma clang diagnostic pop
/**
* Configures ML_Top with runtime information.
diff --git a/sw/device/lib/dif/dif_tlul_mailbox.h b/sw/device/lib/dif/dif_tlul_mailbox.h
index 69aaf20..6b09fdf 100644
--- a/sw/device/lib/dif/dif_tlul_mailbox.h
+++ b/sw/device/lib/dif/dif_tlul_mailbox.h
@@ -39,6 +39,8 @@
dif_result_t dif_tlul_mailbox_read_message(const dif_tlul_mailbox_t *tlul_mailbox, uint32_t *buf);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextern-c-compat"
/**
* Runtime configuration for tlul_mailbox for SC<->SMC communication.
*
@@ -62,6 +64,7 @@
typedef struct dif_tlul_mailbox_output {
// Your fields here.
} dif_tlul_mailbox_output_t;
+#pragma clang diagnostic pop
/**
* Configures tlul_mailbox for SC<->SMC communication with runtime information.