Host->device USB transfer example/lib
- Device-side library and example app that transfers data over USB into
the ML memory, and starts the ML core.
- Host side application that transfers a chosen binary to a waiting
device.
Change-Id: I1b0a69c986953f0f47255431fb4faec4150462c2
diff --git a/sw/device/examples/usb_data_loader/BUILD b/sw/device/examples/usb_data_loader/BUILD
new file mode 100644
index 0000000..2353a2e
--- /dev/null
+++ b/sw/device/examples/usb_data_loader/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2025 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.
+
+load("//rules:matcha.bzl", "NEXUS_CORE_TARGETS", "OPENTITAN_CPU", "sec_flash_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+sec_flash_binary(
+ name = "usb_data_loader_sc",
+ srcs = [
+ "usb_data_loader_sc.c",
+ ],
+ copts = [
+ "-nostdlib",
+ "-ffreestanding",
+ ],
+ deps = [
+ "//sw/device/lib/testing/test_framework:ottf_start",
+ "//sw/device/lib/testing/test_framework:test_util",
+ "//sw/device/lib/usbdev",
+ "@lowrisc_opentitan//sw/device/lib/testing/test_framework:check",
+ "@lowrisc_opentitan//sw/device/lib/testing/test_framework:ottf_test_config",
+ ],
+)
diff --git a/sw/device/examples/usb_data_loader/usb_data_loader_sc.c b/sw/device/examples/usb_data_loader/usb_data_loader_sc.c
new file mode 100644
index 0000000..377f011
--- /dev/null
+++ b/sw/device/examples/usb_data_loader/usb_data_loader_sc.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2025 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 <stdbool.h>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "ml_top_regs.h"
+#include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_test_config.h"
+#include "sw/device/lib/testing/test_framework/test_util.h"
+#include "sw/device/lib/usbdev/usbdev.h"
+
+OTTF_DEFINE_TEST_CONFIG();
+
+static dif_ml_top_t ml_top;
+static dif_uart_t uart;
+
+void _ottf_main(void) {
+ if (kDeviceType != kDeviceSimDV) {
+ init_uart(TOP_MATCHA_UART0_BASE_ADDR, &uart);
+ }
+ LOG_INFO("usb_data_loader_sc");
+
+ UsbdevContext ctx;
+ uint8_t* ml_dmem = (uint8_t*)TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR;
+ ctx.verbose = true;
+ ctx.dst = ml_dmem;
+ CHECK_DIF_OK(UsbdevInit(&ctx, kUsbdevProfileBulk));
+
+ while (true) {
+ CHECK_DIF_OK(UsbdevPoll(&ctx));
+ if (ctx.finished) {
+ break;
+ }
+ }
+
+ mmio_region_t ml_top_region =
+ mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
+ CHECK_DIF_OK(dif_ml_top_init(ml_top_region, &ml_top));
+ CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+ CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
+
+ bool kelvin_done = false;
+ bool kelvin_fault = false;
+ while (true) {
+ if (!kelvin_done) {
+ dif_ml_top_irq_state_snapshot_t snapshot;
+ CHECK_DIF_OK(dif_ml_top_irq_get_state(&ml_top, &snapshot));
+ if (snapshot & (1 << ML_TOP_INTR_STATE_FINISH_BIT)) {
+ kelvin_done = true;
+ }
+ if (snapshot & (1 << ML_TOP_INTR_STATE_FAULT_BIT)) {
+ kelvin_fault = true;
+ }
+ if (kelvin_done) {
+ LOG_INFO("Kelvin done. Fault? %s", kelvin_fault ? "yes" : "no");
+ }
+ }
+ CHECK_DIF_OK(UsbdevPoll(&ctx));
+ }
+}
diff --git a/sw/device/lib/usbdev/BUILD b/sw/device/lib/usbdev/BUILD
new file mode 100644
index 0000000..58fb7f7
--- /dev/null
+++ b/sw/device/lib/usbdev/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2025 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.
+
+package(default_visibility = ["//visibility:public"])
+
+load("@lowrisc_opentitan//rules:opentitan.bzl", "OPENTITAN_CPU")
+
+cc_library(
+ name = "usbdev",
+ srcs = [
+ "usbdev.c",
+ ],
+ hdrs = [
+ "usbdev.h",
+ ],
+ deps = [
+ "//sw/device/lib/dif:usbdev",
+ "//sw/device/lib/testing:pinmux_testutils",
+ "//sw/device/lib/testing:usb_testutils",
+ "@lowrisc_opentitan//sw/device/lib/dif:base",
+ "@lowrisc_opentitan//sw/device/lib/dif:pinmux",
+ ],
+ target_compatible_with = [OPENTITAN_CPU],
+)
\ No newline at end of file
diff --git a/sw/device/lib/usbdev/usbdev.c b/sw/device/lib/usbdev/usbdev.c
new file mode 100644
index 0000000..bbde9ab
--- /dev/null
+++ b/sw/device/lib/usbdev/usbdev.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2025 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 "sw/device/lib/usbdev/usbdev.h"
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_pinmux.h"
+#include "sw/device/lib/testing/pinmux_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/usb_testutils_controlep.h"
+
+const unsigned kDataEp = 1;
+const unsigned kControlEp = 2;
+
+typedef struct {
+ uint32_t size;
+} ControlPkt;
+
+static dif_pinmux_t pinmux;
+
+static const uint8_t kUsbdevProfileBulkDescriptors[] = {
+ USB_CFG_DSCR_HEAD(USB_CFG_DSCR_LEN + (2 * (USB_INTERFACE_DSCR_LEN +
+ 2 * (USB_EP_DSCR_LEN))),
+ 2),
+ VEND_INTERFACE_DSCR(0, 2, 0x51, 1),
+ USB_BULK_EP_DSCR(0, kDataEp, USBDEV_MAX_PACKET_SIZE, 0),
+ USB_BULK_EP_DSCR(1, kDataEp, USBDEV_MAX_PACKET_SIZE, 0),
+
+ VEND_INTERFACE_DSCR(1, 2, 0x51, 1),
+ USB_BULK_EP_DSCR(0, kControlEp, USBDEV_MAX_PACKET_SIZE, 0),
+ USB_BULK_EP_DSCR(1, kControlEp, USBDEV_MAX_PACKET_SIZE, 0),
+};
+
+static void rx_data(void* ctx, dif_usbdev_rx_packet_info_t packet_info,
+ dif_usbdev_buffer_t buf) {
+ UsbdevContext* uctx = (UsbdevContext*)ctx;
+ size_t bytes_written;
+ CHECK_DIF_OK(dif_usbdev_buffer_read(
+ uctx->usbdev.dev, uctx->usbdev.buffer_pool, &buf,
+ uctx->dst + uctx->bytes_received, /*dst_len=*/1024, &bytes_written));
+ uctx->bytes_received += packet_info.length;
+}
+
+static void rx_control(void* ctx, dif_usbdev_rx_packet_info_t packet_info,
+ dif_usbdev_buffer_t buf) {
+ ControlPkt control;
+ size_t bytes_written;
+ UsbdevContext* uctx = (UsbdevContext*)ctx;
+ CHECK(packet_info.length == sizeof(control));
+ CHECK_DIF_OK(dif_usbdev_buffer_read(
+ uctx->usbdev.dev, uctx->usbdev.buffer_pool, &buf, (uint8_t*)&control,
+ sizeof(control), &bytes_written));
+ CHECK(bytes_written == sizeof(control));
+ uctx->bytes_to_receive = control.size;
+ if (uctx->verbose) {
+ LOG_INFO("The host will send %d bytes of data.", uctx->bytes_to_receive);
+ }
+}
+
+dif_result_t UsbdevInit(UsbdevContext* ctx, UsbdevProfile profile) {
+ if (ctx == NULL) {
+ return kDifBadArg;
+ }
+ if (profile != kUsbdevProfileBulk) {
+ return kDifBadArg;
+ }
+
+ memset(&ctx->usbdev, 0, sizeof(ctx->usbdev));
+ memset(&ctx->usbdev_control, 0, sizeof(ctx->usbdev_control));
+ ctx->finished = false;
+ ctx->bytes_received = 0;
+ ctx->bytes_to_receive = ~0U;
+
+ CHECK_DIF_OK(dif_pinmux_init(
+ mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux));
+ pinmux_testutils_init(&pinmux);
+
+ CHECK_DIF_OK(dif_pinmux_input_select(&pinmux,
+ kTopMatchaPinmuxPeripheralInUsbdevSense,
+ kTopMatchaPinmuxInselIoc7));
+ // VBUS SENSE on Nexus is active-low. USBDEV wants active high, so invert the
+ // signal via pinmux.
+ if (kDeviceType == kDeviceFpgaNexus) {
+ dif_pinmux_pad_attr_t attrs;
+ CHECK_DIF_OK(dif_pinmux_pad_get_attrs(&pinmux, kTopMatchaMuxedPadsIoc7,
+ kDifPinmuxPadKindMio, &attrs));
+ attrs.flags |= kDifPinmuxPadAttrInvertLevel;
+ CHECK_DIF_OK(dif_pinmux_pad_write_attrs(
+ &pinmux, kTopMatchaMuxedPadsIoc7, kDifPinmuxPadKindMio, attrs, &attrs));
+ }
+
+ bool en_diff_rcvr = kDeviceType == kDeviceFpgaNexus ? true : false;
+ usb_testutils_init(&ctx->usbdev, false, en_diff_rcvr, false);
+ usb_testutils_controlep_init(&ctx->usbdev_control, &ctx->usbdev, /*ep=*/0,
+ kUsbdevProfileBulkDescriptors,
+ sizeof(kUsbdevProfileBulkDescriptors), NULL, 0);
+
+ if (ctx->verbose) {
+ LOG_INFO("Waiting for USB to finish configuring...");
+ }
+ while (ctx->usbdev_control.device_state != kUsbTestutilsDeviceConfigured) {
+ usb_testutils_poll(&ctx->usbdev);
+ }
+ if (ctx->verbose) {
+ LOG_INFO("USB finished configuration!");
+ }
+
+ usb_testutils_endpoint_setup(&ctx->usbdev, kDataEp, kUsbdevOutStream, ctx,
+ /*tx_done=*/NULL, rx_data, /*flush=*/NULL,
+ /*reset=*/NULL);
+ usb_testutils_endpoint_setup(&ctx->usbdev, kControlEp, kUsbdevOutStream, ctx,
+ /*tx_done=*/NULL, rx_control, /*flush=*/NULL,
+ /*reset=*/NULL);
+
+ return kDifOk;
+}
+
+dif_result_t UsbdevPoll(UsbdevContext* ctx) {
+ usb_testutils_poll(&ctx->usbdev);
+ if (ctx->bytes_received >= ctx->bytes_to_receive) {
+ ctx->finished = true;
+ }
+ return kDifOk;
+}
diff --git a/sw/device/lib/usbdev/usbdev.h b/sw/device/lib/usbdev/usbdev.h
new file mode 100644
index 0000000..d923d51
--- /dev/null
+++ b/sw/device/lib/usbdev/usbdev.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2025 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 SW_DEVICE_LIB_USBDEV_USBDEV_H_
+#define SW_DEVICE_LIB_USBDEV_USBDEV_H_
+
+#include <stdbool.h>
+
+#include "sw/device/lib/dif/dif_base.h"
+#include "sw/device/lib/testing/usb_testutils.h"
+#include "sw/device/lib/testing/usb_testutils_controlep.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ usb_testutils_ctx_t usbdev;
+ usb_testutils_controlep_ctx_t usbdev_control;
+ size_t bytes_received;
+ size_t bytes_to_receive;
+ bool finished;
+ uint8_t* dst;
+ bool verbose;
+} UsbdevContext;
+
+typedef enum {
+ kUsbdevProfileBulk,
+} UsbdevProfile;
+
+dif_result_t UsbdevInit(UsbdevContext* ctx, UsbdevProfile profile);
+dif_result_t UsbdevPoll(UsbdevContext* ctx);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // SW_DEVICE_LIB_USBDEV_USBDEV_H_
diff --git a/sw/host/examples/usb_data_loader/BUILD b/sw/host/examples/usb_data_loader/BUILD
new file mode 100644
index 0000000..870eec7
--- /dev/null
+++ b/sw/host/examples/usb_data_loader/BUILD
@@ -0,0 +1,28 @@
+# Copyright 2025 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.
+
+package(default_visibility = ["//visibility:public"])
+
+cc_binary(
+ name = "usb_data_loader",
+ srcs = ["usb_data_loader.cc"],
+ deps = [
+ "@com_google_absl//absl/flags:flag",
+ "@com_google_absl//absl/flags:parse",
+ "@com_google_absl//absl/flags:usage",
+ "@com_google_absl//absl/log",
+ "@com_google_absl//absl/log:check",
+ "@lowrisc_opentitan//sw/host/tests/usbdev/usbdev_stream",
+ ],
+)
diff --git a/sw/host/examples/usb_data_loader/usb_data_loader.cc b/sw/host/examples/usb_data_loader/usb_data_loader.cc
new file mode 100644
index 0000000..54904d2
--- /dev/null
+++ b/sw/host/examples/usb_data_loader/usb_data_loader.cc
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2025 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 <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "sw/host/tests/usbdev/usbdev_stream/usb_device.h"
+#include "sw/host/tests/usbdev/usbdev_stream/usbdev_int.h"
+
+ABSL_FLAG(std::string, file, "", "File to transfer to remote device.");
+ABSL_FLAG(bool, verbose, false, "Enable verbose logging");
+
+// Compared to the device side, these values are one lower.
+// The USBDevStream derived classes increment the endpoint value
+// by 1 automatically.
+constexpr unsigned kDataEp = 0;
+constexpr unsigned kControlEp = 1;
+
+struct ControlPkt {
+ uint32_t size;
+};
+
+int main(int argc, char** argv) {
+ absl::SetProgramUsageMessage("Matcha USB data loader");
+ auto args = absl::ParseCommandLine(argc, argv);
+ argc = args.size();
+ argv = &args[0];
+
+ if (absl::GetFlag(FLAGS_file) == "") {
+ LOG(ERROR) << "--file is required!";
+ return -1;
+ }
+
+ USBDevice dev(absl::GetFlag(FLAGS_verbose));
+ CHECK(dev.Init(0x18d1, 0x503a, 0, 0));
+ CHECK(dev.Open());
+
+ uint8_t* data;
+ int fd = open(absl::GetFlag(FLAGS_file).c_str(), 0);
+ CHECK(fd > 0);
+ struct stat sb;
+ CHECK(fstat(fd, &sb) == 0);
+ data = (uint8_t*)mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ CHECK(data != MAP_FAILED);
+ close(fd);
+
+ uint32_t transfer_bytes = sb.st_size;
+ std::unique_ptr<USBDevInt> data_ep, control_ep;
+ data_ep = std::make_unique<USBDevInt>(
+ &dev, /*bulk=*/true, /*idx=*/kDataEp, transfer_bytes,
+ /*retrieve=*/false, /*check=*/false, /*send=*/true,
+ absl::GetFlag(FLAGS_verbose));
+ CHECK(data_ep->Open(/*interface=*/0));
+
+ control_ep = std::make_unique<USBDevInt>(
+ &dev, /*bulk=*/true, /*idx=*/kControlEp, sizeof(ControlPkt),
+ /*retrieve=*/false, /*check=*/false, /*send=*/true,
+ absl::GetFlag(FLAGS_verbose));
+ CHECK(control_ep->Open(/*interface=*/1));
+
+ ControlPkt control;
+ control.size = transfer_bytes;
+ CHECK(control_ep->SpaceAvailable(nullptr) >= sizeof(ControlPkt));
+ CHECK(control_ep->AddData(
+ const_cast<const uint8_t*>(reinterpret_cast<uint8_t*>(&control)),
+ sizeof(control)));
+ do {
+ switch (dev.CurrentState()) {
+ case USBDevice::StateStreaming:
+ CHECK(control_ep->Service());
+ break;
+ default:
+ CHECK(false);
+ }
+ dev.Service();
+ } while (control_ep->BytesSent() < sizeof(ControlPkt));
+
+ uint32_t bytes_sent = 0;
+ do {
+ uint32_t avail = data_ep->SpaceAvailable(nullptr);
+ uint32_t bytes_to_queue = std::min(avail, transfer_bytes - bytes_sent);
+ CHECK(data_ep->AddData(data + bytes_sent, bytes_to_queue));
+ if (absl::GetFlag(FLAGS_verbose)) {
+ LOG(INFO) << "Queued " << bytes_to_queue << " bytes of data.";
+ }
+ bool done = false;
+ do {
+ switch (dev.CurrentState()) {
+ case USBDevice::StateStreaming:
+ CHECK(data_ep->Service());
+ if (data_ep->BytesSent() >= bytes_sent + bytes_to_queue) {
+ done = true;
+ }
+ break;
+ default:
+ CHECK(false);
+ }
+ dev.Service();
+ } while (!done);
+ bytes_sent = data_ep->BytesSent();
+ if (absl::GetFlag(FLAGS_verbose)) {
+ LOG(INFO) << "Sent " << bytes_to_queue << " (total: " << bytes_sent
+ << ") bytes.";
+ }
+ } while (bytes_sent < transfer_bytes);
+
+ munmap(data, transfer_bytes);
+
+ if (dev.Fin()) {
+ return 0;
+ } else {
+ return 1;
+ }
+}