]> sigrok.org Git - sigrok-util.git/commitdiff
Add a Wireshark dissector for the Saleae Logic16 protocol.
authorStefan Bruens <redacted>
Mon, 28 Sep 2015 12:15:45 +0000 (14:15 +0200)
committerUwe Hermann <redacted>
Mon, 28 Sep 2015 12:16:41 +0000 (14:16 +0200)
debug/saleae-logic16/saleae-logic16-dissector.lua [new file with mode: 0644]

diff --git a/debug/saleae-logic16/saleae-logic16-dissector.lua b/debug/saleae-logic16/saleae-logic16-dissector.lua
new file mode 100644 (file)
index 0000000..916862e
--- /dev/null
@@ -0,0 +1,244 @@
+-- Logic16 protocol dissector for Wireshark
+--
+-- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de>
+--
+-- based on the LWLA dissector, which is
+--   Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
+--
+-- 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/>.
+
+-- Usage: wireshark -X lua_script:saleae-logic16-dissector.lua
+--
+-- Create custom protocol for the Saleae Logic16 analyzer.
+p_logic16 = Proto("Logic16", "Saleae Logic16 USB Protocol")
+
+-- Known IDs of Logic16 control commands.
+local control_commands = {
+    [0x01] = "START_ACQUISITION",
+    [0x02] = "ABORT_ACQUISITION_ASYNC",
+    [0x06] = "WRITE_EEPROM",
+    [0x07] = "READ_EEPROM",
+    [0x7a] = "WRITE_LED_TABLE",
+    [0x7b] = "SET_LED_MODE",
+    [0x7c] = "RETURN_TO_BOOTLOADER",
+    [0x7d] = "ABORT_ACQUISITION_SYNC",
+    [0x7e] = "FPGA_UPLOAD_INIT",
+    [0x7f] = "FPGA_UPLOAD_DATA",
+    [0x80] = "FPGA_WRITE_REGISTER",
+    [0x81] = "FPGA_READ_REGISTER",
+    [0x82] = "GET_REVID"
+}
+
+-- Known Logic16 FPGA registers
+local fpga_registers = {
+    [0x00] = "Bitstream version",
+    [0x01] = "Status and control",
+    [0x02] = "Channel select low",
+    [0x03] = "Channel select high",
+    [0x04] = "Sample rate divisor",
+    [0x05] = "LED brightness",
+    [0x06] = "??? [0x06]",
+    [0x07] = "??? [0x07]",
+    [0x08] = "??? [0x08]",
+    [0x09] = "??? [0x09]",
+    [0x0a] = "Base clock",
+    [0x0c] = "??? [0x0c]",
+}
+
+-- Create the fields exhibited by the protocol.
+p_logic16.fields.command  = ProtoField.uint8("logic16.cmd", "Command ID", base.HEX_DEC, control_commands)
+p_logic16.fields.regaddr  = ProtoField.uint8("logic16.regaddr", "Register Address", base.HEX, fpga_registers)
+p_logic16.fields.regdata  = ProtoField.uint8("logic16.regdata", "Register Value", base.HEX_DEC)
+p_logic16.fields.regcount = ProtoField.uint8("logic16.regcount", "Register Count", base.HEX_DEC)
+p_logic16.fields.EE_len   = ProtoField.uint8("logic16.eeprom_len", "Read len", base.HEX_DEC)
+p_logic16.fields.unknown  = ProtoField.bytes("logic16.unknown", "Unidentified message data")
+p_logic16.fields.reg_addrs  = ProtoField.bytes("logic16.reg_addrs", "Register addresses")
+p_logic16.fields.reg_av_pairs  = ProtoField.bytes("logic16.reg_av_pairs", "Register address/value pairs")
+
+p_logic16.fields.rawdata   = ProtoField.bytes("logic16.rawdata", "Raw Message Data")
+p_logic16.fields.decrypted = ProtoField.bytes("logic16.ep1_decrypted", "Decrypted message data")
+
+-- Referenced USB URB dissector fields.
+local f_urb_type = Field.new("usb.urb_type")
+local f_transfer_type = Field.new("usb.transfer_type")
+local f_endpoint = Field.new("usb.endpoint_number.endpoint")
+local f_direction = Field.new("usb.endpoint_number.direction")
+
+-- Decrypt EP1 message
+local function decrypt_ep1_message(range)
+   local iv = {0x9b, 0x54}
+   local out = ByteArray.new()
+   out:set_size(range:len())
+
+   for i = 0, range:len() - 1, 1 do
+      local s = range(i,1):uint()
+      local dec = bit32.bxor(
+                      (bit32.bxor((s +   0x45), 0x38) + 0xb0)
+                    , 0x5a, iv[1])
+            dec = bit32.bxor(
+                      (bit32.bxor((dec + 0x39), 0x35) + 0x05)
+                    , 0x2b, iv[2])
+      local b = bit32.band(dec, 0xff)
+      out:set_index(i, b)
+      iv[1] = b
+      iv[2] = s
+   end
+
+   return ByteArray.tvb(out, "Decrypted")
+end
+
+-- Dissect control command messages.
+local function dissect_response(range, pinfo, tree)
+    pinfo.cols.info = string.format("<- response: %s",
+                                    tostring(range))
+    local item = tree:add(p_logic16.fields.unknown, range())
+    item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data")
+    return range:len()
+end
+
+-- Dissect control command messages.
+local function dissect_command(range, pinfo, tree)
+
+    local command = range(0,1):uint()
+    tree:add(p_logic16.fields.command, range(0,1))
+
+    if command == 1 then -- start acquisition
+        pinfo.cols.info = string.format("-> [%d]: START acquisition",
+                                        command)
+    elseif command == 2 then -- ABORT_ACQUISITION_ASYNC
+        pinfo.cols.info = string.format("-> [%d]: ABORT acquisition",
+                                        command)
+    elseif command == 6 then -- WRITE_EEPROM
+    elseif command == 7 then -- READ_EEPROM
+        if range:len() == 5 then
+            local regaddr = range(3,1):uint()
+            local len     = range(4,1):uint()
+            tree:add(p_logic16.fields.regaddr, range(3,1))
+            tree:add(p_logic16.fields.EE_len,  range(4,1))
+            pinfo.cols.info = string.format("-> [%d]: read EEPROM 0x%02X len=%d",
+                                            command, regaddr, len)
+            return 3
+        end
+    elseif command == 0x7a then -- WRITE_LED_TABLE
+        local offset = range(1,1):uint()
+        local len    = range(2,1):uint()
+        pinfo.cols.info = string.format("-> [%d]: write LED table offset=%d len=%d",
+                                        command, offset, len)
+
+    elseif command == 0x7b then -- SET_LED_MODE
+        pinfo.cols.info = string.format("-> [%d]: set LED mode flashing=%s",
+                                        command, range(1,1):uint() and "on" or "off")
+
+    elseif command == 0x7c then -- RETURN_TO_BOOTLOADER
+    elseif command == 0x7d then -- ABORT_ACQUISITION_SYNC
+        pinfo.cols.info = string.format("[%d]: ABORT acquisition SYNC",
+                                        command)
+    elseif command == 0x7e then -- FPGA_UPLOAD_INIT
+    elseif command == 0x7f then -- FPGA_UPLOAD_DATA
+    elseif command == 0x80 then -- FPGA_WRITE_REGISTER
+        tree:add(p_logic16.fields.regcount, range(1,1))
+        if range:len() == 4 then
+            local regaddr = range(2,1):uint()
+            local regval  = range(3,1):uint()
+            tree:add(p_logic16.fields.regaddr,  range(2,1))
+            tree:add(p_logic16.fields.regdata,  range(3,1))
+            pinfo.cols.info = string.format("-> [%d]: FPGA write reg 0x%02X [%s] value 0x%02X",
+                                            command, regaddr, fpga_registers[regaddr], regval)
+            return 4
+        else
+            local n_regs = range(1,1):uint()
+            pinfo.cols.info = string.format("-> [%d]: FPGA write reg (%d x)",
+                                            command, n_regs)
+            tree:add(p_logic16.fields.reg_av_pairs, range(2))
+            return range:len()
+        end
+
+    elseif command == 0x81 then -- FPGA_READ_REGISTER
+        tree:add(p_logic16.fields.regcount, range(1,1))
+        if range:len() == 3 then
+            local regaddr = range(2,1):uint()
+            tree:add(p_logic16.fields.regaddr,  range(2,1))
+            pinfo.cols.info = string.format("-> [%d]: FPGA read reg 0x%02X [%s]",
+                                            command, regaddr, fpga_registers[regaddr])
+            return 3
+        else
+            local n_regs = range(1,1):uint()
+            pinfo.cols.info = string.format("-> [%d]: FPGA read reg (%d x)",
+                                            command, n_regs)
+            tree:add(p_logic16.fields.reg_addrs, range(2))
+            return range:len()
+        end
+
+    elseif command == 0x82 then -- GET_REVID"
+    end
+    return range:len()
+end
+
+-- Main dissector function.
+function p_logic16.dissector(tvb, pinfo, tree)
+    local transfer_type = tonumber(tostring(f_transfer_type()))
+
+    -- Bulk transfers only.
+    if transfer_type == 3 then
+        local urb_type = tonumber(tostring(f_urb_type()))
+        local endpoint = tonumber(tostring(f_endpoint()))
+        local direction = tonumber(tostring(f_direction()))
+
+        -- Payload-carrying packets only.
+        if (urb_type == 83 and endpoint == 1)   -- 'S' - Submit
+            or (urb_type == 67 and endpoint == 1) -- 'C' - Complete
+        then
+            pinfo.cols.protocol = p_logic16.name
+
+            local subtree = tree:add(p_logic16, tvb(), "Logic16")
+            subtree:add(p_logic16.fields.rawdata, tvb())
+
+            local dec = decrypt_ep1_message(tvb)
+
+            local dectree = subtree:add(p_logic16.fields.decrypted, dec())
+            dectree:set_generated()
+
+            -- Dispatch to message-specific dissection handler.
+            if (direction == 0) then
+                return dissect_command(dec, pinfo, dectree)
+            else
+                return dissect_response(dec, pinfo, dectree)
+            end
+        end
+    end
+    return 0
+end
+
+-- Register Logic16 protocol dissector during initialization.
+function p_logic16.init()
+    local usb_product_dissectors = DissectorTable.get("usb.product")
+
+    -- Dissection by vendor+product ID requires that Wireshark can get the
+    -- the device descriptor.  Making a USB device available inside a VM
+    -- will make it inaccessible from Linux, so Wireshark cannot fetch the
+    -- descriptor by itself.  However, it is sufficient if the guest requests
+    -- the descriptor once while Wireshark is capturing.
+    usb_product_dissectors:add(0x21a91001, p_logic16)
+
+    -- Addendum: Protocol registration based on product ID does not always
+    -- work as desired.  Register the protocol on the interface class instead.
+    -- The downside is that it would be a bad idea to put this into the global
+    -- configuration, so one has to make do with -X lua_script: for now.
+    -- local usb_bulk_dissectors = DissectorTable.get("usb.bulk")
+
+    -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes
+    -- 0xFFFF.  Register both to make it work all the time.
+    -- usb_bulk_dissectors:add(0xFF, p_logic16)
+    -- usb_bulk_dissectors:add(0xFFFF, p_logic16)
+end