//=================================================================
//
//        Port.cpp
//
//        Port test class
//
//=================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class abstracts the serial port for use in the testing infrastructure
// Usage:
//
//####DESCRIPTIONEND####

#include "stdafx.h"
#include "Port.h"
#include "TestResource.h"
#include "eCosTestUtils.h"
#include "eCosTestSocket.h"

// Hardware with reset
CPort::CPort(CeCosTest::TargetType target, const char * pszDownloadPort, int nBaud, const char *pszResetHostPort,
			 char cControl1, char cControl2, const char *pszAuxPort,int nDelay):
	m_strPort(pszDownloadPort),
	m_Target(target),
	m_bInUse(false),
	m_nBaud(nBaud),
	m_cControl1(cControl1),
	m_cControl2(cControl2),
	m_bSim(false),
    m_bLocked(false),
    m_strAuxPort(pszAuxPort),
    m_nDelay(nDelay)
{
	// Decompose host:port
    TRACE("Created hw port : target=%s port=%s baud=%d reset=%s %c %c aux=%s\n",
        CeCosTest::Image(target),pszDownloadPort, nBaud,pszResetHostPort,cControl1,cControl2,(const char *)m_strAuxPort);
	bool b=CeCosTestUtils::ParseHostPort(pszResetHostPort, m_strResetHost, m_nResetPort);
	assert(b);
	Chain();
}

// Hardware without reset
CPort::CPort(CeCosTest::TargetType target, const char * pszDownloadPort, int nBaud):
	m_strPort(pszDownloadPort),
	m_Target(target),
	m_bInUse(false),
	m_nBaud(nBaud),
	m_cControl1('\0'),
	m_cControl2('\0'),
	m_bSim(false),
    m_bLocked(false),
    m_nDelay(0)
{
    TRACE("Created hw port : target=%s port=%s baud=%d\n",CeCosTest::Image(target),pszDownloadPort, nBaud);
	Chain();
}

// Simulator
CPort::CPort(CeCosTest::TargetType target):
	m_Target(target),
	m_bInUse(false),
	m_nBaud(0),
	m_cControl1('\0'),
	m_cControl2('\0'),
	m_bSim(true),
    m_bLocked(false),
    m_nDelay(0)
{
    TRACE("Created sim port : target=%s\n",CeCosTest::Image(target));
	Chain();
}


CPort::~CPort() 
{
	ENTERCRITICAL;
        if(pFirstInstance==this){
            pFirstInstance=m_pNextInstance;
        }
        if(m_pPrevInstance){
            m_pPrevInstance->m_pNextInstance=m_pNextInstance;
        }
        if(m_pNextInstance){
            m_pNextInstance->m_pPrevInstance=m_pPrevInstance;
        }
	LEAVECRITICAL;
}

void CPort::Release ()
{
	// No need to use a critical section here
    TRACE("Release port %s\n",Name());
	m_bInUse=false;
	return;
}

// This function determines whether the board startup has all that is required
static bool CALLBACK StopFunc (void *pParam)
{
    const char *buf=(const char *)pParam;
    const char *tpkt=strstr(buf,"$T");
    if(tpkt){
        // T packet ends with #hh
        const char *d=strchr(tpkt,'#');
        if(d && d[1] && d[2]){
            return true;
        }
    } else if (strstr(buf,"cygmon> ")) {
        return true;
    } 
    return false;
}


bool CPort::Reset(int &nErr, CeCosTestUtils::String &str)
{
    str.SetLength(0);
    const int dTimeout=20*1000;
	bool rc=false;
    nErr=0;
    bool bReading=false;
    CeCosTestSocket *pSock=0;
	if(m_strResetHost.GetLength()==0){
		rc=true;
	} else {
        const char *pszPort=m_strAuxPort.GetLength()>0 ?m_strAuxPort:m_strPort;
TRACE("Reset port %s\n",pszPort);
        if(*pszPort){
            if(0==strchr(pszPort,':')){
                // Comms device
                m_Serial.SetReadTimeOuts(dTimeout,500);
                bReading=m_Serial.Open(pszPort,Baud());
                if(!bReading){
                    TRACE("Failed to open comms port %s\n",pszPort);
                }
            } else {
                CeCosTestUtils::String strHost;
                int nPort;
                if(CeCosTestUtils::ParseHostPort(pszPort,strHost,nPort)){
                    pSock=new CeCosTestSocket(strHost,nPort,dTimeout);
                    bReading=pSock->Ok();
                    if(!bReading){
                        TRACE("Failed to open %s\n",pszPort);
                    }
                } else {
                    TRACE("Illegal host:port %s\n",pszPort);
                }
            }
        }
		CeCosTestSocket sock(m_strResetHost,m_nResetPort,dTimeout);
		if(sock.Ok()){
			// Write the message to the socket
			char send_buff[4]={0,m_cControl1,m_cControl2,(char)(m_nDelay/100)};
			if(sock.send(send_buff, sizeof send_buff, "Reset control codes", dTimeout)){
				// Wait for an acknowledgement
				if(!sock.recv(&nErr, 1, "Reset ack", dTimeout)){
					TRACE("Failed to read ack\n");
                    nErr=sock.SocketError();
                } else {
                    rc=(0==nErr);
                }
			} else {
				TRACE("Failed to write to socket\n");
                nErr=sock.SocketError();
			}
        } else {
            nErr=sock.OpenRC();
        }
        if(bReading){
            if(rc){
                // Board has apparently been powered on.  Suck initial output.
                str.SetLength(0);
                TRACE("Reading board startup output from %s\n",pszPort);
                CeCosTestUtils::Time t=CeCosTestUtils::Time::Now();
                enum {BUFSIZE=512};
                char *buf=str.GetBuffer(BUFSIZE);
                memset(buf,0,BUFSIZE); // safety for string functions in StopFunc
                if(pSock){
                    // Single blocking read: StopFunc tells us when to stop
                    pSock->recv(buf,BUFSIZE,"Startup",dTimeout,StopFunc,buf);
                } else {
                    char *c=buf;
                    do {
                        unsigned int dwRead;
                        if (!m_Serial.Read(c,BUFSIZE-(c-buf),dwRead)){
                            m_Serial.ClearError();
                        } else if(dwRead>0){
                            TRACE("Read %d bytes - '%s'\n",dwRead,c);
                            c+=dwRead;
                            break;
                        }
                    } while (CeCosTestUtils::Time::Now()-t<dTimeout);
                }
                str.ReleaseBuffer();
            }
            m_Serial.Close();
        }
	}
    delete pSock;
	return rc;
}

CPort *CPort::pFirstInstance=0;

CPort *CPort::GetPort (const CeCosTest::ExecutionParameters &e)
{
	CPort *p=0;
	if(0==Count(e)){
		TRACE("GetPort: no candidates available\n");
	} else {
		ENTERCRITICAL;
		    for(p=pFirstInstance;p;p=p->m_pNextInstance){
			    if(p->Matches(e)&&!p->m_bInUse){
                    TRACE("Acquired port %s\n",p->Name());
				    p->m_bInUse=true;
				    break;
			    }
		    }
		LEAVECRITICAL;
	}
	return p;
}


bool CPort::Lock (const CeCosTest::ExecutionParameters &e)
{
	CPort *p=0;
    bool rc=false;
	if(0==Count(e)){
		TRACE("GetPort: no candidates available\n");
	} else {
		ENTERCRITICAL;
		    for(p=pFirstInstance;p;p=p->m_pNextInstance){
			    if(p->Matches(e)&&!p->m_bInUse){
                    TRACE("Lock port %s\n",p->Name());
				    p->m_bLocked=true;
                    rc=true;
			    }
		    }
		LEAVECRITICAL;
	}
	return rc;
}


void CPort::DeleteAllInstances()
{
	while(pFirstInstance){
		delete pFirstInstance;
	}
}

CPort::GarbageCollector CPort::GarbageCollector::gc;

CPort::GarbageCollector::GarbageCollector()
{
};

CPort::GarbageCollector::~GarbageCollector()
{
	CPort::DeleteAllInstances();
};

int CPort::Count(const CeCosTest::ExecutionParameters &e)
{
	int nCount=0;
	for(const CPort *p=pFirstInstance;p;p=p->m_pNextInstance){
		if(p->Matches(e)){
			nCount++;
		}
	}
	return nCount;
}

void CPort::Chain()
{
	ENTERCRITICAL;
        m_pNextInstance=pFirstInstance;
        if(m_pNextInstance){
            m_pNextInstance->m_pPrevInstance=this;
        }
        m_pPrevInstance=0;
        pFirstInstance=this;
	LEAVECRITICAL;

}

