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