]> sigrok.org Git - sigrok-util.git/blob - debug/saleae-logic16/saleae-logic16-dissector.lua
Add a Wireshark dissector for the Saleae Logic16 protocol.
[sigrok-util.git] / debug / saleae-logic16 / saleae-logic16-dissector.lua
1 -- Logic16 protocol dissector for Wireshark
2 --
3 -- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de>
4 --
5 -- based on the LWLA dissector, which is
6 --   Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
7 --
8 -- This program is free software; you can redistribute it and/or modify
9 -- it under the terms of the GNU General Public License as published by
10 -- the Free Software Foundation; either version 3 of the License, or
11 -- (at your option) any later version.
12 --
13 -- This program is distributed in the hope that it will be useful,
14 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
15 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 -- GNU General Public License for more details.
17 --
18 -- You should have received a copy of the GNU General Public License
19 -- along with this program; if not, see <http://www.gnu.org/licenses/>.
20
21 -- Usage: wireshark -X lua_script:saleae-logic16-dissector.lua
22 --
23 -- Create custom protocol for the Saleae Logic16 analyzer.
24 p_logic16 = Proto("Logic16", "Saleae Logic16 USB Protocol")
25
26 -- Known IDs of Logic16 control commands.
27 local control_commands = {
28     [0x01] = "START_ACQUISITION",
29     [0x02] = "ABORT_ACQUISITION_ASYNC",
30     [0x06] = "WRITE_EEPROM",
31     [0x07] = "READ_EEPROM",
32     [0x7a] = "WRITE_LED_TABLE",
33     [0x7b] = "SET_LED_MODE",
34     [0x7c] = "RETURN_TO_BOOTLOADER",
35     [0x7d] = "ABORT_ACQUISITION_SYNC",
36     [0x7e] = "FPGA_UPLOAD_INIT",
37     [0x7f] = "FPGA_UPLOAD_DATA",
38     [0x80] = "FPGA_WRITE_REGISTER",
39     [0x81] = "FPGA_READ_REGISTER",
40     [0x82] = "GET_REVID"
41 }
42
43 -- Known Logic16 FPGA registers
44 local fpga_registers = {
45     [0x00] = "Bitstream version",
46     [0x01] = "Status and control",
47     [0x02] = "Channel select low",
48     [0x03] = "Channel select high",
49     [0x04] = "Sample rate divisor",
50     [0x05] = "LED brightness",
51     [0x06] = "??? [0x06]",
52     [0x07] = "??? [0x07]",
53     [0x08] = "??? [0x08]",
54     [0x09] = "??? [0x09]",
55     [0x0a] = "Base clock",
56     [0x0c] = "??? [0x0c]",
57 }
58
59 -- Create the fields exhibited by the protocol.
60 p_logic16.fields.command  = ProtoField.uint8("logic16.cmd", "Command ID", base.HEX_DEC, control_commands)
61 p_logic16.fields.regaddr  = ProtoField.uint8("logic16.regaddr", "Register Address", base.HEX, fpga_registers)
62 p_logic16.fields.regdata  = ProtoField.uint8("logic16.regdata", "Register Value", base.HEX_DEC)
63 p_logic16.fields.regcount = ProtoField.uint8("logic16.regcount", "Register Count", base.HEX_DEC)
64 p_logic16.fields.EE_len   = ProtoField.uint8("logic16.eeprom_len", "Read len", base.HEX_DEC)
65 p_logic16.fields.unknown  = ProtoField.bytes("logic16.unknown", "Unidentified message data")
66 p_logic16.fields.reg_addrs  = ProtoField.bytes("logic16.reg_addrs", "Register addresses")
67 p_logic16.fields.reg_av_pairs  = ProtoField.bytes("logic16.reg_av_pairs", "Register address/value pairs")
68
69 p_logic16.fields.rawdata   = ProtoField.bytes("logic16.rawdata", "Raw Message Data")
70 p_logic16.fields.decrypted = ProtoField.bytes("logic16.ep1_decrypted", "Decrypted message data")
71
72 -- Referenced USB URB dissector fields.
73 local f_urb_type = Field.new("usb.urb_type")
74 local f_transfer_type = Field.new("usb.transfer_type")
75 local f_endpoint = Field.new("usb.endpoint_number.endpoint")
76 local f_direction = Field.new("usb.endpoint_number.direction")
77
78 -- Decrypt EP1 message
79 local function decrypt_ep1_message(range)
80    local iv = {0x9b, 0x54}
81    local out = ByteArray.new()
82    out:set_size(range:len())
83
84    for i = 0, range:len() - 1, 1 do
85       local s = range(i,1):uint()
86       local dec = bit32.bxor(
87                       (bit32.bxor((s +   0x45), 0x38) + 0xb0)
88                     , 0x5a, iv[1])
89             dec = bit32.bxor(
90                       (bit32.bxor((dec + 0x39), 0x35) + 0x05)
91                     , 0x2b, iv[2])
92       local b = bit32.band(dec, 0xff)
93       out:set_index(i, b)
94       iv[1] = b
95       iv[2] = s
96    end
97
98    return ByteArray.tvb(out, "Decrypted")
99 end
100
101 -- Dissect control command messages.
102 local function dissect_response(range, pinfo, tree)
103     pinfo.cols.info = string.format("<- response: %s",
104                                     tostring(range))
105     local item = tree:add(p_logic16.fields.unknown, range())
106     item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data")
107     return range:len()
108 end
109
110 -- Dissect control command messages.
111 local function dissect_command(range, pinfo, tree)
112
113     local command = range(0,1):uint()
114     tree:add(p_logic16.fields.command, range(0,1))
115
116     if command == 1 then -- start acquisition
117         pinfo.cols.info = string.format("-> [%d]: START acquisition",
118                                         command)
119     elseif command == 2 then -- ABORT_ACQUISITION_ASYNC
120         pinfo.cols.info = string.format("-> [%d]: ABORT acquisition",
121                                         command)
122     elseif command == 6 then -- WRITE_EEPROM
123     elseif command == 7 then -- READ_EEPROM
124         if range:len() == 5 then
125             local regaddr = range(3,1):uint()
126             local len     = range(4,1):uint()
127             tree:add(p_logic16.fields.regaddr, range(3,1))
128             tree:add(p_logic16.fields.EE_len,  range(4,1))
129             pinfo.cols.info = string.format("-> [%d]: read EEPROM 0x%02X len=%d",
130                                             command, regaddr, len)
131             return 3
132         end
133     elseif command == 0x7a then -- WRITE_LED_TABLE
134         local offset = range(1,1):uint()
135         local len    = range(2,1):uint()
136         pinfo.cols.info = string.format("-> [%d]: write LED table offset=%d len=%d",
137                                         command, offset, len)
138
139     elseif command == 0x7b then -- SET_LED_MODE
140         pinfo.cols.info = string.format("-> [%d]: set LED mode flashing=%s",
141                                         command, range(1,1):uint() and "on" or "off")
142
143     elseif command == 0x7c then -- RETURN_TO_BOOTLOADER
144     elseif command == 0x7d then -- ABORT_ACQUISITION_SYNC
145         pinfo.cols.info = string.format("[%d]: ABORT acquisition SYNC",
146                                         command)
147     elseif command == 0x7e then -- FPGA_UPLOAD_INIT
148     elseif command == 0x7f then -- FPGA_UPLOAD_DATA
149     elseif command == 0x80 then -- FPGA_WRITE_REGISTER
150         tree:add(p_logic16.fields.regcount, range(1,1))
151         if range:len() == 4 then
152             local regaddr = range(2,1):uint()
153             local regval  = range(3,1):uint()
154             tree:add(p_logic16.fields.regaddr,  range(2,1))
155             tree:add(p_logic16.fields.regdata,  range(3,1))
156             pinfo.cols.info = string.format("-> [%d]: FPGA write reg 0x%02X [%s] value 0x%02X",
157                                             command, regaddr, fpga_registers[regaddr], regval)
158             return 4
159         else
160             local n_regs = range(1,1):uint()
161             pinfo.cols.info = string.format("-> [%d]: FPGA write reg (%d x)",
162                                             command, n_regs)
163             tree:add(p_logic16.fields.reg_av_pairs, range(2))
164             return range:len()
165         end
166
167     elseif command == 0x81 then -- FPGA_READ_REGISTER
168         tree:add(p_logic16.fields.regcount, range(1,1))
169         if range:len() == 3 then
170             local regaddr = range(2,1):uint()
171             tree:add(p_logic16.fields.regaddr,  range(2,1))
172             pinfo.cols.info = string.format("-> [%d]: FPGA read reg 0x%02X [%s]",
173                                             command, regaddr, fpga_registers[regaddr])
174             return 3
175         else
176             local n_regs = range(1,1):uint()
177             pinfo.cols.info = string.format("-> [%d]: FPGA read reg (%d x)",
178                                             command, n_regs)
179             tree:add(p_logic16.fields.reg_addrs, range(2))
180             return range:len()
181         end
182
183     elseif command == 0x82 then -- GET_REVID"
184     end
185     return range:len()
186 end
187
188 -- Main dissector function.
189 function p_logic16.dissector(tvb, pinfo, tree)
190     local transfer_type = tonumber(tostring(f_transfer_type()))
191
192     -- Bulk transfers only.
193     if transfer_type == 3 then
194         local urb_type = tonumber(tostring(f_urb_type()))
195         local endpoint = tonumber(tostring(f_endpoint()))
196         local direction = tonumber(tostring(f_direction()))
197
198         -- Payload-carrying packets only.
199         if (urb_type == 83 and endpoint == 1)   -- 'S' - Submit
200             or (urb_type == 67 and endpoint == 1) -- 'C' - Complete
201         then
202             pinfo.cols.protocol = p_logic16.name
203
204             local subtree = tree:add(p_logic16, tvb(), "Logic16")
205             subtree:add(p_logic16.fields.rawdata, tvb())
206
207             local dec = decrypt_ep1_message(tvb)
208
209             local dectree = subtree:add(p_logic16.fields.decrypted, dec())
210             dectree:set_generated()
211
212             -- Dispatch to message-specific dissection handler.
213             if (direction == 0) then
214                 return dissect_command(dec, pinfo, dectree)
215             else
216                 return dissect_response(dec, pinfo, dectree)
217             end
218         end
219     end
220     return 0
221 end
222
223 -- Register Logic16 protocol dissector during initialization.
224 function p_logic16.init()
225     local usb_product_dissectors = DissectorTable.get("usb.product")
226
227     -- Dissection by vendor+product ID requires that Wireshark can get the
228     -- the device descriptor.  Making a USB device available inside a VM
229     -- will make it inaccessible from Linux, so Wireshark cannot fetch the
230     -- descriptor by itself.  However, it is sufficient if the guest requests
231     -- the descriptor once while Wireshark is capturing.
232     usb_product_dissectors:add(0x21a91001, p_logic16)
233
234     -- Addendum: Protocol registration based on product ID does not always
235     -- work as desired.  Register the protocol on the interface class instead.
236     -- The downside is that it would be a bad idea to put this into the global
237     -- configuration, so one has to make do with -X lua_script: for now.
238     -- local usb_bulk_dissectors = DissectorTable.get("usb.bulk")
239
240     -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes
241     -- 0xFFFF.  Register both to make it work all the time.
242     -- usb_bulk_dissectors:add(0xFF, p_logic16)
243     -- usb_bulk_dissectors:add(0xFFFF, p_logic16)
244 end