

#include "hint_device.h"
#include "hint_error.h"
#include "hint_log.h"

#include <scsi/scsi_driver.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_cmnd.h>
#include <linux/slab.h>

static void hint_device_send_fast_done(struct scsi_cmnd *sc)
{
  struct request *req;

  if (sc->request->cmd[0] == WRITE_10) {
    hint_log("send hint done sc=%p", sc);
  }
  else {
    hint_error("unknown cmd done, sc=%p", sc);
  }
  
  req = sc->request;
  kfree(req->sense);
  bio_put(req->bio);
  req->bio = NULL;
  kfree(req);
}

void hint_device_init(hint_dev_t *hint_dev)
{
  memset(hint_dev, 0, sizeof(hint_dev));
}

void hint_device_set(hint_dev_t *hint_dev, struct scsi_device *sd)
{
  hint_dev->sd = sd;
}

int hint_device_is_ready(hint_dev_t *hint_dev)
{
  return hint_dev->sd != NULL;
}

int hint_device_send(hint_dev_t *hint_dev, const char* buffer, int size, int timeout)
{
  char write_cmd[] = {WRITE_10,0,0,0,
                      0,0,0,0,
                      1,0};
  char sense[SCSI_SENSE_BUFFERSIZE];
  int resid;
  int ret;

  if (!hint_dev->sd)
    return H_NO_DEVICE;
  
  memset(sense, 0, sizeof(sense));
  ret = scsi_execute(hint_dev->sd, write_cmd, DMA_TO_DEVICE,
                     (void*)buffer, size,
                     sense,
                     timeout,
                     0,       /* retries */
                     0,       /* flags */
                     &resid);
  if (ret != 0) {
    hint_error("scsi_execute STATUS:%d HOST:%d DRIVER:%d",
               status_byte(ret),
               host_byte(ret),
               driver_byte(ret));
    return H_FAIL;
  }

  return H_OK;
}

int hint_device_send_fast(hint_dev_t *hint_dev, const char* buffer, int size, int timeout)
{
  struct request *req;
  struct scsi_device *sd = hint_dev->sd;
  int ret;

  hint_log("buffer=%p, size=%d, timeout=%d", buffer, size, timeout);
  if (!hint_dev->sd)
    return H_NO_DEVICE;

  req = kmalloc(sizeof(*req), GFP_ATOMIC);
  if (!req) {
    hint_error("kmalloc request");
    return H_FAIL;
  }
  blk_rq_init(sd->request_queue, req);
  req->cmd_flags = WRITE;

  ret = blk_rq_map_kern(req->q, req,
                        (void*)buffer, size,
                        GFP_ATOMIC);
  if (ret != 0) {
    hint_error_ret(ret,"blk_rq_map_kern");
    blk_put_request(req);
    return H_FAIL;
  }

  req->cmd_type = REQ_TYPE_BLOCK_PC;
  req->cmd_len = COMMAND_SIZE(WRITE_10);
  req->cmd[0] = WRITE_10;
  req->cmd[8] = 1;
  req->timeout = timeout;
  req->sense = kmalloc(SCSI_SENSE_BUFFERSIZE, GFP_ATOMIC);
  if (!req->sense) {
    hint_error("kmalloc(SCSI_SENSE_BUFFERSIZE, GFP_ATOMIC)");
    bio_put(req->bio);
    req->bio = NULL;
    blk_put_request(req);
    return H_FAIL;
  }
  memset(req->sense, 0, SCSI_SENSE_BUFFERSIZE);
	req->sense_len = 0;

  ret = scsi_prep_fn(req->q, req);
  if (ret != BLKPREP_OK) {
    hint_error_ret(ret, "scsi_prep_fn");
    kfree(req->sense);
    bio_put(req->bio);
    req->bio = NULL;
    blk_put_request(req);
    return H_FAIL;
  }

  hint_log("send hint sc=%p", req->special);
  ret = sd->host->hostt->queuecommand(req->special, hint_device_send_fast_done);
  if (ret) {
    hint_error_ret(ret, "queuecommand");
    hint_device_send_fast_done(req->special);
    return H_FAIL;
  }

  return H_OK;
}

int hint_device_recv(hint_dev_t *hint_dev, char* buffer, int size, int timeout)
{
  char read_cmd[] = {READ_10,0,0,0,
                      0,0,0,0,
                      1,0};
  char sense[SCSI_SENSE_BUFFERSIZE];
  int resid;
  int ret;

  if (!hint_dev->sd)
    return H_NO_DEVICE;

  memset(sense, 0, sizeof(sense));
  ret = scsi_execute(hint_dev->sd, read_cmd, DMA_FROM_DEVICE,
                     buffer, size,
                     sense,
                     timeout,
                     0,       /* retries */
                     0,       /* flags */
                     &resid);
  if (ret != 0) {
    hint_error("scsi_execute STATUS:%d HOST:%d DRIVER:%d",
               status_byte(ret),
               host_byte(ret),
               driver_byte(ret));
    return H_FAIL;
  }

  return H_OK;
}
