/*++

Copyright (c) 1997 SCM Microsystems, Inc.

Module Name:

    PscrRdWr.c

Abstract:

	Hardware access functions for SCM PSCR smartcard reader

Author:

	Andreas Straub

Environment:

	Win 95		Sys... calls are resolved by Pscr95Wrap.asm functions and
				Pscr95Wrap.h macros, resp.

	NT	4.0		Sys... functions resolved by PscrNTWrap.c functions and
				PscrNTWrap.h macros, resp.

Revision History:

	Andreas Straub			7/16/1997	1.00	Initial Version
	Klaus Schuetz			9/20/1997	1.01	Timing changed
	Andreas Straub			9/24/1997	1.02	Low Level error handling,
												minor bugfixes, clanup

--*/

#if defined( SMCLIB_VXD )

#include <Pscr95.h>

#else	//	SMCLIB_VXD

#include <PscrNT.h>

#endif	//	SMCLIB_VXD

#include <PscrCmd.h>
#include <PscrRdWr.h>

#pragma optimize( "", off )
VOID
PscrFlushInterface( PREADER_EXTENSION ReaderExtension )
/*++

PscrFlushInterface:
	Read & discard data from the pcmcia interface

Arguments:
	ReaderExtension	context of call

Return Value:
	void

--*/
{
	UCHAR			Status;
	ULONG			Length;
	PPSCR_REGISTERS	IOBase;

	IOBase = ReaderExtension->IOBase;

	Status = READ_PORT_UCHAR( &IOBase->CmdStatusReg );
	if(( Status & PSCR_DATA_AVAIL_BIT ) && ( Status & PSCR_FREE_BIT ))
	{

		//	take control over
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, PSCR_HOST_CONTROL_BIT );

		//	get number of available bytes
		Length = ((ULONG)READ_PORT_UCHAR( &IOBase->SizeMSReg )) << 8;
		Length |= READ_PORT_UCHAR( &IOBase->SizeLSReg );

		//	perform a dummy read
		while( Length-- )
		{
			READ_PORT_UCHAR( &IOBase->DataReg );
		}
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, CLEAR_BIT );
	}
	return;
}

NTSTATUS
PscrRead( 
	PREADER_EXTENSION	ReaderExtension,
	PUCHAR				pData,
	ULONG				DataLen,
	PULONG				pNBytes
	)
/*++
PscrRead:
	wait until data available & transfer data from reader to host

Arguments:
	ReaderExtension	context of call
	pData			ptr to data buffer
	DataLen			length of data buffer
	pNBytes			number of bytes returned

Return Value:
	STATUS_SUCCESS
	STATUS_BUFFER_TOO_SMALL
	STATUS_UNSUCCESSFUL

--*/
{
	NTSTATUS		NTStatus = STATUS_UNSUCCESSFUL;
	USHORT			ReaderStatus;
	PPSCR_REGISTERS	IOBase;
	USHORT			InDataLen;

	IOBase = ReaderExtension->IOBase;

	//	wait until interface is ready to transfer
	InDataLen = 0;

	if( NT_SUCCESS( NTStatus = PscrWait( ReaderExtension, PSCR_DATA_AVAIL_BIT | PSCR_FREE_BIT )))
	{
		//	take control over
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, PSCR_HOST_CONTROL_BIT );

		//	get number of available bytes
		InDataLen = ( READ_PORT_UCHAR( &IOBase->SizeMSReg ) << 8 );
		InDataLen |= READ_PORT_UCHAR( &IOBase->SizeLSReg );

		//	check buffer size. if buffer to small, the data will be discarded
		if( ( ULONG )InDataLen <= DataLen )
		{
			//	read data
			ULONG Idx;
            for (Idx = 0; Idx < InDataLen; Idx++) {
             	
				pData[ Idx ] = READ_PORT_UCHAR( &IOBase->DataReg );
			} 

			//	error check
			if( pData[ InDataLen - 1 ] !=
				PscrCalculateLRC( pData, (USHORT)( InDataLen-1 )))
			{
				NTStatus = STATUS_CRC_ERROR;
			}
			else
			{
				//
				//	Evaluation of reader errors. A reader error is indicated
				//	if the T1 length is 2 and the Nad indicates that this 
				//	packet came from the reader
				//
				if( ( ( pData[ PSCR_NAD ] & 0x0F ) == 0x01 ) &&
					( pData[ PSCR_LEN ] == 0x02 )
					)
				{
					ReaderStatus = (( USHORT ) pData[3] ) << 8;
					ReaderStatus |= (( USHORT ) pData[4] );
					
					if( ( ReaderStatus != 0x9000 ) &&
						( ReaderStatus != 0x9001 )
						)
					{
	                    SmartcardDebug( 
		                    DEBUG_TRACE, 
		                    ( "PSCR!PscrRead: ReaderStatus = %lx\n", ReaderStatus )
		                    );

						InDataLen	= 0;

                        if (ReaderStatus == PSCR_SW_PROTOCOL_ERROR) {

                            NTStatus = STATUS_IO_TIMEOUT;                         	

                        } else {
                         	
						    NTStatus = STATUS_UNSUCCESSFUL;
                        }
					}
				}
			}
		}
		else
		{
			//	flush interface in case of wrong buffer size
			do
        	{
				READ_PORT_UCHAR( &IOBase->DataReg );
			} while( --InDataLen );
			NTStatus = STATUS_BUFFER_TOO_SMALL;
		}

		//	clean up
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, CLEAR_BIT );
	}

	//	write number of bytes received
	if( InDataLen )
	{
		if( pNBytes != NULL )
		{
			( *pNBytes ) = ( ULONG ) InDataLen;
		}
		NTStatus = STATUS_SUCCESS;
	}
	return ( NTStatus );
}

NTSTATUS
PscrWrite( 
	PREADER_EXTENSION	ReaderExtension,
	PUCHAR				pData,
	ULONG				DataLen,
	PULONG				pNBytes
	)
/*++
PscrWrite:
	calculates the LRC of the buffer & sends command to the reader

Arguments:
	ReaderExtension	context of call
	pData				ptr to data buffer
	DataLen				length of data buffer (exclusive LRC!)
	pNBytes				number of bytes written

Return Value:
	return value of PscrWriteDirect

--*/
{
	NTSTATUS	NTStatus;

	//	Add the EDC field to the end of the data
	pData[ DataLen ] = PscrCalculateLRC( pData, ( USHORT ) DataLen );

	//	Send buffer
	NTStatus = PscrWriteDirect( 
		ReaderExtension,
		pData,
		DataLen + PSCR_EPILOGUE_LENGTH,
		pNBytes
		);

	return( NTStatus );
}

NTSTATUS
PscrWriteDirect( 
	PREADER_EXTENSION	ReaderExtension,
	PUCHAR				pData,
	ULONG				DataLen,
	PULONG				pNBytes
	)

/*++
PscrWriteDirect:
	sends command to the reader. The LRC / CRC must be calculated by caller!

Arguments:
	ReaderExtension	context of call
	pData				ptr to data buffer
	DataLen				length of data buffer (exclusive LRC!)
	pNBytes				number of bytes written

Return Value:
	STATUS_SUCCESS
	STATUS_DEVICE_BUSY

--*/
{
	NTSTATUS		NTStatus = STATUS_SUCCESS;
	UCHAR			Status;
	PPSCR_REGISTERS	IOBase;

	IOBase = ReaderExtension->IOBase;

	//	in case of card change, there may be data available
	Status = READ_PORT_UCHAR( &IOBase->CmdStatusReg );
	if( Status & PSCR_DATA_AVAIL_BIT )
	{
		NTStatus = STATUS_DEVICE_BUSY;
	}
	else
	{
		//
		//	wait until reader is ready
		//
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, PSCR_HOST_CONTROL_BIT );
		NTStatus = PscrWait( ReaderExtension, PSCR_FREE_BIT );
		
		if( NT_SUCCESS( NTStatus ))
		{
			ULONG	Idx;

			//	take control over
			WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, PSCR_HOST_CONTROL_BIT );

			//	write the buffer size
			WRITE_PORT_UCHAR( &IOBase->SizeMSReg, ( UCHAR )( DataLen >> 8 ));
			SysDelay( DELAY_WRITE_PSCR_REG );
			WRITE_PORT_UCHAR( &IOBase->SizeLSReg, ( UCHAR )( DataLen & 0x00FF ));
			SysDelay( DELAY_WRITE_PSCR_REG );

			//	write data
            for (Idx = 0; Idx < DataLen; Idx++) {

				WRITE_PORT_UCHAR( &IOBase->DataReg, pData[ Idx ] );
			}

			if( pNBytes != NULL )
			{
				*pNBytes = DataLen;
			}
		}

		//	clean up
		WRITE_PORT_UCHAR( &IOBase->CmdStatusReg, CLEAR_BIT );
	}
	return( NTStatus );
}

UCHAR
PscrCalculateLRC(
	PUCHAR	pData, 
	USHORT	DataLen
	)
/*++

PscrCalculateLRC:
	calculates the XOR LRC of a buffer.

Arguments:
	pData		ptr to data buffer
	DataLen		length of range

Return Value:
	LRC

--*/
{
	UCHAR	Lrc;
	USHORT	Idx;

	//
	//	Calculate LRC by XORing all the bytes.
	//
	Lrc = pData[ 0 ];
	for ( Idx = 1 ; Idx < DataLen; Idx++ )
	{
		Lrc ^= pData[ Idx ];
	}
	return ( Lrc );
}

NTSTATUS
PscrWait(
	PREADER_EXTENSION	ReaderExtension, 
	UCHAR				Mask 
	)
/*++
PscrWait:
	Test the status port of the reader until ALL bits in the mask are set.
	The maximum of time until DEVICE_BUSY is returned is approx.
	MaxRetries * DELAY_PSCR_WAIT if MaxRetries != 0.
	If MaxRetries = 0 the driver waits until the requested status is reported or the
	user defines a timeout.

Arguments:
	ReaderExtension		context of call
	Mask				mask of bits to test the status register

Return Value:
	STATUS_SUCCESS
	STATUS_DEVICE_BUSY

--*/
{
	NTSTATUS		NTStatus;
	PPSCR_REGISTERS	IOBase;
	ULONG			Retries;
	UCHAR			Status;

	IOBase		= ReaderExtension->IOBase;
	NTStatus	= STATUS_DEVICE_BUSY;

	//	wait until condition fulfilled or specified timeout expired
	for ( Retries = 0; Retries < ReaderExtension->MaxRetries; Retries++)
	{
		//	canceled?
		if( ReaderExtension->RequestCancelled == TRUE )
		{
			NTStatus = STATUS_CANCELLED;
			break;
		}

		if (( (READ_PORT_UCHAR( &IOBase->CmdStatusReg )) == 0x01) && 
			ReaderExtension->InvalidStatus)

		{
			NTStatus = STATUS_CANCELLED;
			break;
		}

		//	test requested bits
		if(( (READ_PORT_UCHAR( &IOBase->CmdStatusReg )) & Mask ) == Mask )
		{
			NTStatus = STATUS_SUCCESS;
			break;
		}
		SysDelay( DELAY_PSCR_WAIT );
	}

	Status = READ_PORT_UCHAR( &IOBase->CmdStatusReg );
	return NTStatus;
}

#pragma optimize( "", on )

