]>
Commit | Line | Data |
---|---|---|
f16da0bd DE |
1 | -- SysClk LWLA protocol dissector for Wireshark |
2 | -- | |
3 | -- Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com> | |
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", | |
39 | [5] = "Write ???", | |
40 | [6] = "Read memory", | |
41 | [7] = "Capture setup", | |
42 | [8] = "Capture status" | |
43 | } | |
44 | ||
45 | -- Create the fields exhibited by the protocol. | |
46 | p_lwla.fields.msgtype = ProtoField.uint8("lwla.msgtype", "Message Type", base.DEC, message_types) | |
47 | p_lwla.fields.command = ProtoField.uint16("lwla.cmd", "Command ID", base.DEC, control_commands) | |
48 | p_lwla.fields.memaddr = ProtoField.uint32("lwla.memaddr", "Memory Address", base.HEX_DEC) | |
49 | p_lwla.fields.memlen = ProtoField.uint32("lwla.memlen", "Memory Read Length", base.HEX_DEC) | |
50 | p_lwla.fields.regaddr = ProtoField.uint16("lwla.regaddr", "Register Address", base.HEX) | |
51 | p_lwla.fields.regdata = ProtoField.uint32("lwla.regdata", "Register Value", base.HEX_DEC) | |
52 | p_lwla.fields.stataddr = ProtoField.uint16("lwla.stataddr", "Status Memory Address", base.HEX) | |
53 | p_lwla.fields.statlen = ProtoField.uint16("lwla.statlen", "Status Memory Read/Write Length", base.HEX_DEC) | |
54 | p_lwla.fields.statdata = ProtoField.bytes("lwla.statdata", "Status Word") | |
55 | p_lwla.fields.stopdata = ProtoField.uint32("lwla.stopdata", "Stop Data", base.HEX_DEC) | |
56 | p_lwla.fields.unknown = ProtoField.bytes("lwla.unknown", "Unidentified message data") | |
57 | ||
58 | -- Referenced USB URB dissector fields. | |
59 | local f_urb_type = Field.new("usb.urb_type") | |
60 | local f_transfer_type = Field.new("usb.transfer_type") | |
61 | local f_endpoint = Field.new("usb.endpoint_number.endpoint") | |
62 | ||
63 | -- Insert warning for undecoded leftover data. | |
64 | local function warn_undecoded(tree, range) | |
65 | local item = tree:add(p_lwla.fields.unknown, range) | |
66 | item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data") | |
67 | end | |
68 | ||
69 | local function read_mixed_endian(range) | |
70 | return range(0,2):le_uint() * 65536 + range(2,2):le_uint() | |
71 | end | |
72 | ||
73 | local function read_stat_field(range) | |
74 | return string.char(range(5,1):uint(), range(4,1):uint(), range(7,1):uint(), range(6,1):uint(), | |
75 | range(1,1):uint(), range(0,1):uint(), range(3,1):uint(), range(2,1):uint()) | |
76 | end | |
77 | ||
78 | -- Dissect LWLA capture state. | |
79 | local function dissect_capture_state(range, tree) | |
80 | for i = 0, range:len() - 8, 8 do | |
81 | tree:add(p_lwla.fields.statdata, range(i,8), read_stat_field(range(i,8))) | |
82 | end | |
83 | end | |
84 | ||
85 | -- Dissect LWLA control command messages. | |
86 | local function dissect_command(range, pinfo, tree) | |
87 | if range:len() < 4 then | |
88 | return 0 | |
89 | end | |
90 | ||
91 | tree:add_le(p_lwla.fields.command, range(0,2)) | |
92 | local command = range(0,2):le_uint() | |
93 | ||
94 | if command == 1 then -- read register | |
95 | if range:len() == 4 then | |
96 | tree:add_le(p_lwla.fields.regaddr, range(2,2)) | |
97 | pinfo.cols.info = string.format("Cmd %d: read reg 0x%04X", | |
98 | command, range(2,2):le_uint()) | |
99 | return 4 | |
100 | end | |
101 | elseif command == 2 then -- write register | |
102 | if range:len() == 8 then | |
103 | tree:add_le(p_lwla.fields.regaddr, range(2,2)) | |
104 | local regval = read_mixed_endian(range(4,4)) | |
105 | tree:add(p_lwla.fields.regdata, range(4,4), regval) | |
106 | pinfo.cols.info = string.format("Cmd %d: write reg 0x%04X value 0x%08X", | |
107 | command, range(2,2):le_uint(), regval) | |
108 | return 8 | |
109 | end | |
110 | elseif command == 5 then -- write ??? | |
111 | if range:len() == 66 then | |
112 | local infotext = string.format("Cmd %d: write ??? data", command) | |
113 | for i = 2, 62, 4 do | |
114 | local value = read_mixed_endian(range(i,4)) | |
115 | tree:add(p_lwla.fields.stopdata, range(i,4), value) | |
116 | infotext = string.format("%s %02X", infotext, value) | |
117 | end | |
118 | pinfo.cols.info = infotext | |
119 | return 66 | |
120 | end | |
121 | elseif command == 6 then -- read memory at address | |
122 | if range:len() == 10 then | |
123 | local memaddr = read_mixed_endian(range(2,4)) | |
124 | local memlen = read_mixed_endian(range(6,4)) | |
125 | tree:add(p_lwla.fields.memaddr, range(2,4), memaddr) | |
126 | tree:add(p_lwla.fields.memlen, range(6,4), memlen) | |
127 | pinfo.cols.info = string.format("Cmd %d: read mem 0x%06X length %d", | |
128 | command, memaddr, memlen) | |
129 | return 10 | |
130 | end | |
131 | elseif command == 7 then -- capture setup | |
132 | if range:len() >= 6 then | |
133 | tree:add_le(p_lwla.fields.stataddr, range(2,2)) | |
134 | tree:add_le(p_lwla.fields.statlen, range(4,2)) | |
135 | local len = 8 * range(4,2):le_uint() | |
136 | if range:len() ~= len + 6 then | |
137 | warn_undecoded(tree, range(6)) | |
138 | return 6 | |
139 | end | |
140 | dissect_capture_state(range(6,len), tree) | |
141 | pinfo.cols.info = string.format("Cmd %d: setup 0x%X length %d", | |
142 | command, range(2,2):le_uint(), range(4,2):le_uint()) | |
143 | return 6 + len | |
144 | end | |
145 | elseif command == 8 then -- capture status | |
146 | if range:len() == 6 then | |
147 | tree:add_le(p_lwla.fields.stataddr, range(2,2)) | |
148 | tree:add_le(p_lwla.fields.statlen, range(4,2)) | |
149 | pinfo.cols.info = string.format("Cmd %d: state 0x%X length %d", | |
150 | command, range(2,2):le_uint(), range(4,2):le_uint()) | |
151 | return 6 | |
152 | end | |
153 | end | |
154 | warn_undecoded(tree, range(2)) | |
155 | return 2 | |
156 | end | |
157 | ||
158 | -- Dissect LWLA control response messages. | |
159 | local function dissect_response(range, pinfo, tree) | |
160 | -- The heuristics below are ugly and prone to fail, but they do the job | |
161 | -- for the purposes this dissector was written. | |
162 | if range:len() == 40 or range:len() == 80 then -- heuristic: response to command 8 | |
163 | dissect_capture_state(range, tree) | |
164 | pinfo.cols.info = string.format("Ret 8: state length %d", range:len() / 8) | |
165 | return range:len() | |
166 | elseif range:len() == 4 then -- heuristic: response to command 1 | |
167 | local value = read_mixed_endian(range(0,4)) | |
168 | tree:add(p_lwla.fields.regdata, range(0,4), value) | |
169 | pinfo.cols.info = string.format("Ret 1: reg value 0x%08X", value) | |
170 | return 4 | |
171 | elseif range:len() >= 18 and range:len() % 18 == 0 then -- heuristic: response to command 6 | |
172 | pinfo.cols.info = string.format("Ret 6: mem data length %d", range:len() * 2 / 9) | |
173 | return 0 | |
174 | else | |
175 | return 0 | |
176 | end | |
177 | end | |
178 | ||
179 | -- Main LWLA dissector function. | |
180 | function p_lwla.dissector(tvb, pinfo, tree) | |
181 | local transfer_type = tonumber(tostring(f_transfer_type())) | |
182 | ||
183 | -- Bulk transfers only. | |
184 | if transfer_type == 3 then | |
185 | local urb_type = tonumber(tostring(f_urb_type())) | |
186 | local endpoint = tonumber(tostring(f_endpoint())) | |
187 | ||
188 | -- Payload-carrying packets only. | |
189 | if (urb_type == 83 and (endpoint == 2 or endpoint == 4)) | |
190 | or (urb_type == 67 and endpoint == 6) | |
191 | then | |
192 | pinfo.cols.protocol = p_lwla.name | |
193 | ||
194 | local subtree = tree:add(p_lwla, tvb(), "LWLA") | |
195 | subtree:add(p_lwla.fields.msgtype, endpoint):set_generated() | |
196 | ||
197 | -- Dispatch to message-specific dissection handler. | |
198 | if endpoint == 2 then | |
199 | return dissect_command(tvb, pinfo, subtree) | |
200 | elseif endpoint == 4 then | |
201 | pinfo.cols.info = "FPGA bitstream" | |
202 | return 0 | |
203 | elseif endpoint == 6 then | |
204 | return dissect_response(tvb, pinfo, subtree) | |
205 | end | |
206 | end | |
207 | end | |
208 | return 0 | |
209 | end | |
210 | ||
211 | -- Register LWLA protocol dissector during initialization. | |
212 | function p_lwla.init() | |
213 | -- local usb_product_dissectors = DissectorTable.get("usb.product") | |
214 | ||
215 | -- Dissection by vendor+product ID requires that Wireshark can get the | |
216 | -- the device descriptor. Making a USB device available inside VirtualBox | |
217 | -- will make it inaccessible from Linux, so Wireshark cannot fetch the | |
218 | -- descriptor by itself. However, it is sufficient if the VirtualBox | |
219 | -- guest requests the descriptor once while Wireshark is capturing. | |
220 | -- usb_product_dissectors:add(0x29616689, p_lwla) | |
221 | ||
222 | -- Addendum: Protocol registration based on product ID does not always | |
223 | -- work as desired. Register the protocol on the interface class instead. | |
224 | -- The downside is that it would be a bad idea to put this into the global | |
225 | -- configuration, so one has to make do with -X lua_script: for now. | |
226 | local usb_bulk_dissectors = DissectorTable.get("usb.bulk") | |
227 | ||
228 | -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes | |
229 | -- 0xFFFF. Register both to make it work all the time. | |
230 | usb_bulk_dissectors:add(0xFF, p_lwla) | |
231 | usb_bulk_dissectors:add(0xFFFF, p_lwla) | |
232 | end |