diff --git a/Makefile b/Makefile index b2a7270..1c9fa85 100644 --- a/Makefile +++ b/Makefile @@ -14,15 +14,15 @@ REQUIRED+=sinqMotor motorBase_VERSION=7.2.2 # Specify the version of sinqMotor we want to build against -sinqMotor_VERSION=0.11.0 +sinqMotor_VERSION=mathis_s # These headers allow to depend on this library for derived drivers. -HEADERS += src/turboPmacAsynIPPort.h +HEADERS += src/pmacAsynIPPort.h HEADERS += src/turboPmacAxis.h HEADERS += src/turboPmacController.h # Source files to build -SOURCES += src/turboPmacAsynIPPort.c +SOURCES += src/pmacAsynIPPort.c SOURCES += src/turboPmacAxis.cpp SOURCES += src/turboPmacController.cpp @@ -32,4 +32,4 @@ TEMPLATES += db/turboPmac.db # This file registers the motor-specific functions in the IOC shell. DBDS += src/turboPmac.dbd -# USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings +USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings diff --git a/README.md b/README.md index 0a74206..ca408a7 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,15 @@ This is a driver for the Turbo PMAC motion controller with the SINQ communicatio ## User guide -This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. +This driver is a standard sinqMotor-derived which however uses a special low level IP Port driver (`pmacAsynIPPortConfigure`) instead of the standard `drvAsynIPPortConfigure`. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. The folder "utils" contains utility scripts for working with pmac motor controllers. To read their manual, run the scripts without any arguments. - writeRead.py: Allows sending commands to and receiving commands from a pmac controller over an ethernet connection. - analyzeTcpDump.py: Parse the TCP communication between an IOC and a MCU and format it into a dictionary. "demo.py" shows how this data can be easily visualized for analysis. -### Usage in IOC shell + + +### IOC startup script turboPmac exports the following IOC shell functions: - `turboPmacController`: Create a new controller object. @@ -30,7 +32,7 @@ epicsEnvSet("ASYN_PORT","p$(NAME)") # Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name. # We do not use the standard asyn port driver here, but a PMAC-specific one which enables the usage of StreamDevices for # communicating with the controller directly. -pmacAsynIPConfigure("$(ASYN_PORT)","172.28.101.24:1025") +pmacAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025") # Create the controller object with the defined name and connect it to the socket via the port name. # The other parameters are as follows: @@ -59,6 +61,10 @@ dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLE dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)") ``` +### Additional records + +`turboPmac` provides a variety of additional records. See `db/turboPmac.db` for the complete list and the documentation. + ## Developer guide ### Versioning diff --git a/db/turboPmac.db b/db/turboPmac.db index f62edb6..49c2ff9 100644 --- a/db/turboPmac.db +++ b/db/turboPmac.db @@ -16,4 +16,18 @@ record(longout, "$(INSTR)$(M):ReadConfig") { field(DTYP, "asynInt32") field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG") field(PINI, "NO") -} \ No newline at end of file +} + +# PMAC controllers can be "flushed" by setting a certain bit. This empties all +# communication buffers. Once the flush is done, the controller acknowledges +# this by sending an echo character. This procedure can take up to 10 ms (see +# Turbo PMAC User Manual, p. 414) and should therefore not be done as part of +# "normal" communications (like the original pmacAsynIPPort driver from DLS +# does). The SINQ driver for the Turbo PMAC controller therefore offers this PV +# in order to manually trigger a controller flush by writing any value to this PV. +record(longout, "$(INSTR)FlushHardware") { + field(DTYP, "asynInt32") + field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) FLUSH_HARDWARE") + field(PINI, "NO") + field(VAL, "1") +} diff --git a/src/pmacAsynIPPort.c b/src/pmacAsynIPPort.c new file mode 100644 index 0000000..a6d8666 --- /dev/null +++ b/src/pmacAsynIPPort.c @@ -0,0 +1,975 @@ +/* +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); + +// ============================================================================= + +epicsShareFunc 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", + 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.h b/src/pmacAsynIPPort.h new file mode 100644 index 0000000..580a7e1 --- /dev/null +++ b/src/pmacAsynIPPort.h @@ -0,0 +1,35 @@ +#ifndef asynInterposePmac_H +#define asynInterposePmac_H + +#include +#include + +/* +Value is chosen arbitrarily, it just needs to be unique +*/ +#define FLUSH_HARDWARE 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @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(). + * + * @param portName The Asyn Port name string. + * @param hostInfo The hostname or IP address followed by IP port (eg. + * 172.23.243.156:1025) + * @return status + */ +epicsShareFunc int pmacAsynIPPortConfigure(const char *portName, + const char *hostInfo); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* asynInterposePmac_H */ \ No newline at end of file diff --git a/src/turboPmacAsynIPPort.c b/src/turboPmacAsynIPPort.c deleted file mode 100644 index 0866f8c..0000000 --- a/src/turboPmacAsynIPPort.c +++ /dev/null @@ -1,848 +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 supports the octet flush method and issues a VR_PMAC_FLUSH to the - PMAC. - - 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). -*/ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "asynDriver.h" -#include "asynInterposeEos.h" -#include "asynOctet.h" -#include "drvAsynIPPort.h" -#include "epicsThread.h" -#include "turboPmacAsynIPPort.h" -#include - -#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}; - -typedef struct pmacPvt { - char *portName; - int addr; - asynInterface pmacInterface; - 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); - -/* asynOctet methods */ -static asynStatus writeIt(void *ppvt, asynUser *pasynUser, const char *data, - size_t numchars, size_t *nbytesTransfered); -static asynStatus readIt(void *ppvt, asynUser *pasynUser, char *data, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason); -static asynStatus flushIt(void *ppvt, asynUser *pasynUser); -static asynStatus registerInterruptUser(void *ppvt, asynUser *pasynUser, - interruptCallbackOctet callback, - void *userPvt, void **registrarPvt); -static asynStatus cancelInterruptUser(void *drvPvt, asynUser *pasynUser, - void *registrarPvt); -static asynStatus setInputEos(void *ppvt, asynUser *pasynUser, const char *eos, - int eoslen); -static asynStatus getInputEos(void *ppvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen); -static asynStatus setOutputEos(void *ppvt, asynUser *pasynUser, const char *eos, - int eoslen); -static asynStatus getOutputEos(void *ppvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen); - -/* Declare asynOctet here, and assign functions later on -(compatible with both Asyn 4-10 and pre 4-10 versions).*/ -static asynOctet octet; - -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 int pmacAsynIPPortCommon(const char *portName, int addr, - pmacPvt **pPmacPvt, - asynInterface **plowerLevelInterface, - asynUser **pasynUser); -static int pmacAsynIPPortConfigureEos(const char *portName, int addr); -static asynStatus sendPmacGetBuffer(pmacPvt *pPmacPvt, asynUser *pasynUser, - size_t maxchars, size_t *nbytesTransfered); - -/** - * 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(). - * - * @param portName The Asyn Port name string. - * @param hostInfo The hostname or IP address followed by IP port (eg. - * 172.23.243.156:1025) - * @return status - */ -epicsShareFunc int pmacAsynIPConfigure(const char *portName, - const char *hostInfo) { - asynStatus status = 0; - - if ((status = drvAsynIPPortConfigure(portName, hostInfo, 0, 0, 1)) != 0) { - printf("pmacAsynIPConfigure: error from drvAsynIPPortConfigure. Port: " - "%s\n", - portName); - } - - if ((status = pmacAsynIPPortConfigureEos(portName, 0)) != 0) { - printf("pmacAsynIPConfigure: error from pmacAsynIPPortConfigureEos. " - "Port: %s\n", - portName); - } - - return status; -} - -/** - * This reimplements pmacAsynIPPort::pmacAsynIPPortConfigure(), but introduces - * EOS handling interpose layer above the PMAC IP layer. - * - * The setup of the Asyn IP port must set noProcessEos=1 to use this function. - * - * NOTE: The use of this function is not compatible with versions of Asyn before - * 4-10. - * - * @param portName The Asyn port name - * @param addr The Asyn address. - * @return status - */ -epicsShareFunc int pmacAsynIPPortConfigureEos(const char *portName, int addr) { - pmacPvt *pPmacPvt = NULL; - asynInterface *plowerLevelInterface = NULL; - asynStatus status = 0; - asynUser *pasynUser = NULL; - - status = pmacAsynIPPortCommon(portName, addr, &pPmacPvt, - &plowerLevelInterface, &pasynUser); - if (status) { - asynPrint(pasynUser, ASYN_TRACE_ERROR, - "pmacAsynIPPortConfigureEos: error from pmacAsynIPPortCommon " - "%s: %s\n", - portName, pasynUser->errorMessage); - } - - /*Now interpose EOS handling layer, above the PMAC IP layer.*/ - asynInterposeEosConfig(portName, addr, 1, 1); - - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "Done pmacAsynIPPortConfigureEos OK\n"); - return (0); -} - -epicsShareFunc int pmacAsynIPPortConfigure(const char *portName, int addr) { - pmacPvt *pPmacPvt = NULL; - asynInterface *plowerLevelInterface = NULL; - asynUser *pasynUser = NULL; - asynStatus status = 0; - - status = pmacAsynIPPortCommon(portName, addr, &pPmacPvt, - &plowerLevelInterface, &pasynUser); - - status = - pPmacPvt->poctet->setInputEos(pPmacPvt->octetPvt, pasynUser, "\6", 1); - if (status) { - asynPrint( - pasynUser, ASYN_TRACE_ERROR, - "pmacAsynIPPortConfigure: unable to set input EOS on %s: %s\n", - portName, pasynUser->errorMessage); - return -1; - } - status = - pPmacPvt->poctet->setOutputEos(pPmacPvt->octetPvt, pasynUser, "\r", 1); - if (status) { - asynPrint( - pasynUser, ASYN_TRACE_ERROR, - "pmacAsynIPPortConfigure: unable to set output EOS on %s: %s\n", - portName, pasynUser->errorMessage); - return -1; - } - - asynPrint(pasynUser, ASYN_TRACE_FLOW, "Done pmacAsynIPPortConfigure OK\n"); - return (0); -} - -/** - * Common code for both pmacAsynIPPortConfigure and pmacAsynIPPortConfigureEos. - * @param portName - * @param addr - * @param pPmacPvt pointer - * @param plowerLevelInterface pointer - * @param pasynUser pointer - * @return status - */ -static int pmacAsynIPPortCommon(const char *portName, int addr, - pmacPvt **pPmacPvt, - asynInterface **plowerLevelInterface, - asynUser **pasynUser) { - asynStatus status; - size_t len; - - /*Assign static asynOctet functions here.*/ - octet.write = writeIt; - octet.read = readIt; - octet.flush = flushIt; - octet.setInputEos = setInputEos; - octet.setOutputEos = setOutputEos; - octet.getInputEos = getInputEos; - octet.getOutputEos = getOutputEos; - octet.registerInterruptUser = registerInterruptUser; - octet.cancelInterruptUser = cancelInterruptUser; - - len = sizeof(pmacPvt) + strlen(portName) + 1; - *pPmacPvt = callocMustSucceed( - 1, len, - "calloc pPmacPvt error in pmacAsynIPPort::pmacAsynIPPortCommon()."); - (*pPmacPvt)->portName = (char *)(*pPmacPvt + 1); - strcpy((*pPmacPvt)->portName, portName); - (*pPmacPvt)->addr = addr; - (*pPmacPvt)->pmacInterface.interfaceType = asynOctetType; - (*pPmacPvt)->pmacInterface.pinterface = &octet; - (*pPmacPvt)->pmacInterface.drvPvt = *pPmacPvt; - *pasynUser = pasynManager->createAsynUser(0, 0); - (*pPmacPvt)->pasynUser = *pasynUser; - (*pPmacPvt)->pasynUser->userPvt = *pPmacPvt; - - 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)->pmacInterface, &(*plowerLevelInterface)); - if (status != asynSuccess) { - printf("pmacAsynIPPortConfigure: %s interposeInterface failed\n", - portName); - pasynManager->exceptionCallbackRemove(*pasynUser); - pasynManager->freeAsynUser(*pasynUser); - free(*pPmacPvt); - return -1; - } - - (*pPmacPvt)->poctet = (asynOctet *)(*plowerLevelInterface)->pinterface; - (*pPmacPvt)->octetPvt = (*plowerLevelInterface)->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; - - 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) { - ethernetCmd *inCmd; - 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=%d, thisRead=%d, eomReason=%d, status=%d\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=%d, thisRead=%d, " - "eomReason=%d, status=%d\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; - unsigned 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=%d\n", - pPmacPvt->portName, thisRead); - } else { - asynPrint(pasynUser, ASYN_TRACE_ERROR, - "%s read pmacReadReady failed status=%d,retval=%d", - pPmacPvt->portName, status, retval); - } - return retval; -} - -/* - Send Flush command to PMAC and wait for confirmation ctrlX to be returned. - Returns: 0 - failed - 1 - success -*/ -static int pmacFlush(pmacPvt *pPmacPvt, asynUser *pasynUser) { - ethernetCmd cmd; - unsigned 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=%d, eomReason=%d, status=%d\n", - pPmacPvt->portName, thisRead, eomReason, status); - } - - pPmacPvt->inBufTail = 0; - pPmacPvt->inBufHead = 0; - - return retval; -} - -/* asynOctet methods */ - -/* 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 writeIt(void *ppvt, asynUser *pasynUser, const char *data, - size_t numchars, size_t *nbytesTransfered) { - asynStatus status; - ethernetCmd *outCmd; - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - size_t nbytesActual = 0; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::writeIt\n"); - 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, - "writeIt - ERROR TRUNCATED\n"); - } - 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 writeIt\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 readIt(void *ppvt, asynUser *pasynUser, char *data, - size_t maxchars, size_t *nbytesTransfered, - int *eomReason) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynStatus status = asynSuccess; - size_t thisRead = 0; - size_t nRead = 0; - int bell = 0; - int initialRead = 1; - ethernetCmd *inCmd; - - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readIt. START\n"); - 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::readIt.\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::readIt. 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 readIt nbytesTransfered=%d, eomReason=%d, " - "status=%d\n", - pPmacPvt->portName, *nbytesTransfered, *eomReason, status); - - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::readIt. END\n"); - - return status; -} - -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; -} - -static asynStatus flushIt(void *ppvt, asynUser *pasynUser) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynStatus status = asynSuccess; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::flushIt\n"); - assert(pPmacPvt); - - pmacFlush(pPmacPvt, pasynUser); - status = pPmacPvt->poctet->flush(pPmacPvt->octetPvt, pasynUser); - return asynSuccess; -} - -static asynStatus registerInterruptUser(void *ppvt, asynUser *pasynUser, - interruptCallbackOctet callback, - void *userPvt, void **registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::registerInterruptUser\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->registerInterruptUser( - pPmacPvt->octetPvt, pasynUser, callback, userPvt, registrarPvt); -} - -static asynStatus cancelInterruptUser(void *drvPvt, asynUser *pasynUser, - void *registrarPvt) { - pmacPvt *pPmacPvt = (pmacPvt *)drvPvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, - "pmacAsynIPPort::cancelInterruptUser\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->cancelInterruptUser(pPmacPvt->octetPvt, pasynUser, - registrarPvt); -} - -static asynStatus setInputEos(void *ppvt, asynUser *pasynUser, const char *eos, - int eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::setInputEos\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->setInputEos(pPmacPvt->octetPvt, pasynUser, eos, - eoslen); -} - -static asynStatus getInputEos(void *ppvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::getInputEos\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->getInputEos(pPmacPvt->octetPvt, pasynUser, eos, - eossize, eoslen); -} - -static asynStatus setOutputEos(void *ppvt, asynUser *pasynUser, const char *eos, - int eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::setOutputEos\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->setOutputEos(pPmacPvt->octetPvt, pasynUser, eos, - eoslen); -} - -static asynStatus getOutputEos(void *ppvt, asynUser *pasynUser, char *eos, - int eossize, int *eoslen) { - pmacPvt *pPmacPvt = (pmacPvt *)ppvt; - asynPrint(pasynUser, ASYN_TRACE_FLOW, "pmacAsynIPPort::getOutputEos\n"); - assert(pPmacPvt); - - return pPmacPvt->poctet->getOutputEos(pPmacPvt->octetPvt, pasynUser, eos, - eossize, eoslen); -} - -/* register pmacAsynIPPortConfigure*/ -static const iocshArg pmacAsynIPPortConfigArg0 = {"portName", iocshArgString}; -static const iocshArg pmacAsynIPPortConfigArg1 = {"addr", iocshArgInt}; -static const iocshArg *pmacAsynIPPortConfigArgs[] = {&pmacAsynIPPortConfigArg0, - &pmacAsynIPPortConfigArg1}; -static const iocshFuncDef pmacAsynIPPortConfigFuncDef = { - "pmacAsynIPPortConfigure", 2, pmacAsynIPPortConfigArgs}; -static void pmacAsynIPPortConfigCallFunc(const iocshArgBuf *args) { - pmacAsynIPPortConfigure(args[0].sval, args[1].ival); -} - -/* Register pmacAsynIPPortConfigureEos.*/ -static const iocshArg pmacAsynIPPortConfigEosArg0 = {"portName", - iocshArgString}; -static const iocshArg pmacAsynIPPortConfigEosArg1 = {"addr", iocshArgInt}; -static const iocshArg *pmacAsynIPPortConfigEosArgs[] = { - &pmacAsynIPPortConfigEosArg0, &pmacAsynIPPortConfigEosArg1}; -static const iocshFuncDef pmacAsynIPPortConfigEosFuncDef = { - "pmacAsynIPPortConfigureEos", 2, pmacAsynIPPortConfigEosArgs}; -static void pmacAsynIPPortConfigEosCallFunc(const iocshArgBuf *args) { - pmacAsynIPPortConfigureEos(args[0].sval, args[1].ival); -} - -/* Register pmacAsynIPConfigure.*/ -static const iocshArg pmacAsynIPConfigureArg0 = {"portName", iocshArgString}; -static const iocshArg pmacAsynIPConfigureArg1 = {"hostInfo", iocshArgString}; -static const iocshArg *pmacAsynIPConfigureArgs[] = {&pmacAsynIPConfigureArg0, - &pmacAsynIPConfigureArg1}; -static const iocshFuncDef pmacAsynIPConfigureFuncDef = { - "pmacAsynIPConfigure", 2, pmacAsynIPConfigureArgs}; -static void pmacAsynIPConfigureCallFunc(const iocshArgBuf *args) { - pmacAsynIPConfigure(args[0].sval, args[1].sval); -} - -static void pmacAsynIPPortRegister(void) { - static int firstTime = 1; - if (firstTime) { - firstTime = 0; - iocshRegister(&pmacAsynIPPortConfigFuncDef, - pmacAsynIPPortConfigCallFunc); - iocshRegister(&pmacAsynIPPortConfigEosFuncDef, - pmacAsynIPPortConfigEosCallFunc); - iocshRegister(&pmacAsynIPConfigureFuncDef, pmacAsynIPConfigureCallFunc); - } -} -epicsExportRegistrar(pmacAsynIPPortRegister); diff --git a/src/turboPmacAsynIPPort.h b/src/turboPmacAsynIPPort.h deleted file mode 100644 index f400d64..0000000 --- a/src/turboPmacAsynIPPort.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef asynInterposePmac_H -#define asynInterposePmac_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -epicsShareFunc int pmacAsynIPPortConfigure(const char *portName, int addr); -epicsShareFunc int pmacAsynIPConfigure(const char *portName, - const char *hostInfo); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* asynInterposePmac_H */ diff --git a/src/turboPmacAxis.cpp b/src/turboPmacAxis.cpp index c452688..0451b47 100644 --- a/src/turboPmacAxis.cpp +++ b/src/turboPmacAxis.cpp @@ -2,6 +2,7 @@ #include "asynOctetSyncIO.h" #include "epicsExport.h" #include "iocsh.h" +#include "pmacAsynIPPort.h" #include "turboPmacController.h" #include #include @@ -114,7 +115,8 @@ asynStatus turboPmacAxis::init() { // Local variable declaration asynStatus status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; double motorRecResolution = 0.0; double motorPos = 0.0; @@ -229,8 +231,9 @@ asynStatus turboPmacAxis::doPoll(bool *moving) { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_], - userMessage[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; + char userMessage[pC_->MAXBUF_] = {0}; int nvals = 0; int direction = 0; @@ -884,7 +887,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative, // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; double motorCoordinatesPosition = 0.0; double motorRecResolution = 0.0; double motorVelocity = 0.0; @@ -1002,7 +1006,8 @@ asynStatus turboPmacAxis::stop(double acceleration) { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= @@ -1035,7 +1040,8 @@ asynStatus turboPmacAxis::doReset() { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= @@ -1072,7 +1078,8 @@ asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity, // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= @@ -1130,7 +1137,8 @@ asynStatus turboPmacAxis::readEncoderType() { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; int encoder_id = 0; @@ -1274,7 +1282,8 @@ asynStatus turboPmacAxis::rereadEncoder() { asynStatus turboPmacAxis::enable(bool on) { int timeout_enable_disable = 2; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; // Status of read-write-operations of ASCII commands to the controller @@ -1480,8 +1489,11 @@ static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)", static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt}; static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, &CreateAxisArg1}; -static const iocshFuncDef configTurboPmacCreateAxis = {"turboPmacAxis", 2, - CreateAxisArgs}; +static const iocshFuncDef configTurboPmacCreateAxis = { + "turboPmacAxis", 2, CreateAxisArgs, + "Create an instance of a turboPmac axis. The first argument is the " + "controller this axis should be attached to, the second argument is the " + "axis number."}; static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) { turboPmacCreateAxis(args[0].sval, args[1].ival); } diff --git a/src/turboPmacController.cpp b/src/turboPmacController.cpp index 43e9ac7..80d52d7 100644 --- a/src/turboPmacController.cpp +++ b/src/turboPmacController.cpp @@ -1,6 +1,8 @@ #include "turboPmacController.h" +#include "asynInt32SyncIO.h" #include "asynMotorController.h" #include "asynOctetSyncIO.h" +#include "pmacAsynIPPort.h" #include "turboPmacAxis.h" #include #include @@ -79,6 +81,16 @@ turboPmacController::turboPmacController(const char *portName, exit(-1); } + status = createParam("FLUSH_HARDWARE", asynParamInt32, &flushHardware_); + if (status != asynSuccess) { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a " + "parameter failed with %s).\nTerminating IOC", + portName, __PRETTY_FUNCTION__, __LINE__, + stringifyAsynStatus(status)); + exit(-1); + } + /* Define the end-of-string of a message coming from the device to EPICS. It is not necessary to append a terminator to outgoing messages, since @@ -88,7 +100,8 @@ turboPmacController::turboPmacController(const char *portName, const char *message_from_device = "\006"; // Hex-code for ACK (acknowledge) -> Each message from the MCU // is terminated by this value - status = pasynOctetSyncIO->setInputEos(ipPortUser_, message_from_device, + status = pasynOctetSyncIO->setInputEos(pasynOctetSyncIOipPort_, + message_from_device, strlen(message_from_device)); if (status != asynSuccess) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, @@ -96,7 +109,7 @@ turboPmacController::turboPmacController(const char *portName, "(setting input EOS failed with %s).\nTerminating IOC", portName, __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); - pasynOctetSyncIO->disconnect(ipPortUser_); + pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort_); exit(-1); } @@ -108,7 +121,23 @@ turboPmacController::turboPmacController(const char *portName, "with %s).\nTerminating IOC", portName, __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); - pasynOctetSyncIO->disconnect(ipPortUser_); + pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort_); + exit(-1); + } + + // =========================================================================; + + /* + We try to connect to the port via the port name provided by the constructor. + If this fails, the function is terminated via exit. + */ + pasynInt32SyncIO->connect(ipPortConfigName, 0, &pasynInt32SyncIOipPort_, + NULL); + if (status != asynSuccess || pasynInt32SyncIOipPort_ == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot " + "connect to MCU controller).\n" + "Terminating IOC", + portName, __PRETTY_FUNCTION__, __LINE__); exit(-1); } } @@ -175,83 +204,25 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, } const size_t commandLength = strlen(command); - // NOT NEEDED ANYMORE DUE TO THE LOW LEVEL DRIVER - // /* - // 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. - // */ - - // - // const int offset = 9; - - // // Positions 2 to 6 must have the value 0. Since fullCommand is - // initialized - // // as an array of zeros, we don't need to set these bits manually. - // fullCommand[0] = '\x40'; - // fullCommand[1] = '\xBF'; - - // // The size of size_t is platform dependant (pointers-sized), while htons - // // needs an unsigned int. The byte order is then converted from host to - // // network order. The offset "+1" is for the carriage return. - // u_int16_t len = htons(static_cast(commandLength + 1)); - - // // Split up into the upper and the lower byte - // fullCommand[7] = (char)(len >> 8); // Shift the 8 higher bits to the - // right fullCommand[8] = (char)(len & 0xFF); // Mask the higher bits - - // // Write the actual command behind the protocol - // for (size_t i = 0; i < commandLength; i++) { - // fullCommand[i + offset] = command[i]; - // } - // fullCommand[offset + commandLength] = '\x0D'; - - // // +1 for the carriage return. - // const size_t fullComandLength = offset + commandLength + 1; - /* - We use separated write and read commands here, not the combined writeRead - method, because the latter is actually a flushWriteRead (see - https://epics.anl.gov/modules/soft/asyn/R4-14/asynDriver.pdf, p. 31) -> - Calls the flush command, then the write command, then the read command. - The flush itself reads repeatedly from the MCU until no messages are there - anymore. (The Diamond Light Source driver first send a PMAC flush command - and then does the same as the asyn flush). We don't want this behaviour. - (https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c) + The writeRead command performs the following steps: + 1) Flush the socket buffer on the IOC side (not the controller!) + 2) Write a command to the controller + 3) Read the response If a timeout occurs during writing or reading, inform the user that we're trying to reconnect. If the problem persists, ask them to call the support */ + status = pasynOctetSyncIO->writeRead( + pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_, + comTimeout_, &nbytesOut, &nbytesIn, &eomReason); - status = pasynOctetSyncIO->write(ipPortUser_, command, commandLength, - comTimeout_, &nbytesOut); - - msgPrintControlKey writeKey = + msgPrintControlKey comKey = msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); if (status == asynTimeout) { - if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) { + if (msgPrintControl_.shouldBePrinted(comKey, true, pasynUserSelf)) { asynPrint( this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d\nTimeout while " @@ -268,8 +239,9 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, checkMaxSubsequentTimeouts(timeoutCounter, axis); timeoutCounter += 1; - status = pasynOctetSyncIO->write( - ipPortUser_, command, commandLength, comTimeout_, &nbytesOut); + status = pasynOctetSyncIO->writeRead( + pasynOctetSyncIOipPort(), command, commandLength, response, + MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason); if (status != asynTimeout) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " @@ -279,7 +251,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, } } } else if (status != asynSuccess) { - if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) { + if (msgPrintControl_.shouldBePrinted(comKey, true, pasynUserSelf)) { asynPrint( this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d\nError %s while " @@ -288,57 +260,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, stringifyAsynStatus(status), msgPrintControl_.getSuffix()); } } else { - msgPrintControl_.resetCount(writeKey, pasynUserSelf); - } - - // Read the response from the MCU buffer - status = pasynOctetSyncIO->read(ipPortUser_, response, MAXBUF_, comTimeout_, - &nbytesIn, &eomReason); - - msgPrintControlKey readKey = - msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); - - if (status == asynTimeout) { - - if (msgPrintControl_.shouldBePrinted(readKey, true, pasynUserSelf)) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d\nTimeout while " - "reading from the MCU.%s\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - msgPrintControl_.getSuffix()); - } - - // Add this event to the back of the timeout event counter - timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText, - sizeof(drvMessageText)); - - int timeoutCounter = 0; - while (1) { - checkMaxSubsequentTimeouts(timeoutCounter, axis); - timeoutCounter += 1; - - status = pasynOctetSyncIO->read(ipPortUser_, response, MAXBUF_, - comTimeout_, &nbytesIn, &eomReason); - if (status != asynTimeout) { - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line " - "%d\nReconnected after read timeout\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__); - break; - } - } - } else if (status != asynSuccess) { - if (msgPrintControl_.shouldBePrinted(readKey, true, pasynUserSelf)) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d\nError %s while " - "reading from the controller.%s\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - stringifyAsynStatus(status), msgPrintControl_.getSuffix()); - } - } else { - msgPrintControl_.resetCount(readKey, pasynUserSelf); + msgPrintControl_.resetCount(comKey, pasynUserSelf); } if (timeoutStatus == asynError) { @@ -352,7 +274,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, status = asynError; snprintf(drvMessageText, sizeof(drvMessageText), - "Terminated message due to reason %d (should be 2).", + "Terminated message due to reason %d (should be 2). Please " + "call the support.", eomReason); if (msgPrintControl_.shouldBePrinted(terminateKey, true, @@ -469,6 +392,22 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command, return status; } +asynStatus turboPmacController::doFlushHardware() { + /* + Temporarily overwrite the "reason" field with the FLUSH_HARDWARE + constant defined in pmacAsynIPPort.c. This reason is then used within + the write method of pasynInt32SyncIO to select the flush function. + */ + int temp = pasynInt32SyncIOipPort_->reason; + pasynInt32SyncIOipPort_->reason = FLUSH_HARDWARE; + asynStatus status = (asynStatus)pasynInt32SyncIO->write( + pasynInt32SyncIOipPort_, 1, comTimeout_); + + // Reset the status afterwards + pasynInt32SyncIOipPort_->reason = temp; + return status; +} + asynStatus turboPmacController::writeInt32(asynUser *pasynUser, epicsInt32 value) { int function = pasynUser->reason; @@ -482,6 +421,8 @@ asynStatus turboPmacController::writeInt32(asynUser *pasynUser, return axis->rereadEncoder(); } else if (function == readConfig_) { return axis->init(); + } else if (function == flushHardware_) { + return doFlushHardware(); } else { return sinqController::writeInt32(pasynUser, value); } diff --git a/src/turboPmacController.h b/src/turboPmacController.h index eee4a64..74cc97f 100644 --- a/src/turboPmacController.h +++ b/src/turboPmacController.h @@ -103,15 +103,30 @@ class turboPmacController : public sinqController { int axisNo_, const char *functionName, int lineNumber); + /** + * @brief Perform a hardware flush (clearing of communication buffers) + * + * The PMAC controllers hardware buffers can be flushed (see Turbo PMAC user + * manual, p. 414). This "freezes" the PMAC for around 10 ms and should + * therefore only be done if it is necessary (i.e. not as part of the + * regular communication procedure). + * + * @return asynStatus + */ + asynStatus doFlushHardware(); + // Accessors for additional PVs int rereadEncoderPosition() { return rereadEncoderPosition_; } int readConfig() { return readConfig_; } + int flushHardware() { return flushHardware_; } // Set the maximum buffer size. This is an empirical value which must be // large enough to avoid overflows for all commands to the device / // responses from it. static const uint32_t MAXBUF_ = 200; + asynUser *pasynInt32SyncIOipPort() { return pasynInt32SyncIOipPort_; } + protected: /* Timeout for the communication process in seconds @@ -120,11 +135,15 @@ class turboPmacController : public sinqController { char lastResponse[MAXBUF_]; + // User for writing int32 values to the port driver. + asynUser *pasynInt32SyncIOipPort_; + // Indices of additional PVs #define FIRST_turboPmac_PARAM rereadEncoderPosition_ int rereadEncoderPosition_; int readConfig_; -#define LAST_turboPmac_PARAM readConfig_ + int flushHardware_; +#define LAST_turboPmac_PARAM flushHardware_ }; #define NUM_turboPmac_DRIVER_PARAMS \ (&LAST_turboPmac_PARAM - &FIRST_turboPmac_PARAM + 1)