/* @(#)drv_simul.c	1.60 09/07/10 Copyright 1998-2009 J. Schilling */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)drv_simul.c	1.60 09/07/10 Copyright 1998-2009 J. Schilling";
#endif
/*
 *	Simulation device driver
 *
 *	Copyright (c) 1998-2009 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#ifndef	DEBUG
#define	DEBUG
#endif
#include <schily/mconfig.h>

#include <schily/stdio.h>
#include <schily/standard.h>
#include <schily/stdlib.h>
#include <schily/unistd.h>
#include <schily/errno.h>
#include <schily/string.h>
#include <schily/time.h>
#include <schily/utypes.h>
#include <schily/btorder.h>
#include <schily/schily.h>

/*#include <scgio.h>*/
#include <scg/scsidefs.h>
#include <scg/scsireg.h>
#include <scg/scsitransp.h>

#include <schily/libport.h>

#include "cdrecord.h"

extern	int	silent;
extern	int	verbose;
extern	int	lverbose;

LOCAL	int	simul_load		__PR((SCSI *scgp, cdr_t *));
LOCAL	int	simul_unload		__PR((SCSI *scgp, cdr_t *));
LOCAL	cdr_t	*identify_simul		__PR((SCSI *scgp, cdr_t *, struct scsi_inquiry *));
LOCAL	int	init_simul		__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	getdisktype_simul	__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	speed_select_simul	__PR((SCSI *scgp, cdr_t *dp, int *speedp));
LOCAL	int	next_wr_addr_simul	__PR((SCSI *scgp, track_t *trackp, long *ap));
LOCAL	int	cdr_write_simul		__PR((SCSI *scgp, caddr_t bp, long sectaddr, long size, int blocks, BOOL islast));
LOCAL	int	open_track_simul	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	close_track_simul	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	open_session_simul	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	fixate_simul		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	void	tv_sub			__PR((struct timeval *tvp1, struct timeval *tvp2));

LOCAL int
simul_load(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (0);
}

LOCAL int
simul_unload(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (0);
}

cdr_t	cdr_cdr_simul = {
	0, 0, 0,
	CDR_TAO|CDR_SAO|CDR_PACKET|CDR_RAW|CDR_RAW16|CDR_RAW96P|CDR_RAW96R|CDR_SRAW96P|CDR_SRAW96R|CDR_TRAYLOAD|CDR_SIMUL,
	0,
	CDR_CDRW_ALL,
	WM_SAO,
	40, 372,
	"cdr_simul",
	"simulation CD-R driver for timing/speed tests",
	0,
	(dstat_t *)0,
	identify_simul,
	drive_attach,
	init_simul,
	getdisktype_simul,
	no_diskstatus,
	simul_load,
	simul_unload,
	buf_dummy,
	cmd_dummy,					/* recovery_needed */
	(int(*)__PR((SCSI *, cdr_t *, int)))cmd_dummy,	/* recover	*/
	speed_select_simul,
	select_secsize,
	next_wr_addr_simul,
	(int(*)__PR((SCSI *, Ulong)))cmd_ill,	/* reserve_track	*/
	cdr_write_simul,
	(int(*)__PR((track_t *, void *, BOOL)))cmd_dummy,	/* gen_cue */
	(int(*)__PR((SCSI *scgp, cdr_t *, track_t *)))cmd_dummy, /* send_cue */
	(int(*)__PR((SCSI *, cdr_t *, track_t *)))cmd_dummy, /* leadin */
	open_track_simul,
	close_track_simul,
	open_session_simul,
	cmd_dummy,
	cmd_dummy,					/* abort	*/
	read_session_offset,
	fixate_simul,
	cmd_dummy,					/* stats	*/
	blank_dummy,
	format_dummy,
	(int(*)__PR((SCSI *, caddr_t, int, int)))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};

cdr_t	cdr_dvd_simul = {
	0, 0, 0,
	CDR_TAO|CDR_SAO|CDR_PACKET|CDR_RAW|CDR_RAW16|CDR_RAW96P|CDR_RAW96R|CDR_SRAW96P|CDR_SRAW96R|CDR_DVD|CDR_TRAYLOAD|CDR_SIMUL,
	CDR2_NOCD,
	CDR_CDRW_ALL,
	WM_SAO,
	2, 1000,
	"dvd_simul",
	"simulation DVD-R driver for timing/speed tests",
	0,
	(dstat_t *)0,
	identify_simul,
	drive_attach,
	init_simul,
	getdisktype_simul,
	no_diskstatus,
	simul_load,
	simul_unload,
	buf_dummy,
	cmd_dummy,					/* recovery_needed */
	(int(*)__PR((SCSI *, cdr_t *, int)))cmd_dummy,	/* recover	*/
	speed_select_simul,
	select_secsize,
	next_wr_addr_simul,
	(int(*)__PR((SCSI *, Ulong)))cmd_ill,	/* reserve_track	*/
	cdr_write_simul,
	(int(*)__PR((track_t *, void *, BOOL)))cmd_dummy,	/* gen_cue */
	(int(*)__PR((SCSI *scgp, cdr_t *, track_t *)))cmd_dummy, /* send_cue */
	(int(*)__PR((SCSI *, cdr_t *, track_t *)))cmd_dummy, /* leadin */
	open_track_simul,
	close_track_simul,
	open_session_simul,
	cmd_dummy,
	cmd_dummy,					/* abort	*/
	read_session_offset,
	fixate_simul,
	cmd_dummy,					/* stats	*/
	blank_dummy,
	format_dummy,
	(int(*)__PR((SCSI *, caddr_t, int, int)))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};

cdr_t	cdr_bd_simul = {
	0, 0, 0,
	CDR_TAO|CDR_SAO|CDR_PACKET|CDR_TRAYLOAD|CDR_SIMUL,
	CDR2_NOCD|CDR2_BD,
	CDR_CDRW_ALL,
	WM_SAO,
	2, 1000,
	"bd_simul",
	"simulation BD-R driver for timing/speed tests",
	0,
	(dstat_t *)0,
	identify_simul,
	drive_attach,
	init_simul,
	getdisktype_simul,
	no_diskstatus,
	simul_load,
	simul_unload,
	buf_dummy,
	cmd_dummy,					/* recovery_needed */
	(int(*)__PR((SCSI *, cdr_t *, int)))cmd_dummy,	/* recover	*/
	speed_select_simul,
	select_secsize,
	next_wr_addr_simul,
	(int(*)__PR((SCSI *, Ulong)))cmd_ill,	/* reserve_track	*/
	cdr_write_simul,
	(int(*)__PR((track_t *, void *, BOOL)))cmd_dummy,	/* gen_cue */
	(int(*)__PR((SCSI *scgp, cdr_t *, track_t *)))cmd_dummy, /* send_cue */
	(int(*)__PR((SCSI *, cdr_t *, track_t *)))cmd_dummy, /* leadin */
	open_track_simul,
	close_track_simul,
	open_session_simul,
	cmd_dummy,
	cmd_dummy,					/* abort	*/
	read_session_offset,
	fixate_simul,
	cmd_dummy,					/* stats	*/
	blank_dummy,
	format_dummy,
	(int(*)__PR((SCSI *, caddr_t, int, int)))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};

LOCAL cdr_t *
identify_simul(scgp, dp, ip)
	SCSI			*scgp;
	cdr_t			*dp;
	struct scsi_inquiry	*ip;
{
	return (dp);
}

LOCAL	long	simul_nwa;
LOCAL	int	simul_speed = 1;
LOCAL	int	simul_dummy;
LOCAL	int	simul_isdvd;
LOCAL	int	simul_isbd;
LOCAL	int	simul_bufsize = 1024;
LOCAL	Uint	sleep_rest;
LOCAL	Uint	sleep_max;
LOCAL	Uint	sleep_min;

LOCAL int
init_simul(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (speed_select_simul(scgp, dp, NULL));
}

LOCAL int
getdisktype_simul(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	dstat_t	*dsp = dp->cdr_dstat;

	if (strcmp(dp->cdr_drname, cdr_cdr_simul.cdr_drname) == 0) {
		dsp->ds_maxblocks = 333000;
		simul_isdvd = FALSE;
	} else if (strcmp(dp->cdr_drname, cdr_dvd_simul.cdr_drname) == 0) {
		dsp->ds_maxblocks = 2464153;	/* 4.7 GB  */
/*		dsp->ds_maxblocks = 1927896;*/	/* 3.95 GB */
		dsp->ds_flags |= DSF_NOCD|DSF_DVD;
		simul_isdvd = TRUE;
	} else {
		dsp->ds_maxblocks = 12219392;	/* 25 GB  */
		dsp->ds_flags |= DSF_NOCD|DSF_BD;
		simul_isbd = TRUE;
	}
	return (drive_getdisktype(scgp, dp));
}


LOCAL int
speed_select_simul(scgp, dp, speedp)
	SCSI	*scgp;
	cdr_t	*dp;
	int	*speedp;
{
	long	val;
	char	*p;
	BOOL	dummy = (dp->cdr_cmdflags & F_DUMMY) != 0;

	if (speedp)
		simul_speed = *speedp;
	simul_dummy = dummy;

	if ((p = getenv("CDR_SIMUL_BUFSIZE")) != NULL) {
		if (getnum(p, &val) == 1)
			simul_bufsize = val / 1024;
	}

	/*
	 * sleep_max is the time to empty the drive's buffer in µs.
	 * sector size is from 2048 bytes to 2352 bytes.
	 * If sector size is 2048 bytes, 1k takes 6.666 ms.
	 * If sector size is 2352 bytes, 1k takes 5.805 ms.
	 * We take the 6 ms as an average between both values.
	 * simul_bufsize is the number of kilobytes in drive buffer.
	 */
	sleep_max = 6 * 1000 * simul_bufsize / simul_speed;

	/*
	 * DVD single speed is 1385 * 1000 Bytes/s (676.27 sectors/s)
	 */
	if ((dp->cdr_flags & CDR_DVD) != 0)
		sleep_max = 739 * simul_bufsize / simul_speed;

	/*
	 * BD single speed is 4495.5 * 1000 Bytes/s (2195.07 sectors/s)
	 */
	if ((dp->cdr_flags2 & CDR2_BD) != 0)
		sleep_max = 228 * simul_bufsize / simul_speed;

	if (lverbose) {
		printf("Simulation drive buffer size: %d KB\n", simul_bufsize);
		printf("Maximum reserve time in drive buffer: %d.%3.3d ms for speed %dx\n",
					sleep_max / 1000,
					sleep_max % 1000,
					simul_speed);
	}
	return (0);
}

LOCAL int
next_wr_addr_simul(scgp, trackp, ap)
	SCSI	*scgp;
	track_t	*trackp;
	long	*ap;
{
	/*
	 * This will most likely not 100% correct for TAO CDs
	 * but it is better than returning 0 in all cases.
	 */
	if (ap)
		*ap = simul_nwa;
	return (0);
}

LOCAL int
cdr_write_simul(scgp, bp, sectaddr, size, blocks, islast)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	sectaddr;	/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	blocks;		/* sector count */
	BOOL	islast;		/* last write for track */
{
	Uint	sleep_time;
	Uint	sleep_diff;

	struct timeval	tv1;
static	struct timeval	tv2;

	if (lverbose > 1 && islast)
		printf("\nWriting last record for this track.\n");

	simul_nwa += blocks;

	gettimeofday(&tv1, (struct timezone *)0);
	if (tv2.tv_sec != 0) {		/* Already did gettimeofday(&tv2) */
		tv_sub(&tv1, &tv2);
		if (sleep_rest != 0) {
			sleep_diff = tv1.tv_sec * 1000000 + tv1.tv_usec;

			if (sleep_min > (sleep_rest - sleep_diff))
				sleep_min = (sleep_rest - sleep_diff);

			if (sleep_diff > sleep_rest) {
				printf("Buffer underrun: actual delay was %d.%3.3d ms, max delay was %d.%3.3d ms.\n",
						sleep_diff / 1000,
						sleep_diff % 1000,
						sleep_rest / 1000,
						sleep_rest % 1000);
				if (!simul_dummy)
					return (-1);
			}
			/*
			 * If we spent time outside the write function
			 * subtract this time.
			 */
			sleep_diff = tv1.tv_sec * 1000000 + tv1.tv_usec;
			if (sleep_rest >= sleep_diff)
				sleep_rest -= sleep_diff;
			else
				sleep_rest = 0;
		}
	}
	/*
	 * Speed 1 ist 150 Sektoren/s
	 * Bei DVD 676.27 Sektoren/s
	 * Bei BD 2195.07 Sektoren/s
	 */
	sleep_time = 1000000 * blocks / 75 / simul_speed;
	if (simul_isdvd)
		sleep_time = 1000000 * blocks / 676 / simul_speed;
	if (simul_isbd)
		sleep_time = 1000000 * blocks / 2195 / simul_speed;

	sleep_time += sleep_rest;

	if (sleep_time > sleep_max) {
		int	mod;
		long	rsleep;

		sleep_rest = sleep_max;
		sleep_time -= sleep_rest;
		mod = sleep_time % 20000;
		sleep_rest += mod;
		sleep_time -= mod;
		if (sleep_time > 0) {
			gettimeofday(&tv1, (struct timezone *)0);
			usleep(sleep_time);
			gettimeofday(&tv2, (struct timezone *)0);
			tv2.tv_sec -= tv1.tv_sec;
			tv2.tv_usec -= tv1.tv_usec;
			rsleep = tv2.tv_sec * 1000000 + tv2.tv_usec;
			sleep_rest -= rsleep - sleep_time;
		}
	} else {
		sleep_rest = sleep_time;
	}

	gettimeofday(&tv2, (struct timezone *)0);
	return (size);
}

LOCAL int
open_track_simul(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t *trackp;
{
	sleep_min = 999 * 1000000;
	return (0);
}

LOCAL int
close_track_simul(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	if (lverbose) {
		printf("Remaining reserve time in drive buffer: %d.%3.3d ms\n",
					sleep_rest / 1000,
					sleep_rest % 1000);
		printf("Minimum reserve time in drive buffer: %d.%3.3d ms\n",
					sleep_min / 1000,
					sleep_min % 1000);
	}
	usleep(sleep_rest);
	sleep_rest = 0;
	return (0);
}

LOCAL int
open_session_simul(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	simul_nwa = 0L;
	return (0);
}

LOCAL int
fixate_simul(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	return (0);
}

LOCAL void
tv_sub(tvp1, tvp2)
	struct timeval	*tvp1;
	struct timeval	*tvp2;
{
	tvp1->tv_sec -= tvp2->tv_sec;
	tvp1->tv_usec -= tvp2->tv_usec;

	while (tvp1->tv_usec < 0) {
		tvp1->tv_usec += 1000000;
		tvp1->tv_sec -= 1;
	}
}
