/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /* * * SPDX-License-Identifier: Apache-2.0 */ /* * This is a sample Zephyr-based application that contains the logic * needed to control a microTVM-based model via the UART. This is only * intended to be a demonstration, since typically you will want to incorporate * this logic into your own application. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ARCH_POSIX #include "posix_board_if.h" #endif #include "crt_config.h" static const struct device* tvm_uart; #ifdef CONFIG_LED #define LED0_NODE DT_ALIAS(led0) #define LED0 DT_GPIO_LABEL(LED0_NODE, gpios) #define LED0_PIN DT_GPIO_PIN(LED0_NODE, gpios) #define LED0_FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios) static const struct device* led0_pin; #endif // CONFIG_LED static size_t g_num_bytes_requested = 0; static size_t g_num_bytes_written = 0; static size_t g_num_bytes_in_rx_buffer = 0; // Called by TVM to write serial data to the UART. ssize_t write_serial(void* unused_context, const uint8_t* data, size_t size) { #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 1); #endif g_num_bytes_requested += size; for (size_t i = 0; i < size; i++) { uart_poll_out(tvm_uart, data[i]); g_num_bytes_written++; } #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 0); #endif return size; } // This is invoked by Zephyr from an exception handler, which will be invoked // if the device crashes. Here, we turn on the LED and spin. void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t* esf) { #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 1); #endif for (;;) ; } // Called by TVM when a message needs to be formatted. size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, va_list args) { return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); } // Called by TVM when an internal invariant is violated, and execution cannot continue. void TVMPlatformAbort(tvm_crt_error_t error) { TVMLogf("TVMError: 0x%x", error); sys_reboot(SYS_REBOOT_COLD); #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 1); #endif for (;;) ; } // Called by TVM to generate random data. tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { uint32_t random; // one unit of random data. // Fill parts of `buffer` which are as large as `random`. size_t num_full_blocks = num_bytes / sizeof(random); for (int i = 0; i < num_full_blocks; ++i) { random = sys_rand32_get(); memcpy(&buffer[i * sizeof(random)], &random, sizeof(random)); } // Fill any leftover tail which is smaller than `random`. size_t num_tail_bytes = num_bytes % sizeof(random); if (num_tail_bytes > 0) { random = sys_rand32_get(); memcpy(&buffer[num_bytes - num_tail_bytes], &random, num_tail_bytes); } return kTvmErrorNoError; } // Heap for use by TVMPlatformMemoryAllocate. K_HEAP_DEFINE(tvm_heap, 216 * 1024); // Called by TVM to allocate memory. tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { *out_ptr = k_heap_alloc(&tvm_heap, num_bytes, K_NO_WAIT); return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError; } // Called by TVM to deallocate memory. tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { k_heap_free(&tvm_heap, ptr); return kTvmErrorNoError; } #define MILLIS_TIL_EXPIRY 200 #define TIME_TIL_EXPIRY (K_MSEC(MILLIS_TIL_EXPIRY)) K_TIMER_DEFINE(g_microtvm_timer, /* expiry func */ NULL, /* stop func */ NULL); uint32_t g_microtvm_start_time; int g_microtvm_timer_running = 0; // Called to start system timer. tvm_crt_error_t TVMPlatformTimerStart() { if (g_microtvm_timer_running) { TVMLogf("timer already running"); return kTvmErrorPlatformTimerBadState; } #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 1); #endif k_timer_start(&g_microtvm_timer, TIME_TIL_EXPIRY, TIME_TIL_EXPIRY); g_microtvm_start_time = k_cycle_get_32(); g_microtvm_timer_running = 1; return kTvmErrorNoError; } // Called to stop system timer. tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { if (!g_microtvm_timer_running) { TVMLogf("timer not running"); return kTvmErrorSystemErrorMask | 2; } uint32_t stop_time = k_cycle_get_32(); #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 0); #endif // compute how long the work took uint32_t cycles_spent = stop_time - g_microtvm_start_time; if (stop_time < g_microtvm_start_time) { // we rolled over *at least* once, so correct the rollover it was *only* // once, because we might still use this result cycles_spent = ~((uint32_t)0) - (g_microtvm_start_time - stop_time); } uint32_t ns_spent = (uint32_t)k_cyc_to_ns_floor64(cycles_spent); double hw_clock_res_us = ns_spent / 1000.0; // need to grab time remaining *before* stopping. when stopped, this function // always returns 0. int32_t time_remaining_ms = k_timer_remaining_get(&g_microtvm_timer); k_timer_stop(&g_microtvm_timer); // check *after* stopping to prevent extra expiries on the happy path if (time_remaining_ms < 0) { TVMLogf("negative time remaining"); return kTvmErrorSystemErrorMask | 3; } uint32_t num_expiries = k_timer_status_get(&g_microtvm_timer); uint32_t timer_res_ms = ((num_expiries * MILLIS_TIL_EXPIRY) + time_remaining_ms); double approx_num_cycles = (double)k_ticks_to_cyc_floor32(1) * (double)k_ms_to_ticks_ceil32(timer_res_ms); // if we approach the limits of the HW clock datatype (uint32_t), use the // coarse-grained timer result instead if (approx_num_cycles > (0.5 * (~((uint32_t)0)))) { *elapsed_time_seconds = timer_res_ms / 1000.0; } else { *elapsed_time_seconds = hw_clock_res_us / 1e6; } g_microtvm_timer_running = 0; return kTvmErrorNoError; } // Ring buffer used to store data read from the UART on rx interrupt. // This ring buffer size is only required for testing with QEMU and not for physical hardware. #define RING_BUF_SIZE_BYTES (TVM_CRT_MAX_PACKET_SIZE_BYTES + 100) RING_BUF_ITEM_DECLARE_SIZE(uart_rx_rbuf, RING_BUF_SIZE_BYTES); // UART interrupt callback. void uart_irq_cb(const struct device* dev, void* user_data) { uart_irq_update(dev); if (uart_irq_is_pending(dev)) { struct ring_buf* rbuf = (struct ring_buf*)user_data; if (uart_irq_rx_ready(dev) != 0) { uint8_t* data; uint32_t size; size = ring_buf_put_claim(rbuf, &data, RING_BUF_SIZE_BYTES); int rx_size = uart_fifo_read(dev, data, size); // Write it into the ring buffer. g_num_bytes_in_rx_buffer += rx_size; if (g_num_bytes_in_rx_buffer > RING_BUF_SIZE_BYTES) { TVMPlatformAbort((tvm_crt_error_t)0xbeef3); } if (rx_size < 0) { TVMPlatformAbort((tvm_crt_error_t)0xbeef1); } int err = ring_buf_put_finish(rbuf, rx_size); if (err != 0) { TVMPlatformAbort((tvm_crt_error_t)0xbeef2); } // CHECK_EQ(bytes_read, bytes_written, "bytes_read: %d; bytes_written: %d", bytes_read, // bytes_written); } } } // Used to initialize the UART receiver. void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)rbuf); uart_irq_rx_enable(dev); } // The main function of this application. extern void __stdout_hook_install(int (*hook)(int)); void main(void) { #ifdef CONFIG_LED int ret; led0_pin = device_get_binding(LED0); if (led0_pin == NULL) { for (;;) ; } ret = gpio_pin_configure(led0_pin, LED0_PIN, GPIO_OUTPUT_ACTIVE | LED0_FLAGS); if (ret < 0) { TVMPlatformAbort((tvm_crt_error_t)0xbeef4); } gpio_pin_set(led0_pin, LED0_PIN, 1); #endif // Claim console device. tvm_uart = device_get_binding(DT_LABEL(DT_CHOSEN(zephyr_console))); uart_rx_init(&uart_rx_rbuf, tvm_uart); // Initialize microTVM RPC server, which will receive commands from the UART and execute them. microtvm_rpc_server_t server = MicroTVMRpcServerInit(write_serial, NULL); TVMLogf("microTVM Zephyr runtime - running"); #ifdef CONFIG_LED gpio_pin_set(led0_pin, LED0_PIN, 0); #endif // The main application loop. We continuously read commands from the UART // and dispatch them to MicroTVMRpcServerLoop(). while (true) { uint8_t* data; unsigned int key = irq_lock(); uint32_t bytes_read = ring_buf_get_claim(&uart_rx_rbuf, &data, RING_BUF_SIZE_BYTES); if (bytes_read > 0) { g_num_bytes_in_rx_buffer -= bytes_read; size_t bytes_remaining = bytes_read; while (bytes_remaining > 0) { // Pass the received bytes to the RPC server. tvm_crt_error_t err = MicroTVMRpcServerLoop(server, &data, &bytes_remaining); if (err != kTvmErrorNoError && err != kTvmErrorFramingShortPacket) { TVMPlatformAbort(err); } if (g_num_bytes_written != 0 || g_num_bytes_requested != 0) { if (g_num_bytes_written != g_num_bytes_requested) { TVMPlatformAbort((tvm_crt_error_t)0xbeef5); } g_num_bytes_written = 0; g_num_bytes_requested = 0; } } int err = ring_buf_get_finish(&uart_rx_rbuf, bytes_read); if (err != 0) { TVMPlatformAbort((tvm_crt_error_t)0xbeef6); } } irq_unlock(key); } #ifdef CONFIG_ARCH_POSIX posix_exit(0); #endif }