/*
 * \brief   SCSI support emulation
 * \author  Christian Helmuth
 * \author  Sebastian Sumpf
 * \date    2009-10-29
 *
 * XXX NOTES XXX
 *
 * struct scsi_host_template
 *
 * struct scsi_host
 *
 *   host_lock used by scsi_unlock, scsi_lock
 *   max_id    used by usb_stor_report_device_reset
 *
 * struct scsi_cmnd
 *
 * functions
 *
 *   scsi_add_host
 *   scsi_host_alloc
 *   scsi_host_get
 *   scsi_host_put
 *   scsi_remove_host
 *   scsi_report_bus_reset
 *   scsi_report_device_reset
 *   scsi_scan_host
 */

/*
 * Copyright (C) 2009-2017 Genode Labs GmbH
 *
 * This file is distributed under the terms of the GNU General Public License
 * version 2.
 */

#include <lx_emul.h>

#include <storage/scsi.h>

#define DEBUG_SCSI 0

/***************
 ** SCSI host **
 ***************/

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *t, int priv_size)
{
	lx_log(DEBUG_SCSI, "t=%p, priv_size=%d", t, priv_size);

	static int free = 1;

	/* XXX we not some extra space for hostdata[] */
	static char buffer[4096] __attribute__((aligned(4096)));
	static struct Scsi_Host *host = (struct Scsi_Host *)buffer;

	/* FIXME we support only one host for now */
	if (!free) return 0;
	free = 0;

	host->host_lock = &host->default_lock;
	spin_lock_init(host->host_lock);

	host->host_no = 13;
	host->max_id = 8;
	host->hostt = t;

	return host;
}


static struct page *_page(struct scsi_cmnd *cmnd)
{
	return (struct page *)cmnd->sdb.table.sgl->page_link;
}


void scsi_alloc_buffer(size_t size, struct scsi_cmnd *cmnd)
{
	scsi_setup_buffer(cmnd, size, 0, 0);
	struct scatterlist *sgl = cmnd->sdb.table.sgl;
	struct page *page       = _page(cmnd);
	page->virt              = kmalloc(size, GFP_NOIO);
	page->phys              = dma_map_single_attrs(0, page->virt, 0, 0, 0);
	sgl->dma_address        = page->phys;
}


void scsi_setup_buffer(struct scsi_cmnd *cmnd, size_t size, void *virt, dma_addr_t addr)
{
	cmnd->sdb.table.nents = 1;
	cmnd->sdb.length      = size;

	struct scatterlist *sgl = cmnd->sdb.table.sgl;

	struct page *page = _page(cmnd);
	page->virt = virt;
	page->phys = addr;

	sgl->page_link   = (unsigned long)page;
	sgl->offset      = 0;
	sgl->length      = size;
	sgl->dma_address = addr;
	sgl->last        = 1;
}


void scsi_free_buffer(struct scsi_cmnd *cmnd)
{
	struct page *page = _page(cmnd);
	if (page)
		kfree(page->virt);
}


void *scsi_buffer_data(struct scsi_cmnd *cmnd)
{
	return _page(cmnd)->virt;
}


struct scsi_cmnd *_scsi_alloc_command()
{
	struct scsi_cmnd *cmnd = (struct scsi_cmnd *)kmalloc(sizeof(struct scsi_cmnd), GFP_KERNEL);
	cmnd->sdb.table.sgl = (struct scatterlist *)kmalloc(sizeof(struct scatterlist), GFP_KERNEL);
	cmnd->cmnd = kzalloc(MAX_COMMAND_SIZE, 0);
	cmnd->sdb.table.sgl->page_link = (unsigned long) kzalloc(sizeof(struct page), 0);
	cmnd->sense_buffer = kmalloc(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL);
	return cmnd;
}


void _scsi_free_command(struct scsi_cmnd *cmnd)
{
	kfree((void *)cmnd->sdb.table.sgl->page_link);
	kfree(cmnd->sdb.table.sgl);
	kfree(cmnd->sense_buffer);
	kfree(cmnd->cmnd);
	kfree(cmnd);
}


static void inquiry_done(struct scsi_cmnd *cmnd)
{
	char *data = (char *)scsi_buffer_data(cmnd);
	lx_printf("Vendor id: %c%c%c%c%c%c%c%c Product id: %s\n",
	          data[8], data[9], data[10], data[11], data[12],
	          data[13], data[14], data[15], &data[16]);
	complete(cmnd->back);
}


static void scsi_done(struct scsi_cmnd *cmd)
{
	complete(cmd->back);
}


void scsi_scan_host(struct Scsi_Host *host)
{
	struct scsi_cmnd   *cmnd;
	struct scsi_device *sdev;
	struct scsi_target *target;
	struct completion compl;
	void *result;

	init_completion(&compl);

	sdev = (struct scsi_device *)kmalloc(sizeof(struct scsi_device), GFP_KERNEL);
	target = (struct scsi_target *)kmalloc(sizeof(struct scsi_target), GFP_KERNEL);

	cmnd = _scsi_alloc_command();

	/* init device */
	sdev->sdev_target = target;
	sdev->host = host;
	sdev->id  = 0;
	sdev->lun = 0;
	host->hostt->slave_alloc(sdev);
	host->hostt->slave_configure(sdev);

	/* inquiry (36 bytes for usb) */
	scsi_alloc_buffer(sdev->inquiry_len, cmnd);
	cmnd->cmnd[0] = INQUIRY;
	cmnd->cmnd[4] = sdev->inquiry_len;
	cmnd->device  = sdev;
	cmnd->cmd_len = 6;
	cmnd->sc_data_direction = DMA_FROM_DEVICE;

	cmnd->back = &compl;
	cmnd->scsi_done = inquiry_done;

	host->hostt->queuecommand(host, cmnd);
	wait_for_completion(&compl);

	/* if PQ and PDT are zero we have a direct access block device conntected */
	result = scsi_buffer_data(cmnd);
	if (!((char*)result)[0])
		scsi_add_device(sdev);
	else {
		kfree(sdev);
		kfree(target);
	}

	scsi_free_buffer(cmnd);
	_scsi_free_command(cmnd);
}


/**********************
 ** scsi/scsi_cmnd.h **
 **********************/

unsigned scsi_bufflen(struct scsi_cmnd *cmnd)           { return cmnd->sdb.length; }
struct scatterlist *scsi_sglist(struct scsi_cmnd *cmnd) { return cmnd->sdb.table.sgl; }
unsigned scsi_sg_count(struct scsi_cmnd *cmnd)          { return cmnd->sdb.table.nents; }


/********************
 ** scsi/scsi_eh.h **
 *******************/

void scsi_eh_prep_cmnd(struct scsi_cmnd *scmd,
                       struct scsi_eh_save *ses, unsigned char *cmnd,
                       int cmnd_size, unsigned sense_bytes)
{
	ses->cmd_len = scmd->cmd_len;
}


void scsi_eh_restore_cmnd(struct scsi_cmnd* scmd,
                          struct scsi_eh_save *ses)
{
	scmd->cmd_len = ses->cmd_len;
}
