diff --git a/Makefile b/Makefile index 672a261..7cb9ee2 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ HEADERS += src/turboPmacController.h SOURCES += sinqMotor/src/msgPrintControl.cpp SOURCES += sinqMotor/src/sinqAxis.cpp SOURCES += sinqMotor/src/sinqController.cpp -SOURCES += src/pmacAsynIPPort.c +SOURCES += src/pmacAsynIPPort.cpp SOURCES += src/turboPmacAxis.cpp SOURCES += src/turboPmacController.cpp diff --git a/src/pmacAsynIPPort.c b/src/pmacAsynIPPort.c deleted file mode 100644 index 9b19d14..0000000 --- a/src/pmacAsynIPPort.c +++ /dev/null @@ -1,974 +0,0 @@ -/* -NOTES - -This driver is an asyn interpose driver intended for use with pmacAsynMotor -to allow communication over ethernet to a PMAC. - -*** Ensure I3=2 and I6=1 on the PMAC before using this driver. *** - -This driver supports sending ascii commands to the PMAC over asyn IP and -obtaining the response. The driver uses the PMAC ethernet packets -VR_PMAC_GETRESPONSE, VR_PMAC_READREADY and VR_PMAC_GETBUFFER to send commands -and get responses. The PMAC may send responses in several different formats. -1) PMAC ascii command responses for single/multiple commands (e.g. I113 I114 -I130 I131 I133) are in an ACK terminated form as follows (CR seperates the -cmd responses): datadatadata 2) PMAC error -responses to ascii commands ARE NOT ACK terminated as follows: - ERRxxx -3) PMAC may also return the following: - data - -This driver can send ctrl commands (ctrl B/C/F/G/P/V) to the pmac (using -VR_CTRL_REPONSE packet) however because the resulting response data is not -terminated as above the driver does not know when all the response data has -been received. The response data will therefore only be returned after the -asynUser.timeout has expired. - -This driver does NOT support large buffer transfers (VR_PMAC_WRITEBUFFER, -VR_FWDOWNLOAD) or set/get of DPRAM (VR_PMAC_SETMEM etc) or changing comms -setup (VR_IPADDRESS, VR_PMAC_PORT) - - -REVISION HISTORY - -10 Aug 07 - Pete Leicester - Diamond Light Source -Modified to handle responses other than those ending in (e.g. errors) - -No longer used asyn EOS. - -9 Aug 07 - Pete Leicester - Diamond Light Source -Initial version reliant on asyn EOS to return terminated responses. - -2 Feb 09 - Matthew Pearson - Diamond Light Source -Ported to work with Asyn 4-10. Still compiles with pre Asyn4-10 versions but -does not work. Also added new config function, pmacAsynIPPortConfigureEos(), -to be used when disabling low level EOS handling in the Asyn IP layer. This -new function should be used with Asyn 4-10 and above (it is not compatible -with Asyn 4-9). - -10 Apr 25 - Stefan Mathis - Paul Scherrer Institut -Adjusted the driver to the requirements of SINQ: -- Added a lot of comments, removed the predecessor of -pmacAsynIPPortConfigureEos() since SINQ doesn't need it (see comment above) -- Removed the call to `pmacFlush` from `flushItOctet`: We want to flush the -communication buffer on the IOC side before each communication, but we do not -want to flush the PMAC controller itself, since this stalls the controller for -10 ms everytime (see Turbo PMAC User Manual, p. 414). Instead, a PV was added in -the higher level `turboPmac` driver to manually call the `pmacFlush` function. -*/ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "asynDriver.h" -#include "asynInt32.h" -#include "asynInterposeEos.h" -#include "asynOctet.h" -#include "drvAsynIPPort.h" -#include "epicsThread.h" -#include "pmacAsynIPPort.h" -#include - -#define ETHERNET_DATA_SIZE 1492 -#define MAX_BUFFER_SIZE 2097152 -#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 - -/* PMAC control character commands (VR_CTRL_RESPONSE cmd) */ -static char ctrlCommands[] = {CTRLB, CTRLC, CTRLF, CTRLG, CTRLP, CTRLV}; - -/** - * @brief PMAC_specific equivalent of the ioPvt struct from - * asyn/interfaces/asynOctetSyncIo.c - * - */ -typedef struct pmacPvt { - char *portName; - int addr; - asynInterface int32Interface; - asynInt32 *pint32; /* The methods we're overriding */ - void *int32Pvt; - asynInterface octetInterface; - asynOctet *poctet; /* The methods we're overriding */ - void *octetPvt; - asynUser *pasynUser; /* For connect/disconnect reporting */ - ethernetCmd *poutCmd; - ethernetCmd *pinCmd; - char *inBuf; - unsigned int inBufHead; - unsigned int inBufTail; -} pmacPvt; - -/* - Notes on asyn - use asynUser.timeout to specify I/O request timeouts in seconds - asynStatus may return asynSuccess(0),asynTimeout(1),asynOverflow(2) or - asynError(3) eomReason may return ASYN_EOM_CNT (1:Request count reached), - ASYN_EOM_EOS (2:End of String detected), ASYN_EOM_END (4:End indicator - detected) asynError indicates that asynUser.errorMessage has been populated - by epicsSnprintf(). -*/ - -/* Connect/disconnect handling */ -static void pmacInExceptionHandler(asynUser *pasynUser, - asynException exception); - -// ============================================================================= - -/* Declare asynInt32 here, and assign functions later on -(compatible with both Asyn 4-10 and pre 4-10 versions).*/ -static asynInt32 int32; - -/* Declare asynInt32 methods (they are defined down below) */ -static asynStatus writeInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 value); -static asynStatus readInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 *value); -static asynStatus getBoundsInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 *low, epicsInt32 *high); -static asynStatus registerInterruptUserInt32(void *drvPvt, asynUser *pasynUser, - interruptCallbackInt32 callback, - void *userPvt, - void **registrarPvt); -static asynStatus cancelInterruptUserInt32(void *drvPvt, asynUser *pasynUser, - void *registrarPvt); - -// ============================================================================= - -/* Declare asynOctet here, and assign functions later on -(compatible with both Asyn 4-10 and pre 4-10 versions).*/ -static asynOctet octet; - -/* Declare asynOctet methods (they are defined down below) */ -static asynStatus writeItOctet(void *drvPvt, asynUser *pasynUser, - const char *data, size_t numchars, - size_t *nbytesTransfered); -static asynStatus readItOctet(void *drvPvt, asynUser *pasynUser, char *data, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason); -static asynStatus flushItOctet(void *drvPvt, asynUser *pasynUser); -static asynStatus registerInterruptUserOctet(void *drvPvt, asynUser *pasynUser, - interruptCallbackOctet callback, - void *userPvt, - void **registrarPvt); -static asynStatus cancelInterruptUserOctet(void *drvPvt, asynUser *pasynUser, - void *registrarPvt); -static asynStatus setInputEosOctet(void *drvPvt, asynUser *pasynUser, - const char *eos, int eoslen); -static asynStatus getInputEosOctet(void *drvPvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen); -static asynStatus setOutputEosOctet(void *drvPvt, asynUser *pasynUser, - const char *eos, int eoslen); -static asynStatus getOutputEosOctet(void *drvPvt, asynUser *pasynUser, - char *eos, int eossize, int *eoslen); - -// ============================================================================= - -// Declare PMAC-specific functions (they are defined down below) - -static asynStatus readResponse(pmacPvt *pPmacPvt, asynUser *pasynUser, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason); -static int pmacReadReady(pmacPvt *pPmacPvt, asynUser *pasynUser); -static int pmacFlush(pmacPvt *pPmacPvt, asynUser *pasynUser); -static asynStatus sendPmacGetBuffer(pmacPvt *pPmacPvt, asynUser *pasynUser, - size_t maxchars, size_t *nbytesTransfered); - -// ============================================================================= - -int pmacAsynIPPortConfigure(const char *portName, const char *hostInfo) { - asynStatus status = asynSuccess; - asynInterface *int32LowerLevelInterface = NULL; - asynInterface *octetLowerLevelInterface = NULL; - asynUser *pasynUser = NULL; - size_t len = 0; - - /* - The asyn adress is always set to 0. This is taken verbatim from the DLS - source code - (https://github.com/DiamondLightSource/pmac/blob/afe81f8bb9179c3a20eff351f30bc6cfd1539ad9/pmacApp/pmacAsynIPPortSrc/pmacAsynIPPort.c#L192) - */ - int addr = 0; - - /* - This is the generic "parent" function of this function which is also - exported in the IOC shell and used for drivers which don't use a specialized - interpose layer. - */ - if ((status = drvAsynIPPortConfigure(portName, hostInfo, 0, 0, 1)) != 0) { - printf( - "pmacAsynIPPortConfigure: error from drvAsynIPPortConfigure. Port: " - "%s\n", - portName); - } - - /* - Assign specialized versions of the methods from - asyn/interfaces/asynInt32SyncIo.c - */ - int32.write = writeInt32; - int32.read = readInt32; - int32.getBounds = getBoundsInt32; - int32.registerInterruptUser = registerInterruptUserInt32; - int32.cancelInterruptUser = cancelInterruptUserInt32; - - /* - Assign specialized versions of the methods from - asyn/interfaces/asynOctetSyncIO.c - */ - octet.write = writeItOctet; - octet.read = readItOctet; - octet.flush = flushItOctet; - octet.setInputEos = setInputEosOctet; - octet.setOutputEos = setOutputEosOctet; - octet.getInputEos = getInputEosOctet; - octet.getOutputEos = getOutputEosOctet; - octet.registerInterruptUser = registerInterruptUserOctet; - octet.cancelInterruptUser = cancelInterruptUserOctet; - - len = sizeof(pmacPvt) + strlen(portName) + 1; - pmacPvt *pPmacPvt = callocMustSucceed( - 1, len, - "calloc pPmacPvt error in pmacAsynIPPort::pmacAsynIPPortConfigure."); - - pPmacPvt->portName = (char *)(pPmacPvt + 1); - - strcpy(pPmacPvt->portName, portName); - pPmacPvt->addr = addr; - - pPmacPvt->int32Interface.interfaceType = asynInt32Type; - pPmacPvt->int32Interface.pinterface = &int32; - pPmacPvt->int32Interface.drvPvt = pPmacPvt; - - status = pasynInt32Base->initialize(pPmacPvt->portName, - &pPmacPvt->int32Interface); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: Can't register int32.\n"); - pasynManager->freeAsynUser(pasynUser); - free(pPmacPvt); - return -1; - } - - pPmacPvt->octetInterface.interfaceType = asynOctetType; - pPmacPvt->octetInterface.pinterface = &octet; - pPmacPvt->octetInterface.drvPvt = pPmacPvt; - - // We don't need to initialize the octet interface, since the interface type - // asynOctetType has already been initialized in drvAsynIPPortConfigure(..). - - pasynUser = pasynManager->createAsynUser(0, 0); - pPmacPvt->pasynUser = pasynUser; - pPmacPvt->pasynUser->userPvt = pPmacPvt; - - // In case connecting to the given IP port fails, clean up the memory - status = pasynManager->connectDevice(pasynUser, portName, addr); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: %s connectDevice failed\n", portName); - pasynManager->freeAsynUser(pasynUser); - free(pPmacPvt); - return -1; - } - - status = - pasynManager->exceptionCallbackAdd(pasynUser, pmacInExceptionHandler); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: %s exceptionCallbackAdd failed\n", - portName); - pasynManager->freeAsynUser(pasynUser); - free(pPmacPvt); - return -1; - } - - status = pasynManager->interposeInterface( - portName, addr, &pPmacPvt->int32Interface, &(int32LowerLevelInterface)); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: %s interposeInterface failed\n", - portName); - pasynManager->exceptionCallbackRemove(pasynUser); - pasynManager->freeAsynUser(pasynUser); - free(pPmacPvt); - return -1; - } - pPmacPvt->pint32 = (asynInt32 *)int32LowerLevelInterface->pinterface; - pPmacPvt->int32Pvt = int32LowerLevelInterface->drvPvt; - - status = pasynManager->interposeInterface( - portName, addr, &pPmacPvt->octetInterface, &(octetLowerLevelInterface)); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: %s interposeInterface failed\n", - portName); - pasynManager->exceptionCallbackRemove(pasynUser); - pasynManager->freeAsynUser(pasynUser); - free(pPmacPvt); - return -1; - } - pPmacPvt->poctet = (asynOctet *)octetLowerLevelInterface->pinterface; - pPmacPvt->octetPvt = octetLowerLevelInterface->drvPvt; - - pPmacPvt->poutCmd = callocMustSucceed( - 1, sizeof(ethernetCmd), - "calloc poutCmd error in pmacAsynIPPort::pmacAsynIPPortCommon()."); - pPmacPvt->pinCmd = callocMustSucceed( - 1, sizeof(ethernetCmd), - "calloc pinCmd error in pmacAsynIPPort::pmacAsynIPPortCommon()."); - - pPmacPvt->inBuf = callocMustSucceed( - 1, MAX_BUFFER_SIZE, - "calloc inBuf error in pmacAsynIPPort::pmacAsynIPPortCommon()."); - - pPmacPvt->inBufHead = 0; - pPmacPvt->inBufTail = 0; - - // Interpose EOS handling layer, above the PMAC IP layer. - asynInterposeEosConfig(portName, addr, 1, 1); - - return status; -} - -static void pmacInExceptionHandler(asynUser *pasynUser, - asynException exception) { - pmacPvt *pPmacPvt = (pmacPvt *)pasynUser->userPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacInExceptionHandler\n"); - - if (exception == asynExceptionConnect) { - pPmacPvt->inBufHead = 0; - pPmacPvt->inBufTail = 0; - } -} - -/* - Read reponse data from PMAC into buffer pmacPvt.inBuf. If there is no data - in the asyn buffer then issue pmac GETBUFFER command to get any outstanding - data still on the PMAC. -*/ -static asynStatus readResponse(pmacPvt *pPmacPvt, asynUser *pasynUser, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason) { - asynStatus status = asynSuccess; - size_t thisRead = 0; - *nbytesTransfered = 0; - if (maxchars > INPUT_SIZE) - maxchars = INPUT_SIZE; - - asynPrint( - pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::readResponse. Performing pPmacPvt->poctet->read().\n"); - - status = - pPmacPvt->poctet->read(pPmacPvt->octetPvt, pasynUser, pPmacPvt->inBuf, - maxchars, &thisRead, eomReason); - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "%s readResponse1 maxchars=%zd, thisRead=%zd, eomReason=%d, " - "status=%u\n", - pPmacPvt->portName, maxchars, thisRead, *eomReason, status); - if (status == asynTimeout && thisRead == 0 && pasynUser->timeout > 0) { - /* failed to read as many characters as required into the input buffer, - check for more response data on the PMAC */ - if (pmacReadReady(pPmacPvt, pasynUser)) { - - status = sendPmacGetBuffer(pPmacPvt, pasynUser, maxchars, - nbytesTransfered); - asynPrintIO(pasynUser, ASYN_TRACE_FLOW, (char *)pPmacPvt->pinCmd, - ETHERNET_CMD_HEADER, "%s write GETBUFFER\n", - pPmacPvt->portName); - - /* We have nothing to return at the moment so read again */ - status = pPmacPvt->poctet->read(pPmacPvt->octetPvt, pasynUser, - pPmacPvt->inBuf, maxchars, - &thisRead, eomReason); - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "%s readResponse2 maxchars=%zd, thisRead=%zd, " - "eomReason=%d, status=%u\n", - pPmacPvt->portName, maxchars, thisRead, *eomReason, - status); - } - } - - if (thisRead > 0) { - if (status == asynTimeout) - status = asynSuccess; - *nbytesTransfered = thisRead; - pPmacPvt->inBufTail = 0; - pPmacPvt->inBufHead = thisRead; - } - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::readResponse. END\n"); - - return status; -} - -/* - Send ReadReady command to PMAC to discover if there is any data to read from - it. Returns: 0 - no data available 1 - data available -*/ -static int pmacReadReady(pmacPvt *pPmacPvt, asynUser *pasynUser) { - ethernetCmd cmd; - char data[2] = {0}; - asynStatus status; - size_t thisRead = 0; - size_t nbytesTransfered = 0; - int eomReason = 0; - int retval = 0; - - cmd.RequestType = VR_UPLOAD; - cmd.Request = VR_PMAC_READREADY; - cmd.wValue = 0; - cmd.wIndex = 0; - cmd.wLength = htons(2); - - status = - pPmacPvt->poctet->write(pPmacPvt->octetPvt, pasynUser, (char *)&cmd, - ETHERNET_CMD_HEADER, &nbytesTransfered); - - if (status != asynSuccess) { - asynPrintIO(pasynUser, ASYN_TRACE_ERROR, (char *)&cmd, - ETHERNET_CMD_HEADER, "%s write pmacReadReady fail\n", - pPmacPvt->portName); - } - - status = pPmacPvt->poctet->read(pPmacPvt->octetPvt, pasynUser, data, 2, - &thisRead, &eomReason); - - if (status == asynSuccess) { - if (thisRead == 2 && data[0] != 0) { - retval = 1; - } - asynPrintIO(pasynUser, ASYN_TRACE_FLOW, data, thisRead, - "%s read pmacReadReady OK thisRead=%zd\n", - pPmacPvt->portName, thisRead); - } else { - asynPrint(pasynUser, ASYN_TRACE_ERROR, - "%s read pmacReadReady failed status=%d, retval=%d\n", - pPmacPvt->portName, status, retval); - } - return retval; -} - -/** - * @brief Flush the PMAC communication buffer - * -Send a Flush command to PMAC and wait for confirmation ctrlX to be returned -according to the Turbo PMAC user manual, p. 414 Returns: 0 - failed 1 - success - - * @param pPmacPvt - * @param pasynUser - * @return int - */ -static int pmacFlush(pmacPvt *pPmacPvt, asynUser *pasynUser) { - - ethernetCmd cmd; - char data[2]; - asynStatus status = asynSuccess; - size_t thisRead; - size_t nbytesTransfered = 0; - int eomReason = 0; - int retval = 0; - - cmd.RequestType = VR_DOWNLOAD; - cmd.Request = VR_PMAC_FLUSH; - cmd.wValue = 0; - cmd.wIndex = 0; - cmd.wLength = 0; - - status = - pPmacPvt->poctet->write(pPmacPvt->octetPvt, pasynUser, (char *)&cmd, - ETHERNET_CMD_HEADER, &nbytesTransfered); - - if (status != asynSuccess) { - asynPrintIO(pasynUser, ASYN_TRACE_ERROR, (char *)&cmd, - ETHERNET_CMD_HEADER, "%s write pmacFlush fail\n", - pPmacPvt->portName); - } - - /* read flush acknowledgement character */ - /* NB we don't check what the character is (manual sais ctrlX, we get 0x40 - * i.e.VR_DOWNLOAD) */ - status = pPmacPvt->poctet->read(pPmacPvt->octetPvt, pasynUser, data, 1, - &thisRead, &eomReason); - - if (status == asynSuccess) { - asynPrint(pasynUser, ASYN_TRACE_FLOW, "%s read pmacFlush OK\n", - pPmacPvt->portName); - retval = 1; - } else { - asynPrint(pasynUser, ASYN_TRACE_ERROR, - "%s read pmacFlush failed - thisRead=%zd, eomReason=%d, " - "status=%d\n", - pPmacPvt->portName, thisRead, eomReason, status); - } - - pPmacPvt->inBufTail = 0; - pPmacPvt->inBufHead = 0; - - return retval; -} - -static asynStatus sendPmacGetBuffer(pmacPvt *pPmacPvt, asynUser *pasynUser, - size_t maxchars, size_t *nbytesTransfered) { - asynStatus status = 0; - ethernetCmd *inCmd = NULL; - - inCmd = pPmacPvt->pinCmd; - inCmd->RequestType = VR_UPLOAD; - inCmd->Request = VR_PMAC_GETBUFFER; - inCmd->wValue = 0; - inCmd->wIndex = 0; - inCmd->wLength = htons(maxchars); - status = pPmacPvt->poctet->write(pPmacPvt->octetPvt, pasynUser, - (char *)pPmacPvt->pinCmd, - ETHERNET_CMD_HEADER, nbytesTransfered); - - return status; -} - -/* -Implementation of asynInt32 methods -Most of these implementations just redirect to the corresponding implementation -in asyn/interfaces/asynInt32Base.c -*/ -// ============================================================================= - -static asynStatus writeInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 value) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::writeInt32\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - if (pasynUser->reason == FLUSH_HARDWARE) { - /* - According to the Turbo PMAC user manual, p. 414: - 0 - failed 1 - success - */ - if (pmacFlush(pPmacPvt, pasynUser) == 1) { - return asynSuccess; - } else { - return asynError; - } - } else { - return pPmacPvt->pint32->write(pPmacPvt->int32Pvt, pasynUser, value); - } -} - -static asynStatus readInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 *value) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readInt32\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->pint32->read(pPmacPvt->int32Pvt, pasynUser, value); -} - -static asynStatus getBoundsInt32(void *drvPvt, asynUser *pasynUser, - epicsInt32 *low, epicsInt32 *high) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::getBoundsInt32\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->pint32->getBounds(pPmacPvt->int32Pvt, pasynUser, low, - high); -} - -static asynStatus registerInterruptUserInt32(void *drvPvt, asynUser *pasynUser, - interruptCallbackInt32 callback, - void *userPvt, - void **registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::registerInterruptUserInt32\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->pint32->registerInterruptUser( - pPmacPvt->int32Pvt, pasynUser, callback, userPvt, registrarPvt); -} - -static asynStatus cancelInterruptUserInt32(void *drvPvt, asynUser *pasynUser, - void *registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::cancelInterruptUserInt32\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->pint32->cancelInterruptUser(pPmacPvt->int32Pvt, pasynUser, - registrarPvt); -} - -/* -Implementation of asynOctet methods -Most of these implementations just redirect to the corresponding implementation -in asyn/interfaces/asynOctetBase.c -*/ -// ============================================================================= - -/* This function sends either a ascii string command to the PMAC using - VR_PMAC_GETRESPONSE or a single control character (ctrl B/C/F/G/P/V) using - VR_CTRL_RESPONSE -*/ -static asynStatus writeItOctet(void *drvPvt, asynUser *pasynUser, - const char *data, size_t numchars, - size_t *nbytesTransfered) { - asynStatus status; - ethernetCmd *outCmd; - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - size_t nbytesActual = 0; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::writeItOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - /* NB currently we assume control characters arrive as individual - characters/calls to this routine. Idealy we should probably scan the data - buffer for control commands and do PMAC_GETRESPONSE and CTRL_RESPONSE - commands as necessary, */ - outCmd = pPmacPvt->poutCmd; - if (numchars == 1 && strchr(ctrlCommands, data[0])) { - outCmd->RequestType = VR_UPLOAD; - outCmd->Request = VR_CTRL_RESPONSE; - outCmd->wValue = data[0]; - outCmd->wIndex = 0; - outCmd->wLength = htons(0); - status = pPmacPvt->poctet->write(pPmacPvt->octetPvt, pasynUser, - (char *)pPmacPvt->poutCmd, - ETHERNET_CMD_HEADER, &nbytesActual); - *nbytesTransfered = - (nbytesActual == ETHERNET_CMD_HEADER) ? numchars : 0; - } else { - if (numchars > ETHERNET_DATA_SIZE) { - /* NB large data should probably be sent using PMAC_WRITEBUFFER - * which isnt implemented yet - for the moment just truncate */ - numchars = ETHERNET_DATA_SIZE; - asynPrint(pasynUser, ASYN_TRACE_ERROR, - "writeItOctet - ERROR TRUNCATED\n"); - } - - /* - The message protocol of the turboPmac used at PSI looks as follows (all - characters immediately following each other without a newline): - 0x40 (ASCII value of @) -> Request for download - 0xBF (ASCII value of ¿) -> Select mode "get_response" - 0x00 (ASCII value of 0) - 0x00 (ASCII value of 0) - 0x00 (ASCII value of 0) - 0x00 (ASCII value of 0) - 0x00 (ASCII value of 0) - [message length in network byte order] -> Use the htons function for - this value [Actual message] It is not necessary to append a terminator, - since this protocol encodes the message length at the beginning. See - Turbo PMAC User Manual, page 418 in VR_PMAC_GETRESPONSE x0D (ASCII value - of carriage return) -> The controller needs a carriage return at the end - of a "send" command (a command were we transmit data via - =). For "request" commands (e.g. read status or position), this is not - necessary, but it doesn't hurt either, therefore we always add a - carriage return. - - The message has to be build manually into the buffer fullCommand, since - it contains NULL terminators in its middle, therefore the string - manipulation methods of C don't work. - */ - outCmd->RequestType = VR_DOWNLOAD; - outCmd->Request = VR_PMAC_GETRESPONSE; - outCmd->wValue = 0; - outCmd->wIndex = 0; - outCmd->wLength = htons(numchars); - memcpy(outCmd->bData, data, numchars); - status = pPmacPvt->poctet->write( - pPmacPvt->octetPvt, pasynUser, (char *)pPmacPvt->poutCmd, - numchars + ETHERNET_CMD_HEADER, &nbytesActual); - *nbytesTransfered = (nbytesActual > ETHERNET_CMD_HEADER) - ? (nbytesActual - ETHERNET_CMD_HEADER) - : 0; - } - - asynPrintIO(pasynUser, ASYN_TRACE_FLOW, (char *)pPmacPvt->poutCmd, - numchars + ETHERNET_CMD_HEADER, "%s writeItOctet\n", - pPmacPvt->portName); - - return status; -} - -/* -This function reads data using read() into a local buffer and then look for -message terminating characters and returns a complete response (or times -out), adding on ACK if neccessary. The PMAC command response may be any of -the following:- datadata....data data e.g. an -error ERRxxx data (NB asyn EOS only allows one message -terminator to be specified. We add on ACK for the EOS layer above.) -*/ -static asynStatus readItOctet(void *drvPvt, asynUser *pasynUser, char *data, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynStatus status = asynSuccess; - size_t thisRead = 0; - size_t nRead = 0; - int bell = 0; - int initialRead = 1; - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::readItOctet. START\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - if (maxchars > 0) { - for (;;) { - if ((pPmacPvt->inBufTail != pPmacPvt->inBufHead)) { - *data = pPmacPvt->inBuf[pPmacPvt->inBufTail++]; - if (*data == BELL || *data == STX) - bell = 1; - if (*data == '\r' && bell) { - /* xxxxxx or xxxxx received - its - * probably an error response (ERRxxx) - assume - * there is no more response data to come */ - nRead++; /* make sure the is passed to the client app - */ - /*Add on ACK, because that's what we expect to be EOS in EOS - * interpose layer.*/ - if ((nRead + 1) > maxchars) { - /*If maxchars is reached overwrite with ACK, so - * that no more reads will be done from EOS layer.*/ - *data = ACK; - } else { - data++; - nRead++; - *data = ACK; - } - break; - } - if (*data == ACK || *data == '\n') { - /* or received - assume there is no more response - * data to come */ - /* If , replace with an ACK.*/ - if (*data == '\n') { - *data = ACK; - } - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "Message was terminated with ACK in " - "pmacAsynIPPort::readItOctet.\n"); - /*Pass ACK up to Asyn EOS handling layer.*/ - data++; - nRead++; - break; - } - data++; - nRead++; - if (nRead >= maxchars) - break; - continue; - } - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::readItOctet. Calling readResponse().\n"); - if (!initialRead) { - if (pmacReadReady(pPmacPvt, pasynUser)) { - status = sendPmacGetBuffer(pPmacPvt, pasynUser, maxchars, - nbytesTransfered); - } - } - status = readResponse(pPmacPvt, pasynUser, maxchars - nRead, - &thisRead, eomReason); - initialRead = 0; - if (status != asynSuccess || thisRead == 0) - break; - } - } - *nbytesTransfered = nRead; - - asynPrintIO( - pasynUser, ASYN_TRACE_FLOW, data, *nbytesTransfered, - "%s pmacAsynIPPort readItOctet nbytesTransfered=%zd, eomReason=%d, " - "status=%d\n", - pPmacPvt->portName, *nbytesTransfered, *eomReason, status); - - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readItOctet. END\n"); - - return status; -} - -static asynStatus flushItOctet(void *drvPvt, asynUser *pasynUser) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynStatus status; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::flushItOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - /* - See changelog at the file header: We do not want to flush the controller - during "normal" operation, since this stalls the controller. - - pmacFlush(pPmacPvt, pasynUser); - */ - - status = pPmacPvt->poctet->flush(pPmacPvt->octetPvt, pasynUser); - return status; -} - -static asynStatus registerInterruptUserOctet(void *drvPvt, asynUser *pasynUser, - interruptCallbackOctet callback, - void *userPvt, - void **registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::registerInterruptUserOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->registerInterruptUser( - pPmacPvt->octetPvt, pasynUser, callback, userPvt, registrarPvt); -} - -static asynStatus cancelInterruptUserOctet(void *drvPvt, asynUser *pasynUser, - void *registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::cancelInterruptUserOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->cancelInterruptUser(pPmacPvt->octetPvt, pasynUser, - registrarPvt); -} - -static asynStatus setInputEosOctet(void *drvPvt, asynUser *pasynUser, - const char *eos, int eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::setInputEosOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->setInputEos(pPmacPvt->octetPvt, pasynUser, eos, - eoslen); -} - -static asynStatus getInputEosOctet(void *drvPvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::getInputEosOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->getInputEos(pPmacPvt->octetPvt, pasynUser, eos, - eossize, eoslen); -} - -static asynStatus setOutputEosOctet(void *drvPvt, asynUser *pasynUser, - const char *eos, int eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::setOutputEosOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->setOutputEos(pPmacPvt->octetPvt, pasynUser, eos, - eoslen); -} - -static asynStatus getOutputEosOctet(void *drvPvt, asynUser *pasynUser, - char *eos, int eossize, int *eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::getOutputEosOctet\n"); - - // Check that the given pointer is not NULL - assert(pPmacPvt); - - return pPmacPvt->poctet->getOutputEos(pPmacPvt->octetPvt, pasynUser, eos, - eossize, eoslen); -} - -// ============================================================================= - -/* register pmacAsynIPPortConfigure*/ -static const iocshArg pmacAsynIPPortConfigureArg0 = {"portName", - iocshArgString}; -static const iocshArg pmacAsynIPPortConfigureArg1 = {"hostInfo", - iocshArgString}; -static const iocshArg *pmacAsynIPPortConfigureArgs[] = { - &pmacAsynIPPortConfigureArg0, &pmacAsynIPPortConfigureArg1}; -static const iocshFuncDef pmacAsynIPPortConfigFuncDef = { - "pmacAsynIPPortConfigure", 2, pmacAsynIPPortConfigureArgs, ""}; -static void pmacAsynIPPortConfigureCallFunc(const iocshArgBuf *args) { - pmacAsynIPPortConfigure(args[0].sval, args[1].sval); -} - -static void pmacAsynIPPortRegister(void) { - static int firstTime = 1; - if (firstTime) { - firstTime = 0; - iocshRegister(&pmacAsynIPPortConfigFuncDef, - pmacAsynIPPortConfigureCallFunc); - } -} -epicsExportRegistrar(pmacAsynIPPortRegister); \ No newline at end of file diff --git a/src/pmacAsynIPPort.cpp b/src/pmacAsynIPPort.cpp new file mode 100644 index 0000000..7fc1bd1 --- /dev/null +++ b/src/pmacAsynIPPort.cpp @@ -0,0 +1,945 @@ +/* +NOTES + +This driver is an asyn driver intended for use with turboPmacController +to allow communication over ethernet to a PMAC. + +*** Ensure I3=2 and I6=1 on the PMAC before using this driver. *** + +This driver supports sending ascii commands to the PMAC over asyn IP and +obtaining the response. The driver uses the PMAC ethernet packets +VR_PMAC_GETRESPONSE, VR_PMAC_READREADY and VR_PMAC_GETBUFFER to send commands +and get responses. The PMAC may send responses in several different formats. +1) PMAC ascii command responses for single/multiple commands (e.g. I113 I114 +I130 I131 I133) are in an ACK terminated form as follows (CR seperates the +cmd responses): datadatadata 2) PMAC error +responses to ascii commands ARE NOT ACK terminated as follows: + ERRxxx +3) PMAC may also return the following: + data + +This driver can send ctrl commands (ctrl B/C/F/G/P/V) to the pmac (using +VR_CTRL_REPONSE packet) however because the resulting response data is not +terminated as above the driver does not know when all the response data has +been received. The response data will therefore only be returned after the +asynUser.timeout has expired. + +This driver does NOT support large buffer transfers (VR_PMAC_WRITEBUFFER, +VR_FWDOWNLOAD) or set/get of DPRAM (VR_PMAC_SETMEM etc) or changing comms +setup (VR_IPADDRESS, VR_PMAC_PORT) + +Unlike the original DLS driver, this is not an interpose layer on top of +drvAsynIPPort anymore. Instead the driver inherits from the C++ asynPortDriver +class, implements the socket handling itself and overrides the asynCommon, +asynOctet and asynInt32 methods. + +REVISION HISTORY + +10 Aug 07 - Pete Leicester - Diamond Light Source +Modified to handle responses other than those ending in (e.g. errors) - +No longer used asyn EOS. + +9 Aug 07 - Pete Leicester - Diamond Light Source +Initial version reliant on asyn EOS to return terminated responses. + +2 Feb 09 - Matthew Pearson - Diamond Light Source +Ported to work with Asyn 4-10. Still compiles with pre Asyn4-10 versions but +does not work. Also added new config function, pmacAsynIPPortConfigureEos(), +to be used when disabling low level EOS handling in the Asyn IP layer. This +new function should be used with Asyn 4-10 and above (it is not compatible +with Asyn 4-9). + +10 Apr 25 - Stefan Mathis - Paul Scherrer Institut +Adjusted the driver to the requirements of SINQ: +- Added a lot of comments, removed the predecessor of +pmacAsynIPPortConfigureEos() since SINQ doesn't need it (see comment above) +- Removed the call to `pmacFlush` from `flushItOctet`: We want to flush the +communication buffer on the IOC side before each communication, but we do not +want to flush the PMAC controller itself, since this stalls the controller for +10 ms everytime (see Turbo PMAC User Manual, p. 414). Instead, a PV was added in +the higher level `turboPmac` driver to manually call the `pmacFlush` function. +*/ + +#include "pmacAsynIPPort.h" +#include "asynDriver.h" +#include "asynInterposeEos.h" +#include "asynPortDriver.h" +#include "epicsExport.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMAC ethernet commands - RequestType field */ +constexpr uint8_t VR_UPLOAD = 0xC0; +constexpr uint8_t VR_DOWNLOAD = 0x40; + +/* PMAC ethernet commands - Request field */ +constexpr uint8_t VR_PMAC_FLUSH = 0xB3; +constexpr uint8_t VR_PMAC_GETRESPONSE = 0xBF; +constexpr uint8_t VR_PMAC_READREADY = 0xC2; +constexpr uint8_t VR_CTRL_RESPONSE = 0xC4; +constexpr uint8_t VR_PMAC_GETBUFFER = 0xC5; + +constexpr size_t MAX_BUFFER_SIZE = 2097152; +constexpr size_t INPUT_SIZE = + (ETHERNET_DATA_SIZE + 1) /* +1 to allow space to add terminating ACK */; + +/* Connect timeout in seconds, matching asyn's DEFAULT_AUTOCONNECT_TIMEOUT. */ +constexpr double AUTO_CONNECT_TIMEOUT = 0.5; + +constexpr char STX = '\2'; +constexpr char ACK = '\6'; +constexpr char BELL = '\7'; + +/* PMAC control character commands (VR_CTRL_RESPONSE cmd): ctrl B/C/F/G/P/V */ +constexpr char ctrlCommands[] = "\002\003\006\007\016\022"; + +/* This delay is how long to wait in seconds after a send fails with errno == + * EAGAIN or EINTR before trying again */ +constexpr double SEND_RETRY_DELAY = 0.01; + +/* + Notes on asyn + use asynUser.timeout to specify I/O request timeouts in seconds + asynStatus may return asynSuccess(0),asynTimeout(1),asynOverflow(2) or + asynError(3) eomReason may return ASYN_EOM_CNT (1:Request count reached), + ASYN_EOM_EOS (2:End of String detected), ASYN_EOM_END (4:End indicator + detected) asynError indicates that asynUser.errorMessage has been populated + by epicsSnprintf(). +*/ + +/* + * OSI function to control blocking/non-blocking I/O + */ +static int setNonBlock(SOCKET fd, int nonBlockFlag) { +#if defined(vxWorks) + int flags; + flags = nonBlockFlag; + if (ioctl(fd, FIONBIO, (int)&flags) < 0) + return -1; +#elif defined(_WIN32) + unsigned long int flags; + flags = nonBlockFlag; + if (socket_ioctl(fd, FIONBIO, &flags) < 0) + return -1; +#else + int flags; + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + return -1; + if (nonBlockFlag) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + return -1; +#endif + return 0; +} + +pmacAsynIPPort::pmacAsynIPPort(const char *portName, const char *hostInfo) + : asynPortDriver( + portName, 1 /* maxAddr */, asynOctetMask | asynInt32Mask, + 0 /* interruptMask */, ASYN_CANBLOCK, 1 /* autoConnect */, + 0 /* priority */, 0 /* stackSize */), + inBuf_(MAX_BUFFER_SIZE) { + // The base class constructor mirrors drvAsynIPPortConfigure(portName, + // hostInfo, priority, noAutoConnect) with ASYN_CANBLOCK and autoConnect + // enabled. We register the asynOctet, asynInt32 and asynCommon interfaces. + // The low level EOS processing is not enabled in the base class, since the + // EOS interpose layer added in pmacAsynIPPortConfigure handles the + // PMAC-specific message terminators instead. + parseHostInfo(hostInfo); +} + +pmacAsynIPPort::~pmacAsynIPPort() { + if (fd_ != INVALID_SOCKET) + epicsSocketDestroy(fd_); +} + +void pmacAsynIPPort::parseHostInfo(const char *hostInfo) { + std::string hostInfoString(hostInfo); + size_t colon = hostInfoString.find(':'); + if (colon == std::string::npos) { + std::string msg("pmacAsynIPPort: \""); + msg += hostInfo; + msg += "\" is not of the form \":\""; + throw std::runtime_error(msg); + } + IPHostName_ = hostInfoString.substr(0, colon); + std::string portString = hostInfoString.substr(colon + 1); + unsigned long port; + try { + port = std::stoul(portString); + } catch (const std::exception &) { + std::string msg("pmacAsynIPPort: invalid port \""); + msg += portString + "\" in \"" + hostInfo + "\""; + throw std::runtime_error(msg); + } + if (port == 0 || port > 65535) { + std::string msg("pmacAsynIPPort: port \""); + msg += portString + "\" out of range in \"" + hostInfo + "\""; + throw std::runtime_error(msg); + } + port_ = static_cast(port); +} + +asynStatus pmacAsynIPPort::connectIt(asynUser *pasynUser) { + SOCKET fd; + int i; + char sockerrmsg[256]; + osiSockAddr farAddr; + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "Attempting to connect to %s reason:%d fd:%lld\n", + IPHostName_.c_str(), pasynUser->reason, (long long)fd_); + + if (fd_ != INVALID_SOCKET) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s: Link already open!", IPHostName_.c_str()); + return asynError; + } + + /* + * Create the socket + */ + if ((fd = epicsSocketCreate(AF_INET, SOCK_STREAM, 0)) < 0) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Can't create socket: %s", sockerrmsg); + return asynError; + } + + /* + * Convert host name/number to IP address. + * We delay doing this until now in case a device + * has just appeared in a DNS database. + */ + farAddr.ia.sin_family = AF_INET; + farAddr.ia.sin_port = htons(port_); + if (hostToIPAddr(IPHostName_.c_str(), &farAddr.ia.sin_addr) < 0) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Unknown host \"%s\"", IPHostName_.c_str()); + epicsSocketDestroy(fd); + return asynError; + } + + if (setNonBlock(fd, 1) < 0) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Can't set %s O_NONBLOCK option: %s", + IPHostName_.c_str(), sockerrmsg); + epicsSocketDestroy(fd); + return asynError; + } + + { + int connectResult = + ::connect(fd, &farAddr.sa, sizeof(farAddr.ia)); + if (connectResult < 0 && + (SOCKERRNO == SOCK_EWOULDBLOCK || SOCKERRNO == SOCK_EINPROGRESS)) { + int msConnectTimeout; + struct pollfd pollfd; + + // Use asyn's default auto-connect timeout. Older asyn versions do + // not provide asynManager::getAutoConnectTimeout. + msConnectTimeout = (int)(1000 * AUTO_CONNECT_TIMEOUT); + pollfd.fd = fd; + pollfd.events = POLLOUT; + + /* + * poll() returning 1 is the only case where connect might have + * been successful. Otherwise connectResult will remain -1. + */ + if (poll(&pollfd, 1, msConnectTimeout) == 1) { + int so_error; + osiSocklen_t len = sizeof so_error; + + /* + * We must verify SO_ERROR to make sure the connection was + * successful. + */ + int sockErrorStatus = getsockopt(fd, SOL_SOCKET, SO_ERROR, + (void *)&so_error, &len); + if (sockErrorStatus == 0 && so_error == 0) + connectResult = 0; + } + } + if (connectResult < 0) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Can't connect to %s: %s", IPHostName_.c_str(), + sockerrmsg); + epicsSocketDestroy(fd); + return asynError; + } + } + + i = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&i, sizeof i) < 0) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "Can't set %s socket NODELAY option: %s", + IPHostName_.c_str(), sockerrmsg); + epicsSocketDestroy(fd); + return asynError; + } + + asynPrint(pasynUser, ASYN_TRACE_FLOW, "Opened connection OK to %s\n", + IPHostName_.c_str()); + fd_ = fd; + return asynSuccess; +} + +void pmacAsynIPPort::closeConnection(asynUser *pasynUser, const char *why) { + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "Closing %s connection (fd %lld): %s\n", IPHostName_.c_str(), + (long long)fd_, why); + if (fd_ != INVALID_SOCKET) { + epicsSocketDestroy(fd_); + fd_ = INVALID_SOCKET; + } + pasynManager->exceptionDisconnect(pasynUser); +} + +asynStatus pmacAsynIPPort::connect(asynUser *pasynUser) { + int addr; + asynStatus status = getAddress(pasynUser, &addr); + if (status != asynSuccess) + return status; + status = connectIt(pasynUser); + if (status == asynSuccess) { + /* Clear any stale response data left from a previous connection */ + inBufTail_ = 0; + inBufHead_ = 0; + pasynManager->exceptionConnect(pasynUser); + } + return status; +} + +asynStatus pmacAsynIPPort::disconnect(asynUser *pasynUser) { + int addr; + asynStatus status = getAddress(pasynUser, &addr); + if (status != asynSuccess) + return status; + closeConnection(pasynUser, "Disconnect request"); + return asynSuccess; +} + +asynStatus pmacAsynIPPort::socketRead(asynUser *pasynUser, char *data, + size_t maxchars, size_t *nbytesRead) { + int thisRead; + int readPollmsec; + asynStatus status = asynSuccess; + char sockerrmsg[256]; + epicsTimeStamp startTime; + epicsTimeStamp endTime; + + *nbytesRead = 0; + if (fd_ == INVALID_SOCKET) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s disconnected", IPHostName_.c_str()); + return asynError; + } + if (maxchars == 0) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s maxchars %d. Why <=0?", IPHostName_.c_str(), + (int)maxchars); + return asynError; + } + readPollmsec = (int)(pasynUser->timeout * 1000.0); + if (readPollmsec == 0) + readPollmsec = 1; + if (readPollmsec < 0) + readPollmsec = -1; + + { + struct pollfd pollfd; + pollfd.fd = fd_; + pollfd.events = POLLIN; + epicsTimeGetCurrent(&startTime); + while (poll(&pollfd, 1, readPollmsec) < 0) { + if (SOCKERRNO != SOCK_EINTR) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, + pasynUser->errorMessageSize, "Poll() failed: %s", + sockerrmsg); + return asynError; + } + epicsTimeGetCurrent(&endTime); + if (epicsTimeDiffInSeconds(&endTime, &startTime) * 1000. > + readPollmsec) + break; + } + } + + thisRead = recv(fd_, data, (int)maxchars, 0); + if (thisRead < 0) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + if (SOCKERRNO == SOCK_EWOULDBLOCK || SOCKERRNO == SOCK_EINTR) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s timeout: %s", IPHostName_.c_str(), sockerrmsg); + status = asynTimeout; + } else { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s read error: %s", IPHostName_.c_str(), sockerrmsg); + closeConnection(pasynUser, "Read error"); + status = asynError; + } + } + /* If recv() returns 0 on a SOCK_STREAM (TCP) socket, the connection has + * closed */ + if (thisRead == 0) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s connection closed", IPHostName_.c_str()); + closeConnection(pasynUser, "Read from broken connection"); + } + if (thisRead < 0) + thisRead = 0; + *nbytesRead = thisRead; + /* If there is room add a null byte */ + if (thisRead < (int)maxchars) + data[thisRead] = 0; + return status; +} + +asynStatus pmacAsynIPPort::socketWrite(asynUser *pasynUser, const char *data, + size_t numchars, + size_t *nbytesTransferred) { + asynStatus status = asynSuccess; + int thisWrite; + int writePollmsec; + epicsTimeStamp startTime; + epicsTimeStamp endTime; + int haveStartTime = 0; + char sockerrmsg[256]; + + asynPrint(pasynUser, ASYN_TRACE_FLOW, "%s write.\n", IPHostName_.c_str()); + *nbytesTransferred = 0; + if (fd_ == INVALID_SOCKET) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s disconnected", IPHostName_.c_str()); + return asynError; + } + if (numchars == 0) + return asynSuccess; + writePollmsec = (int)(pasynUser->timeout * 1000.0); + if (writePollmsec == 0) + writePollmsec = 1; + if (writePollmsec < 0) + writePollmsec = -1; + for (;;) { + int pollstatus; + struct pollfd pollfd; + pollfd.fd = fd_; + pollfd.events = POLLOUT; + epicsTimeGetCurrent(&startTime); + while ((pollstatus = poll(&pollfd, 1, writePollmsec)) < 0) { + if (SOCKERRNO != SOCK_EINTR) { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, + pasynUser->errorMessageSize, + "%s poll() failed: %s", IPHostName_.c_str(), + sockerrmsg); + return asynError; + } + epicsTimeGetCurrent(&endTime); + if (epicsTimeDiffInSeconds(&endTime, &startTime) * 1000 > + writePollmsec) + break; + } + if (pollstatus == 0) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s poll() timed out", IPHostName_.c_str()); + return asynTimeout; + } + for (;;) { + thisWrite = send(fd_, data, (int)numchars, 0); + if (thisWrite >= 0) + break; + if (SOCKERRNO == SOCK_EWOULDBLOCK || SOCKERRNO == SOCK_EINTR) { + if (!haveStartTime) { + epicsTimeGetCurrent(&startTime); + haveStartTime = 1; + } else if (pasynUser->timeout >= 0) { + epicsTimeGetCurrent(&endTime); + if (epicsTimeDiffInSeconds(&endTime, &startTime) > + pasynUser->timeout) { + thisWrite = 0; + break; + } + } + epicsThreadSleep(SEND_RETRY_DELAY); + } else + break; + } + if (thisWrite > 0) { + *nbytesTransferred += thisWrite; + numchars -= thisWrite; + if (numchars == 0) + break; + data += thisWrite; + } else if (thisWrite == 0) { + status = asynTimeout; + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s send() returned 0", IPHostName_.c_str()); + break; + } else { + epicsSocketConvertErrnoToString(sockerrmsg, sizeof(sockerrmsg)); + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "%s write error: %s", IPHostName_.c_str(), + sockerrmsg); + closeConnection(pasynUser, "Write error"); + status = asynError; + break; + } + } + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "wrote %lu to %s, return %s.\n", + (unsigned long)*nbytesTransferred, IPHostName_.c_str(), + pasynManager->strStatus(status)); + return status; +} + +/* + Read reponse data from PMAC into buffer inBuf_. If there is no data in the + socket buffer then issue pmac GETBUFFER command to get any outstanding data + still on the PMAC. +*/ +asynStatus pmacAsynIPPort::readResponse(asynUser *pasynUser, size_t maxchars, + size_t *nbytesTransfered, + int *eomReason) { + asynStatus status = asynSuccess; + size_t thisRead = 0; + *nbytesTransfered = 0; + if (maxchars > INPUT_SIZE) + maxchars = INPUT_SIZE; + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "pmacAsynIPPort::readResponse. Performing socketRead().\n"); + + status = socketRead(pasynUser, inBuf_.data(), maxchars, &thisRead); + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "%s readResponse1 maxchars=%zd, thisRead=%zd, eomReason=%d, " + "status=%u\n", + portName, maxchars, thisRead, *eomReason, + static_cast(status)); + if (status == asynTimeout && thisRead == 0 && pasynUser->timeout > 0) { + /* failed to read as many characters as required into the input buffer, + check for more response data on the PMAC */ + if (pmacReadReady(pasynUser)) { + + status = sendPmacGetBuffer(pasynUser, maxchars, nbytesTransfered); + asynPrintIO(pasynUser, ASYN_TRACE_FLOW, + reinterpret_cast(&pinCmd_), + ETHERNET_CMD_HEADER, "%s write GETBUFFER\n", portName); + + /* We have nothing to return at the moment so read again */ + status = socketRead(pasynUser, inBuf_.data(), maxchars, &thisRead); + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "%s readResponse2 maxchars=%zd, thisRead=%zd, " + "eomReason=%d, status=%u\n", + portName, maxchars, thisRead, *eomReason, + static_cast(status)); + } + } + + if (thisRead > 0) { + if (status == asynTimeout) + status = asynSuccess; + *nbytesTransfered = thisRead; + inBufTail_ = 0; + inBufHead_ = thisRead; + } + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "pmacAsynIPPort::readResponse. END\n"); + + return status; +} + +/* + Send ReadReady command to PMAC to discover if there is any data to read from + it. Returns: 0 - no data available 1 - data available +*/ +int pmacAsynIPPort::pmacReadReady(asynUser *pasynUser) { + EthernetCmd cmd{}; + char data[2] = {0}; + asynStatus status; + size_t thisRead = 0; + size_t nbytesTransfered = 0; + int retval = 0; + + cmd.RequestType = VR_UPLOAD; + cmd.Request = VR_PMAC_READREADY; + cmd.wValue = 0; + cmd.wIndex = 0; + cmd.wLength = htons(2); + + status = socketWrite(pasynUser, reinterpret_cast(&cmd), + ETHERNET_CMD_HEADER, &nbytesTransfered); + + if (status != asynSuccess) { + asynPrintIO(pasynUser, ASYN_TRACE_ERROR, + reinterpret_cast(&cmd), ETHERNET_CMD_HEADER, + "%s write pmacReadReady fail\n", portName); + } + + status = socketRead(pasynUser, data, 2, &thisRead); + + if (status == asynSuccess) { + if (thisRead == 2 && data[0] != 0) { + retval = 1; + } + asynPrintIO(pasynUser, ASYN_TRACE_FLOW, data, thisRead, + "%s read pmacReadReady OK thisRead=%zd\n", portName, + thisRead); + } else { + asynPrint(pasynUser, ASYN_TRACE_ERROR, + "%s read pmacReadReady failed status=%d, retval=%d\n", + portName, static_cast(status), retval); + } + return retval; +} + +/** + * @brief Flush the PMAC communication buffer + * + * Send a Flush command to PMAC and wait for confirmation ctrlX to be returned + * according to the Turbo PMAC user manual, p. 414. + * + * @return 0 - failed, 1 - success + */ +int pmacAsynIPPort::pmacFlush(asynUser *pasynUser) { + EthernetCmd cmd{}; + char data[2] = {0}; + asynStatus status = asynSuccess; + size_t thisRead = 0; + size_t nbytesTransfered = 0; + int retval = 0; + + cmd.RequestType = VR_DOWNLOAD; + cmd.Request = VR_PMAC_FLUSH; + cmd.wValue = 0; + cmd.wIndex = 0; + cmd.wLength = 0; + + status = socketWrite(pasynUser, reinterpret_cast(&cmd), + ETHERNET_CMD_HEADER, &nbytesTransfered); + + if (status != asynSuccess) { + asynPrintIO(pasynUser, ASYN_TRACE_ERROR, + reinterpret_cast(&cmd), ETHERNET_CMD_HEADER, + "%s write pmacFlush fail\n", portName); + } + + /* read flush acknowledgement character */ + /* NB we don't check what the character is (manual sais ctrlX, we get 0x40 + * i.e.VR_DOWNLOAD) */ + status = socketRead(pasynUser, data, 1, &thisRead); + + if (status == asynSuccess) { + asynPrint(pasynUser, ASYN_TRACE_FLOW, "%s read pmacFlush OK\n", + portName); + retval = 1; + } else { + asynPrint(pasynUser, ASYN_TRACE_ERROR, + "%s read pmacFlush failed - thisRead=%zd, status=%d\n", + portName, thisRead, static_cast(status)); + } + + inBufTail_ = 0; + inBufHead_ = 0; + + return retval; +} + +asynStatus pmacAsynIPPort::sendPmacGetBuffer(asynUser *pasynUser, + size_t maxchars, + size_t *nbytesTransfered) { + pinCmd_.RequestType = VR_UPLOAD; + pinCmd_.Request = VR_PMAC_GETBUFFER; + pinCmd_.wValue = 0; + pinCmd_.wIndex = 0; + pinCmd_.wLength = htons(maxchars); + return socketWrite(pasynUser, reinterpret_cast(&pinCmd_), + ETHERNET_CMD_HEADER, nbytesTransfered); +} + +/* +This function sends either a ascii string command to the PMAC using + VR_PMAC_GETRESPONSE or a single control character (ctrl B/C/F/G/P/V) using + VR_CTRL_RESPONSE +*/ +asynStatus pmacAsynIPPort::writeOctet(asynUser *pasynUser, const char *data, + size_t numchars, + size_t *nbytesTransfered) { + asynStatus status; + size_t nbytesActual = 0; + asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::writeOctet\n"); + + /* NB currently we assume control characters arrive as individual + characters/calls to this routine. Idealy we should probably scan the data + buffer for control commands and do PMAC_GETRESPONSE and CTRL_RESPONSE + commands as necessary, */ + if (numchars == 1 && strchr(ctrlCommands, data[0])) { + poutCmd_.RequestType = VR_UPLOAD; + poutCmd_.Request = VR_CTRL_RESPONSE; + poutCmd_.wValue = data[0]; + poutCmd_.wIndex = 0; + poutCmd_.wLength = htons(0); + status = socketWrite(pasynUser, + reinterpret_cast(&poutCmd_), + ETHERNET_CMD_HEADER, &nbytesActual); + *nbytesTransfered = nbytesActual == ETHERNET_CMD_HEADER ? numchars : 0; + } else { + if (numchars > ETHERNET_DATA_SIZE) { + /* NB large data should probably be sent using PMAC_WRITEBUFFER + * which isnt implemented yet - for the moment just truncate */ + numchars = ETHERNET_DATA_SIZE; + asynPrint(pasynUser, ASYN_TRACE_ERROR, + "writeOctet - ERROR TRUNCATED\n"); + } + + /* + The message protocol of the turboPmac used at PSI looks as follows (all + characters immediately following each other without a newline): + 0x40 (ASCII value of @) -> Request for download + 0xBF (ASCII value of ¿) -> Select mode "get_response" + 0x00 (ASCII value of 0) + 0x00 (ASCII value of 0) + 0x00 (ASCII value of 0) + 0x00 (ASCII value of 0) + 0x00 (ASCII value of 0) + [message length in network byte order] -> Use the htons function for + this value [Actual message] It is not necessary to append a terminator, + since this protocol encodes the message length at the beginning. See + Turbo PMAC User Manual, page 418 in VR_PMAC_GETRESPONSE x0D (ASCII value + of carriage return) -> The controller needs a carriage return at the end + of a "send" command (a command were we transmit data via + =). For "request" commands (e.g. read status or position), this is not + necessary, but it doesn't hurt either, therefore we always add a + carriage return. + + The message has to be build manually into the buffer fullCommand, since + it contains NULL terminators in its middle, therefore the string + manipulation methods of C don't work. + */ + poutCmd_.RequestType = VR_DOWNLOAD; + poutCmd_.Request = VR_PMAC_GETRESPONSE; + poutCmd_.wValue = 0; + poutCmd_.wIndex = 0; + poutCmd_.wLength = htons(numchars); + memcpy(poutCmd_.bData, data, numchars); + status = socketWrite(pasynUser, + reinterpret_cast(&poutCmd_), + numchars + ETHERNET_CMD_HEADER, &nbytesActual); + if (nbytesActual > ETHERNET_CMD_HEADER) + *nbytesTransfered = nbytesActual - ETHERNET_CMD_HEADER; + else + *nbytesTransfered = 0; + } + + asynPrintIO(pasynUser, ASYN_TRACE_FLOW, + reinterpret_cast(&poutCmd_), + numchars + ETHERNET_CMD_HEADER, "%s writeOctet\n", portName); + + return status; +} + +/* +This function reads data using read() into a local buffer and then look for +message terminating characters and returns a complete response (or times +out), adding on ACK if neccessary. The PMAC command response may be any of +the following:- datadata....data data e.g. an +error ERRxxx data (NB asyn EOS only allows one message +terminator to be specified. We add on ACK for the EOS layer above.) +*/ +asynStatus pmacAsynIPPort::readOctet(asynUser *pasynUser, char *data, + size_t maxchars, size_t *nbytesTransfered, + int *eomReason) { + asynStatus status = asynSuccess; + size_t thisRead = 0; + size_t nRead = 0; + bool bell = false; + bool initialRead = true; + + asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readOctet. START\n"); + + if (maxchars > 0) { + for (;;) { + if (inBufTail_ != inBufHead_) { + *data = inBuf_[inBufTail_++]; + if (*data == BELL || *data == STX) + bell = true; + if (*data == '\r' && bell) { + /* xxxxxx or xxxxx received - its + * probably an error response (ERRxxx) - assume + * there is no more response data to come */ + nRead++; /* make sure the is passed to the client app + */ + /*Add on ACK, because that's what we expect to be EOS in EOS + * interpose layer.*/ + if ((nRead + 1) > maxchars) { + /*If maxchars is reached overwrite with ACK, so + * that no more reads will be done from EOS layer.*/ + *data = ACK; + } else { + data++; + nRead++; + *data = ACK; + } + break; + } + if (*data == ACK || *data == '\n') { + /* or received - assume there is no more response + * data to come */ + /* If , replace with an ACK.*/ + if (*data == '\n') { + *data = ACK; + } + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "Message was terminated with ACK in " + "pmacAsynIPPort::readOctet.\n"); + /*Pass ACK up to Asyn EOS handling layer.*/ + data++; + nRead++; + break; + } + data++; + nRead++; + if (nRead >= maxchars) + break; + continue; + } + + asynPrint(pasynUser, ASYN_TRACE_FLOW, + "pmacAsynIPPort::readOctet. Calling readResponse().\n"); + if (!initialRead) { + if (pmacReadReady(pasynUser)) { + status = sendPmacGetBuffer(pasynUser, maxchars, + nbytesTransfered); + } + } + status = readResponse(pasynUser, maxchars - nRead, &thisRead, + eomReason); + initialRead = false; + if (status != asynSuccess || thisRead == 0) + break; + } + } + *nbytesTransfered = nRead; + + asynPrintIO(pasynUser, ASYN_TRACE_FLOW, data, *nbytesTransfered, + "%s pmacAsynIPPort readOctet nbytesTransfered=%zd, " + "eomReason=%d, status=%d\n", + portName, *nbytesTransfered, *eomReason, + static_cast(status)); + + asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readOctet. END\n"); + + return status; +} + +asynStatus pmacAsynIPPort::flushOctet(asynUser *pasynUser) { + int numRecv, numTotal = 0; + char cbuf[512]; + + asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::flushOctet\n"); + + if (fd_ != INVALID_SOCKET) { + /* + * Toss characters until there are none left + */ + while (1) { + numRecv = recv(fd_, cbuf, sizeof cbuf, 0); + if (numRecv <= 0) + break; + numTotal += numRecv; + } + } + if (numTotal > 0) { + asynPrint(pasynUser, ASYN_TRACEIO_DRIVER, "%s flushed %d bytes\n", + portName, numTotal); + } + return asynSuccess; +} + +asynStatus pmacAsynIPPort::writeInt32(asynUser *pasynUser, epicsInt32 value) { + asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::writeInt32\n"); + + if (pasynUser->reason == FLUSH_HARDWARE) { + /* + According to the Turbo PMAC user manual, p. 414: + 0 - failed 1 - success + */ + if (pmacFlush(pasynUser) == 1) { + return asynSuccess; + } else { + return asynError; + } + } else { + return asynPortDriver::writeInt32(pasynUser, value); + } +} + +// ============================================================================= + +int pmacAsynIPPortConfigure(const char *portName, const char *hostInfo) { + /* + The port is registered with asynManager in the asynPortDriver base class + constructor. The object lives for the whole IOC lifetime and is therefore + intentionally never deleted (see turboPmacCreateAxis in turboPmacAxis.cpp + for a similar pattern). + */ + try { + new pmacAsynIPPort(portName, hostInfo); + } catch (const std::exception &e) { + printf("pmacAsynIPPortConfigure: error from pmacAsynIPPort " + "constructor. "); + printf("Port: %s (%s)\n", portName, e.what()); + return -1; + } + + /* Interpose EOS handling layer, above the PMAC IP layer. */ + asynInterposeEosConfig(portName, 0, 1, 1); + + return asynSuccess; +} + +// ============================================================================= + +/* register pmacAsynIPPortConfigure*/ +static const iocshArg pmacAsynIPPortConfigureArg0 = {"portName", + iocshArgString}; +static const iocshArg pmacAsynIPPortConfigureArg1 = {"hostInfo", + iocshArgString}; +static const iocshArg *pmacAsynIPPortConfigureArgs[] = { + &pmacAsynIPPortConfigureArg0, &pmacAsynIPPortConfigureArg1}; +static const iocshFuncDef pmacAsynIPPortConfigFuncDef = { + "pmacAsynIPPortConfigure", 2, pmacAsynIPPortConfigureArgs, ""}; +static void pmacAsynIPPortConfigureCallFunc(const iocshArgBuf *args) { + pmacAsynIPPortConfigure(args[0].sval, args[1].sval); +} + +static void pmacAsynIPPortRegister(void) { + static bool firstTime = true; + if (firstTime) { + firstTime = false; + iocshRegister(&pmacAsynIPPortConfigFuncDef, + pmacAsynIPPortConfigureCallFunc); + } +} +epicsExportRegistrar(pmacAsynIPPortRegister); diff --git a/src/pmacAsynIPPort.h b/src/pmacAsynIPPort.h index eee4efd..dc15aa6 100644 --- a/src/pmacAsynIPPort.h +++ b/src/pmacAsynIPPort.h @@ -1,35 +1,183 @@ -#ifndef asynInterposePmac_H -#define asynInterposePmac_H +#ifndef pmacAsynIPPort_H +#define pmacAsynIPPort_H -#include #include #include +#include "asynPortDriver.h" + +#include +#include +#include +#include + +#include + /* Value is chosen arbitrarily, it just needs to be unique */ #define FLUSH_HARDWARE 1 -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ +constexpr size_t ETHERNET_DATA_SIZE = 1492; + +/* PMAC ethernet command structure */ +#pragma pack(push, 1) +struct EthernetCmd { + uint8_t RequestType; + uint8_t Request; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; /* length of bData */ + uint8_t bData[ETHERNET_DATA_SIZE]; +}; +#pragma pack(pop) + +constexpr size_t ETHERNET_CMD_HEADER = sizeof(EthernetCmd) - ETHERNET_DATA_SIZE; + +/** + * @brief asyn driver speaking the PMAC ethernet protocol directly over a TCP + * socket. + * + * This driver replaces the generic drvAsynIPPort driver for the communication + * with a Turbo PMAC controller. It handles the socket layer itself and builds + * and parses the PMAC ethernet packets in its asynOctet methods. The + * asynCommon connect/disconnect handling and the asynInt32 interface (used for + * FLUSH_HARDWARE) are implemented here as well. + */ +class HIDDEN pmacAsynIPPort : public asynPortDriver { + public: + /** + * @brief Construct a new pmacAsynIPPort object + * + * The port is registered with asynManager in the asynPortDriver base class + * constructor and can therefore safely be "leaked" here. See + * turboPmacCreateAxis in turboPmacAxis.cpp for a similar pattern. + * + * @param portName The Asyn Port name string. + * @param hostInfo The hostname or IP address followed by the IP port (eg. + * 172.23.243.156:1025) + */ + pmacAsynIPPort(const char *portName, const char *hostInfo); + + virtual ~pmacAsynIPPort(); + + // asynCommon interface overrides + virtual asynStatus connect(asynUser *pasynUser) override; + virtual asynStatus disconnect(asynUser *pasynUser) override; + + // asynOctet interface overrides + virtual asynStatus writeOctet(asynUser *pasynUser, const char *data, + size_t numchars, + size_t *nbytesTransfered) override; + virtual asynStatus readOctet(asynUser *pasynUser, char *data, + size_t maxchars, size_t *nbytesTransfered, + int *eomReason) override; + virtual asynStatus flushOctet(asynUser *pasynUser) override; + + // asynInt32 interface overrides + virtual asynStatus writeInt32(asynUser *pasynUser, + epicsInt32 value) override; + + private: + /** + * @brief Parse a ":" string into IPHostName_ and port_. + * + * @throws std::runtime_error if hostInfo is not of the form + * ":". + */ + void parseHostInfo(const char *hostInfo); + + /** + * @brief Open a TCP connection to the PMAC. + * + * The host name lookup is delayed until this point, so that a device which + * gets a new IP address via DHCP is found again on the next reconnect + * attempt. + */ + asynStatus connectIt(asynUser *pasynUser); + + /** + * @brief Close the TCP connection and notify asynManager. + * + * This marks the port as disconnected, so that asynManager's autoConnect + * logic retries to establish a connection. + */ + void closeConnection(asynUser *pasynUser, const char *why); + + /** + * @brief Raw read from the TCP socket. + * + * Polls the socket for up to pasynUser->timeout and returns any received + * data. A timeout with no data returns asynTimeout and does not close the + * connection. + */ + asynStatus socketRead(asynUser *pasynUser, char *data, size_t maxchars, + size_t *nbytesRead); + + /** + * @brief Raw write to the TCP socket. + */ + asynStatus socketWrite(asynUser *pasynUser, const char *data, + size_t numchars, size_t *nbytesWritten); + + /** + * @brief Read response data from the PMAC into the internal input buffer + * + * If there is no data in the socket buffer then a GETBUFFER command is + * issued to get any outstanding data still on the PMAC. + */ + asynStatus readResponse(asynUser *pasynUser, size_t maxchars, + size_t *nbytesTransfered, int *eomReason); + + /** + * @brief Send a ReadReady command to the PMAC to discover if there is any + * data to read from it. + * + * @return 0 - no data available, 1 - data available + */ + int pmacReadReady(asynUser *pasynUser); + + /** + * @brief Send a Flush command to the PMAC and wait for confirmation. + * + * @return 0 - failed, 1 - success + */ + int pmacFlush(asynUser *pasynUser); + + /** + * @brief Send a GETBUFFER command to the PMAC. + */ + asynStatus sendPmacGetBuffer(asynUser *pasynUser, size_t maxchars, + size_t *nbytesTransfered); + + // Host name and TCP port of the PMAC + std::string IPHostName_; + unsigned short port_ = 0; + + // TCP socket used for the connection + SOCKET fd_ = INVALID_SOCKET; + + // Command buffers used for the PMAC ethernet protocol + EthernetCmd poutCmd_{}; + EthernetCmd pinCmd_{}; + + // Buffer for incoming response data + std::vector inBuf_; + size_t inBufHead_ = 0; + size_t inBufTail_ = 0; +}; /** * @brief Initialize a special asyn IP Port for PMAC controllers. * - * Function that first initialises an asyn IP port and then the PMAC Asyn IP - * interpose layer. It is a wrapper for drvAsynIPPort::drvAsynIPPortConfigure() - * and pmacAsynIPPort::pmacAsynIPPortConfigureEos(). + * Wrapper which creates a pmacAsynIPPort object and interposes the EOS + * handling layer above it. * * @param portName The Asyn Port name string. - * @param hostInfo The hostname or IP address followed by IP port (eg. + * @param hostInfo The hostname or IP address followed by the IP port (eg. * 172.23.243.156:1025) * @return status */ -int HIDDEN pmacAsynIPPortConfigure(const char *portName, const char *hostInfo); +int pmacAsynIPPortConfigure(const char *portName, const char *hostInfo); -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* asynInterposePmac_H */ \ No newline at end of file +#endif /* pmacAsynIPPort_H */ diff --git a/src/turboPmacController.cpp b/src/turboPmacController.cpp index 8b7f084..0b69aa5 100644 --- a/src/turboPmacController.cpp +++ b/src/turboPmacController.cpp @@ -456,7 +456,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, asynStatus turboPmacController::doFlushHardware() { /* Temporarily overwrite the "reason" field with the FLUSH_HARDWARE - constant defined in pmacAsynIPPort.c. This reason is then used within + constant defined in pmacAsynIPPort.h. This reason is then used within the write method of pasynInt32SyncIO to select the flush function. */ int temp = pTurboPmacC_->pasynInt32SyncIOipPort->reason;