1 -- SysClk LWLA protocol dissector for Wireshark
3 -- Copyright (c) 2014,2015 Daniel Elstner <daniel.kitta@gmail.com>
5 -- This program is free software; you can redistribute it and/or modify
6 -- it under the terms of the GNU General Public License as published by
7 -- the Free Software Foundation; either version 3 of the License, or
8 -- (at your option) any later version.
10 -- This program is distributed in the hope that it will be useful,
11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 -- GNU General Public License for more details.
15 -- You should have received a copy of the GNU General Public License
16 -- along with this program; if not, see <http://www.gnu.org/licenses/>.
18 -- Usage: wireshark -X lua_script:sysclk-lwla-dissector.lua
20 -- It is not advisable to install this dissector globally, since
21 -- it will try to interpret the communication of any USB device
22 -- using the vendor-specific interface class.
24 -- Create custom protocol for the LWLA logic analyzer.
25 p_lwla
= Proto("lwla", "LWLA USB Protocol")
27 -- LWLA message type. For simplicity, the numerical value is the same
28 -- as the USB end point number the message is sent to or comes from.
29 local message_types
= {
30 [2] = "Control command",
31 [4] = "Firmware transfer",
32 [6] = "Control response"
35 -- Known IDs of LWLA control commands.
36 local control_commands
= {
37 [1] = "Read register",
38 [2] = "Write register",
42 [7] = "Capture setup",
43 [8] = "Capture status"
46 -- Create the fields exhibited by the protocol.
47 p_lwla
.fields
.msgtype
= ProtoField
.uint8("lwla.msgtype", "Message Type", base
.DEC
, message_types
)
48 p_lwla
.fields
.command
= ProtoField
.uint16("lwla.cmd", "Command ID", base
.DEC
, control_commands
)
49 p_lwla
.fields
.memaddr
= ProtoField
.uint32("lwla.memaddr", "Memory Address", base
.HEX_DEC
)
50 p_lwla
.fields
.memlen
= ProtoField
.uint32("lwla.memlen", "Memory Read Length", base
.HEX_DEC
)
51 p_lwla
.fields
.regaddr
= ProtoField
.uint16("lwla.regaddr", "Register Address", base
.HEX
)
52 p_lwla
.fields
.regdata
= ProtoField
.uint32("lwla.regdata", "Register Value", base
.HEX_DEC
)
53 p_lwla
.fields
.stataddr
= ProtoField
.uint16("lwla.stataddr", "Status Memory Address", base
.HEX
)
54 p_lwla
.fields
.statlen
= ProtoField
.uint16("lwla.statlen", "Status Memory Read/Write Length", base
.HEX_DEC
)
55 p_lwla
.fields
.statdata
= ProtoField
.bytes("lwla.statdata", "Status Word")
56 p_lwla
.fields
.secdata
= ProtoField
.uint32("lwla.secdata", "Security Hash", base
.HEX_DEC
)
57 p_lwla
.fields
.unknown
= ProtoField
.bytes("lwla.unknown", "Unidentified message data")
59 -- Referenced USB URB dissector fields.
60 local f_urb_type
= Field
.new("usb.urb_type")
61 local f_transfer_type
= Field
.new("usb.transfer_type")
62 local f_endpoint
= Field
.new("usb.endpoint_number.endpoint")
64 -- Insert warning for undecoded leftover data.
65 local function warn_undecoded(tree
, range
)
66 local item
= tree
:add(p_lwla
.fields
.unknown
, range
)
67 item
:add_expert_info(PI_UNDECODED
, PI_WARN
, "Leftover data")
70 -- Extract a 32-bit mixed endian word.
71 local function read_mixed_endian(range
)
72 return range(0,2):le_uint() * 65536 + range(2,2):le_uint()
75 -- Un-shuffle the bytes of a 64-bit stat field.
76 local function read_stat_field(range
)
77 return string.char(range(5,1):uint(), range(4,1):uint(), range(7,1):uint(), range(6,1):uint(),
78 range(1,1):uint(), range(0,1):uint(), range(3,1):uint(), range(2,1):uint())
81 -- Dissect LWLA1034 capture state.
82 local function dissect_capture_state(range
, tree
)
83 for i
= 0, range
:len() - 8, 8 do
84 tree
:add(p_lwla
.fields
.statdata
, range(i
,8), read_stat_field(range(i
,8)))
88 -- Dissect LWLA control command messages.
89 local function dissect_command(range
, pinfo
, tree
)
90 if range
:len() < 4 then
94 tree
:add_le(p_lwla
.fields
.command
, range(0,2))
95 local command
= range(0,2):le_uint()
97 if command
== 1 then -- read register
98 if range
:len() == 4 then
99 tree
:add_le(p_lwla
.fields
.regaddr
, range(2,2))
100 pinfo
.cols
.info
= string.format("Cmd %d: read reg 0x%04X",
101 command
, range(2,2):le_uint())
104 elseif command
== 2 then -- write register
105 if range
:len() == 8 then
106 tree
:add_le(p_lwla
.fields
.regaddr
, range(2,2))
107 local regval
= read_mixed_endian(range(4,4))
108 tree
:add(p_lwla
.fields
.regdata
, range(4,4), regval
)
109 pinfo
.cols
.info
= string.format("Cmd %d: write reg 0x%04X value 0x%08X",
110 command
, range(2,2):le_uint(), regval
)
113 elseif command
== 5 then -- write security hash?
114 if range
:len() == 66 then
115 local infotext
= string.format("Cmd %d: write security hash?", command
)
117 local value
= read_mixed_endian(range(i
,4))
118 tree
:add(p_lwla
.fields
.secdata
, range(i
,4), value
)
119 infotext
= string.format("%s %02X", infotext
, value
)
121 pinfo
.cols
.info
= infotext
124 elseif command
== 3 or command
== 6 then -- read memory at address
125 if range
:len() == 10 then
126 local memaddr
= read_mixed_endian(range(2,4))
127 local memlen
= read_mixed_endian(range(6,4))
128 tree
:add(p_lwla
.fields
.memaddr
, range(2,4), memaddr
)
129 tree
:add(p_lwla
.fields
.memlen
, range(6,4), memlen
)
130 pinfo
.cols
.info
= string.format("Cmd %d: read mem 0x%06X length %d",
131 command
, memaddr
, memlen
)
134 elseif command
== 7 then -- capture setup (LWLA1034)
135 if range
:len() >= 6 then
136 tree
:add_le(p_lwla
.fields
.stataddr
, range(2,2))
137 tree
:add_le(p_lwla
.fields
.statlen
, range(4,2))
138 local len
= 8 * range(4,2):le_uint()
139 if range
:len() ~= len
+ 6 then
140 warn_undecoded(tree
, range(6))
143 dissect_capture_state(range(6,len
), tree
)
144 pinfo
.cols
.info
= string.format("Cmd %d: setup 0x%X length %d",
145 command
, range(2,2):le_uint(), range(4,2):le_uint())
148 elseif command
== 8 then -- capture status (LWLA1034)
149 if range
:len() == 6 then
150 tree
:add_le(p_lwla
.fields
.stataddr
, range(2,2))
151 tree
:add_le(p_lwla
.fields
.statlen
, range(4,2))
152 pinfo
.cols
.info
= string.format("Cmd %d: state 0x%X length %d",
153 command
, range(2,2):le_uint(), range(4,2):le_uint())
157 warn_undecoded(tree
, range(2))
161 -- Dissect LWLA control response messages.
162 local function dissect_response(range
, pinfo
, tree
)
163 -- The heuristics below are ugly and prone to fail, but they do the job
164 -- for the purposes this dissector was written.
165 if range
:len() == 40 or range
:len() == 80 then -- heuristic: response to command 8
166 dissect_capture_state(range
, tree
)
167 pinfo
.cols
.info
= string.format("Ret 8: state length %d", range
:len() / 8)
169 elseif range
:len() == 4 then -- heuristic: response to command 1
170 local value
= read_mixed_endian(range(0,4))
171 tree
:add(p_lwla
.fields
.regdata
, range(0,4), value
)
172 pinfo
.cols
.info
= string.format("Ret 1: reg value 0x%08X", value
)
174 elseif range
:len() == 1000 or range
:len() == 552 then -- heuristic: response to command 3
175 pinfo
.cols
.info
= string.format("Ret 3: mem data length %d", range
:len() / 4)
177 elseif range
:len() >= 18 and range
:len() % 18 == 0 then -- heuristic: response to command 6
178 pinfo
.cols
.info
= string.format("Ret 6: mem data length %d", range
:len() * 2 / 9)
185 -- Main LWLA dissector function.
186 function p_lwla
.dissector(tvb
, pinfo
, tree
)
187 local transfer_type
= tonumber(tostring(f_transfer_type()))
189 -- Bulk transfers only.
190 if transfer_type
== 3 then
191 local urb_type
= tonumber(tostring(f_urb_type()))
192 local endpoint
= tonumber(tostring(f_endpoint()))
194 -- Payload-carrying packets only.
195 if (urb_type
== 83 and (endpoint
== 2 or endpoint
== 4))
196 or (urb_type
== 67 and endpoint
== 6)
198 pinfo
.cols
.protocol
= p_lwla
.name
200 local subtree
= tree
:add(p_lwla
, tvb(), "LWLA")
201 subtree
:add(p_lwla
.fields
.msgtype
, endpoint
):set_generated()
203 -- Dispatch to message-specific dissection handler.
204 if endpoint
== 2 then
205 return dissect_command(tvb
, pinfo
, subtree
)
206 elseif endpoint
== 4 then
207 pinfo
.cols
.info
= "FPGA bitstream"
209 elseif endpoint
== 6 then
210 return dissect_response(tvb
, pinfo
, subtree
)
217 -- Register LWLA protocol dissector during initialization.
218 function p_lwla
.init()
219 -- local usb_product_dissectors = DissectorTable.get("usb.product")
221 -- Dissection by vendor+product ID requires that Wireshark can get the
222 -- the device descriptor. Making a USB device available inside VirtualBox
223 -- will make it inaccessible from Linux, so Wireshark cannot fetch the
224 -- descriptor by itself. However, it is sufficient if the VirtualBox
225 -- guest requests the descriptor once while Wireshark is capturing.
226 -- usb_product_dissectors:add(0x29616688, p_lwla) -- Sysclk LWLA1016
227 -- usb_product_dissectors:add(0x29616689, p_lwla) -- Sysclk LWLA1034
229 -- Addendum: Protocol registration based on product ID does not always
230 -- work as desired. Register the protocol on the interface class instead.
231 -- The downside is that it would be a bad idea to put this into the global
232 -- configuration, so one has to make do with -X lua_script: for now.
233 local usb_bulk_dissectors
= DissectorTable
.get("usb.bulk")
235 -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes
236 -- 0xFFFF. Register both to make it work all the time.
237 usb_bulk_dissectors
:add(0xFF, p_lwla
)
238 usb_bulk_dissectors
:add(0xFFFF, p_lwla
)