diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs
index 79b4553..d830e5d 100644
--- a/hw/riscv/Makefile.objs
+++ b/hw/riscv/Makefile.objs
@@ -1 +1,3 @@
 obj-y += riscv_rtc.o
+obj-y += htif/elf_symb.o
+obj-y += htif/htif.o
diff --git a/hw/riscv/htif/elf_symb.c b/hw/riscv/htif/elf_symb.c
new file mode 100644
index 0000000..dc8efd6
--- /dev/null
+++ b/hw/riscv/htif/elf_symb.c
@@ -0,0 +1,286 @@
+/*
+ * elf.c - A simple package for manipulating symbol tables in elf binaries.
+ *
+ * Taken from
+ * https://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15213-f03/www/
+ * ftrace/elf.c
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glib.h>
+
+#include "elf_symb.h"
+
+/*
+ * elf_open - Map a binary into the address space and extract the
+ * locations of the static and dynamic symbol tables and their string
+ * tables. Return this information in a Elf object file handle that will
+ * be passed to all of the other elf functions.
+ */
+Elf_obj *elf_open(const char *filename)
+{
+    int i, fd;
+    struct stat sbuf;
+    Elf_obj *ep;
+    Elf64_Shdr *shdr;
+
+    ep = g_new(Elf_obj, 1);
+
+    /* Do some consistency checks on the binary */
+    fd = open(filename, O_RDONLY);
+    if (fd == -1) {
+        fprintf(stderr, "Can't open \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    if (fstat(fd, &sbuf) == -1) {
+        fprintf(stderr, "Can't stat \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    if (sbuf.st_size < sizeof(Elf64_Ehdr)) {
+        fprintf(stderr, "\"%s\" is not an ELF binary object\n", filename);
+        exit(1);
+    }
+
+    /* It looks OK, so map the Elf binary into our address space */
+    ep->mlen = sbuf.st_size;
+    ep->maddr = mmap(NULL, ep->mlen, PROT_READ, MAP_SHARED, fd, 0);
+    if (ep->maddr == (void *)-1) {
+        fprintf(stderr, "Can't mmap \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    close(fd);
+
+    /* The Elf binary begins with the Elf header */
+    ep->ehdr = ep->maddr;
+
+    /* Make sure that this is an Elf binary */
+    /*    if (strncmp(ep->ehdr->e_ident, ELFMAG, SELFMAG)) {
+    fprintf(stderr, "\"%s\" is not an ELF binary object\n", filename);
+    exit(1);
+    }*/
+
+    /*
+     * Find the static and dynamic symbol tables and their string
+     * tables in the the mapped binary. The sh_link field in symbol
+     * table section headers gives the section index of the string
+     * table for that symbol table.
+     */
+    shdr = (Elf64_Shdr *)(ep->maddr + ep->ehdr->e_shoff);
+    for (i = 0; i < ep->ehdr->e_shnum; i++) {
+        if (shdr[i].sh_type == SHT_SYMTAB) {   /* Static symbol table */
+            ep->symtab = (Elf64_Sym *)(ep->maddr + shdr[i].sh_offset);
+            ep->symtab_end = (Elf64_Sym *)((char *)ep->symtab +
+                             shdr[i].sh_size);
+            ep->strtab = (char *)(ep->maddr + shdr[shdr[i].sh_link].sh_offset);
+        }
+        if (shdr[i].sh_type == SHT_DYNSYM) {   /* Dynamic symbol table */
+            ep->dsymtab = (Elf64_Sym *)(ep->maddr + shdr[i].sh_offset);
+            ep->dsymtab_end = (Elf64_Sym *)((char *)ep->dsymtab +
+                              shdr[i].sh_size);
+            ep->dstrtab = (char *)(ep->maddr + shdr[shdr[i].sh_link].sh_offset);
+        }
+    }
+    return ep;
+}
+
+/*
+ * elf_open - Map a binary into the address space and extract the
+ * locations of the static and dynamic symbol tables and their string
+ * tables. Return this information in a Elf object file handle that will
+ * be passed to all of the other elf functions.
+ */
+Elf_obj32 *elf_open32(const char *filename)
+{
+    int i, fd;
+    struct stat sbuf;
+    Elf_obj32 *ep;
+    Elf32_Shdr *shdr;
+
+    ep = g_new(Elf_obj32, 1);
+
+    /* Do some consistency checks on the binary */
+    fd = open(filename, O_RDONLY);
+    if (fd == -1) {
+        fprintf(stderr, "Can't open \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    if (fstat(fd, &sbuf) == -1) {
+        fprintf(stderr, "Can't stat \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    if (sbuf.st_size < sizeof(Elf32_Ehdr)) {
+        fprintf(stderr, "\"%s\" is not an ELF binary object\n", filename);
+        exit(1);
+    }
+
+    /* It looks OK, so map the Elf binary into our address space */
+    ep->mlen = sbuf.st_size;
+    ep->maddr = mmap(NULL, ep->mlen, PROT_READ, MAP_SHARED, fd, 0);
+    if (ep->maddr == (void *)-1) {
+        fprintf(stderr, "Can't mmap \"%s\": %s\n", filename, strerror(errno));
+        exit(1);
+    }
+    close(fd);
+
+    /* The Elf binary begins with the Elf header */
+    ep->ehdr = ep->maddr;
+
+    /* Make sure that this is an Elf binary */
+/*    if (strncmp(ep->ehdr->e_ident, ELFMAG, SELFMAG)) {
+    fprintf(stderr, "\"%s\" is not an ELF binary object\n", filename);
+    exit(1);
+    }*/
+
+    /*
+     * Find the static and dynamic symbol tables and their string
+     * tables in the the mapped binary. The sh_link field in symbol
+     * table section headers gives the section index of the string
+     * table for that symbol table.
+     */
+    shdr = (Elf32_Shdr *)(ep->maddr + ep->ehdr->e_shoff);
+    for (i = 0; i < ep->ehdr->e_shnum; i++) {
+        if (shdr[i].sh_type == SHT_SYMTAB) {   /* Static symbol table */
+            ep->symtab = (Elf32_Sym *)(ep->maddr + shdr[i].sh_offset);
+            ep->symtab_end = (Elf32_Sym *)((char *)ep->symtab +
+                             shdr[i].sh_size);
+            ep->strtab = (char *)(ep->maddr + shdr[shdr[i].sh_link].sh_offset);
+        }
+        if (shdr[i].sh_type == SHT_DYNSYM) {   /* Dynamic symbol table */
+            ep->dsymtab = (Elf32_Sym *)(ep->maddr + shdr[i].sh_offset);
+            ep->dsymtab_end = (Elf32_Sym *)((char *)ep->dsymtab +
+                              shdr[i].sh_size);
+            ep->dstrtab = (char *)(ep->maddr + shdr[shdr[i].sh_link].sh_offset);
+        }
+    }
+    return ep;
+}
+
+/*
+ * elf_close - Free up the resources of an  elf object
+ */
+void elf_close(Elf_obj *ep)
+{
+    if (munmap(ep->maddr, ep->mlen) < 0) {
+        perror("munmap");
+        exit(1);
+    }
+    free(ep);
+}
+
+/*
+ * elf_symname - Return ASCII name of a static symbol
+ */
+char *elf_symname(Elf_obj *ep, Elf64_Sym *sym)
+{
+    return &ep->strtab[sym->st_name];
+}
+
+/*
+ * elf_symname - Return ASCII name of a static symbol
+ */
+char *elf_symname32(Elf_obj32 *ep, Elf32_Sym *sym)
+{
+    return &ep->strtab[sym->st_name];
+}
+
+
+/*
+ * elf_dsymname - Return ASCII name of a dynamic symbol
+ */
+char *elf_dsymname(Elf_obj *ep, Elf64_Sym *sym)
+{
+    return &ep->dstrtab[sym->st_name];
+}
+
+/*
+ * elf_firstsym - Return ptr to first symbol in static symbol table
+ */
+Elf64_Sym *elf_firstsym(Elf_obj *ep)
+{
+    return ep->symtab;
+}
+
+/*
+ * elf_firstsym - Return ptr to first symbol in static symbol table
+ */
+Elf32_Sym *elf_firstsym32(Elf_obj32 *ep)
+{
+    return ep->symtab;
+}
+
+
+/*
+ * elf_nextsym - Return ptr to next symbol in static symbol table,
+ * or NULL if no more symbols.
+ */
+Elf64_Sym *elf_nextsym(Elf_obj *ep, Elf64_Sym *sym)
+{
+    sym++;
+    if (sym < ep->symtab_end) {
+        return sym;
+    } else {
+        return NULL;
+    }
+}
+
+/*
+ * elf_nextsym - Return ptr to next symbol in static symbol table,
+ * or NULL if no more symbols.
+ */
+Elf32_Sym *elf_nextsym32(Elf_obj32 *ep, Elf32_Sym *sym)
+{
+    sym++;
+    if (sym < ep->symtab_end) {
+        return sym;
+    } else {
+        return NULL;
+    }
+}
+
+
+/*
+ * elf_firstdsym - Return ptr to first symbol in dynamic symbol table
+ */
+Elf64_Sym *elf_firstdsym(Elf_obj *ep)
+{
+    return ep->dsymtab;
+}
+
+/*
+ * elf_nextdsym - Return ptr to next symbol in dynamic symbol table,
+ * of NULL if no more symbols.
+ */
+Elf64_Sym *elf_nextdsym(Elf_obj *ep, Elf64_Sym *sym)
+{
+    sym++;
+    if (sym < ep->dsymtab_end) {
+        return sym;
+    } else {
+        return NULL;
+    }
+}
+
+/*
+ * elf_isfunc - Return true if symbol is a static function
+ */
+int elf_isfunc(Elf_obj *ep, Elf64_Sym *sym)
+{
+    return ((ELF32_ST_TYPE(sym->st_info) == STT_FUNC) &&
+        (sym->st_shndx != SHT_NULL));
+}
+
+/*
+ * elf_isdfunc - Return true if symbol is a dynamic function
+ */
+int elf_isdfunc(Elf_obj *ep, Elf64_Sym *sym)
+{
+    return ((ELF32_ST_TYPE(sym->st_info) == STT_FUNC));
+}
diff --git a/hw/riscv/htif/elf_symb.h b/hw/riscv/htif/elf_symb.h
new file mode 100644
index 0000000..a438c60
--- /dev/null
+++ b/hw/riscv/htif/elf_symb.h
@@ -0,0 +1,80 @@
+/*
+ * elf.h - A package for manipulating Elf binaries
+ *
+ * Taken from:
+ * https://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15213-f03/www/ftrace/
+ * elf.h
+ *
+ */
+
+#ifndef ELF_H
+#define ELF_H
+
+#include <stdint.h>
+#include <elf.h>
+
+/*
+ * This is a handle that is created by elf_open and then used by every
+ * other function in the elf package
+*/
+typedef struct {
+    void *maddr;            /* Start of mapped Elf binary segment in memory */
+    int mlen;               /* Length in bytes of the mapped Elf segment */
+    Elf64_Ehdr *ehdr;       /* Start of main Elf header (same as maddr) */
+    Elf64_Sym *symtab;      /* Start of symbol table */
+    Elf64_Sym *symtab_end;  /* End of symbol table (symtab + size) */
+    char *strtab;           /* Address of string table */
+    Elf64_Sym *dsymtab;     /* Start of dynamic symbol table */
+    Elf64_Sym *dsymtab_end; /* End of dynamic symbol table (dsymtab + size) */
+    char *dstrtab;          /* Address of dynamic string table */
+} Elf_obj;
+
+typedef struct {
+    void *maddr;            /* Start of mapped Elf binary segment in memory */
+    int mlen;               /* Length in bytes of the mapped Elf segment */
+    Elf32_Ehdr *ehdr;       /* Start of main Elf header (same as maddr) */
+    Elf32_Sym *symtab;      /* Start of symbol table */
+    Elf32_Sym *symtab_end;  /* End of symbol table (symtab + size) */
+    char *strtab;           /* Address of string table */
+    Elf32_Sym *dsymtab;     /* Start of dynamic symbol table */
+    Elf32_Sym *dsymtab_end; /* End of dynamic symbol table (dsymtab + size) */
+    char *dstrtab;          /* Address of dynamic string table */
+} Elf_obj32;
+
+/*
+ * Create and destroy Elf object handles
+ */
+Elf_obj *elf_open(const char *filename);
+Elf_obj32 *elf_open32(const char *filename);
+
+void elf_close(Elf_obj *ep);
+
+/*
+ * Functions for manipulating static symbols
+ */
+
+/* Returns pointer to the first symbol */
+Elf64_Sym *elf_firstsym(Elf_obj *ep);
+Elf32_Sym *elf_firstsym32(Elf_obj32 *ep);
+
+/* Returns pointer to the next symbol, or NULL if no more symbols */
+Elf64_Sym *elf_nextsym(Elf_obj *ep, Elf64_Sym *sym);
+Elf32_Sym *elf_nextsym32(Elf_obj32 *ep, Elf32_Sym *sym);
+
+/* Return symbol string name */
+char *elf_symname(Elf_obj *ep, Elf64_Sym *sym);
+char *elf_symname32(Elf_obj32 *ep, Elf32_Sym *sym);
+
+/* True if symbol is a function */
+int elf_isfunc(Elf_obj *ep, Elf64_Sym *sym);
+
+/*
+ * Corresponding functions for manipulating dynamic symbols
+ */
+Elf64_Sym *elf_firstdsym(Elf_obj *ep);
+Elf64_Sym *elf_nextdsym(Elf_obj *ep, Elf64_Sym *sym);
+char *elf_dsymname(Elf_obj *ep, Elf64_Sym *sym);
+int elf_isdfunc(Elf_obj *ep, Elf64_Sym *sym);
+
+
+#endif
diff --git a/hw/riscv/htif/htif.c b/hw/riscv/htif/htif.c
new file mode 100644
index 0000000..6093ebd
--- /dev/null
+++ b/hw/riscv/htif/htif.c
@@ -0,0 +1,423 @@
+/*
+ * QEMU RISC-V Host Target Interface (HTIF) Emulation
+ *
+ * Author: Sagar Karandikar, sagark@eecs.berkeley.edu
+ *
+ * This provides HTIF device emulation for QEMU. At the moment this allows
+ * for identical copies of bbl/linux to run on both spike and QEMU.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/char/serial.h"
+#include "sysemu/char.h"
+#include "hw/riscv/htif/htif.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include "elf_symb.h"
+
+#define ENABLE_CHARDEV
+/*#define DEBUG_CHARDEV */
+/*#define DEBUG_HTIF */
+
+#ifdef ENABLE_CHARDEV
+/*
+ * Called by the char dev to see if HTIF is ready to accept input.
+ */
+static int htif_can_recv(void *opaque)
+{
+    return 1;
+}
+
+/*
+ * Called by the char dev to supply input to HTIF console.
+ * We assume that we will receive one character at a time.
+ */
+static void htif_recv(void *opaque, const uint8_t *buf, int size)
+{
+    if (size != 1) {
+        return;
+    }
+
+    HTIFState *htifstate = opaque;
+
+    #ifdef DEBUG_CHARDEV
+    if (htifstate->env->mfromhost != 0x0) {
+        fprintf(stderr, "recv handler: fromhost was not ready to \
+                         accept input\n");
+        fprintf(stderr, "recv handler: prev value was: %016lx\n",
+                htifstate->env->mfromhost);
+    }
+    #endif
+
+    uint64_t val_written = htifstate->pending_read;
+    uint64_t resp = 0x100 | *buf;
+
+    htifstate->env->mfromhost = (val_written >> 48 << 48) | (resp << 16 >> 16);
+    qemu_irq_raise(htifstate->irq);
+}
+
+/*
+ * Called by the char dev to supply special events to the HTIF console.
+ * Not used for HTIF.
+ */
+static void htif_event(void *opaque, int event)
+{
+    #ifdef DEBUG_CHARDEV
+    fprintf(stderr, "GOT EVENT: %d\n", event);
+    #endif
+}
+#endif
+
+static void htif_pre_save(void *opaque)
+{
+    return;
+}
+
+static int htif_post_load(void *opaque, int version_id)
+{
+    return 0;
+}
+
+const VMStateDescription vmstate_htif = {
+    .name = "htif",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .pre_save = htif_pre_save,
+    .post_load = htif_post_load,
+    .fields      = (VMStateField []) { /* TODO what */
+        VMSTATE_UINT64(tohost_offset, HTIFState),
+        VMSTATE_UINT64(fromhost_offset, HTIFState),
+        VMSTATE_UINT64(tohost_size, HTIFState),
+        VMSTATE_UINT64(fromhost_size, HTIFState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void dma_strcopy(HTIFState *htifstate, char *str, hwaddr phys_addr)
+{
+    int i = 0;
+    void *base_copy_addr = htifstate->main_mem_ram_ptr + phys_addr;
+    while (*(str + i)) {
+        stb_p((void *)(base_copy_addr + i), *(str + i));
+        i++;
+    }
+    stb_p((void *)(base_copy_addr + i), 0); /* store null term */
+}
+
+static void htif_handle_tohost_write(HTIFState *htifstate, uint64_t val_written)
+{
+    #ifdef DEBUG_HTIF
+    fprintf(stderr, "TOHOST WRITE WITH val 0x%016lx\n", val_written);
+    #endif
+
+    uint8_t device = val_written >> 56;
+    uint8_t cmd = val_written >> 48;
+    uint64_t payload = val_written & 0xFFFFFFFFFFFFULL;
+
+    uint64_t addr = payload >> 8;
+    hwaddr real_addr = (hwaddr)addr;
+    uint8_t what = payload & 0xFF;
+    int resp;
+
+    resp = 0; /* stop gcc complaining */
+    #ifdef DEBUG_HTIF
+    fprintf(stderr, "mtohost write:\n-device: %d\n-cmd: %d\n-what: %02lx\n\
+                     -payload: %016lx\n", device, cmd, payload & 0xFF, payload);
+    #endif
+
+    /*
+     * Currently, there is a fixed mapping of devices:
+     * 0: riscv-tests Pass/Fail Reporting Only (no syscall proxy)
+     * 1: Console
+     */
+    if (unlikely(device == 0x0)) {
+        /* frontend syscall handler, only test pass/fail support */
+        if (cmd == 0x0) {
+            #ifdef DEBUG_HTIF
+            fprintf(stderr, "frontend syscall handler\n");
+            #endif
+            if (payload & 0x1) {
+                /* test result */
+                if (payload >> 1) {
+                    printf("*** FAILED *** (exitcode = %016lx)\n",
+                           payload >> 1);
+                } else {
+                    printf("TEST PASSED\n");
+                }
+                exit(payload >> 1);
+            }
+            fprintf(stderr, "pk syscall proxy not supported\n");
+        } else if (cmd == 0xFF) {
+            /* use what */
+            if (what == 0xFF) {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering name\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"syscall_proxy", real_addr);
+            } else if (what == 0x0) {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering syscall cmd\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"syscall", real_addr);
+            } else {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering end of cmds list\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"", real_addr);
+            }
+            resp = 0x1; /* write to indicate device name placed */
+        } else {
+            fprintf(stderr, "HTIF device %d: UNKNOWN COMMAND\n", device);
+            exit(1);
+        }
+    } else if (likely(device == 0x1)) {
+        /* HTIF Console */
+        if (cmd == 0x0) {
+            /* this should be a queue, but not yet implemented as such */
+            htifstate->pending_read = val_written;
+            htifstate->env->mtohost = 0; /* clear to indicate we read */
+            return;
+        } else if (cmd == 0x1) {
+            #ifdef ENABLE_CHARDEV
+            qemu_chr_fe_write(htifstate->chr, (uint8_t *)&payload, 1);
+            #endif
+            resp = 0x100 | (uint8_t)payload;
+        } else if (cmd == 0xFF) {
+            /* use what */
+            if (what == 0xFF) {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering name\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"bcd", real_addr);
+            } else if (what == 0x0) {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering read cmd\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"read", real_addr);
+            } else if (what == 0x1) {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering write cmd\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"write", real_addr);
+            } else {
+                #ifdef DEBUG_HTIF
+                fprintf(stderr, "registering end of cmds list\n");
+                #endif
+                dma_strcopy(htifstate, (char *)"", real_addr);
+            }
+            resp = 0x1; /* write to indicate device name placed */
+        } else {
+            fprintf(stderr, "HTIF device %d: UNKNOWN COMMAND\n", device);
+            exit(1);
+        }
+    /* all other devices */
+    } else if (device == 0x2 && cmd == 0xFF && what == 0xFF) {
+        #ifdef DEBUG_HTIF
+        fprintf(stderr, "registering no device as last\n");
+        #endif
+        stb_p((void *)(htifstate->main_mem_ram_ptr + real_addr), 0);
+        resp = 0x1; /* write to indicate device name placed */
+    } else {
+        fprintf(stderr, "HTIF UNKNOWN DEVICE OR COMMAND!\n");
+        fprintf(stderr, "-device: %d\n-cmd: %d\n-what: %02lx\n-payload: \
+                         %016lx\n", device, cmd, payload & 0xFF, payload);
+        exit(1);
+    }
+    while (!htifstate->fromhost_inprogress &&
+            htifstate->env->mfromhost != 0x0) {
+        /* wait */
+    }
+    htifstate->env->mfromhost = (val_written >> 48 << 48) | (resp << 16 >> 16);
+    htifstate->env->mtohost = 0; /* clear to indicate we read */
+    if (htifstate->env->mfromhost != 0) {
+        /* raise HTIF interrupt */
+        qemu_irq_raise(htifstate->irq);
+    }
+}
+
+#define TOHOST_OFFSET1 (htifstate->tohost_offset)
+#define TOHOST_OFFSET2 (htifstate->tohost_offset + 4)
+#define FROMHOST_OFFSET1 (htifstate->fromhost_offset)
+#define FROMHOST_OFFSET2 (htifstate->fromhost_offset + 4)
+
+/* CPU wants to read an HTIF register */
+static uint64_t htif_mm_read(void *opaque, hwaddr addr, unsigned size)
+{
+    HTIFState *htifstate = opaque;
+    if (addr == TOHOST_OFFSET1) {
+        return htifstate->env->mtohost & 0xFFFFFFFF;
+    } else if (addr == TOHOST_OFFSET2) {
+        return (htifstate->env->mtohost >> 32) & 0xFFFFFFFF;
+    } else if (addr == FROMHOST_OFFSET1) {
+        return htifstate->env->mfromhost & 0xFFFFFFFF;
+    } else if (addr == FROMHOST_OFFSET2) {
+        return (htifstate->env->mfromhost >> 32) & 0xFFFFFFFF;
+    } else {
+        printf("Invalid htif register address %016lx\n", (uint64_t)addr);
+        exit(1);
+    }
+}
+
+/* CPU wrote to an HTIF register */
+static void htif_mm_write(void *opaque, hwaddr addr,
+                            uint64_t value, unsigned size)
+{
+    HTIFState *htifstate = opaque;
+    if (addr == TOHOST_OFFSET1) {
+        if (htifstate->env->mtohost == 0x0) {
+            htifstate->allow_tohost = 1;
+            htifstate->env->mtohost = value & 0xFFFFFFFF;
+            if (unlikely(htifstate->tohost_size != 8)) {
+#ifdef DEBUG_HTIF
+                fprintf(stderr, "Using non-8 htif width\n");
+#endif
+                /* tests have a zero tohost size in elf symb tab and they
+                   use sw to write to mm_write, so TOHOST_OFFSET2 will never
+                   be written to. Thus, initiate side effects here. */
+                htif_handle_tohost_write(htifstate, htifstate->env->mtohost);
+            }
+        } else {
+            htifstate->allow_tohost = 0;
+        }
+    } else if (addr == TOHOST_OFFSET2) {
+        if (htifstate->allow_tohost) {
+            htifstate->env->mtohost |= value << 32;
+            htif_handle_tohost_write(htifstate, htifstate->env->mtohost);
+        }
+    } else if (addr == FROMHOST_OFFSET1) {
+        htifstate->fromhost_inprogress = 1;
+        htifstate->env->mfromhost = value & 0xFFFFFFFF;
+    } else if (addr == FROMHOST_OFFSET2) {
+        htifstate->env->mfromhost |= value << 32;
+        if (htifstate->env->mfromhost == 0x0) {
+            qemu_irq_lower(htifstate->irq);
+        }
+        htifstate->fromhost_inprogress = 0;
+    } else {
+        printf("Invalid htif register address %016lx\n", (uint64_t)addr);
+        exit(1);
+    }
+}
+
+static const MemoryRegionOps htif_mm_ops[3] = {
+    [DEVICE_LITTLE_ENDIAN] = {
+        .read = htif_mm_read,
+        .write = htif_mm_write,
+        .endianness = DEVICE_LITTLE_ENDIAN,
+    },
+};
+
+HTIFState *htif_mm_init(MemoryRegion *address_space,
+           const char *kernel_filename, qemu_irq irq, MemoryRegion *main_mem,
+           CPURISCVState *env, CharDriverState *chr)
+{
+    uint64_t fromhost_addr = 0;
+    uint64_t fromhost_size = 0; /* for pk vs tests */
+    uint64_t tohost_addr = 0;
+    uint64_t tohost_size = 0; /* for pk vs tests */
+
+    /* get fromhost/tohost addresses from the ELF, as spike/fesvr do */
+    if (NULL != kernel_filename) {
+#if defined(TARGET_RISCV64)
+        Elf_obj *e = elf_open(kernel_filename);
+#else
+        Elf_obj32 *e = elf_open32(kernel_filename);
+#endif
+
+        const char *fromhost = "fromhost";
+        const char *tohost = "tohost";
+
+#if defined(TARGET_RISCV64)
+        Elf64_Sym *curr_sym = elf_firstsym(e);
+#else
+        Elf32_Sym *curr_sym = elf_firstsym32(e);
+#endif
+        while (curr_sym) {
+#if defined(TARGET_RISCV64)
+            char *symname = elf_symname(e, curr_sym);
+#else
+            char *symname = elf_symname32(e, curr_sym);
+#endif
+
+            if (strcmp(fromhost, symname) == 0) {
+                /* get fromhost addr */
+                fromhost_addr = curr_sym->st_value;
+                fromhost_size = curr_sym->st_size; /* this is correctly set to 8
+                                                      by pk */
+            } else if (strcmp(tohost, symname) == 0) {
+                /* get tohost addr */
+                tohost_addr = curr_sym->st_value;
+                tohost_size = curr_sym->st_size; /* this is correctly set to 8
+                                                    by pk */
+            }
+#if defined(TARGET_RISCV64)
+            curr_sym = elf_nextsym(e, curr_sym);
+#else
+            curr_sym = elf_nextsym32(e, curr_sym);
+#endif
+        }
+    }
+
+    /* now setup HTIF device */
+    HTIFState *htifstate;
+
+    htifstate = g_malloc0(sizeof(HTIFState));
+    htifstate->irq = irq;
+    htifstate->address_space = address_space;
+    htifstate->main_mem = main_mem;
+    htifstate->main_mem_ram_ptr = memory_region_get_ram_ptr(main_mem);
+    htifstate->env = env;
+    htifstate->chr = chr;
+    htifstate->pending_read = 0;
+    htifstate->allow_tohost = 0;
+    htifstate->fromhost_inprogress = 0;
+    htifstate->fromhost_size = fromhost_size;
+    htifstate->tohost_size = tohost_size;
+
+#ifdef ENABLE_CHARDEV
+    qemu_chr_add_handlers(htifstate->chr, htif_can_recv, htif_recv, htif_event,
+                          htifstate);
+#endif
+
+    uint64_t base = tohost_addr < fromhost_addr ? tohost_addr : fromhost_addr;
+    uint64_t second = tohost_addr < fromhost_addr ? fromhost_addr : tohost_addr;
+    uint64_t regionwidth = second - base + 8;
+
+    htifstate->tohost_offset = base == tohost_addr ? 0 : tohost_addr -
+                                                         fromhost_addr;
+    htifstate->fromhost_offset = base == fromhost_addr ? 0 : fromhost_addr -
+                                                             tohost_addr;
+
+    vmstate_register(NULL, base, &vmstate_htif, htifstate);
+
+    memory_region_init_io(&htifstate->io, NULL,
+                          &htif_mm_ops[DEVICE_LITTLE_ENDIAN],
+                           htifstate, "htif", regionwidth);
+    memory_region_add_subregion(address_space, base, &htifstate->io);
+
+    return htifstate;
+}
diff --git a/include/hw/riscv/htif/htif.h b/include/hw/riscv/htif/htif.h
new file mode 100644
index 0000000..3499947
--- /dev/null
+++ b/include/hw/riscv/htif/htif.h
@@ -0,0 +1,61 @@
+/*
+ * QEMU RISCV Host Target Interface (HTIF) Emulation
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef HW_RISCV_HTIF_H
+#define HW_RISCV_HTIF_H 1
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "exec/memory.h"
+#include "target-riscv/cpu.h"
+
+typedef struct HTIFState HTIFState;
+
+struct HTIFState {
+    int allow_tohost;
+    int fromhost_inprogress;
+
+    hwaddr tohost_offset;
+    hwaddr fromhost_offset;
+    uint64_t tohost_size;
+    uint64_t fromhost_size;
+    qemu_irq irq; /* host interrupt line */
+    MemoryRegion io;
+    MemoryRegion *address_space;
+    MemoryRegion *main_mem;
+    void *main_mem_ram_ptr;
+
+    CPURISCVState *env;
+    CharDriverState *chr;
+    uint64_t pending_read;
+};
+
+extern const VMStateDescription vmstate_htif;
+extern const MemoryRegionOps htif_io_ops;
+
+/* legacy pre qom */
+HTIFState *htif_mm_init(MemoryRegion *address_space,
+                        const char *kernel_filename, qemu_irq irq,
+                        MemoryRegion *main_mem,
+                        CPURISCVState *env, CharDriverState *chr);
+
+#endif
