/*
 * SCSI block command processing
 *
 * Copyright (C) 2004-2007 FUJITA Tomonori <tomof@acm.org>
 * Copyright (C) 2005-2007 Mike Christie <michaelc@cs.wisc.edu>
 *
 * This code is based on Ardis's iSCSI implementation.
 *   http://www.ardistech.com/iscsi/
 *   Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>,
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 2 of the
 * License.
 *
 * 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
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/fs.h>

#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "tgtadm_error.h"
#include "target.h"
#include "driver.h"
#include "scsi.h"
#include "spc.h"
#include "tgtadm_error.h"
#include "ssd/ssd.h"
#include "hint/hint_error.h"
#include "hint/hint_log.h"
#include "ssd/nand.h"
#include "ssd/tm.h"
#include "ssd/ngssd.h"
#include "ssd/scsi_layer.h"

//#define DISK_SIZE (NAND_PAGE_COUNT*NAND_PAGE_SIZE) // 1M
#define DISK_SIZE ((uint64_t)(TM_SECTOR_COUNT*NAND_PAGE_SIZE))
#define BLK_SHIFT	POWER_NAND_PAGE_SIZE
#define BLK_SIZE NAND_PAGE_SIZE
#define HINT_DATA_GET_PAYLOAD(HINT_DATA) ((hint_payload_t*)(&((HINT_DATA)->hint_payload)))

#ifdef PROFILING
extern uint32_t hint_accessed;
extern uint32_t read_accessed;
extern int nand_total_reads;
extern int nand_total_writes;
extern int nand_erases;
#endif

static void ssd_notify_hint(struct scsi_lu *lu, hint_data_t *hint_data)
{
  ssd_device_info_t *ssd_info = dtype_priv(lu);
  hint_container_t *hint_container;

  //hint_log_debug("start");
   
  /* allocate and copy hint data */
  hint_container = malloc(sizeof(*hint_container));
  hint_container->hint_data = malloc(sizeof(*hint_data));
  memcpy(hint_container->hint_data, hint_data, sizeof(*hint_data));
        
  pthread_mutex_lock(&ssd_info->hints_list_lock);
  list_add_tail(&hint_container->list, &ssd_info->hints_list);
  pthread_mutex_unlock(&ssd_info->hints_list_lock);
}
      
static void ssd_notify_unregister(struct scsi_lu *lu)
{
  hint_log_debug("start");
}

#if 0
static void ssd_check_hints(struct scsi_cmd *cmd)
{
  ssd_device_info_t *ssd_info = dtype_priv(cmd->dev);
  hint_container_t *hint_container, *next_hint_container;
  
  hint_log_debug("start");
  pthread_mutex_lock(&ssd_info->hints_list_lock);
  list_for_each_entry_safe(hint_container, next_hint_container,
                           &ssd_info->hints_list, list) {
    list_del(&hint_container->list);
    hint_log_debug("hint_data: version=0x%x, lun=%d",
                   hint_container->hint_data->version,
                   hint_container->hint_data->lun);
    free(hint_container->hint_data);
    free(hint_container);
  }
  pthread_mutex_unlock(&ssd_info->hints_list_lock);
}
#else
void ssd_ftl_hint_write_notifier(void* dummy, hint_data_t* hint_data)
{
	/*free hint_data */
	free(hint_data);
}

static void
ssd_delete_hints_list_prefix(ssd_device_info_t *ssd_info, hint_container_t *start_container){
	hint_container_t *hint_container, *next_hint_container;
	list_for_each_entry_safe(hint_container, next_hint_container,
														 &ssd_info->hints_list, list) {
			if(hint_container == start_container)
				break;
//			printf("ssd_delete_hints_list_prefix() - delete hint lba %u version %u last lba %u\n",
//								HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->start_lba, HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->version,
//								((uint32_t*)(HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->data))[CH_SECTORS_PER_CHUNK-1]);
			list_del(&hint_container->list);

	}
}

/**
 * check hints aux
 * find first hint in list for given lba
 */
static hint_data_t*
ssd_get_hint(ssd_device_info_t *ssd_info, uint32_t lba){
	hint_container_t *hint_container, *next_hint_container;
	hint_data_t *hint_data;
	hint_log_debug("check hint for lba %u (DISK_SIZE %llu)\n", lba, DISK_SIZE);
	lba = lba - (lba % HINT_SECTORS_IN_DATA);
         
        //hint_log_debug("list all hints");
	//list_for_each_entry_safe(hint_container, next_hint_container,
	//													 &ssd_info->hints_list, list) {
	//	hint_log_debug("hint lba %u version %u (last lba %u)",
        //                       HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->start_lba, HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->version,	
        //                       ((uint32_t*)(HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->data))[CH_SECTORS_PER_CHUNK-1]);
	//}

	//hint_log_debug("start.");
	pthread_mutex_lock(&ssd_info->hints_list_lock);
	list_for_each_entry_safe(hint_container, next_hint_container, 
				 &ssd_info->hints_list, list) {
//		printf("hint lba %u version %u\n", HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->start_lba, HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->version);
		if(lba == HINT_DATA_GET_PAYLOAD(hint_container->hint_data)->start_lba){
			hint_data = hint_container->hint_data;

			/* delete all preceding hints. if their request hadn't used them by now
			 * then they'll will never be useful*/
			ssd_delete_hints_list_prefix(ssd_info, hint_container);
			list_del(&hint_container->list);
			free(hint_container);
			pthread_mutex_unlock(&ssd_info->hints_list_lock);
			return hint_data;
		}
	}

	/*none found*/
	pthread_mutex_unlock(&ssd_info->hints_list_lock);
	hint_log_debug("no hint found. return 0");
	return 0;
}

/**
 * my new check hints
 */
static void ssd_check_hints(struct scsi_cmd *cmd)
{
  ssd_device_info_t *ssd_info = dtype_priv(cmd->dev);
  uint32_t lba = scsi_rw_offset(cmd->scb);
  /* iterate hints list/table and find relevant hint
   * insert it to ngssd */
  /*XXX from hash table?*/
  //hint_log_debug("start\n");
  hint_data_t* hint_data;
  cdb_t* cdb;
  req_t* req;
  uint32_t length = scsi_rw_count(cmd->scb);
  uint32_t lbas_to_next_chunk = HINT_SECTORS_IN_DATA - (lba % HINT_SECTORS_IN_DATA); 
  do{
    hint_data =  ssd_get_hint(ssd_info, lba);
//  printf("hint_data %x\n", hint_data);

    if(hint_data){
//  	printf("HINT_DATA_GET_PAYLOAD(hint_data) %x\n", HINT_DATA_GET_PAYLOAD(hint_data));
//  	  printf("HINT_DATA_GET_PAYLOAD(hint_data)->start_lba %x\n", HINT_DATA_GET_PAYLOAD(hint_data)->start_lba);
 	cdb = (cdb_t*)malloc(sizeof(cdb_t));

  	if(!cdb){
  	  /* malloc error.
  	   * hint cannot be used. free it and return*/
  	  free(hint_data);
	  return;
  	}

	cdb->cmd = SCSI_WRITE;
	cdb->buf = (char*)HINT_DATA_GET_PAYLOAD(hint_data);
	cdb->len = 1;
	cdb->start_lba = HINT_DATA_GET_PAYLOAD(hint_data)->start_lba;
	cdb->lba_size  = HINT_PAYLOAD_SIZE;
	cdb->private = hint_data;

//		printf("ssd_check_hints() - last lba %u\n", ((uint32_t*)(HINT_DATA_GET_PAYLOAD(hint_data)->data))[CH_SECTORS_PER_CHUNK-1]);
    	req = scsiRequest((handler_t)ssd_ftl_hint_write_notifier, cdb, 1);

    	if (req == 0) {
	  hint_log_error("Failed to initiate scsiRequest");
	  assert(0);
	}
    }

    /*prepare for next run*/
    lbas_to_next_chunk = HINT_SECTORS_IN_DATA - (lba % HINT_SECTORS_IN_DATA);
    if(length > lbas_to_next_chunk)
      length -= lbas_to_next_chunk;
    else
      length = 0;
    lba += lbas_to_next_chunk;
  }while(length > 0);
}
#endif

static int ssd_mode_page_update(struct scsi_cmd *cmd, uint8_t *data, int *changed)
{
	uint8_t pcode = data[0] & 0x3f;
	struct mode_pg *pg = cmd->dev->mode_pgs[pcode];
	uint8_t old;

	eprintf("%x %x\n", pg->mode_data[0], data[2]);

	if (pcode == 0x08) {
		old = pg->mode_data[0];
		if (0x4 & data[2])
			pg->mode_data[0] |= 0x4;
		else
			pg->mode_data[0] &= ~0x4;

		if (old != pg->mode_data[0])
			*changed = 1;

		return 0;
	}

	return 1;
}

static int ssd_mode_select(int host_no, struct scsi_cmd *cmd)
{
	return spc_mode_select(host_no, cmd, ssd_mode_page_update);
}

static void ssd_ftl_notifier(void* dummy, cdb_t* cdb)
{
  ssd_request_t *ssd_request = (ssd_request_t*)cdb->private;
  //hint_log_debug("ssd_ftl_notifier, ssd_request=%p (hint_accessed %u read_accessed %u)", ssd_request, hint_accessed, read_accessed);
  /* TODO: handle case when scsi request failure */
  if (__sync_sub_and_fetch(&ssd_request->count, 1) == 0) {
    //hint_log_debug("ssd scsi cmd completed, cmd=%p", ssd_request->scsi_cmd);
    target_cmd_io_done(ssd_request->scsi_cmd, SAM_STAT_GOOD);
    free(ssd_request->cdbs);
    free(ssd_request);
  }
}

static int ssd_submit_to_ftl(struct scsi_cmd *cmd)
{
  int scsi_cmd_type;
  uint8_t *buf;
  int buf_len;
  int i;
  ssd_request_t* ssd_request;

  switch (cmd->scb[0]) {
	case WRITE_6:
	case WRITE_10:
	case WRITE_12:
	case WRITE_16:
    scsi_cmd_type = SCSI_WRITE;
    buf = scsi_get_out_buffer(cmd);
    buf_len = scsi_get_out_length(cmd);
		break;
	case READ_6:
	case READ_10:
	case READ_12:
	case READ_16:
    scsi_cmd_type = SCSI_READ;
    buf = scsi_get_in_buffer(cmd);
    buf_len = scsi_get_in_length(cmd);
    break;
  default:
    hint_log_error("Got unsupported R/W CDB 0x%x", cmd->scb[0]);
    return -1;
  }

  hint_log_debug("Scsi request: cmd=%p, cmd_type=%d, start_lba=%lld, "
                 "lba_count=%d, lba_size=%d, buf=%p, buf_len=%d",
                 cmd, scsi_cmd_type, scsi_rw_offset(cmd->scb),
                 scsi_rw_count(cmd->scb), BLK_SIZE, buf, buf_len);

  if (scsi_rw_count(cmd->scb)*BLK_SIZE > buf_len) {
    hint_log_error("buffer too small for command");
    return -1;
  }

  ssd_request = malloc(sizeof(ssd_request_t));
  if (ssd_request == 0) {
    return -1;
  }
  memset(ssd_request, 0, sizeof(ssd_request_t));
#if 1
  ssd_request->cdbs_count = scsi_rw_count(cmd->scb);
  ssd_request->cdbs = malloc(sizeof(cdb_t)*ssd_request->cdbs_count);
  if (ssd_request->cdbs == 0) {
    free(ssd_request);
    return -1;
  }
  memset(ssd_request->cdbs, 0, sizeof(cdb_t)*ssd_request->cdbs_count);


  for (i=0; i<scsi_rw_count(cmd->scb); i++) {
    ssd_request->cdbs[i].cmd = scsi_cmd_type;
    ssd_request->cdbs[i].buf = buf+i*BLK_SIZE;
    ssd_request->cdbs[i].len = 1;
    ssd_request->cdbs[i].start_lba = scsi_rw_offset(cmd->scb)+i;
    ssd_request->cdbs[i].lba_size  = BLK_SIZE;
    ssd_request->cdbs[i].private = ssd_request;
    ssd_request->count++;
  }
  
  ssd_request->scsi_cmd = cmd;
  hint_log_debug("Scsi request issue: ssd_request %s cdb i=%d. start_lba %u", (ssd_request->cdbs[i].cmd == SCSI_READ)?"SCSI_READ":"SCSI_WRITE",i, ssd_request->cdbs[i].start_lba);
  for (i=0; i< scsi_rw_count(cmd->scb); i++) {
//    hint_log_debug("Scsi request issue: ssd_request %s cdb i=%d. start_lba %u", (ssd_request->cdbs[i].cmd == SCSI_READ)?"SCSI_READ":"SCSI_WRITE",i, ssd_request->cdbs[i].start_lba);
    req_t* req = scsiRequest((handler_t)ssd_ftl_notifier, &ssd_request->cdbs[i], 0);
    if (req == 0) {
      hint_log_error("Failed to initiate scsiRequest");
      assert(0);
    }
  }
  #ifdef PROFILING
  hint_log_debug("accessed hint %u read %u (hint/total %f)\n", hint_accessed, read_accessed, (float)hint_accessed/(float)(hint_accessed + read_accessed));
  #endif
#else
  ssd_request->cdbs_count = 1;
  ssd_request->cdbs = malloc(sizeof(cdb_t)*ssd_request->cdbs_count);
  if (ssd_request->cdbs == 0) {
    free(ssd_request);
    return -1;
  }
  memset(ssd_request->cdbs, 0, sizeof(cdb_t)*ssd_request->cdbs_count);

  ssd_request->cdbs[0].cmd = scsi_cmd_type;
  ssd_request->cdbs[0].buf = buf;
  ssd_request->cdbs[0].len = scsi_rw_count(cmd->scb);
  ssd_request->cdbs[0].start_lba = scsi_rw_offset(cmd->scb);
  ssd_request->cdbs[0].lba_size  = BLK_SIZE;
  ssd_request->cdbs[0].private = ssd_request;
  ssd_request->count = 1;

  ssd_request->scsi_cmd = cmd;
  //hint_log_debug("Scsi request issue: ssd_request %s cdb i=%d. start_lba %u", (ssd_request->cdbs[i].cmd == SCSI_READ)?"SCSI_READ":"SCSI_WRITE",i, ssd_request->cdbs[i].start_lba);
  req_t* req = scsiRequest((handler_t)ssd_ftl_notifier, &ssd_request->cdbs[0], 0);
  if (req == 0) {
    hint_log_error("Failed to initiate scsiRequest");
    assert(0);
  }
  
#endif
  return 0;
}

static int ssd_rw(int host_no, struct scsi_cmd *cmd)
{
	int ret;
	unsigned char key = ILLEGAL_REQUEST;
	uint16_t asc = ASC_LUN_NOT_SUPPORTED;
  hint_data_t *hint_data;

	ret = device_reserved(cmd);
	if (ret)
		return SAM_STAT_RESERVATION_CONFLICT;

  hint_log_cdb(cmd->scb);
	cmd->scsi_cmd_done = target_cmd_io_done;

	cmd->offset = (scsi_rw_offset(cmd->scb) << BLK_SHIFT);

  ssd_check_hints(cmd);
  ret = ssd_submit_to_ftl(cmd);
	if (ret) {
		key = HARDWARE_ERROR;
		asc = ASC_INTERNAL_TGT_FAILURE;
	} else {
		set_cmd_async(cmd);
#if 0
    hint_data = zalloc(sizeof(*hint_data));
    if (hint_data) {
      ret = hint_send(cmd->itn_lu_info->lu, hint_data);
      if (ret != H_OK) {
        free(hint_data);
      }
      hint_log_debug("sending hint, ret=%d", ret);
    }
#endif
		return SAM_STAT_GOOD;
	}

	cmd->offset = 0;
	scsi_set_in_resid_by_actual(cmd, 0);
	scsi_set_out_resid_by_actual(cmd, 0);

	sense_data_build(cmd, key, asc);
	return SAM_STAT_CHECK_CONDITION;
}

static int ssd_reserve(int host_no, struct scsi_cmd *cmd)
{
	if (device_reserve(cmd))
		return SAM_STAT_RESERVATION_CONFLICT ;
	else
		return SAM_STAT_GOOD;
}

static int ssd_release(int host_no, struct scsi_cmd *cmd)
{
	int ret;

	ret = device_release(cmd->c_target->tid, cmd->cmd_itn_id,
			     cmd->dev->lun, 0);

	return ret ? SAM_STAT_RESERVATION_CONFLICT : SAM_STAT_GOOD;
}

static int ssd_read_capacity(int host_no, struct scsi_cmd *cmd)
{
	uint32_t *data;
	uint64_t size;
	uint8_t *scb = cmd->scb;
	unsigned char key = ILLEGAL_REQUEST;
	uint16_t asc = ASC_LUN_NOT_SUPPORTED;

	if (!(scb[8] & 0x1) && (scb[2] | scb[3] | scb[4] | scb[5])) {
		asc = ASC_INVALID_FIELD_IN_CDB;
		goto sense;
	}

	if (scsi_get_in_length(cmd) < 8)
		goto overflow;

	data = scsi_get_in_buffer(cmd);
	size = DISK_SIZE >> BLK_SHIFT; // cmd->dev->size >> BLK_SHIFT;

	data[0] = (size >> 32) ?
		__cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1);
	data[1] = __cpu_to_be32(1U << BLK_SHIFT);

overflow:
	scsi_set_in_resid_by_actual(cmd, 8);
	return SAM_STAT_GOOD;
sense:
	scsi_set_in_resid_by_actual(cmd, 0);
	sense_data_build(cmd, key, asc);
	return SAM_STAT_CHECK_CONDITION;
}

static int ssd_verify(int host_no, struct scsi_cmd *cmd)
{
	return SAM_STAT_GOOD;
}

static int ssd_service_action(int host_no, struct scsi_cmd *cmd)
{
	uint32_t *data;
	uint64_t size;

	if (cmd->scb[1] != SAI_READ_CAPACITY_16)
		goto sense;

	if (scsi_get_in_length(cmd) < 12)
		goto overflow;

	data = scsi_get_in_buffer(cmd);
	memset(data, 0, 12);

	size = DISK_SIZE >> BLK_SHIFT; //cmd->dev->size >> BLK_SHIFT;

	*((uint64_t *)(data)) = __cpu_to_be64(size - 1);
	data[2] = __cpu_to_be32(1UL << BLK_SHIFT);

overflow:
	scsi_set_in_resid_by_actual(cmd, 12);
	return SAM_STAT_GOOD;
sense:
	sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE);
	return SAM_STAT_CHECK_CONDITION;
}

static int ssd_sync_cache(int host_no, struct scsi_cmd *cmd)
{
	int ret, len;
	uint8_t key = ILLEGAL_REQUEST;
	uint16_t asc = ASC_LUN_NOT_SUPPORTED;

	if (device_reserved(cmd))
		return SAM_STAT_RESERVATION_CONFLICT;

	cmd->scsi_cmd_done = target_cmd_io_done;

	ret = cmd->dev->bst->bs_cmd_submit(cmd);
	switch (ret) {
	case EROFS:
	case EINVAL:
	case EBADF:
	case EIO:
		/*
		 * is this the right sense code?
		 * what should I put for the asc/ascq?
		 */
		key = HARDWARE_ERROR;
		asc = ASC_INTERNAL_TGT_FAILURE;
		goto sense;
	default:
		len = 0;
		return SAM_STAT_GOOD;
	}

sense:
	scsi_set_in_resid_by_actual(cmd, 0);
	sense_data_build(cmd, key, asc);
	return SAM_STAT_CHECK_CONDITION;
}

static void set_device_attributes(struct scsi_lu *lu)
{
  uint64_t size;
	uint8_t *data;

  strncpy(lu->attrs.product_id, "VIRTUAL-SSD-DISK", sizeof(lu->attrs.product_id));
	lu->attrs.version_desc[0] = 0x04C0; /* SBC-3 no version claimed */
	lu->attrs.version_desc[1] = 0x0960; /* iSCSI */
	lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */

  lu->attrs.device_type = TYPE_DISK;

	data = lu->mode_block_descriptor;
	size = DISK_SIZE >> BLK_SHIFT; //lu->size >> BLK_SHIFT;

	*(uint32_t *)(data) = (size >> 32) ?
			__cpu_to_be32(0xffffffff) : __cpu_to_be32(size);
	*(uint32_t *)(data + 4) = __cpu_to_be32(1 << BLK_SHIFT);

	/* Vendor uniq - However most apps seem to call for mode page 0*/
	add_mode_page(lu, "0:0:0");
	/* Disconnect page */
	add_mode_page(lu, "2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0");
	/* Caching Page */
	add_mode_page(lu, "8:0:18:0x14:0:0xff:0xff:0:0:"
		      "0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0");
	{
		uint8_t mask[18];
		memset(mask, 0, sizeof(mask));
		mask[0] = 0x4;

		set_mode_page_changeable_mask(lu, 8, mask);
	}

	/* Control page */
	add_mode_page(lu, "10:0:10:2:0x10:0:0:0:0:0:0:2:0");
	/* Informational Exceptions Control page */
	add_mode_page(lu, "0x1c:0:10:8:0:0:0:0:0:0:0:0:0");
}

static int ssd_lu_init(struct scsi_lu *lu)
{
  int ret;
  ssd_device_info_t *ssd_info;
  hint_service_template_t hint_register_template;

  ssd_info = zalloc(sizeof(*ssd_info));
  if (!ssd_info) {
    ret = TGTADM_NOMEM;
    goto Exit;
  }

  INIT_LIST_HEAD(&ssd_info->hints_list);
  pthread_mutex_init(&ssd_info->hints_list_lock, NULL);
  
	if (spc_lu_init(lu)) {
    ret = TGTADM_NOMEM;
    goto DestroyThreads;
  }

  dtype_priv(lu) = ssd_info;
  set_device_attributes(lu);

  /* ngssd device init*/
  ngssdInit(lu);

  memset(&hint_register_template, 0, sizeof(hint_register_template));
  hint_register_template.lu = lu;
  hint_register_template.notify_unregister = ssd_notify_unregister;
  hint_register_template.notify_hint = ssd_notify_hint;

  hint_log_debug("Register to hint device");
  ret = hint_register(&hint_register_template);
  if (ret == H_NO_DEVICE) {
    hint_log_error("Can't find ssd device to assign to hint device");
  }
  else if (ret != H_OK) {
    hint_log_error("Failed to register to ssd hint device, ret=%d", ret);
  }
  return 0;
  
DestroyThreads:
  pthread_mutex_destroy(&ssd_info->hints_list_lock);
  free(ssd_info);
Exit:
	return ret;
}

static void ssd_lu_exit(struct scsi_lu *lu)
{
  ssd_device_info_t *ssd_info = dtype_priv(lu);
  hint_container_t *hint_container, *next_hint_container;
  
  hint_unregister(lu);

  pthread_mutex_lock(&ssd_info->hints_list_lock);
  list_for_each_entry_safe(hint_container, next_hint_container,
                           &ssd_info->hints_list, list) {
    list_del(&hint_container->list);
    free(hint_container->hint_data);
    free(hint_container);
  }
  pthread_mutex_unlock(&ssd_info->hints_list_lock);
  
  pthread_mutex_destroy(&ssd_info->hints_list_lock);
  free(ssd_info);
  spc_lu_exit(lu);
}

static struct device_type_template ssd_template = {
	.type		= TYPE_SSD,
	.lu_init	= ssd_lu_init,
	.lu_config	= spc_lu_config,
	.lu_online	= spc_lu_online,
	.lu_offline	= spc_lu_offline,
	.lu_exit	= ssd_lu_exit,
	.ops		= {
		{spc_test_unit,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_request_sense,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{ssd_rw, NULL, PR_EA_FA|PR_EA_FN},
		{spc_illegal_op,},
		{ssd_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		/* 0x10 */
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_inquiry,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{ssd_reserve,},
		{ssd_release,},

		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_start_stop, NULL, PR_SPECIAL},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		/* 0x20 */
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_read_capacity,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{ssd_rw, NULL, PR_EA_FA|PR_EA_FN},
		{spc_illegal_op,},
		{ssd_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_verify, NULL, PR_EA_FA|PR_EA_FN},

		/* 0x30 */
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		[0x40 ... 0x4f] = {spc_illegal_op,},

		/* 0x50 */
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_service_action, persistent_reserve_in_actions,},
		{spc_service_action, persistent_reserve_out_actions,},

		[0x60 ... 0x7f] = {spc_illegal_op,},

		/* 0x80 */
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{ssd_rw, NULL, PR_EA_FA|PR_EA_FN},
		{spc_illegal_op,},
		{ssd_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		/* 0x90 */
		{spc_illegal_op,},
		{ssd_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{ssd_service_action,},
		{spc_illegal_op,},

		/* 0xA0 */
		{spc_report_luns,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_service_action, maint_in_service_actions,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		{ssd_rw, NULL, PR_EA_FA|PR_EA_FN},
		{spc_illegal_op,},
		{ssd_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},
		{spc_illegal_op,},

		[0xb0 ... 0xff] = {spc_illegal_op},
	}
};

__attribute__((constructor)) static void ssd_init(void)
{
	device_type_register(&ssd_template);
}
