diff --git a/linux-ramdump-parser-v2/README b/linux-ramdump-parser-v2/README
index ab3704b5899d35f2469cbba11c2382d1408154cf..2b9f5da8ca3be5cc0383ad2a97d717252f42469e 100644
--- a/linux-ramdump-parser-v2/README
+++ b/linux-ramdump-parser-v2/README
@@ -40,6 +40,10 @@ linux-parser-output.txt is used
 --stdout : Write to stdout instead of the out-file. This overrides any
 --out-file given.
 
+--qtf : Use QTF tool to parse and save QDSS trace data
+
+--qtf-path <path> : QTF tool executable
+
 The list of features parsed is constantly growing. Please use --help option
 to see the full list of features that can be parsed.
 
@@ -86,6 +90,7 @@ gdb_path - absolute path to the gdb tool for the ramdumps
 nm_path - absolute path to the gdb tool for the ramdumps
 gdb64_path - absolute path to the 64-bit gdb tool for the ramdumps
 nm64_path - absolute path to the 64-bit nm tool for the ramdumps
+qtf_path - absolute path to qtf tool executable
 
 Note that local_settings.py is just a python file so the file may take advantage
 of python features.
diff --git a/linux-ramdump-parser-v2/debug_image_v2.py b/linux-ramdump-parser-v2/debug_image_v2.py
index 04d72c2c7c10063450613b5a69fce4b12ad43cc6..78fcae803b6f5645f05d07d2e59097fff71d1970 100644
--- a/linux-ramdump-parser-v2/debug_image_v2.py
+++ b/linux-ramdump-parser-v2/debug_image_v2.py
@@ -10,6 +10,12 @@
 # GNU General Public License for more details.
 
 import struct
+import linux_list as llist
+import re
+import shutil
+import os
+import platform
+import subprocess
 
 from print_out import print_out_str
 from qdss import QDSSDump
@@ -85,6 +91,130 @@ class DebugImage_v2():
         else:
             setattr(self.qdss, qdss_tag_to_field_name[client_name], start)
 
+    def ftrace_field_func(self, common_list, ram_dump):
+        name_offset = ram_dump.field_offset('struct ftrace_event_field', 'name')
+        type_offset = ram_dump.field_offset('struct ftrace_event_field', 'type')
+        filter_type_offset = ram_dump.field_offset('struct ftrace_event_field', 'filter_type')
+        field_offset = ram_dump.field_offset('struct ftrace_event_field', 'offset')
+        size_offset = ram_dump.field_offset('struct ftrace_event_field', 'size')
+        signed_offset = ram_dump.field_offset('struct ftrace_event_field', 'is_signed')
+
+        name = ram_dump.read_word(common_list + name_offset)
+        field_name = ram_dump.read_cstring(name, 256)
+        type_name = ram_dump.read_word(common_list + type_offset)
+        type_str = ram_dump.read_cstring(type_name, 256)
+        offset = ram_dump.read_u32(common_list + field_offset)
+        size = ram_dump.read_u32(common_list + size_offset)
+        signed = ram_dump.read_u32(common_list + signed_offset)
+
+        if re.match('(.*)\[(.*)', type_str) and not(re.match('__data_loc', type_str)):
+            s = re.split('\[', type_str)
+            s[1] = '[' + s[1]
+            self.formats_out.write("\tfield:{0} {1}{2};\toffset:{3};\tsize:{4};\tsigned:{5};\n".format(s[0], field_name, s[1], offset, size, signed))
+        else:
+            self.formats_out.write("\tfield:{0} {1};\toffset:{2};\tsize:{3};\tsigned:{4};\n".format(type_str, field_name, offset, size, signed))
+
+    def ftrace_events_func(self, ftrace_list, ram_dump):
+        name_offset = ram_dump.field_offset('struct ftrace_event_call', 'name')
+        event_offset = ram_dump.field_offset('struct ftrace_event_call', 'event')
+        fmt_offset = ram_dump.field_offset('struct ftrace_event_call', 'print_fmt')
+        class_offset = ram_dump.field_offset('struct ftrace_event_call', 'class')
+        type_offset = ram_dump.field_offset('struct trace_event', 'type')
+        fields_offset = ram_dump.field_offset('struct ftrace_event_class', 'fields')
+        common_field_list = ram_dump.addr_lookup('ftrace_common_fields')
+        field_next_offset = ram_dump.field_offset('struct ftrace_event_field', 'link')
+
+        name = ram_dump.read_word(ftrace_list + name_offset)
+        name_str = ram_dump.read_cstring(name, 512)
+        event_id = ram_dump.read_word(ftrace_list + event_offset + type_offset)
+        fmt = ram_dump.read_word(ftrace_list + fmt_offset)
+        fmt_str = ram_dump.read_cstring(fmt, 2048)
+
+        self.formats_out.write("name: {0}\n".format(name_str))
+        self.formats_out.write("ID: {0}\n".format(event_id))
+        self.formats_out.write("format:\n")
+
+        list_walker = llist.ListWalker(ram_dump, common_field_list, field_next_offset)
+        list_walker.walk_prev(common_field_list, self.ftrace_field_func, ram_dump)
+        self.formats_out.write("\n")
+
+        event_class = ram_dump.read_word(ftrace_list + class_offset)
+        field_list =  event_class + fields_offset
+        list_walker = llist.ListWalker(ram_dump, field_list, field_next_offset)
+        list_walker.walk_prev(field_list, self.ftrace_field_func, ram_dump)
+        self.formats_out.write("\n")
+        self.formats_out.write("print fmt: {0}\n".format(fmt_str))
+
+    def collect_ftrace_format(self, ram_dump):
+        formats = os.path.join(self.qtf_dir, 'map_files\\formats.txt')
+        formats_out = ram_dump.open_file(formats)
+        self.formats_out = formats_out
+
+        ftrace_events_list = ram_dump.addr_lookup('ftrace_events')
+        next_offset = ram_dump.field_offset('struct ftrace_event_call', 'list')
+        list_walker = llist.ListWalker(ram_dump, ftrace_events_list, next_offset)
+        list_walker.walk_prev(ftrace_events_list, self.ftrace_events_func, ram_dump)
+
+        self.formats_out.close
+
+    def parse_qtf(self, ram_dump):
+        if platform.system() != 'Windows':
+            return
+
+        qtf_path = ram_dump.qtf_path
+        if qtf_path is None:
+            try:
+                import local_settings
+                try:
+                    qtf_path = local_settings.qtf_path
+                except AttributeError as e:
+                    print_out_str("attribute qtf_path in local_settings.py looks bogus. Please see README.txt")
+                    print_out_str("Full message: %s" % e.message)
+                    return
+            except ImportError:
+                print_out_str("missing qtf_path local_settings.")
+                print_out_str("Please see the README for instructions on setting up local_settings.py")
+                return
+
+        if qtf_path is None:
+            print_out_str("!!! Incorrect path for qtf specified.")
+            print_out_str("!!! Please see the README for instructions on setting up local_settings.py")
+            return
+
+        if not os.path.exists(qtf_path):
+            print_out_str("!!! qtf_path {0} does not exist! Check your settings!".format(qtf_path))
+            return
+
+        if not os.access(qtf_path, os.X_OK):
+            print_out_str("!!! No execute permissions on qtf path {0}".format(qtf_path))
+            return
+
+        if os.path.getsize('tmc-etf.bin') > 0:
+            trace_file = 'tmc-etf.bin'
+        elif os.path.getsize('tmc-etr.bin') > 0:
+            trace_file = 'tmc-etr.bin'
+        else:
+            return
+
+        port = 12345
+        qtf_dir = 'qtf'
+        workspace = os.path.join(qtf_dir, 'qtf.workspace')
+        qtf_out = 'qtf.txt'
+        chipset = 'msm' + str(ram_dump.hw_id)
+        hlos = 'LA'
+
+        p = subprocess.Popen([qtf_path, '-s', '{0}'.format(port)])
+        subprocess.call('{0} -c {1} new workspace {2} {3} {4}'.format(qtf_path, port, qtf_dir, chipset, hlos))
+
+        self.qtf_dir = qtf_dir
+        self.collect_ftrace_format(ram_dump)
+
+        subprocess.call('{0} -c {1} open workspace {2}'.format(qtf_path, port, workspace))
+        subprocess.call('{0} -c {1} open bin {2}'.format(qtf_path, port, trace_file))
+        subprocess.call('{0} -c {1} stream trace table {2}'.format(qtf_path, port, qtf_out))
+        subprocess.call('{0} -c {1} exit'.format(qtf_path, port))
+        p.communicate('quit')
+
     def parse_dump_v2(self, ram_dump):
         self.dump_type_lookup_table = ram_dump.gdbmi.get_enum_lookup_table(
             'msm_dump_type', 2)
@@ -241,3 +371,5 @@ class DebugImage_v2():
                                                  client_id, ram_dump)
 
             self.qdss.dump_all(ram_dump)
+            if ram_dump.qtf:
+                self.parse_qtf(ram_dump)
diff --git a/linux-ramdump-parser-v2/linux_list.py b/linux-ramdump-parser-v2/linux_list.py
index e1637bed6268c136dbc2b1e0a38669ef605c31c0..119b351d8cde83aee286dcd5b87a371e9b3cc556 100644
--- a/linux-ramdump-parser-v2/linux_list.py
+++ b/linux-ramdump-parser-v2/linux_list.py
@@ -58,3 +58,30 @@ class ListWalker(object):
                 break
             node_addr = next_node
             self.seen_nodes.append(node_addr)
+
+    def walk_prev(self, node_addr, func, *args):
+        """Walk the linked list starting at `node_addr' previous node and traverse the list in
+        reverse order, calling `func' on each node. `func' will be passed the current node and *args,
+        if given.
+
+        """
+        node_addr = self.ram_dump.read_word(node_addr + self.ram_dump.field_offset('struct list_head', 'prev'))
+        while True:
+            if node_addr == 0:
+                break
+
+            funcargs = [node_addr - self.list_elem_offset] + list(args)
+            func(*funcargs)
+
+            prev_node_addr = node_addr + self.ram_dump.field_offset('struct list_head', 'prev')
+            prev_node = self.ram_dump.read_word(prev_node_addr)
+
+            if prev_node == self.last_node:
+                break
+
+            if prev_node in self.seen_nodes:
+                print_out_str(
+                   '[!] WARNING: Cycle found in attach list. List is corrupted!')
+                break
+            node_addr = prev_node
+            self.seen_nodes.append(node_addr)
diff --git a/linux-ramdump-parser-v2/ramdump.py b/linux-ramdump-parser-v2/ramdump.py
index 9eec2f8f5515f7108f1f77628b7f7612df746a66..0ba4e820b610b9ee7dcd72c02cbc8ec89ea1caca 100755
--- a/linux-ramdump-parser-v2/ramdump.py
+++ b/linux-ramdump-parser-v2/ramdump.py
@@ -436,7 +436,7 @@ class RamDump():
                 if urc < 0:
                     break
 
-    def __init__(self, vmlinux_path, nm_path, gdb_path, objdump_path, ebi, file_path, phys_offset, outdir, hw_id=None, hw_version=None, arm64=False, page_offset=None):
+    def __init__(self, vmlinux_path, nm_path, gdb_path, objdump_path, ebi, file_path, phys_offset, outdir, qtf_path, hw_id=None, hw_version=None, arm64=False, page_offset=None, qtf=False):
         self.ebi_files = []
         self.phys_offset = None
         self.tz_start = 0
@@ -456,6 +456,8 @@ class RamDump():
         self.arm64 = arm64
         self.page_offset = 0xc0000000
         self.thread_size = 8192
+        self.qtf_path = qtf_path
+        self.qtf = qtf
         if ebi is not None:
             # TODO sanity check to make sure the memory regions don't overlap
             for file_path, start, end in ebi:
diff --git a/linux-ramdump-parser-v2/ramparse.py b/linux-ramdump-parser-v2/ramparse.py
index 5c221d521c18e17c5b47eda276a0e798db597f03..9920ee95eae1e6bac378cda8594c94f770966084 100755
--- a/linux-ramdump-parser-v2/ramparse.py
+++ b/linux-ramdump-parser-v2/ramparse.py
@@ -122,6 +122,10 @@ if __name__ == '__main__':
                       help='Run an interactive python interpreter with the ramdump loaded')
     parser.add_option('', '--classic-shell', action='store_true',
                       help='Like --shell, but forces the use of the classic python shell, even if ipython is installed')
+    parser.add_option('', '--qtf', action='store_true', dest='qtf',
+                      help='Use QTF tool to parse and save QDSS trace data')
+    parser.add_option('', '--qtf-path', dest='qtf_path',
+                      help='QTF tool executable')
 
     for p in parser_util.get_parsers():
         parser.add_option(p.shortopt or '',
@@ -263,11 +267,14 @@ if __name__ == '__main__':
         print_out_str("!!! If this tool is being run from a shared location, contact the maintainer")
         sys.exit(1)
 
+    if options.everything:
+        options.qtf = True
+
     dump = RamDump(options.vmlinux, nm_path, gdb_path, objdump_path, options.ram_addr,
-                   options.autodump, options.phys_offset, options.outdir,
+                   options.autodump, options.phys_offset, options.outdir, options.qtf_path,
                    options.force_hardware, options.force_hardware_version,
                    arm64=options.arm64,
-                   page_offset=options.page_offset)
+                   page_offset=options.page_offset, qtf=options.qtf)
 
     if options.shell or options.classic_shell:
         print("Entering interactive shell mode.")