diff options
Diffstat (limited to 'epan/dissectors/packet-ftdi-mpsse.c')
-rw-r--r-- | epan/dissectors/packet-ftdi-mpsse.c | 1712 |
1 files changed, 1712 insertions, 0 deletions
diff --git a/epan/dissectors/packet-ftdi-mpsse.c b/epan/dissectors/packet-ftdi-mpsse.c new file mode 100644 index 00000000..f303e0e2 --- /dev/null +++ b/epan/dissectors/packet-ftdi-mpsse.c @@ -0,0 +1,1712 @@ +/* packet-ftdi-mpsse.c + * Routines for FTDI Multi-Protocol Synchronous Serial Engine dissection + * + * Copyright 2020 Tomasz Mon + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <epan/packet.h> +#include <epan/expert.h> +#include <wsutil/str_util.h> +#include "packet-ftdi-ft.h" + +static int proto_ftdi_mpsse = -1; + +static gint hf_mpsse_command = -1; +/* Data Shifting commands bits (b0-b6 are relevant only if b7 is 0) */ +static gint hf_mpsse_command_b0 = -1; +static gint hf_mpsse_command_b1 = -1; +static gint hf_mpsse_command_b2 = -1; +static gint hf_mpsse_command_b3 = -1; +static gint hf_mpsse_command_b4 = -1; +static gint hf_mpsse_command_b5 = -1; +static gint hf_mpsse_command_b6 = -1; +static gint hf_mpsse_command_b7 = -1; +static gint hf_mpsse_command_with_parameters = -1; +static gint hf_mpsse_bad_command_error = -1; +static gint hf_mpsse_bad_command_code = -1; +static gint hf_mpsse_response = -1; +static gint hf_mpsse_command_in = -1; +static gint hf_mpsse_response_in = -1; +static gint hf_mpsse_length_uint8 = -1; +static gint hf_mpsse_length_uint16 = -1; +static gint hf_mpsse_bytes_out = -1; +static gint hf_mpsse_bytes_in = -1; +static gint hf_mpsse_bits_out = -1; +static gint hf_mpsse_bits_in = -1; +static gint hf_mpsse_value = -1; +static gint hf_mpsse_value_b0 = -1; +static gint hf_mpsse_value_b1 = -1; +static gint hf_mpsse_value_b2 = -1; +static gint hf_mpsse_value_b3 = -1; +static gint hf_mpsse_value_b4 = -1; +static gint hf_mpsse_value_b5 = -1; +static gint hf_mpsse_value_b6 = -1; +static gint hf_mpsse_value_b7 = -1; +static gint hf_mpsse_direction = -1; +static gint hf_mpsse_direction_b0 = -1; +static gint hf_mpsse_direction_b1 = -1; +static gint hf_mpsse_direction_b2 = -1; +static gint hf_mpsse_direction_b3 = -1; +static gint hf_mpsse_direction_b4 = -1; +static gint hf_mpsse_direction_b5 = -1; +static gint hf_mpsse_direction_b6 = -1; +static gint hf_mpsse_direction_b7 = -1; +static gint hf_mpsse_cpumode_address_short = -1; +static gint hf_mpsse_cpumode_address_extended = -1; +static gint hf_mpsse_cpumode_data = -1; +static gint hf_mpsse_clk_divisor = -1; +static gint hf_mpsse_open_drain_enable_low = -1; +static gint hf_mpsse_open_drain_enable_low_b0 = -1; +static gint hf_mpsse_open_drain_enable_low_b1 = -1; +static gint hf_mpsse_open_drain_enable_low_b2 = -1; +static gint hf_mpsse_open_drain_enable_low_b3 = -1; +static gint hf_mpsse_open_drain_enable_low_b4 = -1; +static gint hf_mpsse_open_drain_enable_low_b5 = -1; +static gint hf_mpsse_open_drain_enable_low_b6 = -1; +static gint hf_mpsse_open_drain_enable_low_b7 = -1; +static gint hf_mpsse_open_drain_enable_high = -1; +static gint hf_mpsse_open_drain_enable_high_b0 = -1; +static gint hf_mpsse_open_drain_enable_high_b1 = -1; +static gint hf_mpsse_open_drain_enable_high_b2 = -1; +static gint hf_mpsse_open_drain_enable_high_b3 = -1; +static gint hf_mpsse_open_drain_enable_high_b4 = -1; +static gint hf_mpsse_open_drain_enable_high_b5 = -1; +static gint hf_mpsse_open_drain_enable_high_b6 = -1; +static gint hf_mpsse_open_drain_enable_high_b7 = -1; + +static gint ett_ftdi_mpsse = -1; +static gint ett_mpsse_command = -1; +static gint ett_mpsse_command_with_parameters = -1; +static gint ett_mpsse_response_data = -1; +static gint ett_mpsse_value = -1; +static gint ett_mpsse_direction = -1; +static gint ett_mpsse_open_drain_enable = -1; +static gint ett_mpsse_skipped_response_data = -1; + +static expert_field ei_undecoded = EI_INIT; +static expert_field ei_response_without_command = EI_INIT; +static expert_field ei_skipped_response_data = EI_INIT; +static expert_field ei_reassembly_unavailable = EI_INIT; + +static dissector_handle_t ftdi_mpsse_handle; + +/* Commands expecting response add command_data_t entry to a list. The list is created when first command + * appears on MPSSE instance TX or when previously created list has matched responses to all entries. + * When a new list is created, head pointer is inserted into both tx_command_info and rx_command_info tree. + * + * When RX packet is dissected, it obtains the pointer to a list (if there isn't any then the capture is + * incomplete/malformed and ei_response_without_command is presented to the user). The RX dissection code + * matches commands with responses and updates the response_in_packet and is_response_set flag. When next + * RX packet is being dissected, it skips all the command_data_t entries that have is_response_set flag set. + * To reduce the effective list length that needs to be traversed, a pointer to the first element that does + * not have is_response_set flag set, is added to rx_command_info with the current packet number in the key. + * + * After first pass, RX packets always obtain relevant command_data_t entry without traversing the list. + * If there wasn't a separate tree TX packets (tx_command_info), TX packet dissection would have to + * traverse the list from the pointer obtained from rx_command_info. In normal conditions the number of + * entries to skip in such case is low. However, when the capture file has either: + * * A lot of TX packets with commands expecting response but no RX packets, or + * * Bad Command in TX packet that does not have matching Bad Command response in RX data + * then the traversal time in TX packet dissection becomes significant. To bring performance to acceptable + * levels, tx_command_info tree is being used. It contains pointers to the same list as rx_command_info but + * allows TX dissection to obtain the relevant command_data_t entry without traversing the list. + */ +static wmem_tree_t *rx_command_info = NULL; +static wmem_tree_t *tx_command_info = NULL; + +typedef struct _command_data command_data_t; + +struct _command_data { + ftdi_mpsse_info_t mpsse_info; + + /* TRUE if complete command parameters were not dissected yet */ + gboolean preliminary; + /* TRUE if response_in_packet has been set (response packet is known) */ + gboolean is_response_set; + guint8 cmd; + gint32 response_length; + guint32 command_in_packet; + guint32 response_in_packet; + + command_data_t *next; +}; + +void proto_register_ftdi_mpsse(void); + +#define BAD_COMMAND_SYNC_CODE 0xFA + +#define CMD_SET_DATA_BITS_LOW_BYTE 0x80 +#define CMD_READ_DATA_BITS_LOW_BYTE 0x81 +#define CMD_SET_DATA_BITS_HIGH_BYTE 0x82 +#define CMD_READ_DATA_BITS_HIGH_BYTE 0x83 +#define CMD_CLOCK_SET_DIVISOR 0x86 +#define CMD_CLOCK_N_BITS 0x8E +#define CMD_CLOCK_N_TIMES_8_BITS 0x8F +#define CMD_CPUMODE_READ_SHORT_ADDR 0x90 +#define CMD_CPUMODE_READ_EXT_ADDR 0x91 +#define CMD_CPUMODE_WRITE_SHORT_ADDR 0x92 +#define CMD_CPUMODE_WRITE_EXT_ADDR 0x93 +#define CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_HIGH 0x9C +#define CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_LOW 0x9D +#define CMD_IO_OPEN_DRAIN_ENABLE 0x9E + +static const value_string command_vals[] = { + {0x10, "Clock Data Bytes Out on + ve clock edge MSB first(no read) [Use if CLK starts at '1']"}, + {0x11, "Clock Data Bytes Out on -ve clock edge MSB first (no read) [Use if CLK starts at '0']"}, + {0x12, "Clock Data Bits Out on +ve clock edge MSB first (no read) [Use if CLK starts at '1']"}, + {0x13, "Clock Data Bits Out on -ve clock edge MSB first (no read) [Use if CLK starts at '0']"}, + {0x18, "Clock Data Bytes Out on +ve clock edge LSB first (no read) [Use if CLK starts at '1']"}, + {0x19, "Clock Data Bytes Out on -ve clock edge LSB first (no read) [Use if CLK starts at '0']"}, + {0x1A, "Clock Data Bits Out on +ve clock edge LSB first (no read) [Use if CLK starts at '1']"}, + {0x1B, "Clock Data Bits Out on -ve clock edge LSB first (no read) [Use if CLK starts at '0']"}, + {0x20, "Clock Data Bytes In on +ve clock edge MSB first (no write)"}, + {0x22, "Clock Data Bits In on +ve clock edge MSB first (no write) [TDO/DI sampled just prior to rising edge]"}, + {0x24, "Clock Data Bytes In on -ve clock edge MSB first (no write)"}, + {0x26, "Clock Data Bits In on -ve clock edge MSB first (no write) [TDO/DI sampled just prior to falling edge]"}, + {0x28, "Clock Data Bytes In on +ve clock edge LSB first (no write)"}, + {0x29, "Clock Data Bytes In on +ve clock edge LSB first (no write) [undocumented alternative]"}, + {0x2A, "Clock Data Bits In on +ve clock edge LSB first (no write) [TDO/DI sampled just prior to rising edge]"}, + {0x2B, "Clock Data Bits In on +ve clock edge LSB first (no write) [TDO/DI sampled just prior to rising edge] [undocumented alternative]"}, + {0x2C, "Clock Data Bytes In on -ve clock edge LSB first (no write)"}, + {0x2E, "Clock Data Bits In on -ve clock edge LSB first (no write) [TDO/DI sampled just prior to falling edge]"}, + {0x31, "Clock Data Bytes In and Out MSB first [out on -ve edge, in on +ve edge]"}, + {0x33, "Clock Data Bits In and Out MSB first [out on -ve edge, in on +ve edge]"}, + {0x34, "Clock Data Bytes In and Out MSB first [out on +ve edge, in on -ve edge]"}, + {0x36, "Clock Data Bits In and Out MSB first [out on +ve edge, in on -ve edge]"}, + {0x39, "Clock Data Bytes In and Out LSB first [out on -ve edge, in on +ve edge]"}, + {0x3B, "Clock Data Bits In and Out LSB first [out on -ve edge, in on +ve edge]"}, + {0x3C, "Clock Data Bytes In and Out LSB first [out on +ve edge, in on -ve edge]"}, + {0x3E, "Clock Data Bits In and Out LSB first [out on +ve edge, in on -ve edge]"}, + {0x4A, "Clock Data to TMS pin (no read) [TMS with LSB first on +ve clk edge - use if clk is set to '1']"}, + {0x4B, "Clock Data to TMS pin (no read) [TMS with LSB first on -ve clk edge - use if clk is set to '0']"}, + {0x6A, "Clock Data to TMS pin with read [TMS with LSB first on +ve clk edge, read on +ve edge - use if clk is set to '1']"}, + {0x6B, "Clock Data to TMS pin with read [TMS with LSB first on -ve clk edge, read on +ve edge - use if clk is set to '0']"}, + {0x6E, "Clock Data to TMS pin with read [TMS with LSB first on +ve clk edge, read on -ve edge - use if clk is set to '1']"}, + {0x6F, "Clock Data to TMS pin with read [TMS with LSB first on -ve clk edge, read on -ve edge - use if clk is set to '0']"}, + {CMD_SET_DATA_BITS_LOW_BYTE, "Set Data bits LowByte"}, + {CMD_READ_DATA_BITS_LOW_BYTE, "Read Data bits LowByte"}, + {CMD_SET_DATA_BITS_HIGH_BYTE, "Set Data bits HighByte"}, + {CMD_READ_DATA_BITS_HIGH_BYTE, "Read Data bits HighByte"}, + {0x84, "Connect TDI to TDO for Loopback"}, + {0x85, "Disconnect TDI to TDO for Loopback"}, + {0x87, "Send Immediate (flush buffer back to the PC)"}, + {0x88, "Wait On I/O High (wait until GPIOL1 (JTAG) or I/O1 (CPU) is high)"}, + {0x89, "Wait On I/O Low (wait until GPIOL1 (JTAG) or I/O1 (CPU) is low)"}, + {0, NULL} +}; +static value_string_ext command_vals_ext = VALUE_STRING_EXT_INIT(command_vals); + +static const value_string cpumode_command_vals[] = { + {CMD_CPUMODE_READ_SHORT_ADDR, "CPUMode Read Short Address"}, + {CMD_CPUMODE_READ_EXT_ADDR, "CPUMode Read Extended Address"}, + {CMD_CPUMODE_WRITE_SHORT_ADDR, "CPUMode Write Short Address"}, + {CMD_CPUMODE_WRITE_EXT_ADDR, "CPUMode Write Extended Address"}, + {0, NULL} +}; +static value_string_ext cpumode_command_vals_ext = VALUE_STRING_EXT_INIT(cpumode_command_vals); + +static const value_string ft2232d_only_command_vals[] = { + {CMD_CLOCK_SET_DIVISOR, "Set TCK/SK Divisor"}, + {0, NULL} +}; + +/* FT232H, FT2232H and FT4232H only commands */ +static const value_string h_only_command_vals[] = { + {CMD_CLOCK_SET_DIVISOR, "Set clk divisor"}, + {0x8A, "Disable Clk Divide by 5"}, + {0x8B, "Enable Clk Divide by 5"}, + {0x8C, "Enable 3 Phase Data Clocking"}, + {0x8D, "Disable 3 Phase Data Clocking"}, + {CMD_CLOCK_N_BITS, "Clock For n bits with no data transfer"}, + {CMD_CLOCK_N_TIMES_8_BITS, "Clock For n x 8 bits with no data transfer"}, + {0x94, "Clk continuously and Wait On I/O High"}, + {0x95, "Clk continuously and Wait On I/O Low"}, + {0x96, "Turn On Adaptive clocking"}, + {0x97, "Turn Off Adaptive clocking"}, + {CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_HIGH, "Clock For n x 8 bits with no data transfer or Until GPIOL1 is High"}, + {CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_LOW, "Clock For n x 8 bits with no data transfer or Until GPIOL1 is Low"}, + {0, NULL} +}; +static value_string_ext h_only_command_vals_ext = VALUE_STRING_EXT_INIT(h_only_command_vals); + +static const value_string ft232h_only_command_vals[] = { + {CMD_IO_OPEN_DRAIN_ENABLE, "Set I/O to only drive on a '0' and tristate on a '1'"}, + {0, NULL} +}; + +static const value_string data_shifting_command_b1_vals[] = { + {0, "Byte mode"}, + {1, "Bit mode"}, + {0, NULL} +}; + +static const value_string data_shifting_command_b3_vals[] = { + {0, "MSB first"}, + {1, "LSB first"}, + {0, NULL} +}; + +static const value_string command_b7_vals[] = { + {0, "Data Shifting Command"}, + {1, "Other (Not Data Shifting) Command"}, + {0, NULL} +}; + +#define IS_DATA_SHIFTING_COMMAND_BIT_ACTIVE(cmd) ((cmd & (1u << 7)) == 0) +#define IS_DATA_SHIFTING_BYTE_MODE(cmd) ((cmd & (1u << 1)) == 0) +#define IS_DATA_SHIFTING_MSB_FIRST(cmd) ((cmd & (1u << 3)) == 0) +#define IS_DATA_SHIFTING_WRITING_TDI(cmd) (cmd & (1u << 4)) +#define IS_DATA_SHIFTING_READING_TDO(cmd) (cmd & (1u << 5)) +#define IS_DATA_SHIFTING_WRITING_TMS(cmd) (cmd & (1u << 6)) + +static gboolean is_data_shifting_command(guint8 cmd) +{ + switch (cmd) + { + /* Not all data shifting commands (with bit 7 clear) are explicitly listed in MPSSE documentation + * Some undocumented data shifting commands trigger BadCommmand response, but some seem to be handled by the device. + * + * Commands listed below (with bit 7 clear) trigger BadCommand response on FT2232L, FT232H and FT2232H + */ + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: + case 0x4C: case 0x4D: + case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: + case 0x5C: case 0x5D: + case 0x60: case 0x61: + case 0x64: case 0x65: + case 0x68: case 0x69: + case 0x6C: case 0x6D: + case 0x70: case 0x71: + case 0x74: case 0x75: + case 0x78: case 0x79: + case 0x7C: case 0x7D: + return FALSE; + default: + return IS_DATA_SHIFTING_COMMAND_BIT_ACTIVE(cmd); + } +} + +static gboolean is_data_shifting_command_returning_response(guint8 cmd, ftdi_mpsse_info_t *mpsse_info) +{ + DISSECTOR_ASSERT(is_data_shifting_command(cmd)); + if (mpsse_info->mcu_mode) + { + /* MCU mode seems to consume data shifting payloads but do not actually return any response data */ + return FALSE; + } + + return IS_DATA_SHIFTING_READING_TDO(cmd) ? TRUE : FALSE; +} + +/* Returns human-readable command description string or NULL on BadCommand */ +static const char * +get_command_string(guint8 cmd, ftdi_mpsse_info_t *mpsse_info) +{ + const char *str; + + /* First, try commands that are common on all chips */ + str = try_val_to_str_ext(cmd, &command_vals_ext); + if (str) + { + return str; + } + + if (is_data_shifting_command(cmd)) + { + return "Undocumented Data Shifting Command"; + } + + /* Check chip specific commands */ + switch (mpsse_info->chip) + { + case FTDI_CHIP_FT2232D: + str = try_val_to_str(cmd, ft2232d_only_command_vals); + break; + case FTDI_CHIP_FT232H: + str = try_val_to_str(cmd, ft232h_only_command_vals); + if (str) + { + break; + } + /* Fallthrough */ + case FTDI_CHIP_FT2232H: + case FTDI_CHIP_FT4232H: + str = try_val_to_str_ext(cmd, &h_only_command_vals_ext); + break; + default: + break; + } + + if (!str && mpsse_info->mcu_mode) + { + str = try_val_to_str_ext(cmd, &cpumode_command_vals_ext); + } + + return str; +} + +static gboolean is_valid_command(guint8 cmd, ftdi_mpsse_info_t *mpsse_info) +{ + return get_command_string(cmd, mpsse_info) != NULL; +} + +static gboolean is_same_mpsse_instance(ftdi_mpsse_info_t *info1, ftdi_mpsse_info_t *info2) +{ + return (info1->bus_id == info2->bus_id) && + (info1->device_address == info2->device_address) && + (info1->chip == info2->chip) && + (info1->iface == info2->iface) && + (info1->mcu_mode == info2->mcu_mode); +} + +static command_data_t * +get_recorded_command_data(wmem_tree_t *command_tree, packet_info *pinfo, ftdi_mpsse_info_t *mpsse_info) +{ + guint32 k_bus_id = mpsse_info->bus_id; + guint32 k_device_address = mpsse_info->device_address; + guint32 k_chip = (guint32)mpsse_info->chip; + guint32 k_interface = (guint32)mpsse_info->iface; + guint32 k_mcu_mode = mpsse_info->mcu_mode; + wmem_tree_key_t key[] = { + {1, &k_bus_id}, + {1, &k_device_address}, + {1, &k_chip}, + {1, &k_interface}, + {1, &k_mcu_mode}, + {1, &pinfo->num}, + {0, NULL} + }; + + command_data_t *data = NULL; + data = (command_data_t *)wmem_tree_lookup32_array_le(command_tree, key); + if (data && is_same_mpsse_instance(mpsse_info, &data->mpsse_info)) + { + return data; + } + + return NULL; +} + +static void +insert_command_data_pointer(wmem_tree_t *command_tree, packet_info *pinfo, ftdi_mpsse_info_t *mpsse_info, command_data_t *data) +{ + guint32 k_bus_id = mpsse_info->bus_id; + guint32 k_device_address = mpsse_info->device_address; + guint32 k_chip = (guint32)mpsse_info->chip; + guint32 k_interface = (guint32)mpsse_info->iface; + guint32 k_mcu_mode = mpsse_info->mcu_mode; + wmem_tree_key_t key[] = { + {1, &k_bus_id}, + {1, &k_device_address}, + {1, &k_chip}, + {1, &k_interface}, + {1, &k_mcu_mode}, + {1, &pinfo->num}, + {0, NULL} + }; + + wmem_tree_insert32_array(command_tree, key, data); +} + +static void +record_command_data(command_data_t **cmd_data, packet_info *pinfo, ftdi_mpsse_info_t *mpsse_info, guint8 cmd, + gint32 response_length, gboolean preliminary) +{ + command_data_t *data = *cmd_data; + + DISSECTOR_ASSERT(response_length > 0); + + if (data && data->preliminary) + { + DISSECTOR_ASSERT(data->cmd == cmd); + DISSECTOR_ASSERT(data->response_length == response_length); + data->command_in_packet = pinfo->num; + data->preliminary = preliminary; + return; + } + + data = wmem_new(wmem_file_scope(), command_data_t); + memcpy(&data->mpsse_info, mpsse_info, sizeof(ftdi_mpsse_info_t)); + data->preliminary = preliminary; + data->is_response_set = FALSE; + data->cmd = cmd; + data->response_length = response_length; + data->command_in_packet = pinfo->num; + data->response_in_packet = 0; + data->next = NULL; + + if (*cmd_data && (!(*cmd_data)->is_response_set)) + { + DISSECTOR_ASSERT((*cmd_data)->next == NULL); + (*cmd_data)->next = data; + if ((*cmd_data)->command_in_packet != pinfo->num) + { + insert_command_data_pointer(tx_command_info, pinfo, mpsse_info, data); + } + } + else + { + insert_command_data_pointer(rx_command_info, pinfo, mpsse_info, data); + insert_command_data_pointer(tx_command_info, pinfo, mpsse_info, data); + } + *cmd_data = data; +} + +static void expect_response(command_data_t **cmd_data, packet_info *pinfo, proto_tree *tree, + ftdi_mpsse_info_t *mpsse_info, guint8 cmd, guint16 response_length) +{ + if (pinfo->fd->visited) + { + DISSECTOR_ASSERT(*cmd_data); + DISSECTOR_ASSERT((*cmd_data)->cmd == cmd); + DISSECTOR_ASSERT((*cmd_data)->response_length == response_length); + if ((*cmd_data)->is_response_set) + { + proto_tree *response_in = proto_tree_add_uint(tree, hf_mpsse_response_in, NULL, 0, 0, (*cmd_data)->response_in_packet); + proto_item_set_generated(response_in); + DISSECTOR_ASSERT((*cmd_data)->command_in_packet == pinfo->num); + } + *cmd_data = (*cmd_data)->next; + } + else + { + record_command_data(cmd_data, pinfo, mpsse_info, cmd, response_length, FALSE); + } +} + +static gchar* freq_to_str(gfloat freq) +{ + if (freq < 1e3) + { + return ws_strdup_printf("%.12g Hz", freq); + } + else if (freq < 1e6) + { + return ws_strdup_printf("%.12g kHz", freq / 1e3); + } + else + { + return ws_strdup_printf("%.12g MHz", freq / 1e6); + } +} + +static gint +dissect_data_shifting_command_parameters(guint8 cmd, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, + ftdi_mpsse_info_t *mpsse_info, command_data_t **cmd_data) +{ + gint offset_start = offset; + gint32 length; + + DISSECTOR_ASSERT(is_data_shifting_command(cmd)); + + if (IS_DATA_SHIFTING_BYTE_MODE(cmd)) + { + length = tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN); + proto_tree_add_uint_format(tree, hf_mpsse_length_uint16, tvb, offset, 2, length, "Length: %d byte%s", length + 1, plurality(length + 1, "", "s")); + offset += 2; + + if (IS_DATA_SHIFTING_WRITING_TDI(cmd)) + { + proto_tree_add_item(tree, hf_mpsse_bytes_out, tvb, offset, length + 1, ENC_NA); + offset += length + 1; + } + } + else + { + length = tvb_get_guint8(tvb, offset); + proto_tree_add_uint_format(tree, hf_mpsse_length_uint8, tvb, offset, 1, length, "Length: %d bit%s", length + 1, plurality(length + 1, "", "s")); + offset += 1; + + if (IS_DATA_SHIFTING_WRITING_TMS(cmd) && IS_DATA_SHIFTING_READING_TDO(cmd) && IS_DATA_SHIFTING_MSB_FIRST(cmd)) + { + /* These undocumented commands do not seem to consume the data byte, only the length */ + } + else if (IS_DATA_SHIFTING_WRITING_TDI(cmd) || IS_DATA_SHIFTING_WRITING_TMS(cmd)) + { + proto_tree_add_item(tree, hf_mpsse_bits_out, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset += 1; + } + } + + if (is_data_shifting_command_returning_response(cmd, mpsse_info)) + { + expect_response(cmd_data, pinfo, tree, mpsse_info, cmd, IS_DATA_SHIFTING_BYTE_MODE(cmd) ? length + 1 : 1); + } + + return offset - offset_start; +} + +static gint +dissect_set_data_bits_parameters(guint8 cmd _U_, tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, + const char *signal_names[8], const char *pin_prefix, guint num_pins) +{ + static const gint *value_bits_hf[] = { + &hf_mpsse_value_b0, + &hf_mpsse_value_b1, + &hf_mpsse_value_b2, + &hf_mpsse_value_b3, + &hf_mpsse_value_b4, + &hf_mpsse_value_b5, + &hf_mpsse_value_b6, + &hf_mpsse_value_b7, + }; + static const gint *direction_bits_hf[] = { + &hf_mpsse_direction_b0, + &hf_mpsse_direction_b1, + &hf_mpsse_direction_b2, + &hf_mpsse_direction_b3, + &hf_mpsse_direction_b4, + &hf_mpsse_direction_b5, + &hf_mpsse_direction_b6, + &hf_mpsse_direction_b7, + }; + guint32 value, direction; + proto_item *item; + proto_item *value_item, *direction_item; + proto_tree *value_tree, *direction_tree; + guint bit; + + value_item = proto_tree_add_item_ret_uint(tree, hf_mpsse_value, tvb, offset, 1, ENC_LITTLE_ENDIAN, &value); + direction_item = proto_tree_add_item_ret_uint(tree, hf_mpsse_direction, tvb, offset + 1, 1, ENC_LITTLE_ENDIAN, &direction); + + value_tree = proto_item_add_subtree(value_item, ett_mpsse_value); + for (bit = 0; bit < 8; bit++) + { + const char *state; + if ((1 << bit) & direction) + { + state = ((1 << bit) & value) ? "Output High" : "Output Low"; + } + else + { + state = "N/A (Input)"; + } + item = proto_tree_add_uint_format_value(value_tree, *value_bits_hf[bit], tvb, offset, 1, value, "%s", signal_names[bit]); + if (pin_prefix && (bit < num_pins)) + { + proto_item_append_text(item, " [%s%d]", pin_prefix, bit); + } + proto_item_append_text(item, " %s", state); + } + + direction_tree = proto_item_add_subtree(direction_item, ett_mpsse_direction); + for (bit = 0; bit < 8; bit++) + { + const char *type = ((1 << bit) & direction) ? "Output" : "Input"; + item = proto_tree_add_uint_format_value(direction_tree, *direction_bits_hf[bit], tvb, offset + 1, 1, direction, "%s", signal_names[bit]); + if (pin_prefix && (bit < num_pins)) + { + proto_item_append_text(item, " [%s%d]", pin_prefix, bit); + } + proto_item_append_text(item, " %s", type); + } + + return 2; +} + +static gint +dissect_cpumode_parameters(guint8 cmd, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, + ftdi_mpsse_info_t *mpsse_info, command_data_t **cmd_data) +{ + gint offset_start = offset; + + /* Address is either short (1 byte) or extended (2 bytes) */ + if ((cmd == CMD_CPUMODE_READ_SHORT_ADDR) || (cmd == CMD_CPUMODE_WRITE_SHORT_ADDR)) + { + proto_tree_add_item(tree, hf_mpsse_cpumode_address_short, tvb, offset, 1, ENC_BIG_ENDIAN); + offset += 1; + } + else if ((cmd == CMD_CPUMODE_READ_EXT_ADDR) || (cmd == CMD_CPUMODE_WRITE_EXT_ADDR)) + { + proto_tree_add_item(tree, hf_mpsse_cpumode_address_extended, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + } + + /* Write commands have data parameter (1 byte) */ + if ((cmd == CMD_CPUMODE_WRITE_SHORT_ADDR) || (cmd == CMD_CPUMODE_WRITE_EXT_ADDR)) + { + proto_tree_add_item(tree, hf_mpsse_cpumode_data, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset += 1; + } + + if ((cmd == CMD_CPUMODE_READ_SHORT_ADDR) || (cmd == CMD_CPUMODE_READ_EXT_ADDR)) + { + expect_response(cmd_data, pinfo, tree, mpsse_info, cmd, 1); + } + + return offset - offset_start; +} + +static gint +dissect_clock_parameters(guint8 cmd _U_, tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, ftdi_mpsse_info_t *mpsse_info) +{ + gint offset_start = offset; + guint32 value; + proto_item *item; + gchar *str_old, *str; + + item = proto_tree_add_item_ret_uint(tree, hf_mpsse_clk_divisor, tvb, offset, 2, ENC_LITTLE_ENDIAN, &value); + offset += 2; + + str_old = freq_to_str((gfloat) 12e6 / ((1 + value) * 2)); + str = freq_to_str((gfloat) 60e6 / ((1 + value) * 2)); + + if (mpsse_info->chip == FTDI_CHIP_FT2232D) + { + proto_item_append_text(item, ", TCK/SK Max: %s", str_old); + } + else + { + proto_item_append_text(item, ", TCK Max: %s (60 MHz master clock) or %s (12 MHz master clock)", str, str_old); + } + + g_free(str_old); + g_free(str); + + return offset - offset_start; +} + +static gint +dissect_clock_n_bits_parameters(guint8 cmd _U_, tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, ftdi_mpsse_info_t *mpsse_info _U_) +{ + guint32 length = tvb_get_guint8(tvb, offset); + proto_tree_add_uint_format(tree, hf_mpsse_length_uint8, tvb, offset, 1, length, "Length: %d clock%s", length + 1, plurality(length + 1, "", "s")); + return 1; +} + +static gint +dissect_clock_n_times_8_bits_parameters(guint8 cmd _U_, tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, ftdi_mpsse_info_t *mpsse_info _U_) +{ + guint32 length = tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN); + proto_tree_add_uint_format(tree, hf_mpsse_length_uint16, tvb, offset, 2, length, "Length: %d clocks", (length + 1) * 8); + return 2; +} + +static const char * +get_data_bit_pin_prefix(gboolean is_high_byte, ftdi_mpsse_info_t *mpsse_info, guint *out_num_pins, const char *(**out_names)[8]) +{ + static const char *low_byte_signal_names[8] = { + "TCK/SK", + "TDI/DO", + "TDO/DI", + "TMS/CS", + "GPIOL0", + "GPIOL1", + "GPIOL2", + "GPIOL3", + }; + static const char *high_byte_signal_names[8] = { + "GPIOH0", + "GPIOH1", + "GPIOH2", + "GPIOH3", + "GPIOH4", + "GPIOH5", + "GPIOH6", + "GPIOH7", + }; + + *out_names = (is_high_byte) ? &high_byte_signal_names : &low_byte_signal_names; + + /* Based on table from FTDI AN_108 chapter 2.1 Data bit Definition */ + switch (mpsse_info->chip) + { + case FTDI_CHIP_FT2232D: + if (mpsse_info->iface == FTDI_INTERFACE_A) + { + *out_num_pins = (is_high_byte) ? 4 : 8; + return (is_high_byte) ? "ACBUS" : "ADBUS"; + } + break; + case FTDI_CHIP_FT232H: + *out_num_pins = 8; + return (is_high_byte) ? "ACBUS" : "ADBUS"; + case FTDI_CHIP_FT2232H: + if (mpsse_info->iface == FTDI_INTERFACE_A) + { + *out_num_pins = 8; + return (is_high_byte) ? "ACBUS" : "ADBUS"; + } + else if (mpsse_info->iface == FTDI_INTERFACE_B) + { + *out_num_pins = 8; + return (is_high_byte) ? "BCBUS" : "BDBUS"; + } + break; + case FTDI_CHIP_FT4232H: + if (!is_high_byte) + { + if (mpsse_info->iface == FTDI_INTERFACE_A) + { + *out_num_pins = 8; + return "ADBUS"; + } + else if (mpsse_info->iface == FTDI_INTERFACE_B) + { + *out_num_pins = 8; + return "BDBUS"; + } + } + break; + default: + break; + } + + *out_num_pins = 0; + return NULL; +} + +static gint +dissect_io_open_drain_enable_parameters(guint8 cmd _U_, tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, ftdi_mpsse_info_t *mpsse_info _U_) +{ + static const gint *low_byte_bits_hf[] = { + &hf_mpsse_open_drain_enable_low_b0, + &hf_mpsse_open_drain_enable_low_b1, + &hf_mpsse_open_drain_enable_low_b2, + &hf_mpsse_open_drain_enable_low_b3, + &hf_mpsse_open_drain_enable_low_b4, + &hf_mpsse_open_drain_enable_low_b5, + &hf_mpsse_open_drain_enable_low_b6, + &hf_mpsse_open_drain_enable_low_b7, + }; + static const gint *high_byte_bits_hf[] = { + &hf_mpsse_open_drain_enable_high_b0, + &hf_mpsse_open_drain_enable_high_b1, + &hf_mpsse_open_drain_enable_high_b2, + &hf_mpsse_open_drain_enable_high_b3, + &hf_mpsse_open_drain_enable_high_b4, + &hf_mpsse_open_drain_enable_high_b5, + &hf_mpsse_open_drain_enable_high_b6, + &hf_mpsse_open_drain_enable_high_b7, + }; + gint offset_start = offset; + const char *pin_prefix = NULL; + guint num_pins = 0; + const char *(*signal_names)[8] = NULL; + guint32 value; + proto_item *item; + proto_item *byte_item; + proto_tree *byte_tree; + guint bit; + + pin_prefix = get_data_bit_pin_prefix(FALSE, mpsse_info, &num_pins, &signal_names); + byte_item = proto_tree_add_item_ret_uint(tree, hf_mpsse_open_drain_enable_low, tvb, offset, 1, ENC_LITTLE_ENDIAN, &value); + byte_tree = proto_item_add_subtree(byte_item, ett_mpsse_open_drain_enable); + for (bit = 0; bit < 8; bit++) + { + const char *output_type = ((1 << bit) & value) ? "Open-Drain" : "Push-Pull"; + item = proto_tree_add_uint_format_value(byte_tree, *low_byte_bits_hf[bit], tvb, offset, 1, value, "%s", (*signal_names)[bit]); + if (pin_prefix && (bit < num_pins)) + { + proto_item_append_text(item, " [%s%d]", pin_prefix, bit); + } + proto_item_append_text(item, " %s", output_type); + } + offset++; + + pin_prefix = get_data_bit_pin_prefix(TRUE, mpsse_info, &num_pins, &signal_names); + byte_item = proto_tree_add_item_ret_uint(tree, hf_mpsse_open_drain_enable_high, tvb, offset, 1, ENC_LITTLE_ENDIAN, &value); + byte_tree = proto_item_add_subtree(byte_item, ett_mpsse_open_drain_enable); + for (bit = 0; bit < 8; bit++) + { + const char *output_type = ((1 << bit) & value) ? "Open-Drain" : "Push-Pull"; + item = proto_tree_add_uint_format_value(byte_tree, *high_byte_bits_hf[bit], tvb, offset, 1, value, "%s", (*signal_names)[bit]); + if (pin_prefix && (bit < num_pins)) + { + proto_item_append_text(item, " [%s%d]", pin_prefix, bit); + } + proto_item_append_text(item, " %s", output_type); + } + offset++; + + return offset - offset_start; +} + +static gint +dissect_non_data_shifting_command_parameters(guint8 cmd, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, + ftdi_mpsse_info_t *mpsse_info, command_data_t **cmd_data) +{ + const char *pin_prefix = NULL; + guint num_pins = 0; + const char *(*signal_names)[8] = NULL; + + DISSECTOR_ASSERT(!is_data_shifting_command(cmd) && is_valid_command(cmd, mpsse_info)); + + switch (cmd) + { + case CMD_SET_DATA_BITS_LOW_BYTE: + pin_prefix = get_data_bit_pin_prefix(FALSE, mpsse_info, &num_pins, &signal_names); + return dissect_set_data_bits_parameters(cmd, tvb, pinfo, tree, offset, *signal_names, pin_prefix, num_pins); + case CMD_SET_DATA_BITS_HIGH_BYTE: + pin_prefix = get_data_bit_pin_prefix(TRUE, mpsse_info, &num_pins, &signal_names); + return dissect_set_data_bits_parameters(cmd, tvb, pinfo, tree, offset, *signal_names, pin_prefix, num_pins); + case CMD_READ_DATA_BITS_LOW_BYTE: + case CMD_READ_DATA_BITS_HIGH_BYTE: + expect_response(cmd_data, pinfo, tree, mpsse_info, cmd, 1); + return 0; + case CMD_CPUMODE_READ_SHORT_ADDR: + case CMD_CPUMODE_READ_EXT_ADDR: + case CMD_CPUMODE_WRITE_SHORT_ADDR: + case CMD_CPUMODE_WRITE_EXT_ADDR: + return dissect_cpumode_parameters(cmd, tvb, pinfo, tree, offset, mpsse_info, cmd_data); + case CMD_CLOCK_SET_DIVISOR: + return dissect_clock_parameters(cmd, tvb, pinfo, tree, offset, mpsse_info); + case CMD_CLOCK_N_BITS: + return dissect_clock_n_bits_parameters(cmd, tvb, pinfo, tree, offset, mpsse_info); + case CMD_CLOCK_N_TIMES_8_BITS: + case CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_HIGH: + case CMD_CLOCK_N_TIMES_8_BITS_OR_UNTIL_L1_LOW: + return dissect_clock_n_times_8_bits_parameters(cmd, tvb, pinfo, tree, offset, mpsse_info); + case CMD_IO_OPEN_DRAIN_ENABLE: + return dissect_io_open_drain_enable_parameters(cmd, tvb, pinfo, tree, offset, mpsse_info); + default: + return 0; + } +} + +static gint estimated_command_parameters_length(guint8 cmd, tvbuff_t *tvb, packet_info *pinfo, gint offset, + ftdi_mpsse_info_t *mpsse_info, command_data_t **cmd_data) +{ + gint parameters_length = 0; + + if (!is_valid_command(cmd, mpsse_info)) + { + return 0; + } + + if (is_data_shifting_command(cmd)) + { + gint32 data_length = 0; + if (IS_DATA_SHIFTING_BYTE_MODE(cmd)) + { + parameters_length = 2; + if (IS_DATA_SHIFTING_WRITING_TDI(cmd)) + { + if (tvb_reported_length_remaining(tvb, offset) >= 2) + { + data_length = (gint32)tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN) + 1; + parameters_length += data_length; + } + /* else length is not available already so the caller will know that reassembly is needed */ + } + } + else /* bit mode */ + { + parameters_length = (IS_DATA_SHIFTING_WRITING_TDI(cmd) || IS_DATA_SHIFTING_WRITING_TMS(cmd)) ? 2 : 1; + data_length = 1; + if (IS_DATA_SHIFTING_WRITING_TMS(cmd) && IS_DATA_SHIFTING_READING_TDO(cmd) && IS_DATA_SHIFTING_MSB_FIRST(cmd)) + { + /* These undocumented commands do not seem to consume the data byte, only the length */ + parameters_length = 1; + } + } + + if (!pinfo->fd->visited) + { + if (is_data_shifting_command_returning_response(cmd, mpsse_info) && data_length) + { + /* Record preliminary command info so the response handler can find the matching command + * if host starts reading data before all output is sent. If this command requires reassembly + * the command_in_packet member will continue updating until the reassembly is complete. + * The preliminary flag will be reset when expect_response() executes. + */ + record_command_data(cmd_data, pinfo, mpsse_info, cmd, data_length, TRUE); + } + } + } + else + { + switch (cmd) + { + case CMD_CPUMODE_WRITE_EXT_ADDR: + parameters_length = 3; + break; + case CMD_SET_DATA_BITS_LOW_BYTE: + case CMD_SET_DATA_BITS_HIGH_BYTE: + case CMD_CPUMODE_READ_EXT_ADDR: + case CMD_CPUMODE_WRITE_SHORT_ADDR: + case CMD_CLOCK_SET_DIVISOR: + case 0x8F: case 0x9C: case 0x9D: case 0x9E: + parameters_length = 2; + break; + case CMD_CPUMODE_READ_SHORT_ADDR: + case 0x8E: + parameters_length = 1; + break; + case 0x81: case 0x83: case 0x84: case 0x85: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x94: case 0x95: case 0x96: case 0x97: + parameters_length = 0; + break; + default: + DISSECTOR_ASSERT_NOT_REACHED(); + } + } + + return parameters_length; +} + +static guint8 +dissect_command_code(guint8 cmd, const char *cmd_str, tvbuff_t *tvb, proto_tree *tree, gint offset, ftdi_mpsse_info_t *mpsse_info _U_) +{ + proto_item *cmd_item; + proto_tree *cmd_tree; + int * const *cmd_bits; + static int * const data_shifting_cmd_bits[] = { + &hf_mpsse_command_b7, + &hf_mpsse_command_b6, + &hf_mpsse_command_b5, + &hf_mpsse_command_b4, + &hf_mpsse_command_b3, + &hf_mpsse_command_b2, + &hf_mpsse_command_b1, + &hf_mpsse_command_b0, + NULL + }; + + static int * const non_data_shifting_cmd_bits[] = { + &hf_mpsse_command_b7, + NULL + }; + + cmd_item = proto_tree_add_uint_format(tree, hf_mpsse_command, tvb, offset, 1, cmd, "Command: %s (0x%02x)", cmd_str, cmd); + cmd_tree = proto_item_add_subtree(cmd_item, ett_mpsse_command); + cmd_bits = IS_DATA_SHIFTING_COMMAND_BIT_ACTIVE(cmd) ? data_shifting_cmd_bits : non_data_shifting_cmd_bits; + + proto_tree_add_bitmask_list_value(cmd_tree, tvb, offset, 1, cmd_bits, cmd); + + return cmd; +} + +static gint +dissect_command(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, gboolean *need_reassembly, + ftdi_mpsse_info_t *mpsse_info, command_data_t **cmd_data) +{ + guint8 cmd; + const char *cmd_str; + gint offset_start = offset; + gint parameters_length; + gint dissected; + proto_item *cmd_with_parameters; + proto_tree *cmd_tree; + + cmd = tvb_get_guint8(tvb, offset); + cmd_str = get_command_string(cmd, mpsse_info); + parameters_length = estimated_command_parameters_length(cmd, tvb, pinfo, offset + 1, mpsse_info, cmd_data); + if (tvb_reported_length_remaining(tvb, offset + 1) < parameters_length) + { + *need_reassembly = TRUE; + return 0; + } + + if (!cmd_str) + { + cmd_str = "Bad Command"; + } + + cmd_with_parameters = proto_tree_add_bytes_format(tree, hf_mpsse_command_with_parameters, tvb, offset, 1 + parameters_length, NULL, "%s", cmd_str); + cmd_tree = proto_item_add_subtree(cmd_with_parameters, ett_mpsse_command_with_parameters); + + cmd = dissect_command_code(cmd, cmd_str, tvb, cmd_tree, offset, mpsse_info); + offset += 1; + + *need_reassembly = FALSE; + if (is_valid_command(cmd, mpsse_info)) + { + if (IS_DATA_SHIFTING_COMMAND_BIT_ACTIVE(cmd)) + { + dissected = dissect_data_shifting_command_parameters(cmd, tvb, pinfo, cmd_tree, offset, mpsse_info, cmd_data); + DISSECTOR_ASSERT(dissected == parameters_length); + offset += dissected; + } + else + { + dissected = dissect_non_data_shifting_command_parameters(cmd, tvb, pinfo, cmd_tree, offset, mpsse_info, cmd_data); + if (parameters_length > dissected) + { + proto_tree_add_expert(cmd_tree, pinfo, &ei_undecoded, tvb, offset + dissected, parameters_length - dissected); + } + offset += parameters_length; + } + } + else + { + /* Expect Bad Command response */ + expect_response(cmd_data, pinfo, cmd_tree, mpsse_info, cmd, 2); + } + + return offset - offset_start; +} + + +static gint +dissect_read_data_bits_response(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset, + const char *signal_names[8], const char *pin_prefix, guint num_pins) +{ + static const gint *value_bits_hf[] = { + &hf_mpsse_value_b0, + &hf_mpsse_value_b1, + &hf_mpsse_value_b2, + &hf_mpsse_value_b3, + &hf_mpsse_value_b4, + &hf_mpsse_value_b5, + &hf_mpsse_value_b6, + &hf_mpsse_value_b7, + }; + guint32 value; + proto_item *item; + proto_item *value_item; + proto_tree *value_tree; + guint bit; + + value_item = proto_tree_add_item_ret_uint(tree, hf_mpsse_value, tvb, offset, 1, ENC_LITTLE_ENDIAN, &value); + value_tree = proto_item_add_subtree(value_item, ett_mpsse_value); + for (bit = 0; bit < 8; bit++) + { + const char *state; + state = ((1 << bit) & value) ? "High" : "Low"; + item = proto_tree_add_uint_format_value(value_tree, *value_bits_hf[bit], tvb, offset, 1, value, "%s", signal_names[bit]); + if (pin_prefix && (bit < num_pins)) + { + proto_item_append_text(item, " [%s%d]", pin_prefix, bit); + } + proto_item_append_text(item, " %s", state); + } + + return 1; +} + +static gint +dissect_cpumode_response(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset) +{ + proto_tree_add_item(tree, hf_mpsse_cpumode_data, tvb, offset, 1, ENC_LITTLE_ENDIAN); + return 1; +} + +static gint +dissect_non_data_shifting_command_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, command_data_t *cmd_data) +{ + const char *pin_prefix = NULL; + guint num_pins = 0; + const char *(*signal_names)[8] = NULL; + + DISSECTOR_ASSERT(!is_data_shifting_command(cmd_data->cmd) && is_valid_command(cmd_data->cmd, &cmd_data->mpsse_info)); + + switch (cmd_data->cmd) + { + case CMD_READ_DATA_BITS_LOW_BYTE: + pin_prefix = get_data_bit_pin_prefix(FALSE, &cmd_data->mpsse_info, &num_pins, &signal_names); + return dissect_read_data_bits_response(tvb, pinfo, tree, offset, *signal_names, pin_prefix, num_pins); + case CMD_READ_DATA_BITS_HIGH_BYTE: + pin_prefix = get_data_bit_pin_prefix(TRUE, &cmd_data->mpsse_info, &num_pins, &signal_names); + return dissect_read_data_bits_response(tvb, pinfo, tree, offset, *signal_names, pin_prefix, num_pins); + case CMD_CPUMODE_READ_SHORT_ADDR: + case CMD_CPUMODE_READ_EXT_ADDR: + return dissect_cpumode_response(tvb, pinfo, tree, offset); + default: + return 0; + } +} +static gint +dissect_response_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, command_data_t *cmd_data) +{ + gint offset_start = offset; + + if (pinfo->fd->visited) + { + DISSECTOR_ASSERT(cmd_data->is_response_set && cmd_data->response_in_packet == pinfo->num); + } + else + { + DISSECTOR_ASSERT(!cmd_data->is_response_set); + cmd_data->response_in_packet = pinfo->num; + cmd_data->is_response_set = TRUE; + } + + if (is_valid_command(cmd_data->cmd, &cmd_data->mpsse_info)) + { + if (IS_DATA_SHIFTING_COMMAND_BIT_ACTIVE(cmd_data->cmd)) + { + if (IS_DATA_SHIFTING_BYTE_MODE(cmd_data->cmd)) + { + proto_tree_add_item(tree, hf_mpsse_bytes_in, tvb, offset, cmd_data->response_length, ENC_NA); + } + else + { + proto_tree_add_item(tree, hf_mpsse_bits_in, tvb, offset, cmd_data->response_length, ENC_LITTLE_ENDIAN); + } + offset += cmd_data->response_length; + } + else + { + gint dissected; + + dissected = dissect_non_data_shifting_command_response(tvb, pinfo, tree, offset, cmd_data); + offset += dissected; + + DISSECTOR_ASSERT(dissected <= cmd_data->response_length); + if (cmd_data->response_length > dissected) + { + proto_tree_add_expert(tree, pinfo, &ei_undecoded, tvb, offset, cmd_data->response_length - dissected); + offset += (cmd_data->response_length - dissected); + } + } + } + else + { + proto_tree_add_item(tree, hf_mpsse_bad_command_error, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset++; + + proto_tree_add_item(tree, hf_mpsse_bad_command_code, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset++; + } + + return offset - offset_start; +} + +static gint +dissect_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, gboolean *need_reassembly, + command_data_t *cmd_data) +{ + const char *cmd_str; + gint offset_start = offset; + proto_item *rsp_data; + proto_tree *rsp_tree; + proto_item *command_in; + + cmd_str = get_command_string(cmd_data->cmd, &cmd_data->mpsse_info); + if (!cmd_str) + { + gboolean found = FALSE; + gboolean request_reassembly = FALSE; + + DISSECTOR_ASSERT(cmd_data->response_length == 2); + cmd_str = "Bad Command"; + + /* Look for Bad Command response in data */ + while (tvb_reported_length_remaining(tvb, offset) >= 2) + { + if (tvb_get_guint8(tvb, offset) == BAD_COMMAND_SYNC_CODE) + { + if (tvb_get_guint8(tvb, offset + 1) == cmd_data->cmd) + { + found = TRUE; + break; + } + } + offset++; + } + + if (!found) + { + if (tvb_get_guint8(tvb, offset) == BAD_COMMAND_SYNC_CODE) + { + /* Request reassembly only if there is chance it will help */ + request_reassembly = TRUE; + } + else + { + offset++; + } + } + + if (offset != offset_start) + { + proto_item *item; + proto_tree *expert_tree; + + item = proto_tree_add_expert(tree, pinfo, &ei_skipped_response_data, tvb, offset_start, offset - offset_start); + expert_tree = proto_item_add_subtree(item, ett_mpsse_skipped_response_data); + + command_in = proto_tree_add_uint_format(expert_tree, hf_mpsse_command_in, NULL, 0, 0, cmd_data->command_in_packet, + "Bad Command 0x%02x in: %" PRIu32, cmd_data->cmd, cmd_data->command_in_packet); + proto_item_set_generated(command_in); + if (cmd_data->is_response_set) + { + proto_item *response_in; + + response_in = proto_tree_add_uint(expert_tree, hf_mpsse_response_in, NULL, 0, 0, cmd_data->response_in_packet); + proto_item_set_generated(response_in); + } + } + + if (!found) + { + *need_reassembly = request_reassembly; + return offset - offset_start; + } + } + + if (tvb_reported_length_remaining(tvb, offset) < cmd_data->response_length) + { + *need_reassembly = TRUE; + return 0; + } + + rsp_data = proto_tree_add_bytes_format(tree, hf_mpsse_response, tvb, offset, cmd_data->response_length, NULL, "%s", cmd_str); + rsp_tree = proto_item_add_subtree(rsp_data, ett_mpsse_response_data); + + command_in = proto_tree_add_uint_format(rsp_tree, hf_mpsse_command_in, NULL, 0, 0, cmd_data->command_in_packet, + "Command 0x%02x in: %" PRIu32, cmd_data->cmd, cmd_data->command_in_packet); + proto_item_set_generated(command_in); + + offset += dissect_response_data(tvb, pinfo, rsp_tree, offset, cmd_data); + + return offset - offset_start; +} + +static gint +dissect_ftdi_mpsse(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) +{ + gboolean need_reassembly = FALSE; + ftdi_mpsse_info_t *mpsse_info = (ftdi_mpsse_info_t *)data; + gint offset = 0; + proto_item *main_item; + proto_tree *main_tree; + + if (!mpsse_info) + { + return offset; + } + + main_item = proto_tree_add_item(tree, proto_ftdi_mpsse, tvb, offset, -1, ENC_NA); + main_tree = proto_item_add_subtree(main_item, ett_ftdi_mpsse); + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTDI MPSSE"); + + if (pinfo->p2p_dir == P2P_DIR_SENT) + { + command_data_t *iter = get_recorded_command_data(tx_command_info, pinfo, mpsse_info); + + if (!pinfo->fd->visited) + { + /* Not visited yet - advance iterator to last element */ + while (iter && iter->next) + { + DISSECTOR_ASSERT(!iter->preliminary); + iter = iter->next; + } + } + + while ((tvb_reported_length_remaining(tvb, offset) > 0) && (!need_reassembly)) + { + offset += dissect_command(tvb, pinfo, main_tree, offset, &need_reassembly, mpsse_info, &iter); + } + } + else if (pinfo->p2p_dir == P2P_DIR_RECV) + { + command_data_t *head = get_recorded_command_data(rx_command_info, pinfo, mpsse_info); + command_data_t *iter = head; + + if (!pinfo->fd->visited) + { + while (iter && iter->is_response_set) + { + iter = iter->next; + } + + if (iter != head) + { + insert_command_data_pointer(rx_command_info, pinfo, mpsse_info, iter); + } + } + + while ((tvb_reported_length_remaining(tvb, offset) > 0) && (!need_reassembly)) + { + if (!iter) + { + proto_tree_add_expert(main_tree, pinfo, &ei_response_without_command, tvb, offset, -1); + offset += tvb_reported_length_remaining(tvb, offset); + } + else + { + offset += dissect_response(tvb, pinfo, main_tree, offset, &need_reassembly, iter); + iter = iter->next; + } + } + } + + if (need_reassembly) + { + if (pinfo->can_desegment) + { + pinfo->desegment_offset = offset; + pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; + } + else + { + proto_tree_add_expert(main_tree, pinfo, &ei_reassembly_unavailable, tvb, offset, -1); + } + offset += tvb_reported_length_remaining(tvb, offset); + } + + if (tvb_reported_length_remaining(tvb, offset) > 0) + { + proto_tree_add_expert(main_tree, pinfo, &ei_undecoded, tvb, offset, -1); + } + + return tvb_reported_length(tvb); +} + +void +proto_register_ftdi_mpsse(void) +{ + expert_module_t *expert_module; + + static hf_register_info hf[] = { + { &hf_mpsse_command, + { "Command", "ftdi-mpsse.command", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_command_b0, + { "-ve CLK on write", "ftdi-mpsse.command.b0", + FT_BOOLEAN, 8, NULL, (1 << 0), + NULL, HFILL } + }, + { &hf_mpsse_command_b1, + { "Mode", "ftdi-mpsse.command.b1", + FT_UINT8, BASE_DEC, VALS(data_shifting_command_b1_vals), (1 << 1), + NULL, HFILL } + }, + { &hf_mpsse_command_b2, + { "-ve CLK on read", "ftdi-mpsse.command.b2", + FT_BOOLEAN, 8, NULL, (1 << 2), + NULL, HFILL } + }, + { &hf_mpsse_command_b3, + { "Endianness", "ftdi-mpsse.command.b3", + FT_UINT8, BASE_DEC, VALS(data_shifting_command_b3_vals), (1 << 3), + NULL, HFILL } + }, + { &hf_mpsse_command_b4, + { "Do write TDI", "ftdi-mpsse.command.b4", + FT_BOOLEAN, 8, NULL, (1 << 4), + NULL, HFILL } + }, + { &hf_mpsse_command_b5, + { "Do read TDO", "ftdi-mpsse.command.b5", + FT_BOOLEAN, 8, NULL, (1 << 5), + NULL, HFILL } + }, + { &hf_mpsse_command_b6, + { "Do write TMS", "ftdi-mpsse.command.b6", + FT_BOOLEAN, 8, NULL, (1 << 6), + NULL, HFILL } + }, + { &hf_mpsse_command_b7, + { "Type", "ftdi-mpsse.command.b7", + FT_UINT8, BASE_DEC, VALS(command_b7_vals), (1 << 7), + NULL, HFILL } + }, + { &hf_mpsse_command_with_parameters, + { "Command with parameters", "ftdi-mpsse.command_with_parameters", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Command including optional parameter bytes", HFILL } + }, + { &hf_mpsse_bad_command_error, + { "Error code", "ftdi-mpsse.bad_command.error", + FT_UINT8, BASE_HEX, NULL, 0x0, + "Bad Command error code 0xFA", HFILL } + }, + { &hf_mpsse_bad_command_code, + { "Received invalid command", "ftdi-mpsse.bad_command.command", + FT_UINT8, BASE_HEX, NULL, 0x0, + "Byte which caused the bad command", HFILL } + }, + { &hf_mpsse_response, + { "Command response data", "ftdi-mpsse.response", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_command_in, + { "Command in", "ftdi-mpsse.command.in", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_response_in, + { "Response in", "ftdi-mpsse.response.in", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_length_uint8, + { "Length", "ftdi-mpsse.length", + FT_UINT8, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_length_uint16, + { "Length", "ftdi-mpsse.length", + FT_UINT16, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_bytes_out, + { "Bytes out", "ftdi-mpsse.bytes_out", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_bytes_in, + { "Bytes in", "ftdi-mpsse.bytes_in", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_bits_out, + { "Bits out", "ftdi-mpsse.bits_out", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_bits_in, + { "Bits in", "ftdi-mpsse.bits_in", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_value, + { "Value", "ftdi-mpsse.value", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_value_b0, + { "Bit 0", "ftdi-mpsse.value.b0", + FT_UINT8, BASE_DEC, NULL, (1 << 0), + NULL, HFILL } + }, + { &hf_mpsse_value_b1, + { "Bit 1", "ftdi-mpsse.value.b1", + FT_UINT8, BASE_DEC, NULL, (1 << 1), + NULL, HFILL } + }, + { &hf_mpsse_value_b2, + { "Bit 2", "ftdi-mpsse.value.b2", + FT_UINT8, BASE_DEC, NULL, (1 << 2), + NULL, HFILL } + }, + { &hf_mpsse_value_b3, + { "Bit 3", "ftdi-mpsse.value.b3", + FT_UINT8, BASE_DEC, NULL, (1 << 3), + NULL, HFILL } + }, + { &hf_mpsse_value_b4, + { "Bit 4", "ftdi-mpsse.value.b4", + FT_UINT8, BASE_DEC, NULL, (1 << 4), + NULL, HFILL } + }, + { &hf_mpsse_value_b5, + { "Bit 5", "ftdi-mpsse.value.b5", + FT_UINT8, BASE_DEC, NULL, (1 << 5), + NULL, HFILL } + }, + { &hf_mpsse_value_b6, + { "Bit 6", "ftdi-mpsse.value.b6", + FT_UINT8, BASE_DEC, NULL, (1 << 6), + NULL, HFILL } + }, + { &hf_mpsse_value_b7, + { "Bit 7", "ftdi-mpsse.value.b7", + FT_UINT8, BASE_DEC, NULL, (1 << 7), + NULL, HFILL } + }, + { &hf_mpsse_direction, + { "Direction", "ftdi-mpsse.direction", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_direction_b0, + { "Bit 0", "ftdi-mpsse.direction.b0", + FT_UINT8, BASE_DEC, NULL, (1 << 0), + NULL, HFILL } + }, + { &hf_mpsse_direction_b1, + { "Bit 1", "ftdi-mpsse.direction.b1", + FT_UINT8, BASE_DEC, NULL, (1 << 1), + NULL, HFILL } + }, + { &hf_mpsse_direction_b2, + { "Bit 2", "ftdi-mpsse.direction.b2", + FT_UINT8, BASE_DEC, NULL, (1 << 2), + NULL, HFILL } + }, + { &hf_mpsse_direction_b3, + { "Bit 3", "ftdi-mpsse.direction.b3", + FT_UINT8, BASE_DEC, NULL, (1 << 3), + NULL, HFILL } + }, + { &hf_mpsse_direction_b4, + { "Bit 4", "ftdi-mpsse.direction.b4", + FT_UINT8, BASE_DEC, NULL, (1 << 4), + NULL, HFILL } + }, + { &hf_mpsse_direction_b5, + { "Bit 5", "ftdi-mpsse.direction.b5", + FT_UINT8, BASE_DEC, NULL, (1 << 5), + NULL, HFILL } + }, + { &hf_mpsse_direction_b6, + { "Bit 6", "ftdi-mpsse.direction.b6", + FT_UINT8, BASE_DEC, NULL, (1 << 6), + NULL, HFILL } + }, + { &hf_mpsse_direction_b7, + { "Bit 7", "ftdi-mpsse.direction.b7", + FT_UINT8, BASE_DEC, NULL, (1 << 7), + NULL, HFILL } + }, + { &hf_mpsse_cpumode_address_short, + { "Address", "ftdi-mpsse.cpumode_address", + FT_UINT8, BASE_HEX, NULL, 0x0, + "CPUMode Short Address", HFILL } + }, + { &hf_mpsse_cpumode_address_extended, + { "Address", "ftdi-mpsse.cpumode_address", + FT_UINT16, BASE_HEX, NULL, 0x0, + "CPUMode Extended Address", HFILL } + }, + { &hf_mpsse_cpumode_data, + { "Data", "ftdi-mpsse.cpumode_data", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_clk_divisor, + { "Divisor", "ftdi-mpsse.clk_divisor", + FT_UINT16, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low, + { "Low Byte", "ftdi-mpsse.open_drain_enable_low", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b0, + { "Bit 0", "ftdi-mpsse.open_drain_enable_low.b0", + FT_UINT8, BASE_DEC, NULL, (1 << 0), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b1, + { "Bit 1", "ftdi-mpsse.open_drain_enable_low.b1", + FT_UINT8, BASE_DEC, NULL, (1 << 1), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b2, + { "Bit 2", "ftdi-mpsse.open_drain_enable_low.b2", + FT_UINT8, BASE_DEC, NULL, (1 << 2), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b3, + { "Bit 3", "ftdi-mpsse.open_drain_enable_low.b3", + FT_UINT8, BASE_DEC, NULL, (1 << 3), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b4, + { "Bit 4", "ftdi-mpsse.open_drain_enable_low.b4", + FT_UINT8, BASE_DEC, NULL, (1 << 4), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b5, + { "Bit 5", "ftdi-mpsse.open_drain_enable_low.b5", + FT_UINT8, BASE_DEC, NULL, (1 << 5), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b6, + { "Bit 6", "ftdi-mpsse.open_drain_enable_low.b6", + FT_UINT8, BASE_DEC, NULL, (1 << 6), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_low_b7, + { "Bit 7", "ftdi-mpsse.open_drain_enable_low.b7", + FT_UINT8, BASE_DEC, NULL, (1 << 7), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high, + { "High Byte", "ftdi-mpsse.open_drain_enable_high", + FT_UINT8, BASE_HEX, NULL, 0x0, + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b0, + { "Bit 0", "ftdi-mpsse.open_drain_enable_high.b0", + FT_UINT8, BASE_DEC, NULL, (1 << 0), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b1, + { "Bit 1", "ftdi-mpsse.open_drain_enable_high.b1", + FT_UINT8, BASE_DEC, NULL, (1 << 1), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b2, + { "Bit 2", "ftdi-mpsse.open_drain_enable_high.b2", + FT_UINT8, BASE_DEC, NULL, (1 << 2), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b3, + { "Bit 3", "ftdi-mpsse.open_drain_enable_high.b3", + FT_UINT8, BASE_DEC, NULL, (1 << 3), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b4, + { "Bit 4", "ftdi-mpsse.open_drain_enable_high.b4", + FT_UINT8, BASE_DEC, NULL, (1 << 4), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b5, + { "Bit 5", "ftdi-mpsse.open_drain_enable_high.b5", + FT_UINT8, BASE_DEC, NULL, (1 << 5), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b6, + { "Bit 6", "ftdi-mpsse.open_drain_enable_high.b6", + FT_UINT8, BASE_DEC, NULL, (1 << 6), + NULL, HFILL } + }, + { &hf_mpsse_open_drain_enable_high_b7, + { "Bit 7", "ftdi-mpsse.open_drain_enable_high.b7", + FT_UINT8, BASE_DEC, NULL, (1 << 7), + NULL, HFILL } + }, + }; + + static ei_register_info ei[] = { + { &ei_undecoded, { "ftdi-mpsse.undecoded", PI_UNDECODED, PI_WARN, "Not dissected yet (report to wireshark.org)", EXPFILL }}, + { &ei_response_without_command, { "ftdi-mpsse.response_without_command", PI_PROTOCOL, PI_ERROR, "Unable to associate response with command (response without command?)", EXPFILL }}, + { &ei_skipped_response_data, { "ftdi-mpsse.skipped_response_data", PI_PROTOCOL, PI_WARN, "Skipped response data while looking for Bad Command response", EXPFILL }}, + { &ei_reassembly_unavailable, { "ftdi-mpsse.reassembly_unavailable", PI_UNDECODED, PI_ERROR, "Data source dissector does not support reassembly. Dissection will get out of sync.", EXPFILL }}, + }; + + static gint *ett[] = { + &ett_ftdi_mpsse, + &ett_mpsse_command, + &ett_mpsse_command_with_parameters, + &ett_mpsse_response_data, + &ett_mpsse_value, + &ett_mpsse_direction, + &ett_mpsse_open_drain_enable, + &ett_mpsse_skipped_response_data, + }; + + rx_command_info = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope()); + tx_command_info = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope()); + + proto_ftdi_mpsse = proto_register_protocol("FTDI Multi-Protocol Synchronous Serial Engine", "FTDI MPSSE", "ftdi-mpsse"); + proto_register_field_array(proto_ftdi_mpsse, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + ftdi_mpsse_handle = register_dissector("ftdi-mpsse", dissect_ftdi_mpsse, proto_ftdi_mpsse); + + expert_module = expert_register_protocol(proto_ftdi_mpsse); + expert_register_field_array(expert_module, ei, array_length(ei)); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |