text: #define LOG_MODULE "ezusb-iidx"
  
  #include "ezusb-iidx/ezusb-iidx.h"
  
  #include "ezusb/ezusbsys2.h"
  
  #include "util/hex.h"
  #include "util/log.h"
  #include "util/time.h"
  
  #include "msg.h"
  
  //#define DEBUG_DUMP
  
  /* For debugging */
  #ifdef DEBUG_DUMP
  static void ezusb_iidx_log_usb(
      const char *prefix,
      uint32_t ctl_code,
      const BULK_TRANSFER_CONTROL *ctl,
      uint32_t ctl_size,
      void *header,
      uint32_t header_bytes,
      void *data,
      uint32_t data_bytes)
  {
      char header_str[4096];
      char data_str[4096];
      const char *ctl_code_str;
  
      switch (ctl_code) {
          case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR:
              ctl_code_str = "GET_DEVICE_DESCRIPTOR";
              break;
  
          case IOCTL_Ezusb_VENDOR_REQUEST:
              ctl_code_str = "VENDOR_REQUEST";
              break;
  
          case IOCTL_EZUSB_ANCHOR_DOWNLOAD:
              ctl_code_str = "ANCHOR_DOWNLOAD";
              break;
  
          case IOCTL_EZUSB_BULK_READ:
              if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_INTERRUPT_IN) {
                  ctl_code_str = "INT_READ";
              } else if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_BULK_IN) {
                  ctl_code_str = "BULK_READ";
              } else {
                  ctl_code_str = "INVALID_READ";
              }
  
              break;
  
          case IOCTL_EZUSB_BULK_WRITE:
              if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT) {
                  ctl_code_str = "INT_WRITE";
              } else if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_BULK_OUT) {
                  ctl_code_str = "BULK_WRITE";
              } else {
                  ctl_code_str = "INVALID_WRITE";
              }
  
              break;
  
          default:
              ctl_code_str = "UNKNOWN";
              break;
      }
  
      hex_encode_uc(header, header_bytes, header_str, sizeof(header_str));
      hex_encode_uc(data, data_bytes, data_str, sizeof(data_str));
  
      log_misc(
          "[EZUSB DUMP %s][%s] header(%d) %s |||| data(%d) %s\n",
          prefix,
          ctl_code_str,
          header_bytes,
          header_str,
          data_bytes,
          data_str);
  }
  #endif
  
  static BOOL ezusb_iidx_device_io_control_wrapper(
      HANDLE fd,
      uint32_t code,
      void *in_bytes,
      uint32_t in_nbytes,
      void *out_bytes,
      uint32_t out_nbytes,
      DWORD *out_returned,
      OVERLAPPED *ovl)
  {
      BOOL ret;
  
  #ifdef DEBUG_DUMP
      ezusb_iidx_log_usb(
          "BEFORE",
          code,
          in_bytes,
          in_nbytes,
          in_bytes,
          in_nbytes,
          out_bytes,
          out_nbytes);
  #endif
  
      ret = DeviceIoControl(
          fd,
          code,
          in_bytes,
          in_nbytes,
          out_bytes,
          out_nbytes,
          out_returned,
          ovl);
  
  #ifdef DEBUG_DUMP
      ezusb_iidx_log_usb(
          "AFTER",
          code,
          in_bytes,
          in_nbytes,
          in_bytes,
          in_nbytes,
          out_bytes,
          out_nbytes);
  #endif
  
      return ret;
  }
  
  bool ezusb_iidx_ioctl(
      HANDLE handle,
      uint32_t code,
      void *in_bytes,
      uint32_t in_nbytes,
      void *out_bytes,
      uint32_t out_nbytes,
      uint32_t *out_returned)
  {
      return DeviceIoControl(
          handle,
          code,
          in_bytes,
          in_nbytes,
          out_bytes,
          out_nbytes,
          (LPDWORD) out_returned,
          NULL);
  }
  
  bool ezusb_iidx_interrupt_read(
      HANDLE handle, struct ezusb_iidx_msg_interrupt_read_packet *packet)
  {
      BULK_TRANSFER_CONTROL transfer;
      DWORD outpkt;
  
      transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_IN;
  
      return ezusb_iidx_device_io_control_wrapper(
          handle,
          IOCTL_EZUSB_BULK_READ,
          &transfer,
          sizeof(transfer),
          packet,
          sizeof(struct ezusb_iidx_msg_interrupt_read_packet),
          &outpkt,
          NULL);
  }
  
  bool ezusb_iidx_interrupt_write(
      HANDLE handle, const struct ezusb_iidx_msg_interrupt_write_packet *packet)
  {
      BULK_TRANSFER_CONTROL transfer;
      DWORD outpkt;
  
      transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT;
  
      return ezusb_iidx_device_io_control_wrapper(
          handle,
          IOCTL_EZUSB_BULK_WRITE,
          &transfer,
          sizeof(transfer),
          (void *) packet,
          sizeof(struct ezusb_iidx_msg_interrupt_write_packet),
          &outpkt,
          NULL);
  }
  
  bool ezusb_iidx_bulk_read(
      HANDLE handle, struct ezusb_iidx_msg_bulk_packet *packet)
  {
      BULK_TRANSFER_CONTROL transfer;
      DWORD outpkt;
  
      transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_BULK_IN;
  
      return ezusb_iidx_device_io_control_wrapper(
          handle,
          IOCTL_EZUSB_BULK_READ,
          &transfer,
          sizeof(transfer),
          packet,
          sizeof(struct ezusb_iidx_msg_bulk_packet),
          &outpkt,
          NULL);
  }
  
  bool ezusb_iidx_bulk_write(
      HANDLE handle, const struct ezusb_iidx_msg_bulk_packet *packet)
  {
      BULK_TRANSFER_CONTROL transfer;
      DWORD outpkt;
  
      transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_BULK_OUT;
  
      return ezusb_iidx_device_io_control_wrapper(
          handle,
          IOCTL_EZUSB_BULK_WRITE,
          &transfer,
          sizeof(transfer),
          (void *) packet,
          sizeof(struct ezusb_iidx_msg_bulk_packet),
          &outpkt,
          NULL);
  }
  
  bool ezusb_iidx_execute_cmd(
      HANDLE handle,
      uint8_t node,
      uint8_t cmd,
      uint8_t cmd_detail,
      uint8_t cmd_detail2,
      struct ezusb_iidx_msg_interrupt_read_packet *int_read_data)
  {
      struct ezusb_iidx_msg_interrupt_write_packet int_write;
  
      memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet));
  
      int_write.node = node;
      int_write.cmd = cmd;
      int_write.cmd_detail[0] = cmd_detail;
      int_write.cmd_detail[1] = cmd_detail2;
  
      log_misc(
          "Execute cmd: %02X %02X %02X %02X", node, cmd, cmd_detail, cmd_detail2);
  
      if (!ezusb_iidx_interrupt_write(handle, &int_write)) {
          log_warning("Interrupt write failed");
          return false;
      }
  
      Sleep(10);
  
      if (!ezusb_iidx_interrupt_read(handle, int_read_data)) {
          log_warning("Interrupt read failed");
          return false;
      }
  
      log_misc("Cmd result: %02X", int_read_data->status);
  
      return true;
  }
  
  uint8_t ezusb_iidx_execute_cmd_retry(
      HANDLE handle,
      uint8_t node,
      uint8_t cmd,
      uint8_t cmd_detail,
      uint8_t cmd_detail2,
      uint8_t int_reads)
  {
      struct ezusb_iidx_msg_interrupt_read_packet int_read;
      struct ezusb_iidx_msg_interrupt_write_packet int_write;
  
      memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet));
  
      int_write.node = node;
      int_write.cmd = cmd;
      int_write.cmd_detail[0] = cmd_detail;
      int_write.cmd_detail[1] = cmd_detail2;
  
      log_misc(
          "Execute cmd: %02X %02X %02X %02X", node, cmd, cmd_detail, cmd_detail2);
  
      if (!ezusb_iidx_interrupt_write(handle, &int_write)) {
          log_warning("Interrupt write failed");
          return 0xFF;
      }
  
      Sleep(10);
  
      for (uint8_t i = 0; i < int_reads; i++) {
          if (!ezusb_iidx_interrupt_read(handle, &int_read)) {
              log_warning("Interrupt read failed");
              return 0xFF;
          }
      }
  
      log_misc("Cmd result: %02X", int_read.status);
  
      return int_read.status;
  }
  
  bool ezusb_iidx_execute_cmd_timeout(
      HANDLE handle,
      uint8_t node,
      uint8_t cmd,
      uint8_t cmd_detail,
      uint8_t cmd_detail2,
      uint8_t expected_ret_code,
      uint32_t timeout_ms)
  {
      struct ezusb_iidx_msg_interrupt_read_packet int_read;
      struct ezusb_iidx_msg_interrupt_write_packet int_write;
  
      memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet));
  
      int_write.node = node;
      int_write.cmd = cmd;
      int_write.cmd_detail[0] = cmd_detail;
      int_write.cmd_detail[1] = cmd_detail2;
  
      log_misc(
          "Execute cmd: %02X %02X %02X %02X", node, cmd, cmd_detail, cmd_detail2);
  
      if (!ezusb_iidx_interrupt_write(handle, &int_write)) {
          log_warning("Interrupt write failed");
          return false;
      }
  
      Sleep(10);
  
      uint64_t start = time_get_counter();
  
      while (time_get_elapsed_ms(time_get_counter() - start) < timeout_ms) {
          if (!ezusb_iidx_interrupt_read(handle, &int_read)) {
              log_warning("Interrupt read failed");
              return false;
          }
  
          if (int_read.status == expected_ret_code) {
              log_misc("Cmd result: %02X", expected_ret_code);
              return true;
          }
  
          Sleep(10);
      }
  
      log_warning("Cmd failed, result: %02X", int_read.status);
  
      return false;
  }