]>
Commit | Line | Data |
---|---|---|
ed1ce54a MC |
1 | -- SmartScope protocol dissector for Wireshark |
2 | -- | |
3 | -- Copyright (C) 2015 Marcus Comstedt <marcus@mc.pp.se> | |
4 | -- | |
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> | |
9 | -- | |
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. | |
14 | -- | |
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. | |
19 | -- | |
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/>. | |
22 | ||
23 | -- Usage: wireshark -X lua_script:labnation-smartscope-dissector.lua | |
24 | -- | |
25 | -- Create custom protocol for the LabNation SmartScope analyzer. | |
26 | p_smartscope = Proto("SmartScope", "LabNation SmartScope USB Protocol") | |
27 | ||
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") | |
33 | ||
34 | -- Header values | |
35 | local headers = { | |
36 | [0xC0] = "Command", | |
37 | [0xAD] = "Answer Dude", | |
38 | } | |
39 | ||
40 | -- Commands | |
41 | local commands = { | |
42 | [0x01] = "PIC_VERSION", | |
43 | [0x02] = "PIC_WRITE", | |
44 | [0x03] = "PIC_READ", | |
45 | [0x04] = "PIC_RESET", | |
46 | [0x05] = "PIC_BOOTLOADER", | |
47 | [0x06] = "EEPROM_READ", | |
48 | [0x07] = "EEPROM_WRITE", | |
49 | [0x08] = "FLASH_ROM_READ", | |
50 | [0x09] = "FLASH_ROM_WRITE", | |
51 | [0x0a] = "I2C_WRITE", | |
52 | [0x0b] = "I2C_READ", | |
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", | |
58 | } | |
59 | ||
60 | -- Addresses | |
61 | local pic_addresses = { | |
62 | [0x00] = "FORCE_STREAMING", | |
63 | } | |
64 | ||
65 | local i2c_addresses = { | |
66 | [0x0C] = "SETTINGS", | |
67 | [0x0D] = "ROM", | |
68 | [0x0E] = "AWG", | |
69 | } | |
70 | ||
71 | local settings_addresses = { | |
72 | [0] = "STROBE_UPDATE", | |
73 | [1] = "SPI_ADDRESS", | |
74 | [2] = "SPI_WRITE_VALUE", | |
75 | [3] = "DIVIDER_MULTIPLIER", | |
76 | [4] = "CHA_YOFFSET_VOLTAGE", | |
77 | [5] = "CHB_YOFFSET_VOLTAGE", | |
78 | [6] = "TRIGGER_PWM", | |
79 | [7] = "TRIGGER_LEVEL", | |
80 | [8] = "TRIGGER_THRESHOLD", | |
81 | [9] = "TRIGGER_MODE", | |
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", | |
94 | [22] = "VIEW_BURSTS", | |
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", | |
102 | [30] = "AWG_DEBUG", | |
103 | [31] = "AWG_DECIMATION", | |
104 | [32] = "AWG_SAMPLES_B0", | |
105 | [33] = "AWG_SAMPLES_B1", | |
106 | } | |
107 | ||
108 | local rom_addresses = { | |
109 | [0] = "FW_MSB", | |
110 | [1] = "FW_LSB", | |
111 | [2] = "FW_GIT0", | |
112 | [3] = "FW_GIT1", | |
113 | [4] = "FW_GIT2", | |
114 | [5] = "FW_GIT3", | |
115 | [6] = "SPI_RECEIVED_VALUE", | |
116 | [7] = "STROBES", | |
117 | } | |
118 | ||
119 | local strobe_numbers = { | |
120 | [0] = "GLOBAL_RESET", | |
121 | [1] = "INIT_SPI_TRANSFER", | |
122 | [2] = "AWG_ENABLE", | |
123 | [3] = "LA_ENABLE", | |
124 | [4] = "SCOPE_ENABLE", | |
125 | [5] = "SCOPE_UPDATE", | |
126 | [6] = "FORCE_TRIGGER", | |
127 | [7] = "VIEW_UPDATE", | |
128 | [8] = "VIEW_SEND_OVERVIEW", | |
129 | [9] = "VIEW_SEND_PARTIAL", | |
130 | [10] = "ACQ_START", | |
131 | [11] = "ACQ_STOP", | |
132 | [12] = "CHA_DCCOUPLING", | |
133 | [13] = "CHB_DCCOUPLING", | |
134 | [14] = "ENABLE_ADC", | |
135 | [15] = "OVERFLOW_DETECT", | |
136 | [16] = "ENABLE_NEG", | |
137 | [17] = "ENABLE_RAM", | |
138 | [18] = "DOUT_3V_5V", | |
139 | [19] = "EN_OPAMP_B", | |
140 | [20] = "AWG_DEBUG", | |
141 | [21] = "DIGI_DEBUG", | |
142 | [22] = "ROLL", | |
143 | [23] = "LA_CHANNEL", | |
144 | } | |
145 | ||
146 | ||
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") | |
176 | ||
177 | -- State variables | |
178 | local pktFpgaData | |
179 | local fpgaDataCount | |
180 | ||
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() | |
221 | if len > 0 then | |
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)) | |
225 | if len > 1 then | |
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)) | |
237 | else | |
238 | payload = range(2) | |
239 | end | |
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)) | |
247 | elseif payload then | |
248 | tree:add(p_smartscope.fields.i2c_write_payload, payload) | |
249 | pinfo.cols.info:append(string.format(" len=%d", payload:len())) | |
250 | end | |
251 | end | |
252 | end | |
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])) | |
259 | end | |
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() | |
268 | if len > 0 then | |
269 | tree:add(p_smartscope.fields.i2c_write_payload, range(1,len)) | |
270 | pinfo.cols.info:append(string.format(" len=%d", len)) | |
271 | end | |
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() | |
275 | if len > 0 then | |
276 | pinfo.cols.info:append(string.format(" len=%d", len)) | |
277 | end | |
278 | end | |
279 | end | |
280 | ||
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])) | |
312 | end | |
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)) | |
316 | end | |
317 | end | |
318 | ||
319 | -- Main dissector function. | |
320 | function p_smartscope.dissector(tvb, pinfo, tree) | |
321 | local transfer_type = tonumber(tostring(f_transfer_type())) | |
322 | ||
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())) | |
328 | ||
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 | |
332 | then | |
333 | pinfo.cols.protocol = p_smartscope.name | |
334 | ||
335 | local subtree = tree:add(p_smartscope, tvb(), "SmartScope") | |
336 | subtree:add(p_smartscope.fields.rawdata, tvb()) | |
337 | ||
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() | |
343 | else | |
344 | fpgaDataCount = 0 | |
345 | end | |
346 | end | |
347 | ||
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()) | |
351 | return | |
352 | end | |
353 | ||
354 | end | |
355 | ||
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) | |
364 | end | |
365 | end | |
366 | end | |
367 | end | |
368 | ||
369 | -- Register SmartScope protocol dissector during initialization. | |
370 | function p_smartscope.init() | |
371 | ||
372 | pktFpgaData = {} | |
373 | fpgaDataCount = 0 | |
374 | ||
375 | local usb_product_dissectors = DissectorTable.get("usb.product") | |
376 | ||
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) | |
383 | end |