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;
+  }
+}