summaryrefslogtreecommitdiff
path: root/ports/risc-v64/gnu/example_build
diff options
context:
space:
mode:
Diffstat (limited to 'ports/risc-v64/gnu/example_build')
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/board.c32
-rwxr-xr-xports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh6
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/csr.h373
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c371
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/entry.s58
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c25
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h24
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/link.lds49
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/plic.c72
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/plic.h50
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/trap.c46
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S163
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/uart.c100
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/uart.h21
14 files changed, 1390 insertions, 0 deletions
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/board.c b/ports/risc-v64/gnu/example_build/qemu_virt/board.c
new file mode 100644
index 00000000..29652bee
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/board.c
@@ -0,0 +1,32 @@
+#include "plic.h"
+#include "hwtimer.h"
+#include "uart.h"
+#include <stdint.h>
+#include <stddef.h>
+
+void *memset(const void *des, int c,size_t n)
+{
+ if((des == NULL) || n <=0)
+ return (void*)des;
+ char* t = (char*)des;
+ int i;
+ for(i=0;i<n;i++)
+ t[i]=c;
+ return t;
+}
+
+
+int board_init(void)
+{
+ int ret;
+ ret = plic_init();
+ if(ret)
+ return ret;
+ ret = uart_init();
+ if(ret)
+ return ret;
+ ret = hwtimer_init();
+ if(ret)
+ return ret;
+ return 0;
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh
new file mode 100755
index 00000000..24a4c3ac
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+pushd ../../../../../
+cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake .
+cmake --build ./build/
+popd
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/csr.h b/ports/risc-v64/gnu/example_build/qemu_virt/csr.h
new file mode 100644
index 00000000..4e222375
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/csr.h
@@ -0,0 +1,373 @@
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+
+#ifndef RISCV_CSR_H
+#define RISCV_CSR_H
+
+
+// Machine Status Register, mstatus
+#define MSTATUS_MPP_MASK (3L << 11) // previous mode.
+#define MSTATUS_MPP_M (3L << 11)
+#define MSTATUS_MPP_S (1L << 11)
+#define MSTATUS_MPP_U (0L << 11)
+#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable.
+#define MSTATUS_MPIE (1L << 7)
+#define MSTATUS_FS (1L << 13)
+
+// Machine-mode Interrupt Enable
+#define MIE_MTIE (1L << 7)
+#define MIE_MSIE (1L << 3)
+#define MIE_MEIE (1L << 11)
+#define MIE_STIE (1L << 5) // supervisor timer
+#define MIE_SSIE (1L << 1)
+#define MIE_SEIE (1L << 9)
+
+// Supervisor Status Register, sstatus
+#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User
+#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable
+#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable
+#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable
+#define SSTATUS_UIE (1L << 0) // User Interrupt Enable
+#define SSTATUS_SPIE (1L << 5)
+#define SSTATUS_UPIE (1L << 4)
+
+// Supervisor Interrupt Enable
+#define SIE_SEIE (1L << 9) // external
+#define SIE_STIE (1L << 5) // timer
+#define SIE_SSIE (1L << 1) // software
+
+#ifndef __ASSEMBLER__
+
+#include <stdint.h>
+
+static inline uint64_t riscv_get_core()
+{
+ uint64_t x;
+ asm volatile("csrr %0, mhartid" : "=r" (x) );
+ return x;
+}
+
+static inline uint64_t riscv_get_mstatus()
+{
+ uint64_t x;
+ asm volatile("csrr %0, mstatus" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mstatus(uint64_t x)
+{
+ asm volatile("csrw mstatus, %0" : : "r" (x));
+}
+
+// machine exception program counter, holds the
+// instruction address to which a return from
+// exception will go.
+static inline void riscv_writ_mepc(uint64_t x)
+{
+ asm volatile("csrw mepc, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_sstatus()
+{
+ uint64_t x;
+ asm volatile("csrr %0, sstatus" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sstatus(uint64_t x)
+{
+ asm volatile("csrw sstatus, %0" : : "r" (x));
+}
+
+// Supervisor Interrupt Pending
+static inline uint64_t riscv_get_sip()
+{
+ uint64_t x;
+ asm volatile("csrr %0, sip" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sip(uint64_t x)
+{
+ asm volatile("csrw sip, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_sie()
+{
+ uint64_t x;
+ asm volatile("csrr %0, sie" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sie(uint64_t x)
+{
+ asm volatile("csrw sie, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_mie()
+{
+ uint64_t x;
+ asm volatile("csrr %0, mie" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mie(uint64_t x)
+{
+ asm volatile("csrw mie, %0" : : "r" (x));
+}
+
+// supervisor exception program counter, holds the
+// instruction address to which a return from
+// exception will go.
+static inline void riscv_writ_sepc(uint64_t x)
+{
+ asm volatile("csrw sepc, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_sepc()
+{
+ uint64_t x;
+ asm volatile("csrr %0, sepc" : "=r" (x) );
+ return x;
+}
+
+// Machine Exception Delegation
+static inline uint64_t riscv_get_medeleg()
+{
+ uint64_t x;
+ asm volatile("csrr %0, medeleg" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_medeleg(uint64_t x)
+{
+ asm volatile("csrw medeleg, %0" : : "r" (x));
+}
+
+// Machine Interrupt Delegation
+static inline uint64_t riscv_get_mideleg()
+{
+ uint64_t x;
+ asm volatile("csrr %0, mideleg" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mideleg(uint64_t x)
+{
+ asm volatile("csrw mideleg, %0" : : "r" (x));
+}
+
+// Supervisor Trap-Vector Base Address
+// low two bits are mode.
+static inline void riscv_writ_stvec(uint64_t x)
+{
+ asm volatile("csrw stvec, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_stvec()
+{
+ uint64_t x;
+ asm volatile("csrr %0, stvec" : "=r" (x) );
+ return x;
+}
+
+// Supervisor Timer Comparison Register
+static inline uint64_t riscv_get_stimecmp()
+{
+ uint64_t x;
+ // asm volatile("csrr %0, stimecmp" : "=r" (x) );
+ asm volatile("csrr %0, 0x14d" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_stimecmp(uint64_t x)
+{
+ // asm volatile("csrw stimecmp, %0" : : "r" (x));
+ asm volatile("csrw 0x14d, %0" : : "r" (x));
+}
+
+// Machine Environment Configuration Register
+static inline uint64_t riscv_get_menvcfg()
+{
+ uint64_t x;
+ // asm volatile("csrr %0, menvcfg" : "=r" (x) );
+ asm volatile("csrr %0, 0x30a" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_menvcfg(uint64_t x)
+{
+ // asm volatile("csrw menvcfg, %0" : : "r" (x));
+ asm volatile("csrw 0x30a, %0" : : "r" (x));
+}
+
+// Physical Memory Protection
+static inline void riscv_writ_pmpcfg0(uint64_t x)
+{
+ asm volatile("csrw pmpcfg0, %0" : : "r" (x));
+}
+
+static inline void riscv_writ_pmpaddr0(uint64_t x)
+{
+ asm volatile("csrw pmpaddr0, %0" : : "r" (x));
+}
+
+// supervisor address translation and protection;
+// holds the address of the page table.
+static inline void riscv_writ_satp(uint64_t x)
+{
+ asm volatile("csrw satp, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_satp()
+{
+ uint64_t x;
+ asm volatile("csrr %0, satp" : "=r" (x) );
+ return x;
+}
+
+// Supervisor Trap Cause
+static inline uint64_t riscv_get_scause()
+{
+ uint64_t x;
+ asm volatile("csrr %0, scause" : "=r" (x) );
+ return x;
+}
+
+// Supervisor Trap Value
+static inline uint64_t riscv_get_stval()
+{
+ uint64_t x;
+ asm volatile("csrr %0, stval" : "=r" (x) );
+ return x;
+}
+
+// Machine-mode Counter-Enable
+static inline void riscv_writ_mcounteren(uint64_t x)
+{
+ asm volatile("csrw mcounteren, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_mcounteren()
+{
+ uint64_t x;
+ asm volatile("csrr %0, mcounteren" : "=r" (x) );
+ return x;
+}
+
+// machine-mode cycle counter
+static inline uint64_t riscv_get_time()
+{
+ uint64_t x;
+ asm volatile("csrr %0, time" : "=r" (x) );
+ return x;
+}
+
+// enable device interrupts
+static inline void riscv_sintr_on()
+{
+ uint64_t sstatus = riscv_get_sstatus();
+ sstatus |= SSTATUS_SIE;
+ riscv_writ_sstatus(sstatus);
+}
+
+// disable device interrupts
+static inline void riscv_sintr_off()
+{
+ uint64_t sstatus = riscv_get_sstatus();
+ sstatus &= (~SSTATUS_SIE);
+ riscv_writ_sstatus(sstatus);
+}
+
+// are device interrupts enabled?
+static inline int riscv_sintr_get()
+{
+ uint64_t x = riscv_get_sstatus();
+ return (x & SSTATUS_SIE) != 0;
+}
+
+static inline void riscv_sintr_restore(int x)
+{
+ if(x)
+ riscv_sintr_on();
+ else
+ riscv_sintr_off();
+}
+
+// enable device interrupts
+static inline void riscv_mintr_on()
+{
+ uint64_t mstatus = riscv_get_mstatus();
+ mstatus |= MSTATUS_MIE;
+ riscv_writ_mstatus(mstatus);
+}
+
+// disable device interrupts
+static inline void riscv_mintr_off()
+{
+ uint64_t mstatus = riscv_get_mstatus();
+ mstatus &= (~MSTATUS_MIE);
+ riscv_writ_mstatus(mstatus);
+}
+
+// are device interrupts enabled?
+static inline int riscv_mintr_get()
+{
+ uint64_t x = riscv_get_mstatus();
+ return (x & MSTATUS_MIE) != 0;
+}
+
+static inline void riscv_mintr_restore(int x)
+{
+ if(x)
+ riscv_mintr_on();
+ else
+ riscv_mintr_off();
+}
+
+static inline uint64_t riscv_get_sp()
+{
+ uint64_t x;
+ asm volatile("mv %0, sp" : "=r" (x) );
+ return x;
+}
+
+// read and write tp, the thread pointer, which xv6 uses to hold
+// this core's hartid (core number), the index into cpus[].
+static inline uint64_t riscv_get_tp()
+{
+ uint64_t x;
+ asm volatile("mv %0, tp" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_tp(uint64_t x)
+{
+ asm volatile("mv tp, %0" : : "r" (x));
+}
+
+static inline uint64_t riscv_get_ra()
+{
+ uint64_t x;
+ asm volatile("mv %0, ra" : "=r" (x) );
+ return x;
+}
+
+// flush the TLB.
+static inline void sfence_vma()
+{
+ // the zero, zero means flush all TLB entries.
+ asm volatile("sfence.vma zero, zero");
+}
+
+#endif // __ASSEMBLER__
+
+#endif
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c b/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c
new file mode 100644
index 00000000..dfd8b599
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c
@@ -0,0 +1,371 @@
+/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
+ threads of different priorities, using a message queue, semaphore, mutex, event flags group,
+ byte pool, and block pool. */
+
+#include "tx_api.h"
+#include "uart.h"
+#define DEMO_STACK_SIZE 1024
+#define DEMO_BYTE_POOL_SIZE 9120
+#define DEMO_BLOCK_POOL_SIZE 100
+#define DEMO_QUEUE_SIZE 100
+
+
+/* Define the ThreadX object control blocks... */
+
+TX_THREAD thread_0;
+TX_THREAD thread_1;
+TX_THREAD thread_2;
+TX_THREAD thread_3;
+TX_THREAD thread_4;
+TX_THREAD thread_5;
+TX_THREAD thread_6;
+TX_THREAD thread_7;
+TX_QUEUE queue_0;
+TX_SEMAPHORE semaphore_0;
+TX_MUTEX mutex_0;
+TX_EVENT_FLAGS_GROUP event_flags_0;
+TX_BYTE_POOL byte_pool_0;
+TX_BLOCK_POOL block_pool_0;
+UCHAR memory_area[DEMO_BYTE_POOL_SIZE];
+
+
+/* Define the counters used in the demo application... */
+
+ULONG thread_0_counter;
+ULONG thread_1_counter;
+ULONG thread_1_messages_sent;
+ULONG thread_2_counter;
+ULONG thread_2_messages_received;
+ULONG thread_3_counter;
+ULONG thread_4_counter;
+ULONG thread_5_counter;
+ULONG thread_6_counter;
+ULONG thread_7_counter;
+
+
+/* Define thread prototypes. */
+
+void thread_0_entry(ULONG thread_input);
+void thread_1_entry(ULONG thread_input);
+void thread_2_entry(ULONG thread_input);
+void thread_3_and_4_entry(ULONG thread_input);
+void thread_5_entry(ULONG thread_input);
+void thread_6_and_7_entry(ULONG thread_input);
+
+
+/* Define main entry point. */
+
+int main()
+{
+
+ /* Enter the ThreadX kernel. */
+ tx_kernel_enter();
+}
+
+
+/* Define what the initial system looks like. */
+
+void tx_application_define(void *first_unused_memory)
+{
+
+CHAR *pointer = TX_NULL;
+
+
+ /* Create a byte memory pool from which to allocate the thread stacks. */
+ tx_byte_pool_create(&byte_pool_0, "byte pool 0", memory_area, DEMO_BYTE_POOL_SIZE);
+
+ /* Put system definition stuff in here, e.g. thread creates and other assorted
+ create information. */
+
+ /* Allocate the stack for thread 0. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create the main thread. */
+ tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, DEMO_STACK_SIZE,
+ 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+
+ /* Allocate the stack for thread 1. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 1 and 2. These threads pass information through a ThreadX
+ message queue. It is also interesting to note that these threads have a time
+ slice. */
+ tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
+ pointer, DEMO_STACK_SIZE,
+ 16, 16, 4, TX_AUTO_START);
+
+ /* Allocate the stack for thread 2. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
+ pointer, DEMO_STACK_SIZE,
+ 16, 16, 4, TX_AUTO_START);
+
+ /* Allocate the stack for thread 3. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 3 and 4. These threads compete for a ThreadX counting semaphore.
+ An interesting thing here is that both threads share the same instruction area. */
+ tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 4. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 5. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create thread 5. This thread simply pends on an event flag which will be set
+ by thread_0. */
+ tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,
+ pointer, DEMO_STACK_SIZE,
+ 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 6. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 6 and 7. These threads compete for a ThreadX mutex. */
+ tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 7. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the message queue. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT);
+
+ /* Create the message queue shared by threads 1 and 2. */
+ tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG));
+
+ /* Create the semaphore used by threads 3 and 4. */
+ tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
+
+ /* Create the event flags group used by threads 1 and 5. */
+ tx_event_flags_create(&event_flags_0, "event flags 0");
+
+ /* Create the mutex used by thread 6 and 7 without priority inheritance. */
+ tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT);
+
+ /* Allocate the memory for a small block pool. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT);
+
+ /* Create a block memory pool to allocate a message buffer from. */
+ tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer, DEMO_BLOCK_POOL_SIZE);
+
+ /* Allocate a block and release the block memory. */
+ tx_block_allocate(&block_pool_0, (VOID **) &pointer, TX_NO_WAIT);
+
+ /* Release the block back to the pool. */
+ tx_block_release(pointer);
+}
+
+
+
+/* Define the test threads. */
+
+void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This thread simply sits in while-forever-sleep loop. */
+ while(1)
+ {
+ puts("[Thread] : thread_0_entry is here!");
+ /* Increment the thread counter. */
+ thread_0_counter++;
+
+ /* Sleep for 10 ticks. */
+ tx_thread_sleep(10);
+
+ /* Set event flag 0 to wakeup thread 5. */
+ status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
+
+
+void thread_1_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This thread simply sends messages to a queue shared by thread 2. */
+ while(1)
+ {
+ puts("[Thread] : thread_1_entry is here!");
+ /* Increment the thread counter. */
+ thread_1_counter++;
+
+ /* Send message to queue 0. */
+ status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);
+
+ /* Check completion status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Increment the message sent. */
+ thread_1_messages_sent++;
+ }
+}
+
+
+void thread_2_entry(ULONG thread_input)
+{
+
+ULONG received_message;
+UINT status;
+
+ /* This thread retrieves messages placed on the queue by thread 1. */
+ while(1)
+ {
+ puts("[Thread] : thread_2_entry is here!");
+ /* Increment the thread counter. */
+ thread_2_counter++;
+
+ /* Retrieve a message from the queue. */
+ status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);
+
+ /* Check completion status and make sure the message is what we
+ expected. */
+ if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received))
+ break;
+
+ /* Otherwise, all is okay. Increment the received message count. */
+ thread_2_messages_received++;
+ }
+}
+
+
+void thread_3_and_4_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This function is executed from thread 3 and thread 4. As the loop
+ below shows, these function compete for ownership of semaphore_0. */
+ while(1)
+ {
+ puts("[Thread] : thread_3_and_4_entry is here!");
+
+ /* Increment the thread counter. */
+ if (thread_input == 3)
+ thread_3_counter++;
+ else
+ thread_4_counter++;
+
+ /* Get the semaphore with suspension. */
+ status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Sleep for 2 ticks to hold the semaphore. */
+ tx_thread_sleep(2);
+
+ /* Release the semaphore. */
+ status = tx_semaphore_put(&semaphore_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
+
+
+void thread_5_entry(ULONG thread_input)
+{
+
+UINT status;
+ULONG actual_flags;
+
+
+ /* This thread simply waits for an event in a forever loop. */
+ while(1)
+ {
+ puts("[Thread] : thread_5_entry is here!");
+ /* Increment the thread counter. */
+ thread_5_counter++;
+
+ /* Wait for event flag 0. */
+ status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
+ &actual_flags, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if ((status != TX_SUCCESS) || (actual_flags != 0x1))
+ break;
+ }
+}
+
+
+void thread_6_and_7_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This function is executed from thread 6 and thread 7. As the loop
+ below shows, these function compete for ownership of mutex_0. */
+ while(1)
+ {
+ puts("[Thread] : thread_6_and_7_entry is here!");
+ /* Increment the thread counter. */
+ if (thread_input == 6)
+ thread_6_counter++;
+ else
+ thread_7_counter++;
+
+ /* Get the mutex with suspension. */
+ status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Get the mutex again with suspension. This shows
+ that an owning thread may retrieve the mutex it
+ owns multiple times. */
+ status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Sleep for 2 ticks to hold the mutex. */
+ tx_thread_sleep(2);
+
+ /* Release the mutex. */
+ status = tx_mutex_put(&mutex_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Release the mutex again. This will actually
+ release ownership since it was obtained twice. */
+ status = tx_mutex_put(&mutex_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/entry.s b/ports/risc-v64/gnu/example_build/qemu_virt/entry.s
new file mode 100644
index 00000000..9b202ca1
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/entry.s
@@ -0,0 +1,58 @@
+
+.section .text
+.align 4
+.global _start
+.extern main
+.extern _sysstack_start
+.extern _bss_start
+.extern _bss_end
+_start:
+ csrr t0, mhartid
+ bne t0, zero, 1f
+ li x1, 0
+ li x2, 0
+ li x3, 0
+ li x4, 0
+ li x5, 0
+ li x6, 0
+ li x7, 0
+ li x8, 0
+ li x9, 0
+ li x10, 0
+ li x11, 0
+ li x12, 0
+ li x13, 0
+ li x14, 0
+ li x15, 0
+ li x16, 0
+ li x17, 0
+ li x18, 0
+ li x19, 0
+ li x20, 0
+ li x21, 0
+ li x22, 0
+ li x23, 0
+ li x24, 0
+ li x25, 0
+ li x26, 0
+ li x27, 0
+ li x28, 0
+ li x29, 0
+ li x30, 0
+ li x31, 0
+ la t0, _sysstack_start
+ li t1, 0x1000
+ add sp, t0, t1
+ la t0, _bss_start
+ la t1, _bss_end
+_bss_clean_start:
+ bgeu t0, t1, _bss_clean_end
+ sb zero, 0(t0)
+ addi t0, t0, 1
+ j _bss_clean_start
+_bss_clean_end:
+ call main
+1:
+ /* todo smp */
+ wfi
+ j 1b
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c
new file mode 100644
index 00000000..29cf1117
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c
@@ -0,0 +1,25 @@
+#include "tx_port.h"
+#include "csr.h"
+#include "hwtimer.h"
+
+#define CLINT (0x02000000L)
+#define CLINT_TIME (CLINT+0xBFF8)
+#define CLINT_TIMECMP(hart_id) (CLINT+0x4000+8*(hart_id))
+
+
+int hwtimer_init(void)
+{
+ int hart = riscv_get_core();
+ uint64_t time = *((uint64_t*)CLINT_TIME);
+ *((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
+
+int hwtimer_handler(void)
+{
+ int hart = riscv_get_core();
+ uint64_t time = *((uint64_t*)CLINT_TIME);
+ *((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
+
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h
new file mode 100644
index 00000000..f65c75e1
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h
@@ -0,0 +1,24 @@
+
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#ifndef RISCV_HWTIMER_H
+#define RISCV_HWTIMER_H
+
+#include <stdint.h>
+
+#define TICKNUM_PER_SECOND 10000000
+#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10)
+
+int hwtimer_init(void);
+
+int hwtimer_handler(void);
+
+#endif
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/link.lds b/ports/risc-v64/gnu/example_build/qemu_virt/link.lds
new file mode 100644
index 00000000..b2d0d2df
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/link.lds
@@ -0,0 +1,49 @@
+OUTPUT_ARCH( "riscv" )
+ENTRY( _start )
+
+SECTIONS
+{
+ /*
+ * ensure that entry.S / _entry is at 0x80000000,
+ * where qemu's -kernel jumps.
+ */
+ . = 0x80000000;
+
+ .text : {
+ *(.text .text.*)
+ . = ALIGN(0x1000);
+ PROVIDE(etext = .);
+ }
+
+ .rodata : {
+ . = ALIGN(16);
+ *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
+ . = ALIGN(16);
+ *(.rodata .rodata.*)
+ }
+
+ .data : {
+ . = ALIGN(16);
+ *(.sdata .sdata.*) /* do not need to distinguish this from .data */
+ . = ALIGN(16);
+ *(.data .data.*)
+ }
+
+ .bss : {
+ . = ALIGN(16);
+ _bss_start = .;
+ *(.sbss .sbss.*) /* do not need to distinguish this from .bss */
+ . = ALIGN(16);
+ *(.bss .bss.*)
+ _bss_end = .;
+ }
+
+ .stack : {
+ . = ALIGN(4096);
+ _sysstack_start = .;
+ . += 0x1000;
+ _sysstack_end = .;
+ }
+
+ PROVIDE(_end = .);
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/plic.c b/ports/risc-v64/gnu/example_build/qemu_virt/plic.c
new file mode 100644
index 00000000..01e5c71a
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/plic.c
@@ -0,0 +1,72 @@
+#include "plic.h"
+#include <stddef.h>
+irq_callback callbacks[MAX_CALLBACK_NUM];
+
+void plic_irq_enable(int irqno)
+{
+ int hart = riscv_get_core();
+ *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) | (1 << irqno));
+ return;
+}
+
+void plic_irq_disable(int irqno)
+{
+ int hart = riscv_get_core();
+ *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) & (~(1 << irqno)));
+ return;
+}
+
+void plic_prio_set(int irqno, int prio)
+{
+ PLIC_SET_PRIO(irqno, prio);
+}
+
+int plic_prio_get(int irqno)
+{
+ return PLIC_GET_PRIO(irqno);
+}
+
+int plic_register_callback(int irqno, irq_callback callback)
+{
+ if(!(irqno >=0 && irqno < MAX_CALLBACK_NUM))
+ return -1;
+ callbacks[irqno] = callback;
+ return 0;
+}
+
+int plic_unregister_callback(int irqno)
+{
+ return plic_register_callback(irqno, NULL);
+}
+
+int plic_init(void)
+{
+ for(int i=0;i<MAX_CALLBACK_NUM;i++)
+ {
+ callbacks[i] = NULL;
+ }
+ return 0;
+}
+
+int plic_claim(void)
+{
+ int hart = riscv_get_core();
+ return (*(uint32_t*)PLIC_MCLAIM(hart));
+}
+
+void plic_complete(int irqno)
+{
+ int hart = riscv_get_core();
+ *(uint32_t*)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno;
+ return;
+}
+
+int plic_irq_intr(void)
+{
+ int ret = -1;
+ int irqno = plic_claim();
+ if(callbacks[irqno] != NULL)
+ ret = (callbacks[irqno])(irqno);
+ plic_complete(irqno);
+ return ret;
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/plic.h b/ports/risc-v64/gnu/example_build/qemu_virt/plic.h
new file mode 100644
index 00000000..2d4bc085
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/plic.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+
+#ifndef RISCV_PLIC_H
+#define RISCV_PLIC_H
+
+#include "csr.h"
+#include <stdint.h>
+
+#define PLIC 0x0c000000L
+#define PLIC_PRIORITY (PLIC + 0x0)
+#define PLIC_PENDING (PLIC + 0x1000)
+#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)
+#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100)
+#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)
+#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000)
+#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)
+#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)
+#define PLIC_MCOMPLETE(hart) (PLIC + 0x200004 + (hart)*0x2000)
+#define PLIC_SCOMPLETE(hart) (PLIC + 0x201004 + (hart)*0x2000)
+
+
+#define PLIC_GET_PRIO(irqno) (*(uint32_t *)(PLIC_PRIORITY + (irqno)*4))
+#define PLIC_SET_PRIO(irqno, prio) (*(uint32_t *)(PLIC_PRIORITY + (irqno)*4) = (prio))
+
+#define MAX_CALLBACK_NUM 128
+typedef int (*irq_callback)(int irqno);
+
+void plic_irq_enable(int irqno);
+void plic_irq_disable(int irqno);
+int plic_prio_get(int irqno);
+void plic_prio_set(int irqno, int prio);
+int plic_register_callback(int irqno, irq_callback callback);
+int plic_unregister_callback(int irqno);
+int plic_init(void);
+int plic_claim(void);
+void plic_complete(int irqno);
+
+int plic_irq_intr(void);
+
+#endif
+
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/trap.c b/ports/risc-v64/gnu/example_build/qemu_virt/trap.c
new file mode 100644
index 00000000..5de9f37a
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/trap.c
@@ -0,0 +1,46 @@
+#include "csr.h"
+#include <stdint.h>
+#include "uart.h"
+#include "hwtimer.h"
+#include "plic.h"
+#include <tx_port.h>
+#include <tx_api.h>
+
+#define OS_IS_INTERUPT(mcause) (mcause & 0x8000000000000000ull)
+#define OS_IS_EXCEPTION(mcause) (~(OS_IS_INTERUPT))
+#define OS_IS_TICK_INT(mcause) (mcause == 0x8000000000000007ull)
+#define OS_IS_SOFT_INT(mcause) (mcause == 0x8000000000000003ull)
+#define OS_IS_EXT_INT(mcause) (mcause == 0x800000000000000bull)
+#define OS_IS_TRAP_USER(mcause) (mcause == 0x000000000000000bull)
+extern void _tx_timer_interrupt(void);
+
+void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval)
+{
+ if(OS_IS_INTERUPT(mcause))
+ {
+ if(OS_IS_TICK_INT(mcause))
+ {
+ hwtimer_handler();
+ _tx_timer_interrupt();
+ }
+ else if(OS_IS_EXT_INT(mcause))
+ {
+ int ret = plic_irq_intr();
+ if(ret)
+ {
+ puts("[INTERRUPT]: handler irq error!");
+ while(1) ;
+ }
+ }
+ else
+ {
+ puts("[INTERRUPT]: now can't deal with the interrupt!");
+ while(1) ;
+ }
+ }
+ else
+ {
+ puts("[EXCEPTION] : Unkown Error!!");
+ while(1) ;
+ }
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S b/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S
new file mode 100644
index 00000000..04d664a2
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S
@@ -0,0 +1,163 @@
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#include "csr.h"
+#include "tx_port.h"
+
+ .section .text
+ .align 4
+/**************************************************************************/
+/* */
+/* FUNCTION RELEASE */
+/* */
+/* trap_entry RISC-V64/GNU */
+/* 6.2.1 */
+/* AUTHOR */
+/* */
+/* Jer6y , [email protected] */
+/* */
+/* DESCRIPTION */
+/* */
+/* This function is responsible for riscv processor trap handle */
+/* It will do the contex save and call c trap_handler and do contex */
+/* load */
+/* */
+/* INPUT */
+/* */
+/* None */
+/* */
+/* OUTPUT */
+/* */
+/* None */
+/* */
+/* CALLS */
+/* */
+/* trap_handler */
+/* */
+/* CALLED BY */
+/* */
+/* hardware exception */
+/* RELEASE HISTORY */
+/* */
+/* DATE NAME DESCRIPTION */
+/* */
+/* 10-25-2024 Jerry Luo */
+/* */
+/**************************************************************************/
+
+
+/**************************************************************************/
+/**************************************************************************/
+/** */
+/** ThreadX Component */
+/** */
+/** Initialize */
+/** */
+/**************************************************************************/
+/**************************************************************************/
+ .global trap_entry
+ .extern trap_handler
+ .extern _tx_thread_context_restore
+ trap_entry:
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ addi sp, sp, -65*REGBYTES // Allocate space for all registers - with floating point enabled
+#else
+ addi sp, sp, -32*REGBYTES // Allocate space for all registers - without floating point enabled
+#endif
+
+ STORE x1, 28*REGBYTES(sp) // Store RA, 28*REGBYTES(because call will override ra [ra is a calle register in riscv])
+
+ call _tx_thread_context_save
+
+ csrr a0, mcause
+ csrr a1, mepc
+ csrr a2, mtval
+ addi sp, sp, -8
+ sd ra, 0(sp)
+ call trap_handler
+ ld ra, 0(sp)
+ addi sp, sp, 8
+ call _tx_thread_context_restore
+ // it will nerver return
+_err:
+ wfi
+ j _err
+ .section .text
+/**************************************************************************/
+/* */
+/* FUNCTION RELEASE */
+/* */
+/* _tx_initialize_low_level RISC-V64/GNU */
+/* 6.2.1 */
+/* AUTHOR */
+/* */
+/* Scott Larson, Microsoft Corporation */
+/* */
+/* DESCRIPTION */
+/* */
+/* This function is responsible for any low-level processor */
+/* initialization, including setting up interrupt vectors, setting */
+/* up a periodic timer interrupt source, saving the system stack */
+/* pointer for use in ISR processing later, and finding the first */
+/* available RAM memory address for tx_application_define. */
+/* */
+/* INPUT */
+/* */
+/* None */
+/* */
+/* OUTPUT */
+/* */
+/* None */
+/* */
+/* CALLS */
+/* */
+/* None */
+/* */
+/* CALLED BY */
+/* */
+/* _tx_initialize_kernel_enter ThreadX entry function */
+/* */
+/* RELEASE HISTORY */
+/* */
+/* DATE NAME DESCRIPTION */
+/* */
+/* 03-08-2023 Scott Larson Initial Version 6.2.1 */
+/* */
+/**************************************************************************/
+/* VOID _tx_initialize_low_level(VOID)
+{ */
+ .global _tx_initialize_low_level
+ .weak _tx_initialize_low_level
+ .extern _end
+ .extern board_init
+_tx_initialize_low_level:
+ sd sp, _tx_thread_system_stack_ptr, t0 // Save system stack pointer
+
+ la t0, _end // Pickup first free address
+ sd t0, _tx_initialize_unused_memory, t1 // Save unused memory address
+ li t0, MSTATUS_MIE
+ csrrc zero, mstatus, t0 // clear MSTATUS_MIE bit
+ li t0, (MSTATUS_MPP_M | MSTATUS_MPIE )
+ csrrs zero, mstatus, t0 // set MSTATUS_MPP, MPIE bit
+ li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE)
+ csrrs zero, mie, t0 // set mie
+#ifdef __riscv_flen
+ li t0, MSTATUS_FS
+ csrrs zero, mstatus, t0 // set MSTATUS_FS bit to open f/d isa in riscv
+ fscsr x0
+#endif
+ addi sp, sp, -8
+ sd ra, 0(sp)
+ call board_init
+ ld ra, 0(sp)
+ addi sp, sp, 8
+ la t0, trap_entry
+ csrw mtvec, t0
+ ret
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/uart.c b/ports/risc-v64/gnu/example_build/qemu_virt/uart.c
new file mode 100644
index 00000000..145ef28d
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/uart.c
@@ -0,0 +1,100 @@
+#include "uart.h"
+#include "csr.h"
+#include "plic.h"
+#include <stdint.h>
+
+// the UART control registers are memory-mapped
+// at address UART0. this macro returns the
+// address of one of the registers.
+#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg)))
+
+// the UART control registers.
+// some have different meanings for
+// read vs write.
+// see http://byterunner.com/16550.html
+#define RHR 0 // receive holding register (for input bytes)
+#define THR 0 // transmit holding register (for output bytes)
+#define IER 1 // interrupt enable register
+#define IER_RX_ENABLE (1<<0)
+#define IER_TX_ENABLE (1<<1)
+#define FCR 2 // FIFO control register
+#define FCR_FIFO_ENABLE (1<<0)
+#define FCR_FIFO_CLEAR (3<<1) // clear the content of the two FIFOs
+#define ISR 2 // interrupt status register
+#define LCR 3 // line control register
+#define LCR_EIGHT_BITS (3<<0)
+#define LCR_BAUD_LATCH (1<<7) // special mode to set baud rate
+#define LSR 5 // line status register
+#define LSR_RX_READY (1<<0) // input is waiting to be read from RHR
+#define LSR_TX_IDLE (1<<5) // THR can accept another character to send
+
+#define ReadReg(reg) (*(Reg(reg)))
+#define WriteReg(reg, v) (*(Reg(reg)) = (v))
+
+int uart_init(void)
+{
+ // disable interrupts.
+ WriteReg(IER, 0x00);
+
+ // special mode to set baud rate.
+ WriteReg(LCR, LCR_BAUD_LATCH);
+
+ // LSB for baud rate of 38.4K.
+ WriteReg(0, 0x03);
+
+ // MSB for baud rate of 38.4K.
+ WriteReg(1, 0x00);
+
+ // leave set-baud mode,
+ // and set word length to 8 bits, no parity.
+ WriteReg(LCR, LCR_EIGHT_BITS);
+
+ // reset and enable FIFOs.
+ WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
+
+ // enable transmit and receive interrupts.
+ // WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
+
+ //enable UART0 in PLIC
+ plic_irq_enable(UART0_IRQ);
+
+ //set UART0 priority in PLIC
+ plic_prio_set(UART0_IRQ, 1);
+
+ //register callback for UART0
+ //plic_register_callback(UART0_IRQ, uart_intr);
+ puts("[UART0] : Uart Init Done, this is Test output!");
+ return 0;
+}
+
+static inline void uart_putc_nolock(int ch)
+{
+ // wait for Transmit Holding Empty to be set in LSR.
+ while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
+ ;
+ WriteReg(THR, ch);
+ return;
+}
+
+int uart_putc(int ch)
+{
+ int intr_enable = riscv_mintr_get();
+ riscv_mintr_off();
+ uart_putc_nolock(ch);
+ riscv_mintr_restore(intr_enable);
+ return 1;
+}
+
+int uart_puts(const char* str)
+{
+ int i;
+ int intr_enable = riscv_mintr_get();
+ riscv_mintr_off();
+ for(i=0;str[i]!=0;i++)
+ {
+ uart_putc_nolock(str[i]);
+ }
+ uart_putc_nolock('\n');
+ riscv_mintr_restore(intr_enable);
+ return i;
+}
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/uart.h b/ports/risc-v64/gnu/example_build/qemu_virt/uart.h
new file mode 100644
index 00000000..2b95d500
--- /dev/null
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/uart.h
@@ -0,0 +1,21 @@
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#ifndef RISCV_UART_H
+#define RISCV_UART_H
+
+#define UART0 0x10000000L
+#define UART0_IRQ 10
+
+#define puts uart_puts
+int uart_init(void);
+int uart_putc(int ch);
+int uart_puts(const char* str);
+#endif