1 -- SmartScope protocol dissector for Wireshark
3 -- Copyright (C) 2015 Marcus Comstedt <marcus@mc.pp.se>
5 -- based on the Logic16 dissector, which is
6 -- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de>
7 -- based on the LWLA dissector, which is
8 -- Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
10 -- This program is free software; you can redistribute it and/or modify
11 -- it under the terms of the GNU General Public License as published by
12 -- the Free Software Foundation; either version 3 of the License, or
13 -- (at your option) any later version.
15 -- This program is distributed in the hope that it will be useful,
16 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
17 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 -- GNU General Public License for more details.
20 -- You should have received a copy of the GNU General Public License
21 -- along with this program; if not, see <http://www.gnu.org/licenses/>.
23 -- Usage: wireshark -X lua_script:labnation-smartscope-dissector.lua
25 -- Create custom protocol for the LabNation SmartScope analyzer.
26 p_smartscope = Proto("SmartScope", "LabNation SmartScope USB Protocol")
28 -- Referenced USB URB dissector fields.
29 local f_urb_type = Field.new("usb.urb_type")
30 local f_transfer_type = Field.new("usb.transfer_type")
31 local f_endpoint = Field.new("usb.endpoint_number.endpoint")
32 local f_direction = Field.new("usb.endpoint_number.direction")
37 [0xAD] = "Answer Dude",
42 [0x01] = "PIC_VERSION",
46 [0x05] = "PIC_BOOTLOADER",
47 [0x06] = "EEPROM_READ",
48 [0x07] = "EEPROM_WRITE",
49 [0x08] = "FLASH_ROM_READ",
50 [0x09] = "FLASH_ROM_WRITE",
53 [0x0c] = "PROGRAM_FPGA_START",
54 [0x0d] = "PROGRAM_FPGA_END",
55 [0x0e] = "I2C_WRITE_START",
56 [0x0f] = "I2C_WRITE_BULK",
57 [0x10] = "I2C_WRITE_STOP",
61 local pic_addresses = {
62 [0x00] = "FORCE_STREAMING",
65 local i2c_addresses = {
71 local settings_addresses = {
72 [0] = "STROBE_UPDATE",
74 [2] = "SPI_WRITE_VALUE",
75 [3] = "DIVIDER_MULTIPLIER",
76 [4] = "CHA_YOFFSET_VOLTAGE",
77 [5] = "CHB_YOFFSET_VOLTAGE",
79 [7] = "TRIGGER_LEVEL",
80 [8] = "TRIGGER_THRESHOLD",
82 [10] = "TRIGGER_WIDTH",
83 [11] = "INPUT_DECIMATION",
84 [12] = "ACQUISITION_DEPTH",
85 [13] = "TRIGGERHOLDOFF_B0",
86 [14] = "TRIGGERHOLDOFF_B1",
87 [15] = "TRIGGERHOLDOFF_B2",
88 [16] = "TRIGGERHOLDOFF_B3",
89 [17] = "VIEW_DECIMATION",
90 [18] = "VIEW_OFFSET_B0",
91 [19] = "VIEW_OFFSET_B1",
92 [20] = "VIEW_OFFSET_B2",
93 [21] = "VIEW_ACQUISITIONS",
95 [23] = "VIEW_EXCESS_B0",
96 [24] = "VIEW_EXCESS_B1",
97 [25] = "DIGITAL_TRIGGER_RISING",
98 [26] = "DIGITAL_TRIGGER_FALLING",
99 [27] = "DIGITAL_TRIGGER_HIGH",
100 [28] = "DIGITAL_TRIGGER_LOW",
101 [29] = "DIGITAL_OUT",
103 [31] = "AWG_DECIMATION",
104 [32] = "AWG_SAMPLES_B0",
105 [33] = "AWG_SAMPLES_B1",
108 local rom_addresses = {
115 [6] = "SPI_RECEIVED_VALUE",
119 local strobe_numbers = {
120 [0] = "GLOBAL_RESET",
121 [1] = "INIT_SPI_TRANSFER",
124 [4] = "SCOPE_ENABLE",
125 [5] = "SCOPE_UPDATE",
126 [6] = "FORCE_TRIGGER",
128 [8] = "VIEW_SEND_OVERVIEW",
129 [9] = "VIEW_SEND_PARTIAL",
132 [12] = "CHA_DCCOUPLING",
133 [13] = "CHB_DCCOUPLING",
135 [15] = "OVERFLOW_DETECT",
147 -- Create the fields exhibited by the protocol.
148 p_smartscope.fields.header = ProtoField.uint8("smartscope.header", "Header", base.HEX_DEC, headers)
149 p_smartscope.fields.command = ProtoField.uint8("smartscope.cmd", "Command ID", base.HEX_DEC, commands)
150 p_smartscope.fields.pic_version = ProtoField.string("smartscope.pic_version", "PIC version")
151 p_smartscope.fields.pic_address = ProtoField.uint8("smartscope.pic_address", "PIC address", base.HEX, pic_addresses)
152 p_smartscope.fields.pic_length = ProtoField.uint8("smartscope.pic_length", "PIC length", base.HEX_DEC)
153 p_smartscope.fields.pic_data = ProtoField.bytes("smartscope.pic_data", "PIC data")
154 p_smartscope.fields.eeprom_address = ProtoField.uint8("smartscope.eeprom_address", "EEPROM address", base.HEX)
155 p_smartscope.fields.eeprom_length = ProtoField.uint8("smartscope.eeprom_length", "EEPROM length", base.HEX_DEC)
156 p_smartscope.fields.eeprom_data = ProtoField.bytes("smartscope.eeprom_data", "EEPROM data")
157 p_smartscope.fields.flash_rom_address = ProtoField.uint16("smartscope.flash_rom_address", "Flash ROM address", base.HEX)
158 p_smartscope.fields.flash_rom_length = ProtoField.uint8("smartscope.flash_rom_length", "Flash ROM length", base.HEX_DEC)
159 p_smartscope.fields.flash_rom_data = ProtoField.bytes("smartscope.flash_rom_data", "Flash ROM data")
160 p_smartscope.fields.i2c_write_length = ProtoField.uint8("smartscope.i2c_write_length", "I2C write length")
161 p_smartscope.fields.i2c_write_rawdata = ProtoField.bytes("smartscope.i2c_write_rawdata", "Raw I2C write data")
162 p_smartscope.fields.i2c_write_slave_address = ProtoField.uint8("smartscope.i2c_write_slave_address", "I2C write slave address", base.HEX_DEC, i2c_addresses, 0xfe)
163 p_smartscope.fields.i2c_write_mode = ProtoField.bool("smartscope.i2c_write_mode", "I2C write mode", 8, {"READ", "WRITE"}, 0x01)
164 p_smartscope.fields.settings_subaddress = ProtoField.uint8("smartscope.settings_subaddress", "I2C subaddress (SETTINGS)", base.HEX, settings_addresses)
165 p_smartscope.fields.rom_subaddress = ProtoField.uint8("smartscope.rom_subaddress", "I2C subaddress (ROM)", base.HEX, rom_addresses)
166 p_smartscope.fields.awg_subaddress = ProtoField.uint8("smartscope.awg_subaddress", "I2C subaddress (AWG)", base.HEX)
167 p_smartscope.fields.strobe_number = ProtoField.uint8("smartscope.strobe_number", "Strobe number", base.DEC, strobe_numbers, 0xfe)
168 p_smartscope.fields.strobe_value = ProtoField.uint8("smartscope.strobe_value", "Strobe value", base.DEC, nil, 0x01)
169 p_smartscope.fields.i2c_write_payload = ProtoField.bytes("smartscope.i2c_write_payload", "I2C write payload")
170 p_smartscope.fields.i2c_read_slave_address = ProtoField.uint8("smartscope.i2c_read_slave_address", "I2C read slave address", base.HEX_DEC, i2c_addresses)
171 p_smartscope.fields.i2c_read_length = ProtoField.uint8("smartscope.i2c_read_length", "I2C read length")
172 p_smartscope.fields.i2c_read_payload = ProtoField.bytes("smartscope.i2c_read_payload", "I2C read payload")
173 p_smartscope.fields.fpga_packets = ProtoField.uint16("smartscope.fpga_packets", "FPGA packet count", base.HEX_DEC)
174 p_smartscope.fields.fpga_data = ProtoField.bytes("smartscope.fpga_data", "FPGA bitstream data")
175 p_smartscope.fields.rawdata = ProtoField.bytes("smartscope.rawdata", "Raw Message Data")
181 -- Dissect control command messages.
182 local function dissect_command(range, pinfo, tree, command)
183 pinfo.cols.info = string.format("-> [%d]: %s", command, commands[command] or "???")
184 if command == 2 then -- pic write
185 local addr = range(0,1):uint()
186 tree:add(p_smartscope.fields.pic_address, range(0,1))
187 tree:add(p_smartscope.fields.pic_length, range(1,1))
188 tree:add(p_smartscope.fields.pic_data, range(2,range(1,1):uint()))
189 pinfo.cols.info:append(string.format(" %s len=%d", (pic_addresses[addr] or string.format("0x%02X", addr)), range(1,1):uint()))
190 elseif command == 3 then -- pic read
191 local addr = range(0,1):uint()
192 tree:add(p_smartscope.fields.pic_address, range(0,1))
193 tree:add(p_smartscope.fields.pic_length, range(1,1))
194 pinfo.cols.info:append(string.format(" %s len=%d", (pic_addresses[addr] or string.format("0x%02X", addr)), range(1,1):uint()))
195 elseif command == 6 then -- eeprom read
196 local addr = range(0,1):uint()
197 tree:add(p_smartscope.fields.eeprom_address, range(0,1))
198 tree:add(p_smartscope.fields.eeprom_length, range(1,1))
199 pinfo.cols.info:append(string.format(" 0x%02X len=%d", addr, range(1,1):uint()))
200 elseif command == 7 then -- eeprom write
201 local addr = range(0,1):uint()
202 tree:add(p_smartscope.fields.eeprom_address, range(0,1))
203 tree:add(p_smartscope.fields.eeprom_length, range(1,1))
204 tree:add(p_smartscope.fields.eeprom_data, range(2,range(1,1):uint()))
205 pinfo.cols.info:append(string.format(" 0x%02X len=%d", addr, range(1,1):uint()))
206 elseif command == 8 then -- flash rom read
207 local addr = range(0,1):uint()+256*range(2,1):uint()
208 tree:add(p_smartscope.fields.flash_rom_address, range(0,3), addr)
209 tree:add(p_smartscope.fields.flash_rom_length, range(1,1))
210 pinfo.cols.info:append(string.format(" 0x%03X len=%d", addr, range(1,1):uint()))
211 elseif command == 9 then -- flash rom write
212 local addr = range(0,1):uint()+256*range(2,1):uint()
213 tree:add(p_smartscope.fields.flash_rom_address, range(0,3), addr)
214 tree:add(p_smartscope.fields.flash_rom_length, range(1,1))
215 tree:add(p_smartscope.fields.flash_rom_data, range(3,range(1,1):uint()))
216 pinfo.cols.info:append(string.format(" 0x%03X len=%d", addr, range(1,1):uint()))
217 elseif command == 10 or command == 14 then -- i2c write / i2c write start
218 tree:add(p_smartscope.fields.i2c_write_length, range(0,1))
219 tree:add(p_smartscope.fields.i2c_write_rawdata, range(1))
220 local len = range(0,1):uint()
222 local slave = bit.rshift(range(1,1):uint(), 1)
223 tree:add(p_smartscope.fields.i2c_write_slave_address, range(1,1))
224 tree:add(p_smartscope.fields.i2c_write_mode, range(1,1))
226 local subaddress = range(2,1):uint()
227 local payload = len > 2 and range(3)
228 if slave == 12 then -- settings
229 tree:add(p_smartscope.fields.settings_subaddress, range(2,1))
230 pinfo.cols.info:append(" SETTINGS["..(settings_addresses[subaddress] or "???").."]")
231 elseif slave == 13 then -- rom
232 tree:add(p_smartscope.fields.rom_subaddress, range(2,1))
233 pinfo.cols.info:append(" ROM["..(rom_addresses[subaddress] or "???").."]")
234 elseif slave == 14 then -- awg
235 tree:add(p_smartscope.fields.awg_subaddress, range(2,1))
236 pinfo.cols.info:append(string.format(" AWG[%d]", subaddress))
240 if payload and payload:len() == 1 and slave == 12 and subaddress == 0 then
241 local strobe = payload(0,1):uint()
242 local value = bit.band(strobe, 1)
243 strobe = bit.rshift(strobe, 1)
244 tree:add(p_smartscope.fields.strobe_number, payload(0,1))
245 tree:add(p_smartscope.fields.strobe_value, payload(0,1))
246 pinfo.cols.info:append(string.format(" STROBE[%s] = %d", (strobe_numbers[strobe] or string.format("%d", strobe)), value))
248 tree:add(p_smartscope.fields.i2c_write_payload, payload)
249 pinfo.cols.info:append(string.format(" len=%d", payload:len()))
253 elseif command == 11 then -- i2c read
254 local slave = range(0,1):uint()
255 tree:add(p_smartscope.fields.i2c_read_slave_address, range(0,1))
256 tree:add(p_smartscope.fields.i2c_read_length, range(1,1))
257 if i2c_addresses[slave] then
258 pinfo.cols.info:append(string.format(" %s", i2c_addresses[slave]))
260 pinfo.cols.info:append(string.format(" len=%d", range(1,1):uint()))
261 elseif command == 12 then -- program fpga start
262 tree:add(p_smartscope.fields.fpga_packets, range(0,2))
263 fpgaDataCount = range(0,2):uint()*32
264 elseif command == 15 then -- i2c write bulk
265 tree:add(p_smartscope.fields.i2c_write_length, range(0,1))
266 tree:add(p_smartscope.fields.i2c_write_rawdata, range(1))
267 local len = range(0,1):uint()
269 tree:add(p_smartscope.fields.i2c_write_payload, range(1,len))
270 pinfo.cols.info:append(string.format(" len=%d", len))
272 elseif command == 16 then -- i2c write stop
273 tree:add(p_smartscope.fields.i2c_write_length, range(0,1))
274 local len = range(0,1):uint()
276 pinfo.cols.info:append(string.format(" len=%d", len))
281 -- Dissect answers to control command messages.
282 local function dissect_answer(range, pinfo, tree, command)
283 pinfo.cols.info = string.format("<- [%d]: %s", command, commands[command] or "???")
284 if command == 1 then -- pic version
285 local version = string.format("%d.%d.%d", range(4,1):uint(), range(3,1):uint(), range(2,1):uint())
286 tree:add(p_smartscope.fields.pic_version, range(2,3), version)
287 pinfo.cols.info:append(' "' .. version .. '"')
288 elseif command == 3 then -- pic read
289 local addr = range(0,1):uint()
290 tree:add(p_smartscope.fields.pic_address, range(0,1))
291 tree:add(p_smartscope.fields.pic_length, range(1,1))
292 tree:add(p_smartscope.fields.pic_data, range(2,range(1,1):uint()))
293 pinfo.cols.info:append(string.format(" %s len=%d", (pic_addresses[addr] or string.format("0x%02X", addr)), range(1,1):uint()))
294 elseif command == 6 then -- eeprom read
295 local addr = range(0,1):uint()
296 tree:add(p_smartscope.fields.eeprom_address, range(0,1))
297 tree:add(p_smartscope.fields.eeprom_length, range(1,1))
298 tree:add(p_smartscope.fields.eeprom_data, range(2,range(1,1):uint()))
299 pinfo.cols.info:append(string.format(" 0x%02X len=%d", addr, range(1,1):uint()))
300 elseif command == 8 then -- flash rom read
301 local addr = range(0,1):uint()+256*bit.band(range(2,1):uint(),0xf)
302 tree:add(p_smartscope.fields.flash_rom_address, range(0,3), addr)
303 tree:add(p_smartscope.fields.flash_rom_length, range(1,1))
304 tree:add(p_smartscope.fields.flash_rom_data, range(3,range(1,1):uint()))
305 pinfo.cols.info:append(string.format(" 0x%03X len=%d", addr, range(1,1):uint()))
306 elseif command == 11 then -- i2c read
307 local slave = range(0,1):uint()
308 tree:add(p_smartscope.fields.i2c_read_slave_address, range(0,1))
309 tree:add(p_smartscope.fields.i2c_read_length, range(1,1))
310 if i2c_addresses[slave] then
311 pinfo.cols.info:append(string.format(" %s", i2c_addresses[slave]))
313 local len = range(1,1):uint()
314 tree:add(p_smartscope.fields.i2c_read_payload, range(2,len))
315 pinfo.cols.info:append(string.format(" len=%d", len))
319 -- Main dissector function.
320 function p_smartscope.dissector(tvb, pinfo, tree)
321 local transfer_type = tonumber(tostring(f_transfer_type()))
323 -- Bulk transfers only.
324 if transfer_type == 3 then
325 local urb_type = tonumber(tostring(f_urb_type()))
326 local endpoint = tonumber(tostring(f_endpoint()))
327 local direction = tonumber(tostring(f_direction()))
329 -- Payload-carrying packets only.
330 if (urb_type == 83 and endpoint == 2 and direction == 0) -- 'S' - Submit
331 or (urb_type == 67 and endpoint == 3 and direction == 1) -- 'C' - Complete
333 pinfo.cols.protocol = p_smartscope.name
335 local subtree = tree:add(p_smartscope, tvb(), "SmartScope")
336 subtree:add(p_smartscope.fields.rawdata, tvb())
338 if endpoint == 2 and direction == 0 then
339 if pktFpgaData[pinfo.number] == nil then
340 pktFpgaData[pinfo.number] = (fpgaDataCount > 0)
341 if fpgaDataCount > tvb:len() then
342 fpgaDataCount = fpgaDataCount - tvb:len()
348 if pktFpgaData[pinfo.number] == true then
349 subtree:add(p_smartscope.fields.fpga_data, tvb())
350 pinfo.cols.info = string.format("-> FPGA bitstream data (%d bytes)", tvb:len())
356 local header = tvb(0,1):uint()
357 subtree:add(p_smartscope.fields.header, tvb(0,1))
358 local command = tvb(1,1):uint()
359 subtree:add(p_smartscope.fields.command, tvb(1,1))
360 if endpoint == 2 and header == 0xc0 then
361 dissect_command(tvb(2), pinfo, subtree, command)
362 elseif endpoint == 3 and header == 0xad then
363 dissect_answer(tvb(2), pinfo, subtree, command)
369 -- Register SmartScope protocol dissector during initialization.
370 function p_smartscope.init()
375 local usb_product_dissectors = DissectorTable.get("usb.product")
377 -- Dissection by vendor+product ID requires that Wireshark can get the
378 -- the device descriptor. Making a USB device available inside a VM
379 -- will make it inaccessible from Linux, so Wireshark cannot fetch the
380 -- descriptor by itself. However, it is sufficient if the guest requests
381 -- the descriptor once while Wireshark is capturing.
382 usb_product_dissectors:add(0x04d8f4b5, p_smartscope)