]>
Commit | Line | Data |
---|---|---|
f16da0bd DE |
1 | -- SysClk LWLA protocol dissector for Wireshark |
2 | -- | |
a73dda58 | 3 | -- Copyright (c) 2014,2015 Daniel Elstner <daniel.kitta@gmail.com> |
f16da0bd DE |
4 | -- |
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. | |
9 | -- | |
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. | |
14 | -- | |
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/>. | |
17 | ||
18 | -- Usage: wireshark -X lua_script:sysclk-lwla-dissector.lua | |
19 | -- | |
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. | |
23 | ||
24 | -- Create custom protocol for the LWLA logic analyzer. | |
25 | p_lwla = Proto("lwla", "LWLA USB Protocol") | |
26 | ||
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" | |
33 | } | |
34 | ||
35 | -- Known IDs of LWLA control commands. | |
36 | local control_commands = { | |
37 | [1] = "Read register", | |
38 | [2] = "Write register", | |
a73dda58 | 39 | [3] = "Read memory", |
f16da0bd DE |
40 | [5] = "Write ???", |
41 | [6] = "Read memory", | |
42 | [7] = "Capture setup", | |
43 | [8] = "Capture status" | |
44 | } | |
45 | ||
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") | |
a73dda58 | 56 | p_lwla.fields.secdata = ProtoField.uint32("lwla.secdata", "Security Hash", base.HEX_DEC) |
f16da0bd DE |
57 | p_lwla.fields.unknown = ProtoField.bytes("lwla.unknown", "Unidentified message data") |
58 | ||
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") | |
63 | ||
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") | |
68 | end | |
69 | ||
a73dda58 | 70 | -- Extract a 32-bit mixed endian word. |
f16da0bd DE |
71 | local function read_mixed_endian(range) |
72 | return range(0,2):le_uint() * 65536 + range(2,2):le_uint() | |
73 | end | |
74 | ||
a73dda58 | 75 | -- Un-shuffle the bytes of a 64-bit stat field. |
f16da0bd DE |
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()) | |
79 | end | |
80 | ||
a73dda58 | 81 | -- Dissect LWLA1034 capture state. |
f16da0bd DE |
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))) | |
85 | end | |
86 | end | |
87 | ||
88 | -- Dissect LWLA control command messages. | |
89 | local function dissect_command(range, pinfo, tree) | |
90 | if range:len() < 4 then | |
91 | return 0 | |
92 | end | |
93 | ||
94 | tree:add_le(p_lwla.fields.command, range(0,2)) | |
95 | local command = range(0,2):le_uint() | |
96 | ||
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()) | |
102 | return 4 | |
103 | end | |
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) | |
111 | return 8 | |
112 | end | |
a73dda58 | 113 | elseif command == 5 then -- write security hash? |
f16da0bd | 114 | if range:len() == 66 then |
a73dda58 | 115 | local infotext = string.format("Cmd %d: write security hash?", command) |
f16da0bd DE |
116 | for i = 2, 62, 4 do |
117 | local value = read_mixed_endian(range(i,4)) | |
a73dda58 | 118 | tree:add(p_lwla.fields.secdata, range(i,4), value) |
f16da0bd DE |
119 | infotext = string.format("%s %02X", infotext, value) |
120 | end | |
121 | pinfo.cols.info = infotext | |
122 | return 66 | |
123 | end | |
a73dda58 | 124 | elseif command == 3 or command == 6 then -- read memory at address |
f16da0bd DE |
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) | |
132 | return 10 | |
133 | end | |
a73dda58 | 134 | elseif command == 7 then -- capture setup (LWLA1034) |
f16da0bd DE |
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)) | |
141 | return 6 | |
142 | end | |
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()) | |
146 | return 6 + len | |
147 | end | |
a73dda58 | 148 | elseif command == 8 then -- capture status (LWLA1034) |
f16da0bd DE |
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()) | |
154 | return 6 | |
155 | end | |
156 | end | |
157 | warn_undecoded(tree, range(2)) | |
158 | return 2 | |
159 | end | |
160 | ||
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) | |
168 | return range:len() | |
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) | |
173 | return 4 | |
a73dda58 DE |
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) | |
176 | return 0 | |
f16da0bd DE |
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) | |
179 | return 0 | |
180 | else | |
181 | return 0 | |
182 | end | |
183 | end | |
184 | ||
185 | -- Main LWLA dissector function. | |
186 | function p_lwla.dissector(tvb, pinfo, tree) | |
187 | local transfer_type = tonumber(tostring(f_transfer_type())) | |
188 | ||
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())) | |
193 | ||
194 | -- Payload-carrying packets only. | |
195 | if (urb_type == 83 and (endpoint == 2 or endpoint == 4)) | |
196 | or (urb_type == 67 and endpoint == 6) | |
197 | then | |
198 | pinfo.cols.protocol = p_lwla.name | |
199 | ||
200 | local subtree = tree:add(p_lwla, tvb(), "LWLA") | |
201 | subtree:add(p_lwla.fields.msgtype, endpoint):set_generated() | |
202 | ||
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" | |
208 | return 0 | |
209 | elseif endpoint == 6 then | |
210 | return dissect_response(tvb, pinfo, subtree) | |
211 | end | |
212 | end | |
213 | end | |
214 | return 0 | |
215 | end | |
216 | ||
217 | -- Register LWLA protocol dissector during initialization. | |
218 | function p_lwla.init() | |
219 | -- local usb_product_dissectors = DissectorTable.get("usb.product") | |
220 | ||
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. | |
a73dda58 DE |
226 | -- usb_product_dissectors:add(0x29616688, p_lwla) -- SysClk LWLA1016 |
227 | -- usb_product_dissectors:add(0x29616689, p_lwla) -- SysClk LWLA1034 | |
f16da0bd DE |
228 | |
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") | |
234 | ||
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) | |
239 | end |