/*
 *  scsi_freebsd.cpp - SCSI Manager, FreeBSD SCSI Driver implementation
 *  Copyright (C) 1999 Orlando Bassotto
 *
 *  Basilisk II (C) 1997-1999 Christian Bauer
 *
 *  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 of the License, 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; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  History:
 *    29-Jun-1999 Started
 *    05-Jul-1999 Changed from array to queue removing the limit of 8 
 *                devices.
 *                Implemented old SCSI management for FreeBSD 2.x.
 *                (Note: This implementation hasn't been tested;
 *                 I don't own anymore a machine with FreeBSD 2.x,
 *                 so if something goes wrong, please mail me to
 *                 future@mediabit.net).
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>

#ifdef CAM
#include <cam/cam.h>
#include <cam/cam_debug.h>
#include <cam/cam_ccb.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_da.h>
#include <cam/scsi/scsi_pass.h>
#include <cam/scsi/scsi_message.h>
#include <camlib.h>
#else /* !CAM */
#include <sys/scsiio.h>
#include <scsi.h>
#endif /* !CAM */

#include "sysdeps.h"
#include "main.h"
#include "prefs.h"
#include "user_strings.h"
#include "scsi.h"

#define DEBUG 0
#include "debug.h"


#undef u_int8_t
#define u_int8_t unsigned char

typedef struct _SCSIDevice {
	int		controller;		// SCSI Controller
	int		controller_bus;		// SCSI Controller Bus
	char		controller_name[33];	// SCSI Controller name
 	int		mac_unit;		// Macintosh SCSI ID (remapped)
	int		faked_controller;	// "Faked" SCSI Controller (Always 0)
	int		faked_unit;		// "Faked" SCSI ID
	int		unit;			// Real SCSI ID
	int		lun;			// Real SCSI LUN
	u_int8_t	vendor[16];		// SCSI Vendor
	u_int8_t	product[48];		// SCSI Product
	u_int8_t	revision[16];		// SCSI Revision
	char		device[33];		// SCSI Device
#ifdef CAM
	char		pass_device[33];	// SCSI Pass Device
#else /* !CAM */
	int		dev_fd;			// Device File Descriptor
#endif /* !CAM */
	void*		dev_ptr;		// Pointer to CAM/SCSI structure
	bool		enabled;		// Device enabled ?
	struct _SCSIDevice* next;		// Pointer to the next device
} SCSIDevice;

static int nDevices = 0;
static SCSIDevice* Devices = NULL;

static uint32 buffer_size;
static uint8* buffer = NULL;

static uint8 the_cmd[12];
static int the_cmd_len;

static SCSIDevice* CurrentDevice = NULL;

inline static SCSIDevice* _GetSCSIDeviceByID(int id)
{
	SCSIDevice* aux = Devices;
	while(aux) {
		if(aux->faked_unit==id) {
			return aux;
		}
		aux = aux->next;
	}
	return NULL;
}

inline static SCSIDevice* _GetSCSIDeviceByIDLUN(int id, int lun)
{
	SCSIDevice* aux = Devices;
	while(aux) {
		if(aux->faked_unit==id&&aux->lun==lun) {
			return aux;
		}
		aux = aux->next;
	}
	return NULL;
}

inline static SCSIDevice* _GetSCSIDeviceByMacID(int id)
{
	SCSIDevice* aux = Devices;
	while(aux) {
		if(aux->mac_unit==id) {
			return aux;
		}
		aux = aux->next;
	}
	return NULL;
}

inline static SCSIDevice* _GetSCSIDeviceByMacIDLUN(int id, int lun)
{
	SCSIDevice* aux = Devices;
	while(aux) {
		if(aux->mac_unit==id&&aux->lun==lun) {
			return aux;
		}
		aux = aux->next;
	}
	return NULL;
}

inline static SCSIDevice* _AllocNewDevice()
{
	SCSIDevice* aux;
	
	aux = new SCSIDevice;
	if(aux==NULL) return NULL;
	memset(aux, 0, sizeof(SCSIDevice));
	aux->next = Devices;
	Devices = aux;
	return aux;
}

#ifdef CAM
inline static struct cam_device* _GetCurrentSCSIDevice()
{
	if(CurrentDevice==NULL) return NULL;
	
	return (struct cam_device*)CurrentDevice->dev_ptr;
}
#else /* !CAM */
inline static struct scsireq* _GetCurrentSCSIDevice()
{
	if(CurrentDevice==NULL) return NULL;
	
	return (struct scsireq*)CurrentDevice->dev_ptr;
}
#endif /* !CAM */

/*
 * _Build_SCSI_Controller()
 *
 * This function builds a virtual SCSI Controller (Controller=0)
 * where keeps all the devices found, this is due the fact
 * I have two SCSI controllers in my PC. :-)
 * Use scsidump in contrib/ to see how is remapped your 
 * SCSI device (only if you have more than one controller, 
 * that's for sure :-).
 * If you have only one controller, remapping does not take act.
 */

#define GET_FREE_ID(id) \
	{ \
		for(int x=0;x<32;x++) { \
			if(!(busyIDs&(1<<(x+1)))) { \
				id = x; \
				break; \
			} \
		} \
	}
	
static void _Build_SCSI_Controller()
{
	unsigned int id = 0;
	unsigned long long busyIDs = 0x0ll;
	SCSIDevice* aux, * dev;
		
	// What IDs are busy?
	dev = Devices;
	while(dev) {
		dev->enabled = false;
		dev->faked_controller = 0;
		dev->faked_unit = dev->unit;
		busyIDs |= (1 << (dev->unit+1));
		dev = dev->next;
	}
	
	// Find out the duplicate IDs and remap them
	dev = Devices, aux = NULL;
	while(dev) {
		aux = dev;
		while(aux) {
			SCSIDevice* dev1, * dev2;
			
			dev1 = dev, dev2 = aux;
			
			if(dev1->controller!=dev2->controller&&
				dev1->unit==dev2->unit) {
				int free_id;
				GET_FREE_ID(free_id);
				busyIDs |= (1<<(free_id+1));
				dev1->faked_unit = free_id;
			}
			aux = aux->next;
		}
		dev = dev->next;
	}
	
	// Now reorder the queue 
#if 0
	dev = Devices;
	while(dev) {
		aux = dev;
		while(aux) {
			SCSIDevice* dev1, * dev2;
	
			dev1 = dev, dev2 = aux;
			if(dev1->faked_unit>dev2->faked_unit) {
				SCSIDevice tmp;
				
				memcpy(&tmp, dev1, sizeof(SCSIDevice));
				memcpy(dev1, dev2, sizeof(SCSIDevice));
				memcpy(dev2, &tmp, sizeof(SCSIDevice));
			}
			aux = aux->next;
		}
		dev = dev->next;
	}
#endif

	// Now open the selected SCSI devices :-)
	for(int n=0;n<8;n++) {
		char tmp[25];
		
		snprintf(tmp, sizeof(tmp), "scsi%d", n);
		const char* scsi = PrefsFindString(tmp);
		if(scsi) {
			int id, lun;
			
			// The format is: RemappedID (or FakedID)/LUN
			sscanf(scsi, "%d/%d", &id, &lun);
			
			SCSIDevice* dev = _GetSCSIDeviceByIDLUN(id, lun);
			if(dev==NULL) continue;
			dev->enabled = true;
			dev->mac_unit = n;

#ifdef CAM
			struct cam_device* cam;

			cam = cam_open_btl(dev->controller,
				dev->unit,
				dev->lun, O_RDWR, NULL);
			if(cam==NULL) {
				fprintf(stderr, "Failed to open %d:%d:%d = %s!!!\n",
					dev->controller, dev->unit, dev->lun,
					cam_errbuf);
			}
			dev->dev_ptr = (void*)cam;
#else /* !CAM */
			dev->dev_fd = scsi_open(dev->device, O_RDWR);
			if(dev->dev_fd<0) {
				perror("Failed to open %d:%d:%d");
			}
			else {
				dev->dev_ptr = (void*)scsireq_new();
			}
#endif /* !CAM */
		}
	}
}	

	
/*
 *  Initialization
 */

void SCSIInit(void)
{
	// Finds the SCSI hosts in the system filling the SCSIDevices queue.
	// "Stolen" from camcontrol.c
	//    Copyright (C) 1997-99 Kenneth D. Merry
	// Old SCSI detection "stolen" from scsi.c
	//    Copyright (C) 1993 Julian Elischer
        //
	int bufsize, fd;
	int need_close = 0;
	int error = 0;
	int skip_device = 0;
	SCSIDevice* Dev, * dev, * PrevDev = NULL;
	
	nDevices = 0;

	if(PrefsFindBool("noscsi"))
		goto no_scsi;

#ifdef CAM
	union ccb ccb;
	if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) {
		fprintf(stderr, "WARNING: Cannot open CAM device %s (%s)\n", XPT_DEVICE, strerror(errno));
		goto no_scsi;
	}
	
	memset(&(&ccb.ccb_h)[1], 0, sizeof(struct ccb_dev_match)-sizeof(struct ccb_hdr));
	ccb.ccb_h.func_code = XPT_DEV_MATCH;
	bufsize = sizeof(struct dev_match_result) * 100;
	ccb.cdm.match_buf_len = bufsize;
	ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize);
	ccb.cdm.num_matches = 0;
	
	ccb.cdm.num_patterns = 0;
	ccb.cdm.pattern_buf_len = 0;
	
	do {
		Dev = _AllocNewDevice();	
		if(ioctl(fd, CAMIOCOMMAND, &ccb)==-1) {
			fprintf(stderr, "Error sending CAMIOCOMMAND ioctl\n");
			return;
		}
		
		if((ccb.ccb_h.status != CAM_REQ_CMP)
		|| ((ccb.cdm.status != CAM_DEV_MATCH_LAST)
		 && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
		 	fprintf(stderr, "Got CAM error %#x, CDM error %d\n",
		 		ccb.ccb_h.status, ccb.cdm.status);
		 	return;
		}
		 
		char current_controller_name[33];
		int current_controller = -1;
		for(int i=0;i<ccb.cdm.num_matches;i++) {
			switch(ccb.cdm.matches[i].type) {
			case DEV_MATCH_BUS:
			{
				struct bus_match_result* bus_result;
								
				bus_result = &ccb.cdm.matches[i].result.bus_result;
				
				if(bus_result->path_id==-1) break;
				Dev->controller = bus_result->path_id;
				snprintf(Dev->controller_name, sizeof(Dev->controller_name), "%s%d", 
					bus_result->dev_name,
					bus_result->unit_number);
				strncpy(current_controller_name, Dev->controller_name, sizeof(current_controller_name));
				current_controller = Dev->controller;
				Dev->controller_bus = bus_result->bus_id;
				break;
			}
			case DEV_MATCH_DEVICE:
			{
				struct device_match_result* dev_result;
				char tmpstr[256];
				
				dev_result = &ccb.cdm.matches[i].result.device_result;
				if(current_controller==-1||dev_result->target_id==-1) {
					skip_device = 1;
					break;
				}
				else skip_device = 0;
				
				cam_strvis(Dev->vendor, (u_int8_t*)dev_result->inq_data.vendor,
					sizeof(dev_result->inq_data.vendor),
					sizeof(Dev->vendor));
				cam_strvis(Dev->product, (u_int8_t*)dev_result->inq_data.product,
					sizeof(dev_result->inq_data.product),
					sizeof(Dev->product));
				cam_strvis(Dev->revision, (u_int8_t*)dev_result->inq_data.revision,
					sizeof(dev_result->inq_data.revision),
					sizeof(Dev->revision));
				strncpy(Dev->controller_name, current_controller_name, sizeof(Dev->controller_name));
				Dev->controller = current_controller;
				Dev->unit = dev_result->target_id;
				Dev->lun = dev_result->target_lun;
				break;
			}
			case DEV_MATCH_PERIPH:
			{
				struct periph_match_result* periph_result;
				
				periph_result = &ccb.cdm.matches[i].result.periph_result;
				
				if(skip_device != 0) break;
				
				if(need_close==1) {
					snprintf(Dev->device, sizeof(Dev->device), "%s%d*",
						periph_result->periph_name,
						periph_result->unit_number);
					need_close = 0;
				}
				else if(need_close==0) {
					snprintf(Dev->pass_device, sizeof(Dev->pass_device), "%s%d",
						periph_result->periph_name,
						periph_result->unit_number);
					need_close++;
					break;
				}
				else {
					need_close = 0;
				}
				PrevDev = Dev;
				Dev = _AllocNewDevice();
				break;
			}
			}
		}
	} while (ccb.ccb_h.status == CAM_REQ_CMP
	     && ccb.cdm.status == CAM_DEV_MATCH_MORE);

	/* Remove last one (ugly coding) */
	Devices = PrevDev;
	delete Dev;
end_loop:
	close(fd);
#else /* !CAM */
	/*
	 * FreeBSD 2.x SCSI management is quiet different and 
	 * unfortunatly not flexible as CAM library in FreeBSD 3.x...
	 * I probe only the first bus, LUN 0, and the 
	 * first 8 devices only.
	 */
	u_char* inq_buf;
	scsireq_t* scsireq;
	struct scsi_addr scsi;
	int ssc_fd;
	
	if((ssc_fd=open("/dev/ssc", O_RDWR))==-1) {
		fprintf(stderr, "Cannot open SCSI manager: /dev/ssc\n");
		SCSIReset();
		return;
	}	
	
	inq_buf = (u_char*)malloc(96);
	if(inq_buf==NULL) {
		perror("malloc failed");
		SCSIReset();
		return;
	}
	
	scsireq = scsireq_build((scsireq_t*)dev->dev_ptr,
				96, inq_buf, SCCMD_READ,
				"12 0 0 0 v 0", 96);
	
	addr.scbus = 0;
	addr.lun = 0;

	for(int n=0;n<8;n++) {
		addr.target = n;
		
		if(ioctl(ssc_fd, SCIOCADDR, &addr) != -1) {
			Dev = _AllocNewDevice();
			Dev->controller = addr.scbus;
			Dev->lun = addr.lun;
			Dev->unit = addr.target;

			struct scsi_devinfo devInfo;
			devInfo.addr = addr;
			if(ioctl(ssc_fd, SCIOCGETDEVINFO, &devInfo) != -1) {
				strncpy(Dev->device, devInfo.devname, sizeof(Dev->device));
			}
			strncpy(Dev->controller_name, "FreeBSD 2.x SCSI Manager", sizeof(Dev->controller_name));
			if(scsireq_enter(ssc_fd, scsireq)!=-1) {
				Dev->vendor[sizeof(Dev->vendor)-1] = 0;
				Dev->product[sizeof(Dev->product)-1] = 0;
				Dev->revision[sizeof(Dev->revision)-1] = 0;
				
				scsireq_decode(scsireq, "s8 c8 c16 c4",
					Dev->vendor, Dev->product, Dev->revision);
			}			
		}
	}
	free(inq_buf);
	close(ssc_fd);
#endif /* !CAM */
	_Build_SCSI_Controller();

	// Print out the periph with ID:LUNs
	fprintf(stderr, "Device                            RealID FkdID  MacID Enabled\n");
	fprintf(stderr, "-------------------------------------------------------------\n");
		      // 012345678901234567890123456789012 0:0:0    0/0    0:0 Yes
	dev = Devices;
	while(dev) {
		char tmp[40];
		snprintf(tmp, sizeof(tmp), "%s %s %s",
			dev->vendor,
			dev->product,
			dev->revision);
		fprintf(stderr, "%-33s %d:%d:%d    %d/%d    %d:%d %s\n",
			tmp, dev->controller, dev->unit, dev->lun,
			dev->faked_unit, dev->lun,
			dev->mac_unit, dev->lun, dev->enabled?"Yes":"No");
		dev = dev->next;
	}

no_scsi:
	// Reset SCSI bus
	SCSIReset();
}


/*
 *  Deinitialization
 */

void SCSIExit(void)
{
	SCSIDevice* aux;
	while(Devices) {
		aux = Devices->next;
		if(Devices->dev_ptr!=NULL) {
#ifdef CAM
			cam_close_device((struct cam_device*)Devices->dev_ptr);
#else /* !CAM */
			free(Devices->dev_ptr); // Is this right?
			close(Devices->dev_fd); // And this one?
#endif /* !CAM */
		}
		delete Devices;
		Devices = aux;
	}
	nDevices = 0;
}


/*
 *  Set SCSI command to be sent by scsi_send_cmd()
 */

void scsi_set_cmd(int cmd_length, uint8 *cmd)
{
	the_cmd_len = cmd_length;
	memset(the_cmd, 0, sizeof(the_cmd));
	memcpy(the_cmd, cmd, the_cmd_len);
}


/*
 *  Check for presence of SCSI target
 */

bool scsi_is_target_present(int id)
{
	return (_GetSCSIDeviceByMacID(id)!=NULL&&_GetSCSIDeviceByMacID(id)->enabled);
}


/*
 *  Set SCSI target (returns false on error)
 */

bool scsi_set_target(int id, int lun)
{
	SCSIDevice* dev;

	dev = _GetSCSIDeviceByMacIDLUN(id, lun);
	if(dev==NULL) return false;
	CurrentDevice = dev;
	return true;
}


/*
 *  Send SCSI command to active target (scsi_set_command() must have been called),
 *  read/write data according to S/G table (returns false on error)
 */

static bool try_buffer(int size)
{
	if(size <= buffer_size) {
		return true;
	}
	
	D(bug("Allocating buffer of %d bytes.\n", size));
	uint8* new_buffer = (uint8*)valloc(size);
	if(new_buffer==NULL) {
		return false;
	}
	if(buffer!=NULL) free(buffer);
	buffer = new_buffer;
	buffer_size = size;
	return true;
}

bool scsi_send_cmd(size_t data_length, bool reading, int sg_size, uint8 **sg_ptr, uint32 *sg_len, uint16 *stat, uint32 timeout)
{
	int value = 0;
#ifdef CAM
#ifdef VERBOSE_CAM_DEBUG
	D(bug("Sending command %x (len=%d) to SCSI Device %d:%d:%d\n", the_cmd[0],
		the_cmd_len,
		CurrentDevice->controller,
		CurrentDevice->unit,
		CurrentDevice->lun));
	D(bug("DataLength: %d\n", data_length));
	D(bug("Reading: %d\n", reading));
	D(bug("SG Size: %d\n", sg_size));
	D(bug("Timeout: %d\n", timeout));
#endif /* VERBOSE_CAM_DEBUG */
#endif /* CAM */
	if(!try_buffer(data_length)) {
		char str[256];
		sprintf(str, GetString(STR_SCSI_BUFFER_ERR), data_length);
		ErrorAlert(str);
		return false;
	}
	
	if(!reading) {
		uint8* buffer_ptr = buffer;
		for(int i=0;i<sg_size;i++) {
			uint32 len = sg_len[i];
			memcpy(buffer, sg_ptr[i], len);
			buffer_ptr += len;
		}
	}
	
	if(the_cmd[0] == 0x03) {
		// faked cmd
		*stat = 0;
		return true;
	}


#ifdef CAM
	struct cam_device* device = _GetCurrentSCSIDevice();
	if(device==NULL) return false;

	union ccb ccb;

	memset(&ccb, 0, sizeof(ccb));

	int dir_flags = CAM_DIR_NONE;
	if(data_length>0) {
		dir_flags = reading?CAM_DIR_IN:CAM_DIR_OUT;
	}

	ccb.ccb_h.path_id = CurrentDevice->controller;
	ccb.ccb_h.target_id = CurrentDevice->unit;
	ccb.ccb_h.target_lun = CurrentDevice->lun;
	
	cam_fill_csio(&ccb.csio,
			0,
			NULL,
			dir_flags,
			MSG_SIMPLE_Q_TAG,
			(u_int8_t*)buffer,
			data_length,
			SSD_FULL_SIZE,
			the_cmd_len,
			(timeout?timeout:50)*1000);

	ccb.ccb_h.flags |= CAM_DEV_QFRZDIS;

	memcpy(ccb.csio.cdb_io.cdb_bytes, the_cmd, the_cmd_len);

	if(cam_send_ccb(device, &ccb)<0) {
		fprintf(stderr, "%d:%d:%d ", CurrentDevice->controller,
			CurrentDevice->unit, CurrentDevice->lun);
		perror("cam_send_ccb");
		return false;
	}

	value = ccb.ccb_h.status;
	*stat = ccb.csio.scsi_status;

	if((value & CAM_STATUS_MASK) != CAM_REQ_CMP) {
		char tmp[4096];
		if((value & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR) {			
			scsi_sense_string(device, &ccb.csio, tmp, sizeof(tmp));
			fprintf(stderr, "SCSI Status Error:\n%s\n", tmp);
			return false;
		}
	}
#else /* !CAM */
	struct scsireq* scsireq = _GetCurrentSCSIDevice();
	if(device==NULL) return false;
	
	int dir_flags = 0x00;
	if(data_length>0) dir_flags = reading?SCCMD_READ:SCCMD_WRITE;
	
	scsireq_reset(scsireq);
	scsireq->timeout = (timeout?timeout:50)*1000;
	scsireq_build(scsireq, data_length,
			(caddr_t)buffer, dir_flags,
			"0");
	memcpy(scsireq->cmd, the_cmd, scsireq->cmdlen = the_cmd_len);
	
	int result = scsi_enter(dev->dev_fd, scsireq);
	if(SCSIREQ_ERROR(result)) {
		scsi_debug(stderr, result, scsireq);
	}
	*stat = scsireq->status;
#endif /* !CAM */

	if(reading) {
		uint8* buffer_ptr = buffer;
		for(int i=0;i<sg_size;i++) {
			uint32 len = sg_len[i];
			memcpy(sg_ptr[i], buffer_ptr, len);
#ifdef CAM
#ifdef VERBOSE_CAM_DEBUG
			static char line[16];
			for(int r=0, x=0;x<len;x++) {
				if(x!=0&&x%16==0) { D(bug("%s\n", line)); r = 0; } 
				line[r++] = isprint(sg_ptr[i][x])?sg_ptr[i][x]:'.';
				line[r] = 0;
				D(bug("%02x ", sg_ptr[i][x]));
			}
#endif /* VERBOSE_CAM_DEBUG */
#endif /* CAM */
			buffer_ptr += len;
		}
	}
	return true;
}
