81f7523f3dd863a7718e711db413b615ac5e3dc7
[sigrok-util.git] / debug / sysclk-lwla / sysclk-lwla-dissector.lua
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