From: Stefan Bruens Date: Mon, 28 Sep 2015 12:15:45 +0000 (+0200) Subject: Add a Wireshark dissector for the Saleae Logic16 protocol. X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=a2491957ff4cb1eff3ba24608125cc79b90d326c;p=sigrok-util.git Add a Wireshark dissector for the Saleae Logic16 protocol. --- diff --git a/debug/saleae-logic16/saleae-logic16-dissector.lua b/debug/saleae-logic16/saleae-logic16-dissector.lua new file mode 100644 index 0000000..916862e --- /dev/null +++ b/debug/saleae-logic16/saleae-logic16-dissector.lua @@ -0,0 +1,244 @@ +-- Logic16 protocol dissector for Wireshark +-- +-- Copyright (C) 2015 Stefan Bruens +-- +-- based on the LWLA dissector, which is +-- Copyright (C) 2014 Daniel Elstner +-- +-- 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 . + +-- 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