#if SUPPORT_INITIATOR

// Take control of the SCSI bus
void initiatorTakeBus() {
  pinModeFastSlew(ATN, OUTPUT_OPENDRAIN);
  pinModeFastSlew(ACK, OUTPUT_OPENDRAIN);
  pinModeFastSlew(RST, OUTPUT_OPENDRAIN);
  pinModeFastSlew(SEL, OUTPUT_OPENDRAIN);

  pinMode(BSY, INPUT_PULLUP);
  pinMode(MSG, INPUT_PULLUP);
  pinMode(CD,  INPUT_PULLUP);
  pinMode(IO,  INPUT_PULLUP);
  pinMode(REQ, INPUT_PULLUP);

  pinModeFastSlew(DB0, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB1, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB2, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB3, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB4, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB5, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB6, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB7, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB8, OUTPUT_OPENDRAIN);

  SCSI_INITIATOR_INACTIVE();
}

// Return the bus to floating, allowing other hosts to take over
void initiatorReleaseBus() {
  // Input port
  pinMode(ATN, INPUT_PULLUP);
  pinMode(ACK, INPUT_PULLUP);
  pinMode(RST, INPUT_PULLUP);
  pinMode(SEL, INPUT_PULLUP);

  // Output port
  pinModeFastSlew(BSY, OUTPUT_OPENDRAIN);
  pinModeFastSlew(MSG, OUTPUT_OPENDRAIN);
  pinModeFastSlew(CD, OUTPUT_OPENDRAIN);
  pinModeFastSlew(IO, OUTPUT_OPENDRAIN);
  pinModeFastSlew(REQ, OUTPUT_OPENDRAIN);

  pinModeFastSlew(DB0, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB1, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB2, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB3, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB4, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB5, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB6, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB7, OUTPUT_OPENDRAIN);
  pinModeFastSlew(DB8, OUTPUT_OPENDRAIN);

  // Turn off the output port
  SCSI_TARGET_INACTIVE();
}

// Issue a bus reset
void initiatorBusFree() {
  SET_RST_ACTIVE();
  delay(50);
  SET_RST_INACTIVE();
  delay(20);
}

// Selection phase, assert Host & Target ID bits and assert SEL, wait for BSY to be asserted by the target, or timeout.
int initiateSelection(uint8_t target_id, boolean withATN) {
  int timeout = 10000;

  GPIOB_PDDR = SCSI_DB_MASK;
  SCSI_DB_OUTPUT((1 << target_id) | 0x80);

  SET_SEL_ACTIVE();

  while(timeout--) {
    delay(20);
    if(GET_BSY()) break;
  }

  if(withATN) SET_ATN_ACTIVE();

  SET_SEL_INACTIVE();

  SCSI_DB_INPUT();

  return GET_BSY();
}

// Message Out phase, hold ATN asserted until the last message byte
int initiateMessageOut(const uint8_t *buf, uint8_t len) {
  int timeout = 10000;

  SET_ATN_ACTIVE();

  while(timeout--) {
    delay(20);
    if(!GET_CD() && !GET_IO() && GET_MSG()) break;
  }

  if(timeout > 0) {
    for(uint8_t x = 0; x < len; x++) {
      timeout = 10000;
      while(!GET_REQ() && (timeout--));
      if(!timeout) break;

      GPIOB_PDDR = SCSI_DB_MASK;
      SCSI_DB_OUTPUT(buf[x]);

      if((x + 1) == len)
        SET_ATN_INACTIVE();

      SET_ACK_ACTIVE();
      timeout = 10000;
      while(GET_REQ() && (timeout--));
      SET_ACK_INACTIVE();
      if(!timeout) break;
    }
  }

  SET_ATN_INACTIVE();

  SCSI_DB_INPUT();

  return (timeout > 0);
}

// Message In phase, read a single byte from target once it's phase matches
int initiateMessageIn(uint8_t *buf) {
  int timeout = 10000;

  while(timeout--) {
    delay(20);
    if(!GET_CD() && GET_IO() && GET_MSG()) break;
  }

  if(timeout > 0) {
    timeout = 10000;
    while(!GET_REQ() && (timeout--));
    if(!timeout) goto failed;

    buf[0] = readIO();

    SET_ACK_ACTIVE();
    timeout = 10000;
    while(GET_REQ() && (timeout--));
    SET_ACK_INACTIVE();
  }

failed:
  return (timeout > 0);
}

// Command Out phase, tell the target what we want from it.
int initiateCommandOut(const uint8_t *buf, uint8_t len) {
  int timeout = 10000;

  while(timeout--) {
    delay(20);
    if(GET_CD() && !GET_IO() && !GET_MSG()) break;
  }

  if(timeout > 0) {
    for(uint8_t x = 0; x < len; x++) {
      timeout = 10000;
      while(!GET_REQ() && (timeout--));
      if(!timeout) break;

      GPIOB_PDDR = SCSI_DB_MASK;
      SCSI_DB_OUTPUT(buf[x]);

      SET_ACK_ACTIVE();
      timeout = 10000;
      while(GET_REQ() && (timeout--));
      SET_ACK_INACTIVE();
      if(!timeout) break;
    }
  }

  SCSI_DB_INPUT();

  return (timeout > 0);
}

// Data Out phase, send data to target
int initiateDataOut(const uint8_t *buf, uint16_t len) {
  int timeout = 10000;

  while(timeout--) {
    delay(20);
    if(!GET_CD() && !GET_IO() && !GET_MSG()) break;
  }

  if(timeout > 0) {
    for(uint16_t x = 0; x < len; x++) {
      timeout = 10000;
      while(!GET_REQ() && (timeout--));
      if(!timeout) break;

      GPIOB_PDDR = SCSI_DB_MASK;
      SCSI_DB_OUTPUT(buf[x]);

      SET_ACK_ACTIVE();
      timeout = 10000;
      while(GET_REQ() && (timeout--));
      SET_ACK_INACTIVE();

      if(!timeout) break;
    }
  }

  SCSI_DB_INPUT();
  
  return (timeout > 0);
}

// Data In phase, get data from target
int initiateDataIn(uint8_t *buf, uint16_t len) {
  int timeout = 10000;

  while(timeout--) {
    delay(20);
    if(!GET_CD() && GET_IO() && !GET_MSG()) break;
  }

  if(timeout > 0) {
    for(uint16_t x = 0; x < len; x++) {
      timeout = 10000;
      while(!GET_REQ() && (timeout--));
      if(!timeout) break;

      buf[x] = readIO();

      SET_ACK_ACTIVE();
      timeout = 10000;
      while(GET_REQ() && (timeout--));
      SET_ACK_INACTIVE();

      if(!timeout) break;
    }
  }
 
  return (timeout > 0);
}

// Status In phase, read target's command status
int initiateStatusIn(uint8_t *buf) {
  int timeout = 10000;

  while(timeout--) {
    delay(20);
    if(GET_CD() && GET_IO() && !GET_MSG()) break;
  }

  if(timeout > 0) {
    timeout = 10000;
    while(!GET_REQ() && (timeout--));
    if(!timeout) goto failed;

    buf[0] = readIO();

    SET_ACK_ACTIVE();
    timeout = 10000;
    while(GET_REQ() && (timeout--));
    SET_ACK_INACTIVE();
  }
 
failed:
  return (timeout > 0);
}

int initiateReadCapacity(uint8_t target_id, uint32_t *blockSize, uint32_t *blockCount) {
  initiatorTakeBus();

  // Select Target
  if(!initiateSelection(target_id, true)) goto failed;

  // Select LUN 0
  m_buf[0] = 0x80;
  if(!initiateMessageOut(m_buf, 1)) goto failed;

  // Execute Read Capacity
  memset(m_cmd, 0x00, 10);
  m_cmd[0] = CMD_READ_CAPACITY10;
  if(!initiateCommandOut(m_cmd, 10)) goto failed;

  // Get Data Back
  if(!initiateDataIn(m_buf, 8)) goto failed;

  *blockSize = (m_buf[0] << 24) | (m_buf[1] << 16) || (m_buf[2] << 8) | (m_buf[3] << 0); 
  *blockCount = (m_buf[4] << 24) | (m_buf[5] << 16) || (m_buf[6] << 8) | (m_buf[7] << 0);

  // Get Status
  if(!initiateStatusIn(m_buf)) goto failed;

  // Get Messages
  if(!initiateMessageIn(m_buf)) goto failed;

  return 1;

failed:
  initiatorBusFree();
  initiatorReleaseBus();
  return 0;
}

int initiateReadVolume(FsFile file, uint8_t target_id, uint32_t blockSize, uint32_t blockCount) {
  uint32_t pos = 0;
  initiatorTakeBus();

  while(blockCount) {
    uint32_t blocks = min(blockCount, (MAX_BLOCKSIZE / blockSize));
    if(!initiateSelection(target_id, true)) goto failed;

    // Select LUN 0
    m_buf[0] = 0x80;
    if(!initiateMessageOut(m_buf, 1)) goto failed;
    
    // Execute Read
    memset(m_cmd, 0x00, 10);
    m_cmd[0] = CMD_READ10;
    m_cmd[2] = (pos >> 24) & 0xff;
    m_cmd[3] = (pos >> 16) & 0xff;
    m_cmd[4] = (pos >> 8) & 0xff;
    m_cmd[5] = (pos >> 0) & 0xff;
    m_cmd[6] = (blocks >> 8) & 0xff;
    m_cmd[7] = (blocks >> 0) & 0xff;
    if(!initiateCommandOut(m_cmd, 10)) goto failed;
    
    // Get Data Back
    if(!initiateDataIn(m_buf, blockSize * blocks)) goto failed;

    file.write(m_buf, blockSize * blocks);

    // Get Status
    if(!initiateStatusIn(m_buf)) goto failed;
  
    // Get Messages
    if(!initiateMessageIn(m_buf)) goto failed;

    pos += blocks;
    blockCount -= blocks;
  }

  return 1;

failed:
  initiatorBusFree();
  initiatorReleaseBus();
  return 0;
}

void dumpcmd(int argc, char **argv) {
  uint8_t target_id = 0xff;
  uint32_t blockSize, blockCount;
  char tmp_path[MAX_FILE_PATH+1];
  FsFile file;

  if(argc < 3) {
    // Syntax error
    return;
  }

  target_id = strtoul(argv[1], NULL, 0);
  if(target_id > 7) {
    Serial.print("ERROR");
    if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
    Serial.printf(": SCSI ID '%s' is not within range.\r\n", argv[1]);
    return;
  }

  fixupPath(tmp_path, argv[2]);
  if(strncmp(tmp_path, "/sd/", 4)) {
    Serial.print("ERROR");
    if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
    Serial.printf(": Can only create images on the SD Card.\r\n");
    return;
  }

  if(!file.open(tmp_path+3, O_WRONLY | O_CREAT | O_TRUNC)) {
    Serial.print("ERROR");
    if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
    Serial.printf(": Unable to open '%s'.\r\n", tmp_path);
    return;
  }

  detachInterrupt(RST);
  detachInterrupt(SEL);

  if(!initiateReadCapacity(target_id, &blockSize, &blockCount)) {
    Serial.print("ERROR");
    if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
    Serial.printf(": Unable to read the capacity of SCSI target device.\r\n");
  } else {
    initiateReadVolume(file, target_id, blockSize, blockCount);
  }

  attachInterrupt(RST, onBusReset, FALLING);
  attachInterrupt(SEL, SelectionPhaseISR, FALLING);
  
  file.close();
}

#endif
