225 lines
6.3 KiB
C
225 lines
6.3 KiB
C
/**
|
|
* This is an asynchronous protocol driver for the Delta-Tau PMAC
|
|
* series of controllers, connected via TCP/IP. The PMAC needs its
|
|
* commands in a special purpose data structure, describe below.
|
|
* As responses, it can send any of the following formats:
|
|
* data<CR>data<CR>data>CR><ACK>
|
|
* <BELL>ERRxxx<CR>
|
|
* <STX>data<CR>
|
|
* There can be multiple data and errors in a string. However, I wish to
|
|
* restrict this to processing one command at any time. This driver owes
|
|
* some insight and little code to the EPICS driver by
|
|
* Pete Leicester, Diamond.
|
|
*
|
|
* ** Before this can be used, I3=2 and I6=1 must be set on the PMAC **
|
|
*
|
|
* copyright: see file COPYRIGHT
|
|
*
|
|
* Mark Koennecke, December 2008
|
|
*
|
|
* Introduced a delay on read in order to throttle requests
|
|
*
|
|
* Mark Koennecke, June 2011
|
|
*/
|
|
#include <errno.h>
|
|
#include <ascon.h>
|
|
#include <ascon.i>
|
|
#include <dynstring.h>
|
|
|
|
#define ETHERNET_DATA_SIZE 1492
|
|
#define INPUT_SIZE (ETHERNET_DATA_SIZE+1) /* +1 to allow space to add terminating ACK */
|
|
#define STX '\2'
|
|
#define CTRLB '\2'
|
|
#define CTRLC '\3'
|
|
#define ACK '\6'
|
|
#define CTRLF '\6'
|
|
#define BELL '\7'
|
|
#define CTRLG '\7'
|
|
#define CTRLP '\16'
|
|
#define CTRLV '\22'
|
|
#define CTRLX '\24'
|
|
|
|
/* PMAC ethernet command structure */
|
|
#pragma pack(1)
|
|
typedef struct tagEthernetCmd {
|
|
unsigned char RequestType;
|
|
unsigned char Request;
|
|
unsigned short wValue;
|
|
unsigned short wIndex;
|
|
unsigned short wLength; /* length of bData */
|
|
unsigned char bData[ETHERNET_DATA_SIZE];
|
|
} ethernetCmd;
|
|
#pragma pack()
|
|
|
|
#define ETHERNET_CMD_HEADER ( sizeof(ethernetCmd) - ETHERNET_DATA_SIZE )
|
|
|
|
/* PMAC ethernet commands - RequestType field */
|
|
#define VR_UPLOAD 0xC0
|
|
#define VR_DOWNLOAD 0x40
|
|
|
|
/* PMAC ethernet commands - Request field */
|
|
#define VR_PMAC_SENDLINE 0xB0
|
|
#define VR_PMAC_GETLINE 0xB1
|
|
#define VR_PMAC_FLUSH 0xB3
|
|
#define VR_PMAC_GETMEM 0xB4
|
|
#define VR_PMAC_SETMEN 0xB5
|
|
#define VR_PMAC_SETBIT 0xBA
|
|
#define VR_PMAC_SETBITS 0xBB
|
|
#define VR_PMAC_PORT 0xBE
|
|
#define VR_PMAC_GETRESPONSE 0xBF
|
|
#define VR_PMAC_READREADY 0xC2
|
|
#define VR_CTRL_RESPONSE 0xC4
|
|
#define VR_PMAC_GETBUFFER 0xC5
|
|
#define VR_PMAC_WRITEBUFFER 0xC6
|
|
#define VR_PMAC_WRITEERROR 0xC7
|
|
#define VR_FWDOWNLOAD 0xCB
|
|
#define VR_IPADDRESS 0xE0
|
|
|
|
/*---------------------------------------------------------------------------
|
|
* a private data structurli to keep track of the PMAC
|
|
*---------------------------------------------------------------------------*/
|
|
typedef struct {
|
|
ethernetCmd cmd;
|
|
char *ptr;
|
|
int bytesToWrite;
|
|
int expectACK;
|
|
double startTime;
|
|
} PMACPrivate, *pPMACPrivate;
|
|
/*---------------------------------------------------------------------------*
|
|
* wait period before read
|
|
----------------------------------------------------------------------------*/
|
|
#define READDELAY .05
|
|
/*---------------------------------------------------------------------------*/
|
|
static int PMACHandler(Ascon * a)
|
|
{
|
|
char *data = NULL;
|
|
int ret, l;
|
|
char chr;
|
|
pPMACPrivate priv = NULL;
|
|
|
|
priv = a->private;
|
|
|
|
switch (a->state) {
|
|
case AsconWriteStart:
|
|
data = GetCharArray(a->wrBuffer);
|
|
memset(priv, 0, sizeof(PMACPrivate));
|
|
priv->cmd.RequestType = VR_DOWNLOAD;
|
|
priv->cmd.Request = VR_PMAC_GETRESPONSE;
|
|
priv->cmd.wValue = 0;
|
|
priv->cmd.wIndex = 0;
|
|
priv->cmd.wLength = htons(strlen(data)); /* may be one more */
|
|
priv->bytesToWrite = strlen(data) + 1 + ETHERNET_CMD_HEADER;
|
|
strcpy((char *) priv->cmd.bData, data);
|
|
priv->expectACK = 1;
|
|
priv->ptr = (char *) &priv->cmd;
|
|
a->state = AsconWriting;
|
|
a->wrPos = 0;
|
|
break;
|
|
case AsconWriting:
|
|
AsconReadGarbage(a->fd);
|
|
l = priv->bytesToWrite - a->wrPos;
|
|
ret = AsconWriteChars(a->fd, priv->ptr, l);
|
|
if (ret < 0) {
|
|
if (errno != EINTR && errno != EAGAIN) {
|
|
AsconError(a, "send failed:", errno);
|
|
}
|
|
/*
|
|
* Ooops: which state shall we go to after a write fail?
|
|
* This seems to retry.
|
|
*/
|
|
} else {
|
|
a->wrPos += ret;
|
|
if (a->wrPos >= priv->bytesToWrite) {
|
|
a->state = AsconWriteDone;
|
|
priv->startTime = DoubleTime();
|
|
} else {
|
|
priv->ptr += ret;
|
|
}
|
|
}
|
|
break;
|
|
case AsconReadStart:
|
|
DynStringClear(a->rdBuffer);
|
|
if(DoubleTime() > priv->startTime + READDELAY){
|
|
a->start = DoubleTime();
|
|
a->state = AsconReading;
|
|
}
|
|
break;
|
|
case AsconReading:
|
|
ret = AsconReadChar(a->fd, &chr);
|
|
if (ret < 0) {
|
|
/* EINTR means we must retry */
|
|
if (errno != EINTR && errno != EAGAIN) {
|
|
AsconError(a, "AsconReadChar failed:", errno);
|
|
}
|
|
return 1;
|
|
} else if (ret > 0) {
|
|
a->start = DoubleTime();
|
|
if (chr == STX || chr == BELL) {
|
|
priv->expectACK = 0;
|
|
return 1;
|
|
}
|
|
if (priv->expectACK && chr == ACK) {
|
|
if (GetDynStringLength(a->rdBuffer) == 0) {
|
|
DynStringConcat(a->rdBuffer, "ACK");
|
|
}
|
|
DynStringConcatChar(a->rdBuffer, '\0');
|
|
a->state = AsconReadDone;
|
|
break;
|
|
}
|
|
if (priv->expectACK == 0 && chr == '\r') {
|
|
DynStringConcatChar(a->rdBuffer, '\0');
|
|
a->state = AsconReadDone;
|
|
break;
|
|
}
|
|
if (DynStringConcatChar(a->rdBuffer, chr) == 0) {
|
|
AsconError(a, "DynStringConcatChar failed:", ENOMEM);
|
|
break;
|
|
}
|
|
} else if (ret == 0) {
|
|
if (a->timeout > 0) {
|
|
if (DoubleTime() - a->start > a->timeout) {
|
|
AsconError(a, "read timeout", 0);
|
|
a->state = AsconTimeout;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
return AsconStdHandler(a);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int PMACInit(Ascon * a, SConnection * con, int argc, char *argv[])
|
|
{
|
|
pPMACPrivate priv = NULL;
|
|
|
|
priv = calloc(sizeof(PMACPrivate), 1);
|
|
a->fd = -1;
|
|
a->state = AsconConnectStart;
|
|
a->reconnectInterval = 10;
|
|
a->hostport = strdup(argv[1]);
|
|
if (argc > 2) {
|
|
a->timeout = atof(argv[2]);
|
|
} else {
|
|
a->timeout = 2.0; /* sec */
|
|
}
|
|
a->private = priv;
|
|
a->killPrivate = free;
|
|
return 1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
void AddPMACProtocoll()
|
|
{
|
|
AsconProtocol *prot = NULL;
|
|
|
|
prot = calloc(sizeof(AsconProtocol), 1);
|
|
prot->name = strdup("pmac");
|
|
prot->init = PMACInit;
|
|
prot->handler = PMACHandler;
|
|
AsconInsertProtocol(prot);
|
|
}
|