diff --git a/metal/drivers/sifive_trace.h b/metal/drivers/sifive_trace.h
index 00abc11f..3c67522f 100644
--- a/metal/drivers/sifive_trace.h
+++ b/metal/drivers/sifive_trace.h
@@ -4,9 +4,9 @@
 #ifndef METAL__DRIVERS__SIFIVE_TRACE_H
 #define METAL__DRIVERS__SIFIVE_TRACE_H
 
+#include <metal/compiler.h>
 #include <metal/io.h>
 #include <metal/uart.h>
-#include <metal/compiler.h>
 
 struct __metal_driver_vtable_sifive_trace {
     const struct metal_uart_vtable uart;
diff --git a/src/drivers/sifive_trace.c b/src/drivers/sifive_trace.c
index dd4a6135..8b63fbd2 100644
--- a/src/drivers/sifive_trace.c
+++ b/src/drivers/sifive_trace.c
@@ -8,37 +8,88 @@
 #include <metal/drivers/sifive_trace.h>
 #include <metal/machine.h>
 
-#define TRACE_REG(base, offset)   (((unsigned long)base + offset))
-#define TRACE_REGB(base, offset)  (__METAL_ACCESS_ONCE((__metal_io_u8  *)TRACE_REG(base, offset)))
-#define TRACE_REGW(base, offset)  (__METAL_ACCESS_ONCE((__metal_io_u32 *)TRACE_REG(base, offset)))
+#define TRACE_REG(offset) (((unsigned long)base + (offset)))
+#define TRACE_REG8(offset)                                                     \
+    (__METAL_ACCESS_ONCE((__metal_io_u8 *)TRACE_REG(offset)))
+#define TRACE_REG16(offset)                                                    \
+    (__METAL_ACCESS_ONCE((__metal_io_u16 *)TRACE_REG(offset)))
+#define TRACE_REG32(offset)                                                    \
+    (__METAL_ACCESS_ONCE((__metal_io_u32 *)TRACE_REG(offset)))
 
-int __metal_driver_sifive_trace_putc(struct metal_uart *trace, unsigned char c)
-{
+static void write_itc_uint32(struct metal_uart *trace, uint32_t data) {
     long base = __metal_driver_sifive_trace_base(trace);
-    return 0;
+
+    TRACE_REG32(METAL_SIFIVE_TRACE_ITCSTIMULUS) = data;
+}
+
+static void write_itc_uint16(struct metal_uart *trace, uint16_t data) {
+    long base = __metal_driver_sifive_trace_base(trace);
+
+    TRACE_REG16(METAL_SIFIVE_TRACE_ITCSTIMULUS + 2) = data;
 }
 
-int __metal_driver_sifive_trace_getc(struct metal_uart *trace, unsigned char *c)
-{
+static void write_itc_uint8(struct metal_uart *trace, uint8_t data) {
     long base = __metal_driver_sifive_trace_base(trace);
-    return 0;
+
+    TRACE_REG8(METAL_SIFIVE_TRACE_ITCSTIMULUS + 3) = data;
+}
+
+int __metal_driver_sifive_trace_putc(struct metal_uart *trace,
+                                     unsigned char c) {
+    static uint32_t buffer = 0;
+    static int bytes_in_buffer = 0;
+
+    buffer |= (((uint32_t)c) << (bytes_in_buffer * 8));
+
+    bytes_in_buffer += 1;
+
+    if (bytes_in_buffer >= 4) {
+        write_itc_uint32(trace, buffer);
+
+        buffer = 0;
+        bytes_in_buffer = 0;
+    } else if ((c == '\n') || (c == '\r')) { // partial write
+        switch (bytes_in_buffer) {
+        case 3: // do a full word write
+            write_itc_uint16(trace, (uint16_t)(buffer));
+            write_itc_uint8(trace, (uint8_t)(buffer >> 16));
+            break;
+        case 2: // do a 16 bit write
+            write_itc_uint16(trace, (uint16_t)buffer);
+            break;
+        case 1: // do a 1 byte write
+            write_itc_uint8(trace, (uint8_t)buffer);
+            break;
+        }
+
+        buffer = 0;
+        bytes_in_buffer = 0;
+    }
+
+    return (int)c;
 }
 
-void __metal_driver_sifive_trace_init(struct metal_uart *trace, int baud_rate)
-{
+void __metal_driver_sifive_trace_init(struct metal_uart *trace, int baud_rate) {
+    // The only init we do here is to make sure ITC 0 is enabled. It is up to
+    // Freedom Studio or other mechanisms to make sure tracing is enabled. If we
+    // try to enable tracing here, it will likely conflict with Freedom Studio,
+    // and they will just fight with each other.
+
     long base = __metal_driver_sifive_trace_base(trace);
+
+    TRACE_REG32(METAL_SIFIVE_TRACE_ITCTRACEENABLE) |= 0x00000001;
 }
 
 __METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_trace) = {
-    .uart.init          = __metal_driver_sifive_trace_init,
-    .uart.putc          = __metal_driver_sifive_trace_putc,
-    .uart.getc          = __metal_driver_sifive_trace_getc,
+    .uart.init = __metal_driver_sifive_trace_init,
+    .uart.putc = __metal_driver_sifive_trace_putc,
+    .uart.getc = NULL,
 
     .uart.get_baud_rate = NULL,
     .uart.set_baud_rate = NULL,
 
     .uart.controller_interrupt = NULL,
-    .uart.get_interrupt_id     = NULL,
+    .uart.get_interrupt_id = NULL,
 };
 
 #endif /* METAL_SIFIVE_TRACE */