/*
 *	Low-level interface to adapter information.
 *
 *	Copyright (c) 2007 by Jefferson Ogata
 */

/*
 * 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; either version 2, or (at your option)
 * any later version.
 *
 * 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; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


#include	"megaioctl.h"

/* Don't include <sys/types.h> */

#include	<assert.h>
#include	<memory.h>
#include	<malloc.h>
#include	<errno.h>
#include	<sys/ioctl.h>
#include	<scsi/scsi.h>


/* This is arbitrary. */
#define	MEGA_MAX_ADAPTERS	16

struct mega_adapter_map
{
    uint8_t	count;				/* number of adapters found */
    uint8_t	host[MEGA_MAX_ADAPTERS];	/* map of adapter index to host number */
};

static struct mega_adapter_map	*adapterMap = NULL;


int		megaErrno = 0;


static u16 hostMap (u16 adapno)
{
    if ((adapterMap == NULL) || (adapno >= adapterMap->count))
	return 0;

    return adapterMap->host[adapno];
}


static int doIoctl (struct mega_adapter_path *adapter, void *u)
{
    switch (adapter->type)
    {
     case MEGA_ADAPTER_V2:
     case MEGA_ADAPTER_V34:
	return ioctl (adapter->fd, _IOWR(MEGAIOC_MAGIC, 0, struct uioctl_t), u);
     case MEGA_ADAPTER_V5:
	return ioctl (adapter->fd, MEGASAS_IOC_FIRMWARE, u);
    }
    return -1;
}


static int driverQuery (int fd, void *data, uint32_t len, uint8_t subop)
{
    struct uioctl_t		u;
    struct mega_adapter_path	adapter;

    memset (&u, 0, sizeof u);
    u.outlen = len;
    u.ui.fcs.opcode = M_RD_DRIVER_IOCTL_INTERFACE;
    u.ui.fcs.subopcode = subop;
    u.ui.fcs.length = len;
    u.data = data;
    if (data)
	memset (data, 0, len);

    adapter.fd = fd;
    adapter.adapno = 0;
    adapter.type = MEGA_ADAPTER_V34;
    if (doIoctl (&adapter, &u) < 0)
    {
	megaErrno = errno;
	return -1;
    }
    return 0;
}


static int oldCommand (struct mega_adapter_path *adapter, void *data, uint32_t len, uint8_t cmd, uint8_t opcode, uint8_t subopcode)
{
    struct uioctl_t		u;
    int_mbox_t			*m = (int_mbox_t *) &u.mbox;

    memset (&u, 0, sizeof u);
    u.outlen = len;
    u.ui.fcs.opcode = M_RD_IOCTL_CMD;
    u.ui.fcs.adapno = MKADAP(hostMap (adapter->adapno));
    u.data = data;
    m->cmd = cmd;
    m->opcode = opcode;
    m->subopcode = subopcode;
    assert(UINT32_MAX > (uint32_t) data);
    m->xferaddr = (uint32_t) data;
    if (data)
	memset (data, 0, len);

    if (doIoctl (adapter, &u) < 0)
    {
	megaErrno = errno;
	return -1;
    }
    return 0;
}


static int newCommand (struct mega_adapter_path *adapter, void *data, uint32_t len, uint8_t cmd, uint8_t opcode, uint8_t subopcode)
{
    struct uioctl_t		u;
    int_mbox_t			*m = (int_mbox_t *) &u.mbox;

    memset (&u, 0, sizeof u);
    u.outlen = len;
    u.ui.fcs.opcode = M_RD_IOCTL_CMD_NEW;
    u.ui.fcs.adapno = MKADAP(hostMap (adapter->adapno));
    u.ui.fcs.buffer = data;
    u.ui.fcs.length = len;
    u.data = data;
    m->cmd = cmd;
    m->opcode = opcode;
    m->subopcode = subopcode;
    assert(UINT32_MAX > (uint32_t) data);
    m->xferaddr = (uint32_t) data;
    if (data)
	memset (data, 0, len);

    if (doIoctl (adapter, &u) < 0)
    {
	megaErrno = errno;
	return -1;
    }
    return 0;
}


static int sasCommand (struct mega_adapter_path *adapter, void *data, uint32_t len, uint32_t opcode, uint16_t flags, void *mbox, uint32_t mboxlen)
{
    struct megasas_iocpacket	u;
    struct megasas_dcmd_frame	*f = (struct megasas_dcmd_frame *) &u.frame;

    memset (&u, 0, sizeof u);
    u.host_no = hostMap (adapter->adapno);

    f->cmd = MFI_CMD_DCMD;
    f->flags = (u16) flags;
    f->opcode = (u32) opcode;

    if ((data != NULL) && (len > 0))
    {
	u.sgl_off = ((void *) &f->sgl) - ((void *) f);
	u.sge_count = 1;
	u.sgl[0].iov_base = data;
	u.sgl[0].iov_len = len;
	f->sge_count = 1;
	assert(UINT32_MAX > (uint32_t) data);
	f->data_xfer_len = (u32) len;
	f->sgl.sge32[0].phys_addr = (u32) data;
	f->sgl.sge32[0].length = (u32) len;
    }

    if (mbox != NULL)
	memcpy (&f->mbox, mbox, mboxlen);

    if (doIoctl (adapter, &u) < 0)
    {
	megaErrno = errno;
	return -1;
    }
    return f->cmd_status;
}


static int passthruCommand (struct mega_adapter_path *adapter, void *data, uint32_t len, uint8_t target, uint8_t *cdb, uint8_t cdblen)
{
    if ((adapter->type == MEGA_ADAPTER_V2) || (adapter->type == MEGA_ADAPTER_V34))
    {
	struct uioctl_t			u;
	int_mbox_t			*m = (int_mbox_t *) &u.mbox;
	mraid_passthru_t		*p = &u.pthru;

	memset (&u, 0, sizeof u);
	u.outlen = len;
	u.ui.fcs.opcode = M_RD_IOCTL_CMD;
	u.ui.fcs.adapno = MKADAP(hostMap (adapter->adapno));
	u.data = data;
	m->cmd = MBOXCMD_PASSTHRU;
	assert(UINT32_MAX > (uint32_t) p);
	m->xferaddr = (uint32_t) p;
	p->timeout = 3;
	p->ars = 1;
	p->target = target;
	assert(UINT32_MAX > (uint32_t) data);
	p->dataxferaddr = (uint32_t) data;
	p->dataxferlen = len;
	p->scsistatus = 239;	/* HMMM */
	memcpy (p->cdb, cdb, cdblen);
	p->cdblen = cdblen;
	if (data)
	    memset (data, 0, len);

	if (doIoctl (adapter, &u) < 0)
	{
	    megaErrno = errno;
	    return -1;
	}

	if (m->status)
	{
	    megaErrno = - (m->status);
	    return -1;
	}

	if (p->scsistatus & CHECK_CONDITION)
	{
	    megaErrno = - CHECK_CONDITION;
	    return -1;
	}
	if ((p->scsistatus & STATUS_MASK) != GOOD)
	{
	    megaErrno = - (p->scsistatus & STATUS_MASK);
	    return -1;
	}
    }
    else
    {
	struct megasas_iocpacket	u;
	struct megasas_pthru_frame	*f = (struct megasas_pthru_frame *) &u.frame;

	memset (&u, 0, sizeof u);
	u.host_no = hostMap (adapter->adapno);

	f->cmd = MFI_CMD_PD_SCSI_IO;
	f->target_id = target;
	f->cdb_len = cdblen;
	f->flags = MFI_FRAME_DIR_READ;
	memcpy (f->cdb, cdb, cdblen);

	if ((data != NULL) && (len > 0))
	{
	    u.sgl_off = ((void *) &f->sgl) - ((void *) f);
	    u.sge_count = 1;
	    u.sgl[0].iov_base = data;
	    u.sgl[0].iov_len = len;

	    f->sge_count = 1;
	    assert(UINT32_MAX > (uint32_t) len);
	    f->data_xfer_len = (u32) len;
	    f->sgl.sge32[0].phys_addr = (u32) data;
	    f->sgl.sge32[0].length = (u32) len;
	}

	if (doIoctl (adapter, &u) < 0)
	{
	    megaErrno = errno;
	    return -1;
	}

	if (f->cmd_status)
	{
	    megaErrno = - (f->cmd_status);
	    return -1;
	}

	if ((f->scsi_status & STATUS_MASK) != GOOD)
	{
	    megaErrno = - (f->scsi_status & STATUS_MASK);
	    return -1;
	}
    }

    return 0;
}


int megaScsiDriveInquiry (struct mega_adapter_path *adapter, uint8_t target, void *data, uint32_t len, uint8_t pageCode, uint8_t evpd)
{
    uint8_t		cdb[6];

    cdb[0] = INQUIRY;
    cdb[1] = (evpd != 0);
    cdb[2] = pageCode;
    cdb[3] = (len >> 8) & 0xff;
    cdb[4] = len & 0xff;
    cdb[5] = 0;

    return passthruCommand (adapter, data, len, target, cdb, sizeof cdb);
}


int megaScsiModeSense (struct mega_adapter_path *adapter, uint8_t target, void *data, uint32_t len, uint8_t pageControl, uint8_t page, uint8_t subpage)
{
#ifdef	USE_MODE_SENSE_6
    uint8_t		cdb[6];

    cdb[0] = MODE_SENSE;
    cdb[1] = 0;		/* dbd in bit 3 */
    cdb[2] = ((pageControl & 0x3) << 6) | (page & 0x3f);
    cdb[3] = subpage;
    cdb[4] = len & 0xff;
    cdb[5] = 0;
#else
    uint8_t		cdb[10];

    cdb[0] = MODE_SENSE_10;
    cdb[1] = 0;		/* llbaa in bit 4, dbd in bit 3 */
    cdb[2] = ((pageControl & 0x3) << 6) | (page & 0x3f);
    cdb[3] = subpage;
    cdb[4] = 0;
    cdb[5] = 0;
    cdb[6] = 0;
    cdb[7] = (len >> 8) & 0xff;
    cdb[8] = len & 0xff;
    cdb[9] = 0;
#endif

    return passthruCommand (adapter, data, len, target, cdb, sizeof cdb);
}


int megaScsiLogSense (struct mega_adapter_path *adapter, uint8_t target, void *data, uint32_t len, uint8_t pageControl, uint8_t page, uint16_t parameterPointer)
{
    uint8_t		cdb[10];

    cdb[0] = LOG_SENSE;
    cdb[1] = 0;		/* ppc in bit 1, sp in bit 1 */
    cdb[2] = ((pageControl & 0x3) << 6) | (page & 0x3f);
    cdb[3] = 0;
    cdb[4] = 0;
    cdb[5] = (parameterPointer >> 8) & 0xff;
    cdb[6] = parameterPointer & 0xff;
    cdb[7] = (len >> 8) & 0xff;
    cdb[8] = len & 0xff;
    cdb[9] = 0;

    return passthruCommand (adapter, data, len, target, cdb, sizeof cdb);
}


int megaScsiSendDiagnostic (struct mega_adapter_path *adapter, uint8_t target, void *data, uint32_t len, uint8_t testCode, uint8_t unitOffline, uint8_t deviceOffline)
{
    uint8_t		cdb[6];

    cdb[0] = SEND_DIAGNOSTIC;
    cdb[1] = ((testCode & 0x7) << 5) | ((deviceOffline != 0) << 1) | (unitOffline != 0);
    cdb[2] = 0;
    cdb[3] = 0;
    cdb[4] = 0;
    cdb[5] = 0;

    return passthruCommand (adapter, data, len, target, cdb, sizeof cdb);
}


int megaGetAdapterConfig8 (struct mega_adapter_path *adapter, disk_array_8ld_span8_t *config)
{
    return oldCommand (adapter, config, sizeof (*config), NEW_READ_CONFIG_8LD, 0, 0);
}


int megaGetAdapterConfig40 (struct mega_adapter_path *adapter, disk_array_40ld_t *config)
{
    return newCommand (adapter, config, sizeof (*config), FC_NEW_CONFIG, OP_DCMD_READ_CONFIG, 0);
}


int megaGetAdapterInquiry (struct mega_adapter_path *adapter, mraid_inquiry1_t *data)
{
    return oldCommand (adapter, data, sizeof (*data), MBOXCMD_ADAPTERINQ, 0, 0);
}


int megaGetAdapterExtendedInquiry (struct mega_adapter_path *adapter, mraid_extinq1_t *data)
{
    return oldCommand (adapter, data, sizeof (*data), MBOXCMD_ADPEXTINQ, 0, 0);
}


int megaGetAdapterEnquiry3 (struct mega_adapter_path *adapter, mraid_inquiry3_t *data)
{
    return newCommand (adapter, data, sizeof (*data), FC_NEW_CONFIG, NC_SUBOP_ENQUIRY3, 0);
}


int megaGetPredictiveMap (struct mega_adapter_path *adapter, struct mega_predictive_map *data)
{
    return oldCommand (adapter, data, sizeof (*data), MAIN_MISC_OPCODE, 0x0f, 0);
}


int megaGetDriveErrorCount (struct mega_adapter_path *adapter, uint8_t target, struct mega_physical_drive_error_info *data)
{
    return oldCommand (adapter, data, sizeof (*data), 0x77, 0, target);
}


int megaSasGetDeviceList (struct mega_adapter_path *adapter, struct mega_device_list_sas **data)
{
    unsigned char		buf[sizeof(struct mega_device_list_sas)];
    uint32_t			len;

    if (sasCommand (adapter, buf, sizeof buf, 0x02010000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
	return -1;
    len = ((struct mega_device_list_sas *) buf)->length;
    if ((*data = (struct mega_device_list_sas *) malloc (len)) == NULL)
    {
	megaErrno = errno;
	return -1;
    }
    return sasCommand (adapter, *data, len, 0x02010000, MFI_FRAME_DIR_READ, NULL, 0);
}


int megaSasGetDiskInfo (struct mega_adapter_path *adapter, uint8_t target, struct mega_physical_disk_info_sas *data)
{
    uint8_t 			mbox[0xc];

    memset (&mbox, 0, sizeof mbox);
    mbox[0] = target;
    return sasCommand (adapter, data, sizeof (*data), 0x02020000, MFI_FRAME_DIR_READ, mbox, sizeof mbox);
}


int megaSasGetArrayConfig (struct mega_adapter_path *adapter, struct mega_array_config_sas *data)
{
    unsigned char		buf[0x20];
    uint32_t			len;

    if (sasCommand (adapter, buf, sizeof buf, 0x04010000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
	return -1;
    len = ((struct mega_array_header_sas *) buf)->length;
    if ((data->header = (struct mega_array_header_sas *) malloc (len)) == NULL)
    {
	megaErrno = errno;
	return -1;
    }
    if (sasCommand (adapter, data->header, len, 0x04010000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
    {
	megaErrno = errno;
	return -1;
    }

    data->span = (struct mega_array_span_def_sas *) (data->header + 1);
    data->disk = (struct mega_array_disk_def_sas *) (data->span + data->header->num_span_defs);
    data->hotspare = (struct mega_array_hotspare_def_sas *) (data->disk + data->header->num_disk_defs);

    return 0;
}

int megaSasGetBatteryInfo (struct mega_adapter_path *adapter, struct mega_battery_info_sas *data)
{
    if (sasCommand (adapter, &(data->state), sizeof (data->state), 0x05010000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
	return -1;
    if (sasCommand (adapter, &(data->capacity), sizeof (data->capacity), 0x05020000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
	return -1;
    if (sasCommand (adapter, &(data->design), sizeof (data->design), 0x05030000, MFI_FRAME_DIR_READ, NULL, 0) < 0)
	return -1;
    return sasCommand (adapter, &(data->properties), sizeof (data->properties), 0x05050100, MFI_FRAME_DIR_READ, NULL, 0);
}


int megaGetDriverVersion (int fd, uint32_t *version)
{
    return driverQuery (fd, version, sizeof (*version), 'e');
}


int megaGetNumAdapters (int fd, uint32_t *numAdapters, int sas)
{
    static struct mega_adapter_map	realMap;
    uint8_t				k;
    uint8_t				count;

    if (sas)
    {
	if (adapterMap == NULL)
	{
	    struct mega_adapter_map	fakeMap;

	    /* initialize fake map to 1-to-1 map */
	    for (k = 0; k < MEGA_MAX_ADAPTERS; ++k)
		fakeMap.host[k] = k;
	    fakeMap.count = k;
	    adapterMap = &fakeMap;

	    /* ping all possible adapters to build real map */
	    count = 0;
	    for (k = 0; k < MEGA_MAX_ADAPTERS; ++k)
		if (megaSasAdapterPing (fd, k) >= 0)
		    realMap.host[count++] = k;
	    realMap.count = count;
	    adapterMap = &realMap;
	}
    }
    else
    {
	if (adapterMap == NULL)
	{
	    if (driverQuery (fd, &count, sizeof (count), 'm') < 0)
		return -1;
	    if (count > MEGA_MAX_ADAPTERS)
		count = MEGA_MAX_ADAPTERS;

	    for (k = 0; k < count; ++k)
		realMap.host[k] = k;
	    realMap.count = count;
	    adapterMap = &realMap;
	}
    }

    *numAdapters = adapterMap->count;
    return 0;
}


int megaGetAdapterProductInfo (int fd, uint8_t adapno, mraid_pinfo_t *data)
{
    struct mega_adapter_path	adapter;
    adapter.fd = fd;
    adapter.adapno = adapno;
    adapter.type = MEGA_ADAPTER_V34;
    return newCommand (&adapter, data, sizeof (*data), FC_NEW_CONFIG, NC_SUBOP_PRODUCT_INFO, 0);
}


int megaSasGetAdapterProductInfo (int fd, uint8_t adapno, struct megasas_ctrl_info *data)
{
    struct mega_adapter_path	adapter;
    adapter.fd = fd;
    adapter.adapno = adapno;
    adapter.type = MEGA_ADAPTER_V5;
    return sasCommand (&adapter, data, sizeof (*data), MR_DCMD_CTRL_GET_INFO, MFI_FRAME_DIR_READ, NULL, 0);
}


int megaSasAdapterPing (int fd, uint8_t adapno)
{
    struct mega_adapter_path	adapter;
    unsigned char		data[0xc4];
    adapter.fd = fd;
    adapter.adapno = adapno;
    adapter.type = MEGA_ADAPTER_V5;
    return sasCommand (&adapter, data, sizeof data, 0x04060100, MFI_FRAME_DIR_READ, NULL, 0);
}


char *megaErrorString (void)
{
    if (megaErrno >= 0)
	return strerror (megaErrno);
    switch (-megaErrno)
    {
     case CHECK_CONDITION:		return "scsi command status CHECK_CONDITION"; break;
     case CONDITION_GOOD:		return "scsi command status CONDITION_GOOD"; break;
     case BUSY:				return "scsi command status BUSY"; break;
     case INTERMEDIATE_GOOD:		return "scsi command status INTERMEDIATE_GOOD"; break;
     case INTERMEDIATE_C_GOOD:		return "scsi command status INTERMEDIATE_C_GOOD"; break;
     case RESERVATION_CONFLICT:		return "scsi command status RESERVATION_CONFLICT"; break;
     case COMMAND_TERMINATED:		return "scsi command status COMMAND_TERMINATED"; break;
     case QUEUE_FULL:			return "scsi command status QUEUE_FULL"; break;
     default:				return "scsi command status unknown"; break;
    }
}


