diff --git a/Makefile b/Makefile
index 8fb469b..4a47823 100644
--- a/Makefile
+++ b/Makefile
@@ -461,8 +461,10 @@ endif
 endif
 
 install-hypertrace:
-	$(INSTALL_DIR) "$(DESTDIR)$(includedir)"
+	$(INSTALL_DIR) "$(DESTDIR)$(includedir)/linux"
 	$(INSTALL_DATA) "$(SRC_PATH)/hypertrace/guest/user/qemu-hypertrace.h" "$(DESTDIR)$(includedir)/"
+	$(INSTALL_DATA) "$(SRC_PATH)/hypertrace/guest/linux-module/include/linux/qemu-hypertrace.h" "$(DESTDIR)$(includedir)/linux"
+	$(INSTALL_DATA) "$(SRC_PATH)/hypertrace/guest/linux-module/include/linux/qemu-hypertrace-internal.h" "$(DESTDIR)$(includedir)/linux"
 
 
 install: all $(if $(BUILD_DOCS),install-doc) \
diff --git a/configure b/configure
index ad83fff..fadaaf5 100755
--- a/configure
+++ b/configure
@@ -5795,6 +5795,10 @@ fi
 symlink "$source_path/Makefile.target" "$target_dir/Makefile"
 mkdir -p $target_dir/hypertrace/guest/user
 symlink $source_path/hypertrace/guest/user/Makefile $target_dir/hypertrace/guest/user/Makefile
+if test "$target_softmmu" = "yes"; then
+    mkdir -p $target_dir/hypertrace/guest/linux-module
+    symlink $source_path/hypertrace/guest/linux-module/Makefile $target_dir/hypertrace/guest/linux-module/Makefile
+fi
 
 upper() {
     echo "$@"| LC_ALL=C tr '[a-z]' '[A-Z]'
diff --git a/hypertrace/guest/linux-module/Kbuild.in b/hypertrace/guest/linux-module/Kbuild.in
new file mode 100644
index 0000000..56abaea
--- /dev/null
+++ b/hypertrace/guest/linux-module/Kbuild.in
@@ -0,0 +1,7 @@
+# -*- mode: makefile -*-
+
+COMMON_H = $(shell readlink -f $(MOD_SRC_PATH)/../../common.h)
+
+src = $(MOD_SRC_PATH)
+ccflags-y := -I$(src)/include/ -DCOMMON_H=$(COMMON_H)
+obj-m := qemu-hypertrace.o
diff --git a/hypertrace/guest/linux-module/Makefile b/hypertrace/guest/linux-module/Makefile
new file mode 100644
index 0000000..b30cdeb
--- /dev/null
+++ b/hypertrace/guest/linux-module/Makefile
@@ -0,0 +1,23 @@
+include ../../../../config-host.mak
+include ../../../config-target.mak
+include $(SRC_PATH)/rules.mak
+
+MOD_SRC_PATH = $(SRC_PATH)/hypertrace/guest/linux-module
+
+LINUX_BUILD_PATH = /lib/modules/$(shell uname -r)/build
+
+vpath % $(MOD_SRC_PATH)
+
+
+all: Kbuild
+	$(MAKE) -C $(LINUX_BUILD_PATH) M=$(shell pwd) \
+	  MOD_SRC_PATH=$(MOD_SRC_PATH) \
+	  modules
+
+clean: Kbuild
+	$(MAKE) -C $(LINUX_BUILD_PATH) M=$(shell pwd) clean
+	rm -f $<
+
+Kbuild: $(MOD_SRC_PATH)/Kbuild.in Makefile
+	rm -f $@
+	cp $< $@
diff --git a/hypertrace/guest/linux-module/include/linux/qemu-hypertrace-internal.h b/hypertrace/guest/linux-module/include/linux/qemu-hypertrace-internal.h
new file mode 100644
index 0000000..63dfee0
--- /dev/null
+++ b/hypertrace/guest/linux-module/include/linux/qemu-hypertrace-internal.h
@@ -0,0 +1,46 @@
+/* -*- C -*-
+ *
+ * Guest-side management of hypertrace.
+ *
+ * Copyright (C) 2016 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/.
+ */
+
+extern uint64_t _qemu_hypertrace_channel_max_clients;
+extern uint64_t _qemu_hypertrace_channel_num_args;
+extern uint64_t *_qemu_hypertrace_channel_data;
+extern uint64_t *_qemu_hypertrace_channel_control;
+
+static inline uint64_t qemu_hypertrace_max_clients(void)
+{
+    return _qemu_hypertrace_channel_max_clients;
+}
+
+static inline uint64_t qemu_hypertrace_num_args(void)
+{
+    return _qemu_hypertrace_channel_num_args;
+}
+
+static inline uint64_t *qemu_hypertrace_data(uint64_t client)
+{
+    size_t args_size = qemu_hypertrace_num_args() * sizeof(uint64_t);
+    return &_qemu_hypertrace_channel_data[client * args_size];
+}
+
+static inline void qemu_hypertrace (uint64_t client, uint64_t arg1)
+{
+    uint64_t *ctrlp = _qemu_hypertrace_channel_control;
+    ctrlp[client] = arg1;
+}
diff --git a/hypertrace/guest/linux-module/include/linux/qemu-hypertrace.h b/hypertrace/guest/linux-module/include/linux/qemu-hypertrace.h
new file mode 100644
index 0000000..f4b70c9
--- /dev/null
+++ b/hypertrace/guest/linux-module/include/linux/qemu-hypertrace.h
@@ -0,0 +1,73 @@
+/* -*- C -*-
+ *
+ * Guest-side management of hypertrace.
+ *
+ * Copyright (C) 2016 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/.
+ */
+
+#ifndef QEMU_HYPERTRACE_H
+#define QEMU_HYPERTRACE_H
+
+#include <linux/types.h>
+
+
+/**
+ * qemu_hypertrace_max_clients:
+ *
+ * Maximum number of concurrent clients accepted by other calls.
+ */
+static uint64_t qemu_hypertrace_max_clients(void);
+
+/**
+ * qemu_hypertrace_num_args:
+ *
+ * Number of uint64_t values read by each call to qemu_hypertrace().
+ */
+static uint64_t qemu_hypertrace_num_args(void);
+
+/**
+ * qemu_hypertrace_data:
+ * @client: Client identifier.
+ *
+ * Pointer to the start of the data channel for the given client. Clients must
+ * write their arguments there (all but the first one).
+ */
+static uint64_t *qemu_hypertrace_data(uint64_t client);
+
+/**
+ * qemu_hypertrace:
+ * @client: Client identifier.
+ * @arg1: First argument of the hypertrace event.
+ *
+ * Emit a hypertrace event.
+ *
+ * Each of the clients (e.g., CPU) must use a different client identifier to
+ * ensure they can work concurrently without using locks (i.e., each uses a
+ * different portion of the data channel).
+ *
+ * Note: You should use wmb() before writing into the control channel iff you
+ * have written into the data channel.
+ *
+ * Note: Use preempt_disable() and preempt_enable() if you're using data offsets
+ * based on the CPU identifiers (or else data might be mixed if a task is
+ * re-scheduled).
+ */
+static void qemu_hypertrace(uint64_t client, uint64_t arg1);
+
+
+#include <linux/qemu-hypertrace-internal.h>
+
+#endif  /* QEMU_HYPERTRACE_H */
diff --git a/hypertrace/guest/linux-module/qemu-hypertrace.c b/hypertrace/guest/linux-module/qemu-hypertrace.c
new file mode 100644
index 0000000..901d424
--- /dev/null
+++ b/hypertrace/guest/linux-module/qemu-hypertrace.c
@@ -0,0 +1,146 @@
+/*
+ * Guest-side management of hypertrace.
+ *
+ * Copyright (C) 2016 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/.
+ */
+
+#include <linux/qemu-hypertrace.h>
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/uaccess.h>
+
+#define xstr(s) #s
+#define str(s) xstr(s)
+#include str(COMMON_H)
+
+
+#define VERSION_STR "0.1"
+#define PCI_VENDOR_ID_REDHAT_QUMRANET    0x1af4
+#define PCI_DEVICE_ID_HYPERTRACE         0x10f0
+
+
+MODULE_DESCRIPTION("Kernel interface to QEMU's hypertrace device");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lluís Vilanova");
+MODULE_VERSION(VERSION_STR);
+
+
+//////////////////////////////////////////////////////////////////////
+// Kernel interface
+
+uint64_t _qemu_hypertrace_channel_max_clients;
+uint64_t _qemu_hypertrace_channel_num_args;
+static uint64_t *_qemu_hypertrace_channel_config;
+uint64_t *_qemu_hypertrace_channel_data;
+uint64_t *_qemu_hypertrace_channel_control;
+
+EXPORT_SYMBOL(_qemu_hypertrace_channel_max_clients);
+EXPORT_SYMBOL(_qemu_hypertrace_channel_num_args);
+EXPORT_SYMBOL(_qemu_hypertrace_channel_data);
+EXPORT_SYMBOL(_qemu_hypertrace_channel_control);
+
+
+//////////////////////////////////////////////////////////////////////
+// Channel initialization
+
+static
+int
+init_channel (uint64_t **vaddr, struct pci_dev *dev, int bar)
+{
+    void * res;
+    resource_size_t start, size;
+
+    start = pci_resource_start(dev, bar);
+    size = pci_resource_len(dev, bar);
+
+    if (start == 0 || size == 0) {
+        return -ENOENT;
+    }
+
+    res = ioremap(start, size);
+    if (res == 0) {
+        return -EINVAL;
+    }
+
+    *vaddr = res;
+    return 0;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Module (de)initialization
+
+int init_module(void)
+{
+    int res = 0;
+    struct pci_dev *dev = NULL;
+    struct hypertrace_config *config;
+
+    printk(KERN_NOTICE "Loading QEMU hypertrace module (version %s)\n",
+           VERSION_STR);
+
+    dev = pci_get_device(PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_DEVICE_ID_HYPERTRACE, NULL);
+    if (dev == NULL) {
+        res = -ENOENT;
+        printk(KERN_ERR "Unable to find hypertrace device\n");
+        goto error;
+    }
+
+    res = init_channel(&_qemu_hypertrace_channel_config, dev, 0);
+    if (res != 0) {
+        printk(KERN_ERR "Unable to find hypertrace config channel\n");
+        goto error;
+    }
+
+    config = (struct hypertrace_config*)_qemu_hypertrace_channel_config;
+    _qemu_hypertrace_channel_max_clients = config->max_clients;
+    _qemu_hypertrace_channel_num_args = config->client_args;
+
+    res = init_channel(&_qemu_hypertrace_channel_data, dev, 1);
+    if (res != 0) {
+        printk(KERN_ERR "Unable to find hypertrace data channel\n");
+        goto error_config;
+    }
+
+    res = init_channel(&_qemu_hypertrace_channel_control, dev, 0);
+    if (res != 0) {
+        printk(KERN_ERR "Unable to find hypertrace control channel\n");
+        goto error_data;
+    }
+
+    goto ok;
+
+error_data:
+    iounmap(_qemu_hypertrace_channel_data);
+
+error_config:
+    iounmap(_qemu_hypertrace_channel_config);
+    _qemu_hypertrace_channel_config = NULL;
+    _qemu_hypertrace_channel_data = NULL;
+
+error:
+ok:
+    return res;
+}
+
+void cleanup_module(void)
+{
+    printk(KERN_NOTICE "Unloading QEMU hypertrace module\n");
+
+    iounmap(_qemu_hypertrace_channel_config);
+    iounmap(_qemu_hypertrace_channel_data);
+    iounmap(_qemu_hypertrace_channel_control);
+}
