/*-
 * SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0
 *
 * This file is provided under a dual BSD/GPLv2 license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * GPL LICENSE SUMMARY
 *
 * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 * The full GNU General Public License is included in this distribution
 * in the file called LICENSE.GPL.
 *
 * BSD LICENSE
 *
 * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
/**
 * @file
 *
 * @brief This file contains the method implementations for the
 *        SCIF_SAS_STP_IO_REQUEST object.  The contents will implement
 *        SATA/STP specific functionality.
 */

#include <dev/isci/scil/scif_sas_stp_io_request.h>
#include <dev/isci/scil/scif_sas_stp_remote_device.h>
#include <dev/isci/scil/scif_sas_logger.h>
#include <dev/isci/scil/scif_sas_controller.h>

#include <dev/isci/scil/sci_status.h>
#include <dev/isci/scil/scic_io_request.h>

#include <dev/isci/scil/sati.h>
#include <dev/isci/scil/sati_atapi.h>
#include <dev/isci/scil/intel_sat.h>
#include <dev/isci/scil/sati_util.h>
#include <dev/isci/scil/sati_callbacks.h>

//******************************************************************************
// P R I V A T E   M E T H O D S
//******************************************************************************

/**
 * @brief This method provides SATA/STP CONSTRUCTED state specific handling
 *        for when the user attempts to start the supplied IO request.  It
 *        will allocate NCQ tags if necessary.
 *
 * @param[in] io_request This parameter specifies the IO request object
 *            to be started.
 *
 * @return This method returns a value indicating if the IO request was
 *         successfully started or not.
 * @retval SCI_SUCCESS This return value indicates successful starting
 *         of the IO request.
 */
static
SCI_STATUS scif_sas_stp_io_request_constructed_start_handler(
   SCI_BASE_REQUEST_T * io_request
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T *) io_request;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(io_request),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_io_request_constructed_start_handler(0x%x) enter\n",
      io_request
   ));

   if (fw_io->parent.stp.sequence.protocol == SAT_PROTOCOL_FPDMA)
   {
      SATA_FIS_REG_H2D_T * fis;

      // For NCQ, we need to attempt to allocate an available tag.
      fw_io->parent.stp.ncq_tag = scif_sas_stp_remote_device_allocate_ncq_tag(
                                     fw_io->parent.device
                                  );

      if (fw_io->parent.stp.ncq_tag == SCIF_SAS_INVALID_NCQ_TAG)
         return SCI_FAILURE_NO_NCQ_TAG_AVAILABLE;

      // Set the NCQ tag in the host to device register FIS (upper 5 bits
      // of the 8-bit sector count register).
      fis = scic_stp_io_request_get_h2d_reg_address(fw_io->parent.core_object);
      fis->sector_count = (fw_io->parent.stp.ncq_tag << 3);

      // The Core also requires that we inform it separately regarding the
      // NCQ tag for this IO.
      scic_stp_io_request_set_ncq_tag(
         fw_io->parent.core_object, fw_io->parent.stp.ncq_tag
      );
   }

   return SCI_SUCCESS;
}

/**
 * @brief This method provides SATA/STP CONSTRUCTED state specific handling
 *        for when the user attempts to complete the supplied IO request.
 *        This method will be invoked in the event the call to start the
 *        core IO request fails for some reason.  In this situation, the
 *        NCQ tag will be freed.
 *
 * @param[in] io_request This parameter specifies the IO request object
 *            to be started.
 *
 * @return This method returns a value indicating if the IO request was
 *         successfully started or not.
 * @retval SCI_SUCCESS This return value indicates successful starting
 *         of the IO request.
 */
static
SCI_STATUS scif_sas_stp_io_request_constructed_complete_handler(
   SCI_BASE_REQUEST_T * io_request
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T *) io_request;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(io_request),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_io_request_constructed_complete_handler(0x%x) enter\n",
      io_request
   ));

   if (fw_io->parent.stp.sequence.protocol == SAT_PROTOCOL_FPDMA)
   {
      // For NCQ, we need to return the tag back to the free pool.
      if (fw_io->parent.stp.ncq_tag != SCIF_SAS_INVALID_NCQ_TAG)
         scif_sas_stp_remote_device_free_ncq_tag(
            fw_io->parent.device, fw_io->parent.stp.ncq_tag
         );
   }

   sati_sequence_terminate(&fw_io->parent.stp.sequence, fw_io, fw_io);

   return SCI_SUCCESS;
}
/**
 * @brief This method provides SATA/STP STARTED state specific handling for
 *        when the user attempts to complete the supplied IO request.
 *        It will perform data/response translation and free NCQ tags
 *        if necessary.
 *
 * @param[in] io_request This parameter specifies the IO request object
 *            to be started.
 *
 * @return This method returns a value indicating if the IO request was
 *         successfully completed or not.
 */
static
SCI_STATUS scif_sas_stp_core_cb_io_request_complete_handler(
   SCIF_SAS_CONTROLLER_T    * fw_controller,
   SCIF_SAS_REMOTE_DEVICE_T * fw_device,
   SCIF_SAS_REQUEST_T       * fw_request,
   SCI_STATUS               * completion_status
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T *) fw_request;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(fw_controller),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_core_cb_io_request_complete_handler(0x%x, 0x%x, 0x%x, 0x%x) enter\n",
      fw_controller, fw_device, fw_request, *completion_status
   ));

   if (fw_io->parent.stp.sequence.protocol == SAT_PROTOCOL_FPDMA)
      scif_sas_stp_remote_device_free_ncq_tag(
         fw_request->device, fw_io->parent.stp.ncq_tag
      );

   // Translating the response is only necessary if:
   // - some sort of error occurred resulting in having the error bit
   //   set in the ATA status register and values to decode in the
   //   ATA error register.
   // - the command returns information in the register FIS itself,
   //   which requires translation.
   // - the request completed ok but the sequence requires a callback
   //   to possibly continue the translation
   if ((*completion_status == SCI_FAILURE_IO_RESPONSE_VALID) ||
       ((sati_cb_do_translate_response(fw_request)) &&
        (*completion_status != SCI_FAILURE_IO_TERMINATED)))
   {
      SATI_STATUS sati_status = sati_translate_command_response(
                                   &fw_io->parent.stp.sequence, fw_io, fw_io
                                );
      if (sati_status == SATI_COMPLETE)
         *completion_status = SCI_SUCCESS;
      else if (sati_status == SATI_FAILURE_CHECK_RESPONSE_DATA)
         *completion_status = SCI_FAILURE_IO_RESPONSE_VALID;
      else if (sati_status == SATI_SEQUENCE_INCOMPLETE)
      {
         // The translation indicates that additional SATA requests are
         // necessary to finish the original SCSI request.  As a result,
         // do not complete the IO and begin the next stage of the
         // translation.
         return SCI_WARNING_SEQUENCE_INCOMPLETE;
      }
      else if (sati_status == SATI_COMPLETE_IO_DONE_EARLY)
         *completion_status = SCI_SUCCESS_IO_DONE_EARLY;
      else
      {
         // Something unexpected occurred during translation.  Fail the
         // IO request to the user.
         *completion_status = SCI_FAILURE;
      }
   }
   else if (*completion_status != SCI_SUCCESS)
   {
      SCIF_LOG_INFO((
         sci_base_object_get_logger(fw_controller),
         SCIF_LOG_OBJECT_IO_REQUEST,
         "Sequence Terminated(0x%x, 0x%x, 0x%x)\n",
         fw_controller, fw_device, fw_request
      ));

      sati_sequence_terminate(&fw_io->parent.stp.sequence, fw_io, fw_io);
   }

   return SCI_SUCCESS;
}

#if !defined(DISABLE_ATAPI)
/**
 * @brief This method provides STP PACKET io request STARTED state specific handling for
 *        when the user attempts to complete the supplied IO request.
 *        It will perform data/response translation.
 *
 * @param[in] io_request This parameter specifies the IO request object
 *            to be started.
 *
 * @return This method returns a value indicating if the IO request was
 *         successfully completed or not.
 */
static
SCI_STATUS scif_sas_stp_core_cb_packet_io_request_complete_handler(
   SCIF_SAS_CONTROLLER_T    * fw_controller,
   SCIF_SAS_REMOTE_DEVICE_T * fw_device,
   SCIF_SAS_REQUEST_T       * fw_request,
   SCI_STATUS               * completion_status
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T *) fw_request;
   SATI_STATUS sati_status;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(fw_controller),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_packet_core_cb_io_request_complete_handler(0x%x, 0x%x, 0x%x, 0x%x) enter\n",
      fw_controller, fw_device, fw_request, *completion_status
   ));

   if (*completion_status == SCI_FAILURE_IO_RESPONSE_VALID)
   {
      sati_status = sati_atapi_translate_command_response(
                       &fw_io->parent.stp.sequence, fw_io, fw_io
                    );

      if (sati_status == SATI_COMPLETE)
         *completion_status = SCI_SUCCESS;
      else if (sati_status == SATI_FAILURE_CHECK_RESPONSE_DATA)
         *completion_status = SCI_FAILURE_IO_RESPONSE_VALID;
      else if (sati_status == SATI_SEQUENCE_INCOMPLETE)
      {
         // The translation indicates that additional REQUEST SENSE command is
         // necessary to finish the original SCSI request.  As a result,
         // do not complete the IO and begin the next stage of the IO.
         return SCI_WARNING_SEQUENCE_INCOMPLETE;
      }
      else
      {
         // Something unexpected occurred during translation.  Fail the
         // IO request to the user.
         *completion_status = SCI_FAILURE;
      }
   }
   else if (*completion_status == SCI_SUCCESS &&
        fw_request->stp.sequence.state == SATI_SEQUENCE_STATE_INCOMPLETE)
   {
      //The internal Request Sense command is completed successfully.
      sati_atapi_translate_request_sense_response(
         &fw_io->parent.stp.sequence, fw_io, fw_io
      );

      *completion_status = SCI_FAILURE_IO_RESPONSE_VALID;
   }

   return SCI_SUCCESS;
}
#endif // !defined(DISABLE_ATAPI)

//******************************************************************************
// P R O T E C T E D   M E T H O D S
//******************************************************************************

/**
 * @brief This method will construct the SATA/STP specific IO request
 *        object utilizing the SATI.
 *
 * @pre The scif_sas_request_construct() method should be invoked before
 *      calling this method.
 *
 * @param[in,out] stp_io_request This parameter specifies the stp_io_request
 *                to be constructed.
 *
 * @return Indicate if the construction was successful.
 * @return SCI_FAILURE_NO_NCQ_TAG_AVAILABLE
 * @return SCI_SUCCESS_IO_COMPLETE_BEFORE_START
 * @return SCI_FAILURE_IO_RESPONSE_VALID
 * @return SCI_FAILURE This return value indicates a change in the translator
 *         where a new return code has been given, but is not yet understood
 *         by this routine.
 */
SCI_STATUS scif_sas_stp_io_request_construct(
   SCIF_SAS_IO_REQUEST_T * fw_io
)
{
   SATI_STATUS                sati_status;
   SCI_STATUS                 sci_status = SCI_FAILURE;
   SCIF_SAS_REMOTE_DEVICE_T * fw_device  = fw_io->parent.device;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(fw_io),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_io_request_construct(0x%x) enter\n",
      fw_io
   ));

   // The translator will indirectly invoke core methods to set the fields
   // of the ATA register FIS inside of this method.
   sati_status = sati_translate_command(
                    &fw_io->parent.stp.sequence,
                    &fw_device->protocol_device.stp_device.sati_device,
                    fw_io,
                    fw_io
                 );

   if (sati_status == SATI_SUCCESS)
   {
      // Allow the core to finish construction of the IO request.
      sci_status = scic_io_request_construct_basic_sata(fw_io->parent.core_object);
      fw_io->parent.state_handlers = &stp_io_request_constructed_handlers;
      fw_io->parent.protocol_complete_handler
         = scif_sas_stp_core_cb_io_request_complete_handler;
   }
   else if (sati_status == SATI_SUCCESS_SGL_TRANSLATED)
   {
      SCIC_IO_SATA_PARAMETERS_T parms;
      parms.do_translate_sgl = FALSE;

      // The translation actually already caused translation of the
      // scatter gather list.  So, call into the core through an API
      // that will not attempt to translate the SGL.
      scic_io_request_construct_advanced_sata(
                      fw_io->parent.core_object, &parms
                   );
      fw_io->parent.state_handlers = &stp_io_request_constructed_handlers;
      fw_io->parent.protocol_complete_handler
         = scif_sas_stp_core_cb_io_request_complete_handler;
      // Done with translation
      sci_status = SCI_SUCCESS;
   }
   else if (sati_status == SATI_COMPLETE)
      sci_status = SCI_SUCCESS_IO_COMPLETE_BEFORE_START;
   else if (sati_status == SATI_FAILURE_CHECK_RESPONSE_DATA)
      sci_status = SCI_FAILURE_IO_RESPONSE_VALID;
   else
   {
      SCIF_LOG_ERROR((
         sci_base_object_get_logger(fw_io),
         SCIF_LOG_OBJECT_IO_REQUEST,
         "Unexpected SAT translation failure 0x%x\n",
         fw_io
      ));
   }

   return sci_status;
}


#if !defined(DISABLE_ATAPI)
/**
 * @brief This method will construct the STP PACKET protocol specific IO
 *        request object.
 *
 * @pre The scif_sas_request_construct() method should be invoked before
 *      calling this method.
 *
 * @param[in,out] fw_io This parameter specifies the stp packet io request
 *                to be constructed.
 *
 * @return Indicate if the construction was successful.
 * @return SCI_SUCCESS_IO_COMPLETE_BEFORE_START
 * @return SCI_FAILURE_IO_RESPONSE_VALID
 * @return SCI_FAILURE This return value indicates a change in the translator
 *         where a new return code has been given, but is not yet understood
 *         by this routine.
 */
SCI_STATUS scif_sas_stp_packet_io_request_construct(
   SCIF_SAS_IO_REQUEST_T * fw_io
)
{
   SATI_STATUS                sati_status;
   SCI_STATUS                 sci_status = SCI_FAILURE;
   SCIF_SAS_REMOTE_DEVICE_T * fw_device  = fw_io->parent.device;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(fw_io),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scif_sas_stp_packet_io_request_construct(0x%x) enter\n",
      fw_io
   ));

   sati_status = sati_atapi_translate_command(
                    &fw_io->parent.stp.sequence,
                    &fw_device->protocol_device.stp_device.sati_device,
                    fw_io,
                    fw_io
                 );

   if (sati_status == SATI_SUCCESS)
   {
      // Allow the core to finish construction of the IO request.
      sci_status = scic_io_request_construct_basic_sata(fw_io->parent.core_object);

      fw_io->parent.protocol_complete_handler
         = scif_sas_stp_core_cb_packet_io_request_complete_handler;
   }
   else if (sati_status == SATI_COMPLETE)
      sci_status = SCI_SUCCESS_IO_COMPLETE_BEFORE_START;
   else if (sati_status == SATI_FAILURE_CHECK_RESPONSE_DATA)
      sci_status = SCI_FAILURE_IO_RESPONSE_VALID;
   else
   {
      SCIF_LOG_ERROR((
         sci_base_object_get_logger(fw_io),
         SCIF_LOG_OBJECT_IO_REQUEST,
         "Unexpected SAT ATAPI translation failure 0x%x\n",
         fw_io
      ));
   }

   return sci_status;
}
#endif


#if !defined(DISABLE_ATAPI)
/**
 * @brief This method will get the number of bytes transferred in an packet IO.
 *
 * @param[in] fw_io This parameter specifies the stp packet io request whose
 *                     actual transferred length is to be retrieved.
 *
 * @return Actual length of transferred data.
 */
U32 scif_sas_stp_packet_io_request_get_number_of_bytes_transferred(
   SCIF_SAS_IO_REQUEST_T * fw_io
)
{
   SCI_IO_REQUEST_HANDLE_T scic_io = scif_io_request_get_scic_handle(fw_io);
   SCI_IO_STATUS io_status = scic_request_get_sci_status (scic_io);
   U32 actual_data_length;

   if (io_status == SCI_IO_FAILURE_RESPONSE_VALID)
       actual_data_length = 0;
   else if (io_status == SCI_IO_SUCCESS_IO_DONE_EARLY)
   {
      actual_data_length = sati_atapi_translate_number_of_bytes_transferred(
         &fw_io->parent.stp.sequence, fw_io, fw_io);

      if (actual_data_length == 0)
         actual_data_length =
            scic_io_request_get_number_of_bytes_transferred(scic_io);
   }
   else
      actual_data_length =
         scic_io_request_get_number_of_bytes_transferred(scic_io);

   return actual_data_length;
}
#endif


//******************************************************************************
// P U B L I C   M E T H O D S
//******************************************************************************

BOOL scic_cb_io_request_do_copy_rx_frames(
   void * scic_user_io_request
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T*) scic_user_io_request;

   SCIF_LOG_TRACE((
      sci_base_object_get_logger(fw_io),
      SCIF_LOG_OBJECT_IO_REQUEST,
      "scic_cb_io_request_do_copy_rx_frames(0x%x) enter\n",
      fw_io
   ));

   // If the translation was a PIO DATA IN (i.e. read) and the request
   // was actually a READ payload operation, then copy the data, since
   // there will be SGL space allocated for the transfer.
   if (fw_io->parent.stp.sequence.protocol == SAT_PROTOCOL_PIO_DATA_IN)
   {
      if (
            (fw_io->parent.stp.sequence.type ==  SATI_SEQUENCE_ATA_PASSTHROUGH_12)
         || (fw_io->parent.stp.sequence.type ==  SATI_SEQUENCE_ATA_PASSTHROUGH_16)
         || (
               (fw_io->parent.stp.sequence.type >= SATI_SEQUENCE_TYPE_READ_MIN)
            && (fw_io->parent.stp.sequence.type <= SATI_SEQUENCE_TYPE_READ_MAX)
            )
         )
      {
           SCIF_LOG_TRACE((
                 sci_base_object_get_logger(fw_io),
                 SCIF_LOG_OBJECT_IO_REQUEST,
                 "scic_cb_io_request_do_copy_rx_frames(0x%x) TRUE\n",
                 fw_io
              ));
           return TRUE;
      }
   }

   // For all other requests we leave the data in the core buffers.
   // This allows the translation to translate without having to have
   // separate space allocated into which to copy the data.
   return FALSE;
}

// ---------------------------------------------------------------------------

U8 scic_cb_request_get_sat_protocol(
   void * scic_user_io_request
)
{
   SCIF_SAS_IO_REQUEST_T * fw_io = (SCIF_SAS_IO_REQUEST_T*) scic_user_io_request;

   return fw_io->parent.stp.sequence.protocol;
}

U8 *scic_cb_io_request_get_virtual_address_from_sgl(
   void * scic_user_io_request,
   U32    byte_offset
)
{
   SCIF_SAS_REQUEST_T *fw_request =
      (SCIF_SAS_REQUEST_T *) sci_object_get_association(scic_user_io_request);

   return scif_cb_io_request_get_virtual_address_from_sgl(
             sci_object_get_association(fw_request),
             byte_offset
          );
}

#ifdef ENABLE_OSSL_COPY_BUFFER
void scic_cb_io_request_copy_buffer(
   void * scic_user_io_request,
   U8    *source_addr,
   U32   offset,
   U32   length
)
{
   SCIF_SAS_REQUEST_T *fw_request =
      (SCIF_SAS_REQUEST_T *)sci_object_get_association(scic_user_io_request);

   return scif_cb_io_request_copy_buffer(
             sci_object_get_association(fw_request),
             source_addr,
             offset,
             length
          );
}
#endif
// ---------------------------------------------------------------------------

SCI_BASE_REQUEST_STATE_HANDLER_T stp_io_request_constructed_handlers =
{
   scif_sas_stp_io_request_constructed_start_handler,
   scif_sas_io_request_constructed_abort_handler,
   scif_sas_stp_io_request_constructed_complete_handler,
   scif_sas_io_request_default_destruct_handler
};

