Makefile 0100644 0000621 0000620 00000001044 06671302162 011500 0 ustar epics epics # Makefile at top of ipac support tree
# Makefile,v 1.3 1999/03/09 20:28:34 anj Exp
TOP = .
include $(TOP)/config/CONFIG_APP
# Note this is different to most Makefiles at this level because
# different sites may need to able to select which ipac module drivers
# are to be included, thus there is no wildcard for DIRS. Sites may
# comment out any DIRS lines below which are not required. Additional
# lines will need to be added as new drivers are installed.
DIRS += drvIpac
DIRS += drvTip810
DIRS += tyGSOctal
include $(TOP)/config/RULES_TOP
drvIpac/Makefile 0100644 0000621 0000620 00000000112 06671026114 013063 0 ustar epics epics TOP=..
include $(TOP)/config/CONFIG_APP
include $(TOP)/config/RULES_ARCHS
drvIpac/Makefile.Host 0100644 0000621 0000620 00000000313 06671301600 013776 0 ustar epics epics # Makefile.Host
TOP = ../..
include $(TOP)/config/CONFIG_APP
DBDINSTALL += drvIpac.dbd
INC += drvIpac.h
HTMLS_DIR = .
HTMLS += drvIpac.html
HTMLS += ipacRelease.html
include $(TOP)/config/RULES.Host
drvIpac/Makefile.Vx 0100644 0000621 0000620 00000001311 07271622445 013467 0 ustar epics epics # Makefile.Vx
TOP = ../..
include $(TOP)/config/CONFIG_APP
# Source files (for depends target):
SRCS.c += ../drvIpac.c
SRCS.c += ../drvVipc310.c
SRCS.c += ../drvVipc610.c
SRCS.c += ../drvVipc610_01.c
SRCS.c += ../drvVipc616.c
SRCS.c += ../drvVipc616_01.c
SRCS.c += ../drvIpMv162.c
SRCS.c += ../drvAtc40.c
LIBOBJS += drvIpac.o
# GreenSpring VIPC carrier drivers:
LIBOBJS += drvVipc310.o
LIBOBJS += drvVipc610.o
LIBOBJS += drvVipc610_01.o
LIBOBJS += drvVipc616.o
LIBOBJS += drvVipc616_01.o
# GreenSpring ATC40 Carrier driver: for ISA bus systems only.
#LIBOBJS += drvAtc40.o
# MVME162 carrier driver:
LIBOBJS += drvIpMv162.o
# Link everything into a library:
LIBNAME = ipacLib
include $(TOP)/config/RULES.Vx
drvIpac/drvAtc40.c 0100644 0000621 0000620 00000046107 06747665034 013212 0 ustar epics epics /*******************************************************************************
Project:
LEDA Run Permit system
File:
drvAtc40.c
Description:
IPAC Carrier Driver for the GreenSpring ATC40 Quad IndustryPack
Carrier ISA board, provides the interface between IPAC driver and the
hardware.
Original Author:
Peregrine McGehee (interrupt support by Jeff Hill)
Created:
24 August 1998
TODO:
allow the user to configure different memory sizes
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/*
* Characteristics of the card
*/
#define SLOTS 4
#define IO_SPACES 4 /* Address spaces in ISA 16MByte */
#define IPAC_IRQS 2 /* Interrupts per module */
typedef volatile struct {
uint16_t io[0x40];
uint16_t id[0x40];
} atc40IpacIOID;
/*
* It appears that the doc for the atc 40 has the vectors for int0 and int1
* reversed
*/
typedef volatile struct {
uint16_t vecInt0;
uint16_t vecInt1;
uint8_t reserved[0x7c];
} atc40IpacVecs;
typedef volatile struct {
uint16_t config;
uint8_t reserved[0xfe];
} atc40IpacMemConfig;
/*
* overlay structure for the ATC 40 IPAC carrier default memory map
*/
typedef volatile struct {
atc40IpacIOID ioid[4];
atc40IpacVecs vec[4];
uint8_t reserved0[0x200];
uint8_t mem[4][0x800];
uint8_t reserved1[0x1000];
atc40IpacMemConfig memConfig[4];
uint16_t intStatus;
uint8_t reserved2[0xfe];
uint16_t intEnable;
uint8_t reserved3[0x2fe];
} atc40Device;
/*
* Carrier Private structure type, one instance per board
*/
typedef struct {
NODE node;
void *baseAddr[IPAC_ADDR_SPACES][SLOTS];
atc40Device *pDev; /* ATC40 device address */
ushort_t carrier; /* IPAC carrier number */
unsigned char irq; /* interrupt request level */
} atc40Config_t;
/*
* ISA Interrupt levels
*/
#define IRQ_DEFAULT_ATC40 11
#define ISA_N_IRQS 16
/*
* IPAC vectors
*/
#define IPAC_N_VECTORS 0x100
/*
* One entry for each of 256 possible interrupt vectors
*/
typedef struct {
void (*pISR) (int parameter);
int parameter;
atc40Config_t *pConfig;
uchar_t slot;
unsigned vecNum;
} atc40IntDispatch_t;
LOCAL atc40IntDispatch_t intDispatchTable[IPAC_N_VECTORS];
typedef struct {
LIST deviceList;
unsigned char intConnected;
} configATC40IRQ_t;
LOCAL configATC40IRQ_t irqATC40Table[ISA_N_IRQS];
LOCAL int disableAtc40Ints(int startType);
LOCAL void atc40GlobalISR(int irq);
LOCAL void atc40ISR(atc40Device * pDev);
LOCAL void atc40UnexpectedVecISR(int arg);
LOCAL SEM_ID atc40Lock;
LOCAL int intVecConnectLocked(void *cPrivate, ushort_t slot, ushort_t vecNum,
void (*routine) (int parameter), int parameter);
/*
* this is a vxWorks BSP supplied glovbal variable
*/
extern UINT sysVectorIRQ0;
/*
* can be set from the vxWorks shell
*/
unsigned char debugAtc40InterruptJam;
void (*pGlobalATC40ISR) (int irq) = atc40GlobalISR;
/*******************************************************************************
Routine:
initialize
Purpose:
Creates new private table for ATC40 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional size of the memory space for the modules. If both the I/O and
memory base addresses can be reached from the CPU, a private table is
created for this board. The private table is a 2-D array of pointers
to the base addresses of the various accessible parts of the IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at the
start is optional) optionally followed by a comma and a decimal integer.
The first number is the I/O base address of the card in the ISA
address space (the factory default is 0xfc0000 but the software
default is 0x03c000). If present the second number gives the memory space
in Kbytes allocated to each IP module.
The ATC40 must be installed in a 16-bit ISA bus slot. This allows both
byte and word access to IPs.
The ATC40 configuration registers and its four IPs are mapped into
16 Kbytes within the ISA 16 Mbytes memory space. The 16 Kbyte block is
selected by a shunt block on the ATC40.
IP I/O, ID, two Kbytes memory per IP, interrupt and configuration
registers are mapped within this 16 Kbyte address block. Additional
IP memory is supported, however the additional memory must be mapped
above one Mbyte of ISA bus memory space in PC/AT. The additional memory
sizes supported include 512, 1024, 2048, 4096, and 8192 KBytes.
Examples:
"0x6000"
This indicates that the carrier has its I/O base set to 0x6000, and
the slots use 2 Kbytes memory space each.
"1000,512"
Here the I/O base is set to 0x1000, and there is 512 Kbytes of
memory on each module.
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialize(char *cardParams, void **pprivate, ushort_t carrier)
{
static char init = 0;
int params;
int status;
unsigned mSize = 0;
atc40Device *pDev = NULL;
ushort_t slot;
atc40Config_t *pConfig;
ushort_t vecNum;
unsigned irq = IRQ_DEFAULT_ATC40;
int key;
#if 0
/*
* verify offsets in device overlay structure
*/
printf("size %lx\n", sizeof(atc40Device));
printf("ioid[0] at %x\n", offsetof(atc40Device, ioid[0]));
printf("ioid[1] at %x\n", offsetof(atc40Device, ioid[1]));
printf("ioid[2] at %x\n", offsetof(atc40Device, ioid[2]));
printf("ioid[3] at %x\n", offsetof(atc40Device, ioid[3]));
printf("vec[0] at %x\n", offsetof(atc40Device, vec[0]));
printf("vec[1] at %x\n", offsetof(atc40Device, vec[1]));
printf("vec[2] at %x\n", offsetof(atc40Device, vec[2]));
printf("vec[3] at %x\n", offsetof(atc40Device, vec[3]));
printf("intStatus at %x\n", offsetof(atc40Device, intStatus));
printf("intEnable at %x\n", offsetof(atc40Device, intEnable));
#endif
/*
* shut off interrupts when its a soft reboot
*/
if (!init) {
/*
* initialize the global mutex
*/
atc40Lock = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
if (atc40Lock == NULL) {
return errno;
}
/*
* install a reboot hook
*/
status = rebootHookAdd(disableAtc40Ints);
if (status < 0) {
return errno;
}
/*
* initialize the int dispatch table
*/
for (vecNum = 0; vecNum < NELEMENTS(intDispatchTable); vecNum++) {
intDispatchTable[vecNum].pISR = atc40UnexpectedVecISR;
intDispatchTable[vecNum].parameter = vecNum;
intDispatchTable[vecNum].pConfig = NULL;
}
init = TRUE;
}
if (cardParams == NULL || strlen(cardParams) == 0) {
/*
* beginning of the "no memory" portion of the WRS pc486 address map
*/
pDev = (atc40Device *) 0xa0000;
} else {
params = sscanf(cardParams, "%p %i %i", (void **) &pDev, &mSize, &irq);
/*
* verify reasonable parameters
*/
if (params < 1 || params > 3) {
return S_IPAC_badAddress;
}
/*
* currently only the default memory map is supported
*/
if (params >= 2 && mSize != sizeof(atc40Device)) {
return S_IPAC_badAddress;
}
if (params >= 3 && (irq >= ISA_N_IRQS || irq < 0)) {
return S_IPAC_badIntLevel;
}
}
/*
* Unfortunately, the WRS BSP is mapping all of the ISA bus as valid and
* in this situation the Intel archictecture does not generate a page
* fault exception when we access the card and it isnt there. Since this
* module does not provide an identifier, then it is difficult to probe
* for its existence.
*/
/*
* allocate space for the module private configuration structure
*/
pConfig = calloc(1, sizeof(*pConfig));
if (pConfig == NULL) {
return errno;
}
pConfig->pDev = pDev;
pConfig->carrier = carrier;
pConfig->irq = (unsigned char) irq;
/*
* determine the base addresses of the various IPAC address spaces
*/
for (slot = 0; slot < SLOTS; slot++) {
pConfig->baseAddr[ipac_addrID][slot] = (void *) pDev->ioid[slot].id;
}
for (slot = 0; slot < SLOTS; slot++) {
pConfig->baseAddr[ipac_addrIO][slot] = (void *) pDev->ioid[slot].io;
}
for (slot = 0; slot < SLOTS; slot++) {
pConfig->baseAddr[ipac_addrIO32][slot] = NULL;
}
for (slot = 0; slot < SLOTS; slot++) {
pConfig->baseAddr[ipac_addrMem][slot] = (void *) pDev->mem[slot];
}
/*
* initially disable ints
*/
pDev->intEnable = 0;
/*
* install into the reboot int shutdown list
*/
status = semTake(atc40Lock, 5 * sysClkRateGet());
if (status != OK) {
free(pConfig);
return errno;
}
/*
* must also lock interrupts when adding to or deleteing from this list
* because it is accessed at interrupt level
*/
key = intLock();
lstAdd(&irqATC40Table[irq].deviceList, &pConfig->node);
intUnlock(key);
status = semGive(atc40Lock);
assert(status == OK);
*pprivate = pConfig;
return OK;
}
/*
* disableAtc40Ints ()
*/
LOCAL int disableAtc40Ints(int startType)
{
atc40Config_t *pConfig;
int status;
unsigned irq;
if (atc40Lock == NULL) {
return OK;
}
/*
* install into the reboot int shutdown list
*/
status = semTake(atc40Lock, 5 * sysClkRateGet());
if (status != OK) {
return errno;
}
for (irq = 0; irq < NELEMENTS(irqATC40Table); irq++) {
for (pConfig = (atc40Config_t *) lstFirst(&irqATC40Table[irq].deviceList);
pConfig;
pConfig = (atc40Config_t *) lstNext(&pConfig->node)) {
/*
* disable interrupts
*/
pConfig->pDev->intEnable = 0;
printf("Disabled interrupts on ATC 40 carrier %u\n", pConfig->carrier);
}
}
status = semGive(atc40Lock);
assert(status == OK);
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialize routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr(void *private, ushort_t slot, ipac_addr_t space)
{
atc40Config_t *pConfig = (atc40Config_t *) private;
return pConfig->baseAddr[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
ISAbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level.
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd(void *private, ushort_t slot, ushort_t irqNumber,
ipac_irqCmd_t cmd)
{
atc40Config_t *pConfig = (atc40Config_t *) private;
switch (cmd) {
case ipac_irqGetLevel:
return pConfig->irq;
case ipac_irqEnable:
/*
* enable the interrrupt level in the PIC chip
*/
sysIntEnablePIC(pConfig->irq);
/*
* Allow the ATC-40 to pass interrupts to the ISA bus.
*/
pConfig->pDev->intEnable = 1;
return OK;
default:
return S_IPAC_notImplemented;
}
}
/*******************************************************************************
Routine:
ipmIntVecConnect
Function:
Connect module driver to interrupt vector
Description:
Since the interrupt vectoring mechanisms vary between bus types this
routines allows a module driver to connect its routine to an interrupt
vector from a particular IPAC module without knowing the specifics of the
IP carrier or the bus type. In some situations the carrier driver will need
to maintain a private interrupt dispatch table if the bus type (i.e. ISA)
does not support interrupt vectoring.
Returns:
0 = OK,
S_IPAC_badAddress = illegal carrier, slot or vector
*/
LOCAL int intVecConnect(void *cPrivate, ushort_t slot, ushort_t vecNum,
void (*routine) (int parameter), int parameter)
{
int status;
int vxStatus;
/*
* lock out other tasks
*/
vxStatus = semTake(atc40Lock, 5 * sysClkRateGet());
if (vxStatus != OK) {
return errno;
}
status = intVecConnectLocked(cPrivate, slot, vecNum, routine, parameter);
/*
* unlock
*/
vxStatus = semGive(atc40Lock);
assert(vxStatus == OK);
return status;
}
/*
* intVecConnectLocked ()
*
* global int disable is used instead of a device int disable because several
* ATC40 devices might be using the same int dispatch table
*/
LOCAL int intVecConnectLocked(void *cPrivate, ushort_t slot, ushort_t vecNum,
void (*routine) (int parameter), int parameter)
{
atc40Config_t *pConfig = (atc40Config_t *) cPrivate;
int key;
if (vecNum > NELEMENTS(intDispatchTable)) {
return S_IPAC_badVector;
}
if (intDispatchTable[vecNum].pConfig == NULL) {
if (routine == NULL) {
/* disconnect request with nothing connected */
return 0;
}
} else {
/*
* return an error if they attempt to install on top of some other
* module's vector
*/
if ((intDispatchTable[vecNum].pConfig != pConfig) ||
(intDispatchTable[vecNum].slot != slot)) {
logMsg("IPAC interrupt vector %#x in use by carrier %u slot %u,\n",
vecNum, intDispatchTable[vecNum].pConfig->carrier,
intDispatchTable[vecNum].slot, 0, 0, 0);
logMsg("can't reallocate vector to carrier %u slot %u.\n",
pConfig->carrier, slot, 0, 0, 0, 0);
return S_IPAC_vectorInUse;
}
if (routine == NULL) {
/* disconnect request */
key = intLock();
intDispatchTable[vecNum].pISR = atc40UnexpectedVecISR;
intDispatchTable[vecNum].parameter = vecNum;
intDispatchTable[vecNum].pConfig = NULL;
intUnlock(key);
return 0;
}
}
/*
* valid connect request
*/
key = intLock();
intDispatchTable[vecNum].pISR = routine;
intDispatchTable[vecNum].parameter = parameter;
intDispatchTable[vecNum].pConfig = pConfig;
intDispatchTable[vecNum].slot = slot;
intUnlock(key);
/*
* if not connected, then connect to the interrupt
*/
if (!irqATC40Table[pConfig->irq].intConnected) {
int status;
/*
* attach to the IRQ level if its the first ATC40 device installed
*/
status = intConnect(INUM_TO_IVEC(sysVectorIRQ0 + pConfig->irq), pGlobalATC40ISR, pConfig->irq);
if (status == OK) {
irqATC40Table[pConfig->irq].intConnected = TRUE;
} else {
logMsg("Unable to connect to ISA interrupt level %u\n", pConfig->irq,
0, 0, 0, 0, 0);
}
}
return 0;
}
/*
* Routine: atc40GlobalISR
*
* Function: global interrupt service routine for all ATC 40 carriers
*
* Description: calls atc40ISR for each atc 40 carrier that has been installed
*
* Returns: void
*/
LOCAL void atc40GlobalISR(int irq)
{
atc40Config_t *pConfig;
/*
* call atc40 ISR for each carrier card that has been installed
*
* interrupts are locked when adding to, or removing from, this list at task
* level
*/
for (pConfig = (atc40Config_t *) lstFirst(&irqATC40Table[irq].deviceList);
pConfig;
pConfig = (atc40Config_t *) lstNext(&pConfig->node)) {
/*
* dispatch interrupts
*/
atc40ISR(pConfig->pDev);
}
}
/*
* Routine: atc40ISR
*
* Function: interrupt service routine for one ATC 40 carrier
*
* Description: fetches vector and calls the module supplied routine if it
* exists and prints an error message if not
*
* Returns: void
*/
LOCAL void atc40ISR(atc40Device * pDev)
{
atc40IntDispatch_t *pEntry;
unsigned intStatus;
unsigned slot;
unsigned bit;
/*
* if interrupts are not enabled then NOOP
*/
if (!pDev->intEnable) {
return;
}
/*
* disable ints
*/
pDev->intEnable = 0;
intStatus = pDev->intStatus;
#ifdef DEBUG
logMsg("atc 40 int status=%x\n", intStatus, 0, 0, 0, 0, 0);
#endif
/*
* check to see which interrupts are active
*/
for (slot = 0, bit = 1; slot < NELEMENTS(pDev->vec); slot++) {
if ((intStatus & bit) == 0) {
pEntry = &intDispatchTable[pDev->vec[slot].vecInt0 & 0xff];
(*pEntry->pISR) (pEntry->parameter);
}
bit <<= 1;
if ((intStatus & bit) == 0) {
pEntry = &intDispatchTable[pDev->vec[slot].vecInt1 & 0xff];
(*pEntry->pISR) (pEntry->parameter);
}
bit <<= 1;
}
/*
* DEBUG aid --------- code here is only executed if
* "debugAtc40InterruptJam" is set TRUE
*/
if (debugAtc40InterruptJam) {
/*
* check for an interrupt that has not been cleared. note that the
* interrupt may occasionally go active again after it was cleared
* during an ISR in a normal situation
*/
intStatus = pDev->intStatus;
if (intStatus) {
logMsg("atc40ISR carrier device at %p may have a jammed interrupt\n",
(int) pDev, 0, 0, 0, 0, 0);
/*
* check to see which interrupts are active
*/
for (slot = 0, bit = 1; slot < 2 * NELEMENTS(pDev->vec); slot++) {
if ((intStatus & bit) == 0) {
logMsg("atc40ISR: interrupt jam on slot %u irq 0\n",
slot, 0, 0, 0, 0, 0);
}
bit <<= 1;
if ((intStatus & bit) == 0) {
logMsg("atc40ISR: interrupt jam on slot %u irq 1\n",
slot, 0, 0, 0, 0, 0);
}
bit <<= 1;
}
logMsg("atc40ISR interrups were disabled on carrier with jammed interrupt\n",
0, 0, 0, 0, 0, 0);
return;
}
}
/*
* reenable ints
*
* toggle edge trigger if ints are still pending
*/
pDev->intEnable = 1;
}
/*
* unexpected interrupt vector handler
*/
LOCAL void atc40UnexpectedVecISR(int vecNum)
{
logMsg("Unexpect interrupt on vector = %#x from an ATC40?\n",
vecNum, 0, 0, 0, 0, 0);
}
/*
* IO report routine
*/
char *report(void *cPrivate, ushort_t slot)
{
atc40Config_t *pConfig = (atc40Config_t *) cPrivate;
unsigned intStatus;
unsigned bit;
unsigned nChar;
static char report[80];
char *pReport = report;
intStatus = pConfig->pDev->intStatus;
bit = 1 << (slot * 2);
nChar = sprintf(pReport, " vec0=%x", pConfig->pDev->vec[slot].vecInt0);
pReport = &pReport[nChar];
if ((intStatus & bit) == 0) {
nChar = sprintf(pReport, "%c ", '!');
pReport = &pReport[nChar];
}
bit <<= 1;
nChar = sprintf(pReport, " vec1=%x", pConfig->pDev->vec[slot].vecInt1);
pReport = &pReport[nChar];
if ((intStatus & bit) == 0) {
nChar = sprintf(pReport, "%c", '!');
pReport = &pReport[nChar];
}
return report;
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t atc40 = {
"GreenSpring ATC40",
SLOTS,
initialize,
report,
baseAddr,
irqCmd,
intVecConnect
};
drvIpac/drvIpMv162.c 0100644 0000621 0000620 00000016613 06750117467 013436 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvIpMv162.c
Description:
IPAC Carrier Driver for the IndustryPack carriers on the Motorola
MVME162 CPU board, provides the interface between IPAC driver and the
hardware.
Author:
Andrew Johnson
Created:
6 July 1995
Version:
drvIpMv162.c,v 1.4 1999/07/29 18:36:39 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
#include "ipic.h"
/* Characteristics of the card */
#define SLOTS 4
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
#define IPIC_BASE 0xfffbc000
/* Base Addresses of IO and ID spaces */
#define REGS_A 0xfff58000
#define PROM_A 0xfff58080
#define REGS_B 0xfff58100
#define PROM_B 0xfff58180
#define REGS_C 0xfff58200
#define PROM_C 0xfff58280
#define REGS_D 0xfff58300
#define PROM_D 0xfff58380
#define REGS_AB 0xfff58400
#define REGS_CD 0xfff58500
/* IPIC chip */
LOCAL ipic_t *ipic = (ipic_t *) IPIC_BASE;
/* IP Recovery Timers */
LOCAL const uchar_t recoveryTime[] = {
IPIC_GEN_RT_0,
IPIC_GEN_RT_2,
IPIC_GEN_RT_2,
IPIC_GEN_RT_4,
IPIC_GEN_RT_4,
IPIC_GEN_RT_8,
IPIC_GEN_RT_8,
IPIC_GEN_RT_8,
IPIC_GEN_RT_8
};
/* Carrier Base Address structure, only one instance can exist! */
LOCAL long mBase[IPAC_ADDR_SPACES][SLOTS] = {
{ PROM_A, PROM_B, PROM_C, PROM_D },
{ REGS_A, REGS_B, REGS_C, REGS_D },
{ REGS_AB, NULL, REGS_CD, NULL },
{ NULL, NULL, NULL, NULL }
};
/*******************************************************************************
Routine:
initialise
Purpose:
Initialises the MVME162 IPIC chip with settings given in cardParams
Description:
Parameters:
Examples:
"A:m=0x80000000,1024 l=4;B:l=2,2;C:m=0x80100000,64"
Returns:
0 = OK,
S_IPAC_tooMany = Carrier already registered
S_IPAC_badDriver = IPIC chip not found
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
static int initialised = FALSE;
ushort_t slot;
int count, next;
long p1, p2;
char dummy, cmd;
if (initialised) {
return S_IPAC_tooMany;
}
if (vxMemProbe((void *)&ipic->chipId, READ, 1, &dummy) ||
ipic->chipId != IPIC_CHIP_ID) {
return S_IPAC_badDriver;
}
/* Initialise the IPIC chip */
for (slot = 0; slot < SLOTS; slot++) {
ipic->intCtrl[slot][0] = IPIC_INT_ICLR;
ipic->intCtrl[slot][1] = IPIC_INT_ICLR;
ipic->genCtrl[slot] = IPIC_GEN_WIDTH_16 | IPIC_GEN_RT_0;
}
/* Parse the parameter string */
slot = 0;
while ((cmd = *cardParams++) != '\0') {
switch (cmd) {
case 'A':
case 'B':
case 'C':
case 'D':
slot = cmd - 'A';
break;
case 'm':
p1 = p2 = 0;
count = sscanf(cardParams, "=%p,%ld%n",
(void **) &p1, &p2, &next);
if (count != 2 ||
(unsigned) p1 < (unsigned long) sysMemTop() ||
(p1 & 0xffff) != 0 ||
p2 < 64 || p2 > 16384 ||
(unsigned) (p1 + (p2*1024)) > 0xfff00000u) {
return S_IPAC_badAddress;
}
ipic->memBase[slot] = p1 >> 16;
ipic->memSize[slot] = (p2 / 64) - 1;
ipic->genCtrl[slot] |= IPIC_GEN_MEN;
mBase[ipac_addrMem][slot] = p1;
cardParams += next;
break;
case 'l':
p1 = p2 = 0;
count = sscanf(cardParams, "=%ld%n,%ld%n", &p1, &next, &p2, &next);
if (count < 1 || count > 2 ||
p1 < 0 || p1 > 7 ||
p2 < 0 || p2 > 7) {
return S_IPAC_badAddress;
}
ipic->intCtrl[slot][0] = (p1 & IPIC_INT_LEVEL) |
(ipic->intCtrl[slot][0] & ~IPIC_INT_LEVEL);
ipic->intCtrl[slot][1] = (p2 & IPIC_INT_LEVEL) |
(ipic->intCtrl[slot][1] & ~IPIC_INT_LEVEL);
cardParams += next;
break;
case 'r':
p1 = 0;
count = sscanf(cardParams, "=%ld%n", &p1, &next);
if (count != 1 ||
p1 < 0 || p1 > 8) {
return S_IPAC_badAddress;
}
ipic->genCtrl[slot] = (ipic->genCtrl[slot] & ~IPIC_GEN_RT) |
recoveryTime[p1];
cardParams += next;
break;
case 'w':
p1 = 0;
count = sscanf(cardParams, "=%ld%n", &p1, &next);
if (count != 1) {
return S_IPAC_badAddress;
}
switch (p1) {
case 8:
ipic->genCtrl[slot] = IPIC_GEN_WIDTH_8 |
(ipic->genCtrl[slot] & ~IPIC_GEN_WIDTH);
break;
case 16:
ipic->genCtrl[slot] = IPIC_GEN_WIDTH_16 |
(ipic->genCtrl[slot] & ~IPIC_GEN_WIDTH);
break;
case 32:
if (slot & 1) {
/* Illegal for odd-numbered slots */
return S_IPAC_badAddress;
}
ipic->genCtrl[slot] = IPIC_GEN_WIDTH_32 |
(ipic->genCtrl[slot] & ~IPIC_GEN_WIDTH);
ipic->genCtrl[slot+1] &= ~(IPIC_GEN_WIDTH |
IPIC_GEN_MEN);
break;
default:
return S_IPAC_badAddress;
}
}
}
initialised = TRUE;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
This routine only has to do a table lookup in the mBase array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the slot has no memory in the
requested address space.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (void *) mBase[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The IPIC chip allows a lot of control over the IP interrupters, thus
all commands perform the requested action.
Returns:
ipac_irqGetLevel returns the current interrupt level,
ipac_irqPoll returns >0 if interrupt line active else 0,
other calls return 0 = OK.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
switch (cmd) {
case ipac_irqLevel0:
case ipac_irqLevel1:
case ipac_irqLevel2:
case ipac_irqLevel3:
case ipac_irqLevel4:
case ipac_irqLevel5:
case ipac_irqLevel6:
case ipac_irqLevel7:
ipic->intCtrl[slot][irqNumber] = (cmd & IPIC_INT_LEVEL) |
(ipic->intCtrl[slot][irqNumber] & ~IPIC_INT_LEVEL);
return OK;
case ipac_irqGetLevel:
return ipic->intCtrl[slot][irqNumber] & IPIC_INT_LEVEL;
case ipac_irqEnable:
ipic->intCtrl[slot][irqNumber] |= IPIC_INT_IEN;
return OK;
case ipac_irqDisable:
ipic->intCtrl[slot][irqNumber] &= ~IPIC_INT_IEN;
return OK;
case ipac_irqPoll:
return ipic->intCtrl[slot][irqNumber] & IPIC_INT_INT;
case ipac_irqSetEdge:
ipic->intCtrl[slot][irqNumber] |= IPIC_INT_EDGE;
return OK;
case ipac_irqSetLevel:
ipic->intCtrl[slot][irqNumber] &= ~IPIC_INT_EDGE;
return OK;
case ipac_irqClear:
ipic->intCtrl[slot][irqNumber] |= IPIC_INT_ICLR;
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t ipmv162 = {
"Motorola MVME162",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvIpac/drvIpac.c 0100644 0000621 0000620 00000034756 06750137535 013214 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvIpac.c
Description:
IPAC Driver, provides a standard interface between IPAC Module
drivers and the IPAC Carrier drivers.
Author:
Andrew Johnson
Created:
3 July 1995
Version:
drvIpac.c,v 1.5 1999/07/29 20:53:49 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef NO_EPICS
# include
#endif
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
#define IPAC_MAX_CARRIERS 21
/* Private carrier data structures */
struct carrierInfo {
ipac_carrier_t *driver;
void *cPrivate;
};
LOCAL struct {
ushort_t number;
struct carrierInfo *info[IPAC_MAX_CARRIERS];
} carriers = {
0
};
/* Null carrier stuff */
LOCAL ipac_carrier_t nullCarrier = {
"Null carrier (place holder)",
0, /* No slots */
NULL, NULL, NULL, NULL, NULL
};
LOCAL struct carrierInfo nullInfo = {
&nullCarrier,
NULL
};
#ifndef NO_EPICS
/* EPICS Driver Support Entry Table */
struct drvet drvIpac = {
3,
(DRVSUPFUN) ipacReport,
(DRVSUPFUN) ipacInitialise,
NULL
};
#endif
/*******************************************************************************
Routine:
ipacAddCarrier
Purpose:
Used to register a carrier board & carrier driver with the IPAC driver.
Description:
Usually called from the vxWorks (EPICS) startup script. Some types of
carrier may need additional initilisation before or after registering,
but the card parameter string should be sufficient for most carriers.
Note that only the carrier initialise routine is called at this stage.
The order in which carriers are registered with this routine specifies
the carrier number which they will be allocated, starting from zero.
Checks that the carrier descriptor table looks sensible, then calls the
initialise routine with the given card parameters, and saves the carrier
private pointer and carrier table address. The card number allows the
same descriptor to be used for all carriers of the same type.
It may be necessary to remove a carrier temporarily from a system in
some circumstances without wanting to have to change the carrier number
allocated to higher numbered carriers. To allow this, it is legal to
call this routine with a NULL (zero) carrier table address, which
switches in the null carrier table instead.
Returns:
0 = OK,
S_IPAC_tooMany = Carrier Info Table full,
S_IPAC_badTable = Carrier Table invalid.
Example:
ipacAddCarrier(&vipc310, "0x6000");
*/
int ipacAddCarrier (
ipac_carrier_t *pcarrierTable,
char *cardParams
) {
void *cPrivate;
int status;
if (carriers.number >= IPAC_MAX_CARRIERS) {
printf("ipacAddCarrier: Too many carriers registered.\n");
return S_IPAC_tooMany;
}
if (pcarrierTable == NULL) {
carriers.info[carriers.number] = &nullInfo;
carriers.number++;
return OK;
}
if (pcarrierTable->numberSlots == 0 ||
pcarrierTable->initialise == NULL ||
pcarrierTable->baseAddr == NULL ||
pcarrierTable->irqCmd == NULL) {
printf("ipacAddCarrier: Bad carrier table (arg 1).\n");
return S_IPAC_badTable;
}
status = pcarrierTable->initialise(cardParams, &cPrivate, carriers.number);
if (status) {
printf("ipacAddCarrier: %s driver returned an error.\n",
pcarrierTable->carrierType);
return status;
}
carriers.info[carriers.number] = malloc(sizeof (struct carrierInfo));
carriers.info[carriers.number]->driver = pcarrierTable;
carriers.info[carriers.number]->cPrivate = cPrivate;
carriers.number++;
return OK;
}
/*******************************************************************************
Routine:
ipmCheck
Function:
Check on presence of an IPAC module at the given carrier & slot number.
Description:
Does a quick check to make sure the carrier and slot numbers are legal,
probes the IDprom space to ensure an IPAC is installed, and checks that
the IDprom starts with the "IPAC" identifier.
Returns:
0 = OK,
S_IPAC_badAddress = Bad carrier or slot number,
S_IPAC_noModule = No module installed,
S_IPAC_noIpacId = "IPAC" identifier not found
*/
int ipmCheck (
ushort_t carrier,
ushort_t slot
) {
ipac_idProm_t *id;
ushort_t dummy;
if (carrier >= carriers.number ||
slot >= carriers.info[carrier]->driver->numberSlots) {
return S_IPAC_badAddress;
}
id = (ipac_idProm_t *) ipmBaseAddr(carrier, slot, ipac_addrID);
if (id == NULL) {
return S_IPAC_badDriver;
}
if (vxMemProbe((void *)&id->asciiI, READ, sizeof(dummy), (char *)&dummy)) {
return S_IPAC_noModule;
}
/*
* The following code is deliberately de-optimized to fix a problem with
* a particular GPIB module which can't handle the back-to-back accesses
* that the compiler generates if you combine the conditions in one if.
*/
if ((id->asciiI & 0xff) != 'I') {
return S_IPAC_noIpacId;
}
if ((id->asciiP & 0xff) != 'P') {
return S_IPAC_noIpacId;
}
if ((id->asciiA & 0xff) != 'A') {
return S_IPAC_noIpacId;
}
if ((id->asciiC & 0xff) != 'C') {
return S_IPAC_noIpacId;
}
return OK;
}
/*******************************************************************************
Routine:
checkCRC
Function:
Calculate the CRC of the IDprom at the given address.
Description:
Generates an industry standard CRC of the ID Prom data as described
in the GreenSpring Industry Pack specification. The CRC byte in the
Prom (at address 0x17) is set to zero for the purpose of calculating
the CRC.
Returns:
The low 8 bits of the calculated CRC value.
*/
LOCAL int checkCRC (
uint16_t *data,
ushort_t length
) {
uint_t i, crc = 0xffff;
uint16_t mask;
for (i = 0; i < length; i++) {
mask = 0x80;
while (mask) {
if ((data[i] & mask) && (i != 0xb)) {
crc ^= 0x8000;
}
crc += crc;
if (crc > 0xffff) {
crc = (crc & 0xffff) ^ 0x1021;
}
mask >>= 1;
}
}
return (~crc) & 0xff;
}
/*******************************************************************************
Routine:
ipmValidate
Function:
Validate a particular IPAC module type at the given carrier & slot number.
Description:
Uses ipmCheck to ensure the carrier and slot numbers are legal, probe the
IDprom and check that the IDprom looks like an IPAC module. Calculates
the CRC for the ID Prom, and compares the manufacturer and model ID values
in the Prom to the ones given.
Returns:
0 = OK,
S_IPAC_badAddress = Bad carrier or slot number,
S_IPAC_noModule = No module installed,
S_IPAC_noIpacId = "IPAC" identifier not found
S_IPAC_badCRC = CRC Check failed,
S_IPAC_badModule = Manufacturer or model IDs wrong
*/
int ipmValidate (
ushort_t carrier,
ushort_t slot,
uchar_t manufacturerId,
uchar_t modelId
) {
ipac_idProm_t *id;
int crc;
int status;
status = ipmCheck(carrier, slot);
if (status) {
return status;
}
id = (ipac_idProm_t *) ipmBaseAddr(carrier, slot, ipac_addrID);
crc = checkCRC((uint16_t *) id, id->bytesUsed & 0xff);
if (crc != (id->CRC & 0xff)) {
return S_IPAC_badCRC;
}
if ((id->manufacturerId & 0xff)!= manufacturerId ||
(id->modelId & 0xff) != modelId) {
return S_IPAC_badModule;
}
return OK;
}
/*******************************************************************************
Routine:
ipmReport
Function:
returns printable string giving status of module at given carrier/slot.
Description:
Generates a report string describing the given IPAC slot. If a module
is installed, it includes the manufacturer and model ID numbers. If
the report function is supported by the carrier driver this report
string is appended.
Returns:
Pointer to static, printable string.
Sample Output:
"C0 S1 : 0xB1/0x01 - M0 L4,5"
*/
char *ipmReport (
ushort_t carrier,
ushort_t slot
) {
static char report[80];
int status;
sprintf(report, "C%hd S%hd : ", carrier, slot);
status = ipmCheck(carrier, slot);
if (status == S_IPAC_badAddress) {
strcat(report, "No such carrier/slot");
return report;
}
if (status == S_IPAC_noModule) {
strcat(report, "No Module");
} else {
ipac_idProm_t *id;
char module[16];
id = (ipac_idProm_t *) ipmBaseAddr(carrier, slot, ipac_addrID);
sprintf(module, "%#2.2hx/%#2.2hx", id->manufacturerId & 0xff,
id->modelId & 0xff);
strcat(report, module);
}
if (carriers.info[carrier]->driver->report != NULL) {
strcat(report, " - ");
strcat(report, carriers.info[carrier]->driver->report(
carriers.info[carrier]->cPrivate, slot));
}
return report;
}
/*******************************************************************************
Routine:
ipmBaseAddr
Function:
Returns a pointer to the selected IP address space
Description:
Checks its input parameters, then calls the carrier driver. This will
return a pointer to the location of the address space indicated by the
space parameter. All IP modules must provide an ID prom to indicate
the module type (space = ipac_addrID). Most modules need register I/O
locations, which are in the I/O space (space = ipac_addrIO). Some
types of module also provide memory (space = ipac_addrMem), but if
this is not required the carrier may allow it to be disabled, in which
case the driver should return a NULL for this address space. Some
carriers provide a 32-bit wide I/O space for Dual-slot IP modules;
carriers which do not should return NULL for this space.
Returns:
Base CPU address of IP address space, or NULL pointer.
*/
void *ipmBaseAddr (
ushort_t carrier,
ushort_t slot,
ipac_addr_t space
) {
if (carrier >= carriers.number ||
slot >= carriers.info[carrier]->driver->numberSlots) {
return NULL;
}
return carriers.info[carrier]->driver->baseAddr(
carriers.info[carrier]->cPrivate, slot, space);
}
/*******************************************************************************
Routine:
ipmIrqCmd
Function:
Send command to slot interrupt controller.
Description:
Checks input parameters, then passes the interrupt command request to
the carrier driver routine. The driver is only required to support
the command ipac_irqEnable; for other commands it may return the status
code S_IPAC_notImplemented and do nothing.
Returns:
0 = OK,
S_IPAC_badAddress = illegal carrier, slot or irqNumber,
S_IPAC_notImplemented = Driver does not support that command,
other, depending on command.
*/
int ipmIrqCmd (
ushort_t carrier,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
if (irqNumber > 1 ||
carrier >= carriers.number ||
slot >= carriers.info[carrier]->driver->numberSlots) {
return S_IPAC_badAddress;
}
return carriers.info[carrier]->driver->irqCmd(
carriers.info[carrier]->cPrivate, slot, irqNumber, cmd);
}
/*******************************************************************************
Routine:
ipmIntConnect
Function:
Connect module driver to interrupt vector number
Description:
Checks input parameters, then passes the request to the carrier driver
routine. If no carrier routine is provided it calls the standard vxWorks
intConnect routine instead. This is not quite a straight replacement for
intConnect; as well as providing the carrier and slot numbers the module
driver must not use the INUM_TO_IVEC() macro but just give the vector
number.
VxWorks' interrupt vectoring mechanism varies between bus types, and this
routine allows a module driver to connect its routine to an interrupt
vector from a particular IPAC module without knowing the requirements of
the particular bus type. Some carrier drivers will need to maintain a
private interrupt dispatch table if the bus type (i.e. ISA) does not
support interrupt vectoring.
Returns:
0 = OK,
S_IPAC_badAddress = illegal carrier, slot or vector
*/
int ipmIntConnect (
ushort_t carrier,
ushort_t slot,
ushort_t vecNum,
void (*routine)(int parameter),
int parameter
) {
if (vecNum > 0xff ||
carrier >= carriers.number ||
slot >= carriers.info[carrier]->driver->numberSlots) {
return S_IPAC_badAddress;
}
/* Use intConnect if carrier driver doesn't provide one */
if (carriers.info[carrier]->driver->intConnect == NULL) {
return intConnect (INUM_TO_IVEC(vecNum), routine, parameter);
}
return carriers.info[carrier]->driver->intConnect(
carriers.info[carrier]->cPrivate, slot, vecNum,
routine, parameter);
}
/*******************************************************************************
Routine:
ipacReport
Function:
Report status of all known IPAC carriers
Description:
Prints information on each known carrier board and slot according to the
specified interest level. Level 0 lists carriers only, with the number
of slots it supports. Level 1 gives each slot, manufacturer & model ID
of the installed module (if any), and the carrier driver report for that
slot. Level 2 adds the address of each memory space for the slot.
Returns:
OK.
*/
int ipacReport (
int interest
) {
ushort_t carrier, slot;
for (carrier=0; carrier < carriers.number; carrier++) {
printf(" IP Carrier %2d: %s, %d slots\n", carrier,
carriers.info[carrier]->driver->carrierType,
carriers.info[carrier]->driver->numberSlots);
if (interest > 0) {
void *memBase, *io32Base;
for (slot=0; slot < carriers.info[carrier]->driver->numberSlots;
slot++) {
printf(" %s\n", ipmReport(carrier, slot));
if (interest > 1) {
printf(" ID = %p, I/O = %p",
ipmBaseAddr(carrier, slot, ipac_addrID),
ipmBaseAddr(carrier, slot, ipac_addrIO));
io32Base = ipmBaseAddr(carrier, slot, ipac_addrIO32);
if (io32Base != NULL) {
printf(", I/O32 = %p", io32Base);
}
memBase = ipmBaseAddr(carrier, slot, ipac_addrMem);
if (memBase != NULL) {
printf(", Mem = %p", memBase);
}
printf("\n");
}
}
}
}
return OK;
}
/*******************************************************************************
Routine:
ipacInitialise
Function:
Initialise the IPAC driver
Description:
Null routine.
Returns:
OK.
*/
int ipacInitialise (
int after
) {
return OK;
}
drvIpac/drvIpac.dbd 0100644 0000621 0000620 00000000047 06671305074 013502 0 ustar epics epics # IPAC Driver support
driver(drvIpac)
drvIpac/drvIpac.h 0100644 0000621 0000620 00000012536 06747665040 013214 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvIpac.h
Description:
IPAC Driver header file, defines the software interfaces:
1. Upwards to the IPAC Module driver
2. Downwards to the IPAC Carrier driver
Author:
Andrew Johnson
Created:
1 July 1995
Version:
drvIpac.h,v 1.4 1999/07/28 20:37:52 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef INCdrvIpacH
#define INCdrvIpacH
#include
#ifndef NO_EPICS
#include
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Error numbers */
#ifndef OK
#define OK 0
#endif
#ifndef M_ipac
#define M_ipac (600 << 16)
#endif
#define S_IPAC_badTable (M_ipac| 1) /*IPAC Carrier Table invalid*/
#define S_IPAC_tooMany (M_ipac| 2) /*Too many IPAC carriers, table full*/
#define S_IPAC_badAddress (M_ipac| 3) /*Bad IPAC carrier or slot number*/
#define S_IPAC_badDriver (M_ipac| 4) /*Bad value from IPAC carrier driver*/
#define S_IPAC_noModule (M_ipac| 5) /*No IP module installed*/
#define S_IPAC_noIpacId (M_ipac| 6) /*IPAC identifier not found*/
#define S_IPAC_badCRC (M_ipac| 7) /*IPAC CRC Check failed*/
#define S_IPAC_badModule (M_ipac| 8) /*IPAC Manufacturer or model ID wrong*/
#define S_IPAC_notImplemented (M_ipac| 9) /*IPAC Driver command not available*/
#define S_IPAC_badVector (M_ipac| 10) /*Bad interrupt vector*/
#define S_IPAC_vectorInUse (M_ipac| 11) /*Interrupt vector in use*/
#define S_IPAC_badIntLevel (M_ipac| 12) /*Bad interrupt level*/
/* Structure of the IPAC ID Prom, located in the pack ID space. */
typedef volatile struct {
uint16_t asciiI;
uint16_t asciiP;
uint16_t asciiA;
uint16_t asciiC;
uint16_t manufacturerId;
uint16_t modelId;
uint16_t revision;
uint16_t reserved;
uint16_t driverIdLow;
uint16_t driverIdHigh;
uint16_t bytesUsed;
uint16_t CRC;
uint16_t packSpecific[52];
} ipac_idProm_t;
/* These are the types of address space implemented in the IP
specification. Some IP modules only use the ID and IO spaces. */
#define IPAC_ADDR_SPACES 4
typedef enum {
ipac_addrID = 0, /* ID Prom space */
ipac_addrIO = 1, /* Registers etc */
ipac_addrIO32 = 2, /* Registers for 32-bit dual-slot */
ipac_addrMem = 3 /* Memory space */
} ipac_addr_t;
/* The following are the possible commands to the carrier driver to
handle interrupts from the IP modules. Most carriers will only be
able to implement a subset of these commands. Note that irqEnable
should call the vxWorks sysBusEnable routine if this is needed to
pass the carrier interrupts through to the CPU. */
typedef enum {
ipac_irqLevel0 = 0, /* Disables interrupts */
ipac_irqLevel1 = 1, /* Lowest priority */
ipac_irqLevel2 = 2,
ipac_irqLevel3 = 3,
ipac_irqLevel4 = 4,
ipac_irqLevel5 = 5,
ipac_irqLevel6 = 6, /* Highest priority */
ipac_irqLevel7 = 7, /* Non-maskable, don't use */
ipac_irqGetLevel, /* Returns level set (or hard-coded) */
ipac_irqEnable, /* Required to use interrupts */
ipac_irqDisable, /* Not necessarily supported */
ipac_irqPoll, /* Returns interrupt state */
ipac_irqSetEdge, /* Sets edge-triggered interrupts */
ipac_irqSetLevel, /* Sets level-triggered (default) */
ipac_irqClear /* Only needed if using edge-triggered */
} ipac_irqCmd_t;
/* This is a table which each IPAC carrier driver provides to allow
it to be queried by the IPAC driver. One table is required for
each type of carrier. The cPrivate pointer is returned by the
carrier driver initialise routine, and passed to all of the other
routines as a means of identification of the carrier board. */
typedef struct {
char *carrierType;
/* String containing carrier board type */
ushort_t numberSlots;
/* Number of IPAC devices this carrier can hold */
int (*initialise)(char *cardParams, void **cPrivate, ushort_t carrier);
/* Initialise carrier and return *cPrivate */
char *(*report)(void *cPrivate, ushort_t slot);
/* Return string with giving status of this slot */
void *(*baseAddr)(void *cPrivate, ushort_t slot, ipac_addr_t space);
/* Return base addresses for this slot */
int (*irqCmd)(void *cPrivate, ushort_t slot,
ushort_t irqNumber, ipac_irqCmd_t cmd);
/* Interrupt manipulation */
int (*intConnect)(void *cPrivate, ushort_t slot, ushort_t vecNum,
void (*routine)(int parameter), int parameter);
/* Connect routine to interrupt vector */
} ipac_carrier_t;
/* Functions for startup and interactive use */
extern int ipacAddCarrier(ipac_carrier_t *pcarrier, char *cardParams);
extern int ipacReport(int interest);
extern int ipacInitialise(int after);
/* Functions for use in IPAC module drivers */
extern int ipmCheck(ushort_t carrier, ushort_t slot);
extern int ipmValidate(ushort_t carrier, ushort_t slot,
uchar_t manufacturerId, uchar_t modelId);
extern char *ipmReport(ushort_t carrier, ushort_t slot);
extern void *ipmBaseAddr(ushort_t carrier, ushort_t slot, ipac_addr_t space);
extern int ipmIrqCmd(ushort_t carrier, ushort_t slot,
ushort_t irqNumber, ipac_irqCmd_t cmd);
extern int ipmIntConnect(ushort_t carrier, ushort_t slot, ushort_t vector,
void (*routine)(int parameter), int parameter);
#ifdef __cplusplus
}
#endif
#endif /* INCipacH */
drvIpac/drvIpac.html 0100644 0000621 0000620 00000140530 06750344734 013723 0 ustar epics epics
drvIpac - Industry Pack Driver
drvIpac - Industry Pack Driver
Version 2.1
Andrew Johnson
See also the following Module Drivers supplied which use drvIpac:
1. Introduction
This document describes the software interface to a generic Industry Pack
(IPAC) driver module for vxWorks, written as part of a
CANbus EPICS device driver for the
Gemini and
UKIRT telescopes. The
original purpose of the generic IPAC driver was to ensure that the CANbus
driver would not be restricted to use with a single carrier board but could be
used with different carriers as required, including with more than one type of
carrier board simultaneously. The use of the generic driver also ensures that
additional IPAC modules and drivers for other interfaces can be added without
affecting the functioning of the CANbus driver.
To provide a generic IPAC carrier board interface for each IPAC module
driver, all control of or requests for information about the carrier board goes
via the IPAC driver which in turn calls the IPAC Carrier driver written for the
particular type of carrier board. This carrier driver should be simple to
write, comprising three or four short subroutines and an interface structure.
On VMEbus, carrier drivers are available for the
GreenSpring VIPC310, 610 and 616
boards, and a driver for the
Motorola
MVME162 that also works on the MVME172. On ISAbus the GreenSpring ATC40
carrier is now supported by a driver from Jeff Hill.
At present the IPAC driver is limited (by the size of an internal array)
to controlling a maximum of 21 carrier boards, but this limitation should
be easy to dispense with completely without altering any of its interfaces.
Although designed primarily to be used from within an EPICS system, the
software has been written to allow it to be used independent of the presence
of EPICS in other vxWorks-based applications.
Installation
From version 2.0 onwards the drvIpac subsystem has been separated from
the CANbus driver but provided within an EPICS <supporttop> application
which can also be used to build IPAC module drivers. To install and use
the the ipac support, obtain a copy of the tar file or (if you have remote
CVS permissions at APS) export the software from the CVS repository where
it resides as epics/unbundled/ipac - CVS tags of the form `V2-1'
mark the particular file versions required for each release. These instructions
assume you already have EPICS R3.13.x installed and built (this software
should work with EPICS R3.13.0.beta11 and later). Two steps are then required
to install and build the software:
-
Edit the config/RELEASE file and set the correct path for EPICS_BASE at
your site.
-
Run gnumake in the top level directory.
If you don't need all of the module drivers included with the distribution,
you may wish to edit the top level Makefile and comment out the lines mentioning
driver directories you don't need.
Bus Issues
From version 2-1 this software supports little-endian as well as big-endian
busses, and provides a means of isolating module drivers from some of the
differences between interrrupt handling on different busses.
The endian problem only exists when accessing I/O registers and the
IPAC ID PROM using byte addresses. If all accesses occur using 16-bit read/write
cycles then the problem disappears. This is usually done by changing the
structure that declares the structure of a module's registers to use short
instead of char and removing the `padding' char members that separate
the registers (this change has been made to the ipac_idProm_t
structure which is declared in drvIpac.h). This done the other changes
are generally minor, usually consisting of masking off the top 8 bits of
every value read from the hardware.
Some busses such as ISAbus do not support interrupt vectors and require
a different approach to connecting Interrupt Service Routines up to the
relevent hardware interrupts, although the IndustryPack standard does require
that any IP module that generates interrupts should provide a vector. On
ISA bus carriers this vector can be read by the carrier driver to work
out which module caused the interrupt. A module driver should not need
to know about the particular bus type its carrier is on, thus ipac now
provides the routine ipmIntConnect to allow
it to pass such issues off to the carrier driver to handle. The interface
to this is very similar to the standard vxWorks intConnect routine.
2. IPAC Driver Usage
The driver provides a C header file drvIpac.h for use by both module
and carrier drivers.
#include "drvIpac.h"
This header file declares the necessary structures, enumerated types and
functions provided by the driver. These are individually documented below.
This header #includes the vxWorks ANSI header file
types.h
thus the -I C pre-processor switch must be used to indicate
the location of the vxWorks headers to the compiler.
If it is necessary to build a copy of the driver for use without EPICS,
the drvIpac.c file should be compiled with the
-DNO_EPICS
switch to disable the EPICS-specific code.
ipacAddCarrier
Used to register a carrier board and the appropriate carrier driver software
for it with the IPAC Driver.
int ipacAddCarrier(ipac_carrier_t *pcarrier, char *cardParams);
Parameters
-
ipac_carrier_t *pcarrier
-
Pointer to the carrier driver structure which is the only interface to
the IPAC Carrier driver. The same structure is used for every instance
of the same type of carrier board. The carriers already supported provide
the following structures which can be used here:
Board Type |
Carrier Driver |
pcarrier |
GreenSpring VIPC 310 |
drvVipc310 |
&vipc310 |
GreenSpring VIPC610 |
drvVipc610 |
&vipc610 |
GreenSpring VIPC610-01 |
drvVipc610_01 |
&vipc610_01 |
GreenSpring VIPC616 |
drvVipc616 |
&vipc616 |
GreenSpring ATC40 |
drvAtc40 |
&atc40 |
Motorola MVME162/172 |
drvIpMv162 |
&ipmv162 |
-
char *cardParams
-
String containing board-specific initialisation parameters which is passed
to the carrier driver. For carrier boards which rely on jumpers to set
the board address (e.g. the GreenSpring carrier boards), the settings for
each particular board will be reflected in the parameter settings given
here when registering that carrier. For boards such as the MVME162 where
the addresses can be changed by the driver, this string may be used to
indicate how the board should be initialised. See the specific documentation
for each carrier driver (section 4 below) for the
parameter string syntax.
Description
This routine will usually be called from the vxWorks (EPICS) start-up script.
Some types of carrier may need additional initialisation before or after
registering, but this method using the card parameter string should be
sufficient for most carriers. Note that only the carrier initialise
routine is called at this stage. The order in which carriers are registered
with this routine defines the carrier number which they will be allocated,
starting from zero for the first board registered.
The code checks that the carrier descriptor table looks sensible, calls
the initialise routine with the given card parameters, then saves the carrier
private pointer and carrier table address in an internal array. The card
number allows the same descriptor table to be used for all carriers of
the same type.
It may be necessary to remove a carrier temporarily from a system in
some circumstances without wanting to have to change the carrier number
allocated to higher numbered carriers. To allow this, it is legal to call
this routine with a NULL (zero) carrier table address, which switches in
the null carrier table instead. When this facility is used any module driver
which attempts to access a slot on this carrier will be given error status
returns by the module interface routines.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_IPAC_tooMany |
Carrier Info Table full |
S_IPAC_badTable |
Carrier Table invalid |
(others values) |
from carrier initialisation routine. |
Examples
ipacAddCarrier(&vipc610_01, "0x6000,256");
ipacAddCarrier(NULL, "");
ipacAddCarrier(&vipc310, "0x6800");
ipacReport
Prints a report on stdout giving the status of all known IPAC carriers.
int ipacReport(int interest);
Parameters
-
int interest
-
Interest level, defines how much information to provide in the report.
Description
Prints information on each known carrier board and slot according to the
specified interest level. Level 0 lists all the carriers defined, with
the number of IPAC slots each one supports. Level 1 gives details on each
slot on the carriers: the Manufacturer and Model ID bytes of the installed
module if one is present, and the Carrier Driver's report for that slot
(see ipmReport below). Level 2 adds the CPU address
of each memory space for the slot.
Returns
int
Symbol/Value |
Meaning |
0 |
OK. |
ipacInitialise
Initialise the IPAC driver.
int ipacInitialise(int after);
Parameters
-
int after
-
Not currently used, provided for compatibility with EPICS driver initialisation
routine.
Description
Null routine, does nothing.
Returns
int
Symbol/Value |
Meaning |
0 |
OK. |
3. Calls for use by IPAC Module Drivers
The routines documented below are provided for use by the module drivers
which use the services of the generic IPAC driver. In general it is expected
that these routines will only be used at initialisation time. The module
driver should be informed by other means which carrier and slot the particular
IPAC module it is to control is installed in, although it should be possible
to search each carrier and slot number in turn for the module using the
Manufacturer and Model ID codes.
ipmBaseAddr
Returns Base CPU address of selected IP address space
void *ipmBaseAddr(ushort_t carrier, ushort_t slot, ipac_addr_t space);
Parameters
-
ushort_t carrier
-
Carrier number; identifies a particular carrier board in the system. The
carriers are given numbers sequentially starting from zero according to
the order in which they were registered by calling ipacAddCarrier.
-
ushort_t slot
-
Slot number; identifies the particular IP slot on the carrier board, starting
from zero. The number of slots available varies with the type of the carrier
board - the VIPC310 and MVME172 provide 2, the VIPC610, VIPC616, ATC40 and
MVME162 each have four slots.
Together these two parameters uniquely identify a specific IPAC module
in the system, and these are used in this way by all of the following routines.
-
ipac_addr_t space
-
Value identifying the IP address space to be queried. This parameter is
an enumerated type, and must be one of the following values which are defined
in the header file:
IP Address Space |
space |
ID Prom Space |
ipac_addrID |
Register Space |
ipac_addrIO |
32-bit Register Space |
ipac_addrIO32 |
Memory Space |
ipac_addrMem |
Description
Checks its input parameters, then calls the carrier driver. This will return
a pointer to the location of the address space indicated by the
space
parameter.
All IP modules must provide an ID prom to indicate the module type (space
= ipac_addrID). Most modules need register I/O locations, which are
in the I/O space (space = ipac_addrIO). Some types of module also
provide memory (space = ipac_addrMem), but if this is not required
the carrier may allow it to be disabled, in which case the carrier driver
will return a NULL for this address space. Some carriers also provide a
32-bit wide I/O space for accessing 32-bit registers on Dual-slot IP modules
(space = ipac_addrIO32); carriers which do not support this will
return NULL for this space.
Returns
-
void *
-
Pointer to the beginning of the IP address space for the given carrier/slot,
or NULL pointer.
ipmCheck
Check on the presence of an IPAC module at the given carrier and slot number.
int ipmCheck(ushort_t carrier, ushort_t slot);
Parameters
-
ushort_t carrier, ushort_t slot
-
Module identification - see above
Description
Does a quick check to make sure the carrier and slot numbers are legal,
probes the IDprom space to ensure an IPAC is installed, and checks that
the IDprom starts with the "IPAC" identifier.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_IPAC_badAddress |
Bad carrier or slot number |
S_IPAC_badDriver |
Carrier driver returned NULL ID address |
S_IPAC_noModule |
No IP module installed |
S_IPAC_noIpacId |
"IPAC" identifier not found |
ipmValidate
Validates a particular IPAC module type at the given carrier & slot
number.
int ipmValidate(ushort_t carrier, ushort_t slot,
uchar_t manufacturerId, uchar_t modelId);
Parameters
-
ushort_t carrier, ushort_t slot
-
Module identification - see above
-
uchar_t manufacturerId
-
IPAC Manufacturer Identification Number, as allocated by GreenSpring. This
number should be given in the Programmer's Documentation for the IPAC module.
-
uchar_t modelId
-
IPAC Model Identification Number, as allocated by the module manufacturer.
This number should be given in the Programmer's Documentation for the IPAC
module.
Description
Uses ipmCheck to ensure the carrier and slot numbers are legal,
probe the IDprom and check that the IDprom looks like an IPAC module. Then
calculates and verifies the CRC for the ID Prom, and compares the manufacturer
and model ID values in the Prom to the ones given.
The manufacturer and model identification numbers allow a Module Driver
to ensure that the correct hardware has been installed in the particular
slot which the driver has been told to control. If a driver supports more
than one type of module, it should check each module type individually
by calling ipmValidate with each manufacturer/model pair it can
control until it finds a match.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_IPAC_badCRC |
CRC Check failed |
S_IPAC_badModule |
Manufacturer or model IDs wrong |
S_IPAC_badAddress |
Bad carrier or slot number |
S_IPAC_badDriver |
Carrier driver returned NULL ID address |
S_IPAC_noModule |
No IP module installed |
S_IPAC_noIpacId |
"IPAC" identifier not found |
ipmIrqCmd
Manipulate the carrier board interrupt controller.
int ipmIrqCmd(ushort_t carrier, ushort_t slot,
ushort_t irqNumber, ipac_irqCmd_t cmd);
Parameters
-
ushort_t carrier, ushort_t slot
-
Module identification - see above
-
ushort_t irqNumber
-
The IPAC specification provides two interrupt lines for each module. This
parameter identifies the interrupt to the Carrier Driver. It should have
the value 0 or 1 only.
-
ipac_irqCmd_t cmd
-
This parameter gives the action required, and is an enumerated type defined
in the header file. Because some carrier boards do not provide software
access to their interrupt controllers, there is only one command which
must be supported by all carriers. Module Drivers can be written to utilise
the additional functions if they are available.
Description
Checks input parameters, then passes the interrupt command request to the
equivalent Carrier Driver routine. The driver is only required to support
the command ipac_irqEnable; for other commands it may return the
status code S_IPAC_notImplemented and do nothing. Commands available are
as follows:
Command Description |
cmd |
Selects Interrupt Priority 0 (disabled) |
ipac_irqLevel0 |
Selects Interrupt Priority 1 |
ipac_irqLevel1 |
Selects Interrupt Priority 2 |
ipac_irqLevel2 |
Selects Interrupt Priority 3 |
ipac_irqLevel3 |
Selects Interrupt Priority 4 |
ipac_irqLevel4 |
Selects Interrupt Priority 5 |
ipac_irqLevel5 |
Selects Interrupt Priority 6 |
ipac_irqLevel6 |
Selects Interrupt Priority 7 (non-maskable) |
ipac_irqLevel7 |
Returns current Interrupt Priority, 0 to 7 |
ipac_irqGetLevel |
Enable interrupts from module |
ipac_irqEnable |
Disable interrupts from module |
ipac_irqDisable |
Returns current interrupt signal state |
ipac_irqPoll |
The Interrupt Priority (often also known as interrupt level) commands
are defined to be numerically the same as the level number they select.
Level 0 effectively disables the interrupt. Level 7 is not recommended
within vxWorks as it is non-maskable and can cause the kernel to panic.
The irqGetLevel command returns the level currently set. Carrier boards
which have fixed interrupt levels will not support setting the interrupt
level but the driver may still able to return the level number.
The irqEnable command must be supported by all Carrier Drivers, and
must be called by all Module Drivers if they wish to use interrupts. The
Carrier Driver routine is responsible for calling the vxWorks sysIntEnable
routine if this is required to allow IPAC interrupts to be seen by the
CPU. The corresponding irqDisable command is not necessarily supported
by all carriers however -- the module driver must disable interrupts using
the control registers on the IP module itself if this is necessary. A Carrier
Driver must not implement the irqDisable command as a call to
the vxWorks sysIntDisable routine because this would stop any
other devices which still need this interrupt level from working.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_IPAC_badAddress |
No such carrier or slot |
S_IPAC_notImplemented |
Driver does not support that command |
Other values may also be returned depending on the Driver and command
used.
ipmIntConnect
Connects a module driver Interrupt Service Routine to a particular interrupt
vector number.
int ipmIntConnect (ushort_t carrier, ushort_t slot, ushort_t vecNum,
void (*routine)(int parameter), int parameter);
Parameters
-
ushort_t carrier, ushort_t slot
-
Module identification - see above
-
ushort_t vecNum
-
Interrupt vector number
-
void (*routine)(int parameter)
-
Modules Interrupt Service Routine to be connected
-
int parameter
-
Context parameter passed to Interrupt Service Routine
Description
Checks input parameters, then passes the request to the carrier driver
routine. If no carrier routine is provided it uses the standard vxWorks
intConnect routine instead. This is not quite a direct replacement
for the vxWorks intConnect call; as well as providing the carrier and slot
numbers the module driver must not use the INUM_TO_IVEC(vecNum) macro but
just pass the vector number to this routine.
VxWorks' interrupt vectoring mechanism varies between bus types, and
ipmIntConnect allows a module driver to connect its routine to an interrupt
vector from a particular IPAC module without knowing the requirements of
the particular bus type. Some carrier drivers will need to maintain a private
interrupt dispatch table if the bus type (i.e. ISA) does not support interrupt
vectoring.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_IPAC_badAddress |
No such carrier, slot or vector |
Other values may also be returned depending on the Driver and vector
used.
ipmReport
Returns a printable string giving the status/settings of the given slot.
char *ipmReport(ushort_t carrier, ushort_t slot);
Parameters
-
ushort_t carrier, ushort_t slot
-
Module identification - see above
Description
Generates a report string describing the given IPAC slot. If a module is
installed, it includes the manufacturer and model ID numbers. If the report
function is supported by the carrier driver this report string is appended.
This function is uses a single internal static character array to hold
the report string, thus the value will be corrupted if two tasks use this
routine simultaneously.
Returns
-
char *
-
Pointer to a static, printable string.
Sample Output
"C0 S1 : 0xB1/0x01 - M0 L4,5"
This string is made up of three parts. The first two elements before the
colon give the carrier and slot number of the slot. If a module is installed,
the manufacturer and model IDs follow as two hex numbers. Finally if the
Carrier Driver contains a report function it is called and the string it
returns is appended after the hyphen.
4. IPAC Carrier Drivers
GreenSpring VIPC310
This board is probably the simplest possible VME-based IPAC Carrier available.
A 3U VME card, it provides two IP slots (although it cannot support 32-bit
accesses to dual-slot IP modules), and allows no control over the slot
interrupt controllers from software. The base addresses for the card are
set using a series of jumpers on the board which select both the VME short
I/O base address (used for the IPAC ID Prom and Register spaces) and the
VME Standard base address (used for the IPAC Memory space if required).
Additional jumpers allow the size of the IPAC Memory to be selected. The
interrupts are at priority levels fixed by the hardware.
The IPAC Carrier Driver for this board is found in the file drvVipc310.c
which exports only the symbol vipc310 for use as the carrier descriptor
table with ipacAddCarrier.
Configuration Parameter String Format
The parameter string should comprise a hexadecimal number (an 0x or 0X
at the start is optional) optionally followed by a comma and a decimal
integer. The first number is the I/O Base Address of the card in the VME
A16 address space. The factory default setting for the card gives an I/O
Base Address of 0x6000, and this value will be used if the string is empty
or NULL. If supplied the second number in the parameter string gives the
size in Kbytes of the memory space allocated to each IP module.
The Memory Base Address of the VIPC310 card is set using the same jumpers
as the I/O base address and is always 256 times the I/O base address, but
in the VME A24 address space. The factory default for the memory base address
is thus 0x600000.
If the memory size parameter is omitted or set to zero then neither
IP module provides any memory space. Legal memory size values are 0, 64,
128, 256, 512, 1024 or 2048. The memory size interacts with the memory
base address such that it is possible to set the existence of memory in
either slot independently by suitable adjustment of the base address.
Configuration Examples
-
ipacAddCarrier(&vipc310, "0x6000")
-
This indicates that the carrier has its I/O base set to 0x6000, and neither
slot uses any memory space.
-
ipacAddCarrier(&vipc310, "1000,512")
-
Here the I/O base is set to 0x1000, and there is 512 Kbytes of memory on
each module, with the IP module A memory at 0x100000 and module B at 0x180000.
-
ipacAddCarrier(&vipc310, "0xfe00, 128")
-
The I/O Base is at 0xfe00, and hence the carrier board Memory Base address
is 0xfe0000. However because the memory size is set to give each module
128 Kbytes of memory space, module A cannot be selected (128 K = 0x020000,
so the module Memory Base would be decoded at 0xfc0000 but can't be accessed
there because this is below the Memory Base address for the board).
Interrupt Commands Supported
The board uses fixed interrupt levels, and provides no software control
over the interrupt generator. The only commands thus supported are a request
of the interrupt level associated with a particular slot and interrupt
number, or to enable interrupts by making sure the CPU's VMEbus interrupter
is listening on the necessary level.
cmd |
Value Returned |
ipac_irqGetLevel |
slot interrupt level (1, 2, 4 or 5) |
ipac_irqEnable |
0 = OK |
(other commands) |
S_IPAC_notImplemented |
GreenSpring VIPC610
This board is a four-slot, 6U carrier, but otherwise very similar to the
VIPC310. A seperate driver is also provided for the VIPC610-01 option which
changes the Interrupt Priority levels for the IP slots so they are equivalent
to a pair of VIPC310 carrier boards, different to the interrupt levels
for a standard VIPC610 board.
The IPAC Carrier Drivers for the two board versions are found in the
files drvVipc610.c and drvVipc610_01.c which export the symbols
vipc610
and vipc610_01 respectively for use as the carrier descriptor
tables with ipacAddCarrier.
Note that as from version 1.1 of this software vipc610_01 is
a new name for the original vipc610 symbol which has been changed
to allow support for the standard board. Use of the wrong symbol may result
in interrupts from modules in slots B through D being ignored.
Configuration Parameter String Format
The parameter string should comprise a hexadecimal number (an 0x or 0X
at the start is optional) optionally followed by a comma and a decimal
integer. The first number is the I/O Base Address of the card in the VME
A16 address space. The factory default setting for the card gives an I/O
Base Address of 0x6000, and this value will be used if the string is empty
or NULL. If supplied the second number in the parameter string gives the
size of the memory space in Kbytes allocated to each IP module.
The Memory Base Address of the VIPC610 card is set using the same jumpers
as the I/O Base Address and is always 256 times the I/O Base Address, but
in the VME A24 address space. The factory default for the Memory Base address
is thus 0x600000.
If the memory size parameter is omitted or set to zero then none of
the IP modules on the carrier provide any memory space. Legal memory size
values are 0, 64?, 128, 256, 512, 1024 or 2048. The memory size interacts
with the Memory Base Address setting such that it is possible to exclude
memory from the lower slots while still providing access to memory in the
later slots by suitable adjustment of the base address.
Configuration Examples
-
ipacAddCarrier(&vipc610, "0x6000")
-
This indicates that the VIPC610 carrier board has its I/O base set to 0x6000,
and none of the slots provide memory space.
-
ipacAddCarrier(&vipc610_01, "1000,128")
-
Here the I/O base is set to 0x1000, and there is 128Kbytes of memory on
each module, with the IP module A memory at 0x100000, module B at 0x120000,
module C at 0x140000 and D at 0x160000. The board is actually a VIPC610-01.
-
ipacAddCarrier(&vipc610, "7000,1024")
-
The I/O base is at 0x7000, and hence the carrier memory base is 0x700000.
However because the memory size is set to 1024 Kbytes, modules A, B and
C cannot be selected (1024 K = 0x100000, so they are decoded at 0x400000,
0x500000 and 0x600000 but can't be accessed because these are below the
base address for the board).
Interrupt Commands Supported
The board uses fixed interrupt levels, and provides no software control
over the interrupt generator. The only commands thus supported are a request
of the interrupt level associated with a particular slot and interrupt
number, or to enable interrupts by making sure the CPU's VMEbus interrupter
is listening on the necessary level. Note that the VIPC610-01 uses different
interrupt levels to the straight VIPC610.
cmd |
Value Returned |
ipac_irqGetLevel |
slot interrupt level |
ipac_irqEnable |
0 = OK |
(other commands) |
S_IPAC_notImplemented |
GreenSpring VIPC616
The only differences between this carrier and the VIPC610 relate to the
VME addressing capabilities of the card which can now support memory IP
modules in the VME A32 address space. It is possible to use the VIP610
driver to control a VIPC616 board, but the use of this specific driver
is recommended. This driver should also be used for the VIPC618 carrier
which is schematically identical to the VIPC616 but uses different I/O
connectors for the IP module signals.
Configuration Parameter String Format
The parameter string should comprise a hexadecimal number (an 0x or 0X
at the start is not required) optionally followed by a comma and another
hexadecimal number, and then possibly another comma and a decimal integer.
The first number is the I/O Base Address of the card in the VME A16 address
space. The factory default setting for the card gives an I/O Base Address
of 0x6000, and this value will be used if the string is empty or NULL.
The meaning of the second number depends upon whether the third (decimal)
integer is present in the string or not. If there is no third number then
the second number gives the Memory Base Address of the card in the VME
A32 space. In this case each module is allocated a fixed 8 Mbytes of memory
space by the carrier.
If all three numbers are given then the second number is the Memory
Base Address of the card in VME A24 address space, and the third number
gives the size of the memory space in Kbytes allocated to each IP module.
This is the VIPC610 compatibility mode, and the Memory Base Address and
Memory Size parameters are used and interact in exactly the same way as
with the VIPC610.
Configuration Examples
-
ipacAddCarrier(&vipc616, "0x6000")
-
This indicates that the board has its I/O base set to 0x6000, and none
of the slots provide memory space.
[I'm not sure that this example is correct; ommitting the MBA may
result in an error. I don't have a VIPC616 to check this out with, so the
driver is untested.]
-
ipacAddCarrier(&vipc616, "1000,8000000")
-
Here the I/O base is set to 0x1000, and each module is allocated 8Mb of
VME A32 memory space starting at 0x80000000. This puts IP module A memory
at 0x80000000, module B at 0x80800000, module C at 0x81000000 and D at
0x81800000.
-
ipacAddCarrier(&vipc616, "7000,700000,1024")
-
The I/O base is at 0x7000 and the carrier memory base is at 0x700000 in
the VME A24 address space. However because the memory size is set to 1024
Kbytes, modules A, B and C cannot be selected (1024 K = 0x100000, so they
are decoded at 0x400000, 0x500000 and 0x600000 but can't be accessed because
these are below the base address).
Interrupt Commands Supported
The board uses fixed interrupt levels, and provides no software control
over the interrupt generator. The only commands thus supported are a request
of the interrupt level associated with a particular slot and interrupt
number, or to enable interrupts by making sure the CPU's VMEbus interrupter
is listening on the necessary level.
cmd |
Value Returned |
ipac_irqGetLevel |
slot interrupt level |
ipac_irqEnable |
0 = OK |
(other commands) |
S_IPAC_notImplemented |
GreenSpring ATC40
The GreenSpring ATC40 is an IP carrier board for the ISAbus, which is a
little-endian architecture unlike the VMEbus. Providing support for this
board has required some changes to the drvIpac software which are described
in the Bus Issues section above. Unless similar
precautions are taken when writing module drivers these will not be compatible
with little-endian systems. This carrier driver was written by Peregrine
McGehee and Jeff Hill, who should
be approached directly for support. Note that the carrier driver source
file drvIpac/drvAtc40.c will only compile properly for x86 CPUs,
and as shipped this file is commented out in drvIpac/Makefile.Vx.
Motorola MVME162 and MVME172
The Motorola MVME162 CPU board provides four IP slots in addition to the
MC68040 CPU, memory and I/O. Slot pairs can be used with 32-bit dual-slot
IP modules, and the IPIC chip which controls the interface supports all
of the IPAC Driver interrupter commands. The Motorola MVME172 CPU board
uses exactly the same IPIC chip and thus is compatible with the MVME162,
although physically it only has two IP slots. The same software can be
used on either board, although slots C and D on an MVME172 will always
appear to be empty.
When this CPU board is used the IPAC carrier driver drvIpMv162.c
can control the IP slots on the board. The ipacAddCarrier carrier
descriptor table parameter is ipmv162 for this driver. The carrier
initialisation routine has the following additional return-code meanings
(see also ipacInitialise above):
Symbol/Value |
Meaning |
S_IPAC_tooMany |
IpMv162 carrier already registered |
S_IPAC_badDriver |
IPIC chip not found |
S_IPAC_badAddress |
Parameter string error, or address not reachable |
Configuration Parameter String Format
The parameter string is used to initialise the IPIC registers which allow
detailed control of several settings for each slot. The string consists
of a series of single characters to determine the slot and setting to be
controlled and one or more numeric parameters for each setting. The string
is parsed sequentially from left to right, and the characters and their
parameters have the following meanings:
-
A, B, C and D
-
These select the IP slot (also identified as slots 0 through 3) which is
to be controlled by the following commands, up to the appearance of the
next of these slot selection characters.
-
l=level1[,level2]
-
Sets the interrupt level for the slot interrupters to the decimal values given
by level1 and level2 respectively. If the second IP interrupt is
not used by the particular module the ,level2 part may be
omitted and this interrupt will be disabled. The default setting is for both
interrupts to be disabled.
-
m=base,size
-
Enables the slot memory space and sets the base address for the slot to
the hexadecimal value given by base, with extent given by the
size parameter in kilobytes. NB size cannot be omitted.
-
r=recovery
-
This programs the slot recovery timer on the IPIC chip to be at least
recovery
microseconds (legal values are between 0 and 8 inclusive). This option
should only be required with some early IP module designs.
-
w=width
-
The IPIC chip can alter the way in which the IP module memory space is
addressed and is controlled using this option. By default or with a
w=16
setting the memory will be initialised to be 16 bits wide with direct mapping
of CPU addresses to IP memory addresses. By using the setting w=8
it is possible to access only the odd bytes of the IP memory space, i.e.
only the 8 least significant bits of the data bus are used (other carrier
boards may not support this type of operation so this facility should be
used with care). The w=32 setting permits the memory space on
double-width IP modules to be accessed using 32-bit transfers; this should
be used on the even-numbered slot of the pair only.
Other characters will be ignored without affecting the parsing of the remainder
of the parameter string.
Configuration Example
ipacAddCarrier &ipmv162, "A:m=0x80000000,1024 l=5,3 w=8 r=2 B:l=2"
The above example initialises slots 0 and 1 only - slots 2 and 3 are not
used or their modules require register and ID Prom access only in this
particular application.
Slot 0 is set up for a slow (recovery time 2µs) memory board with
1 Mbyte of 8-bit wide RAM, addressed at 0x80000000 and with interrupt priorities
set for levels 5 and 3.
Slot 1 has no memory space, and just a single interrupt at priority
level 2.
Interrupt Commands Supported
The IPIC chip allows a lot of control over the IP interrupters, thus all
commands perform the requested action. The ipmIrqCmd return values
for the commands are:
cmd |
Value Returned |
ipac_irqGetLevel |
current slot interrupt level |
ipac_irqPoll |
>0 if the interrupt line is active, else 0 |
other calls |
0 = OK |
5. Interface to IPAC Carrier Drivers
Writing a new Carrier Driver is quite simple, and requires just three subroutines
to be produced (the report function is optional). The driver will need
some of the definitions in the drvIpac.h file.
#include "drvIpac.h"
All routines should be re-entrant, and no static variables should be used
unless (like the mv162) only one carrier board of this type is possible
in a particular system. In this case re-entrancy is still essential, but
static variables may be used to hold information about the carrier provided
these are protected from simultaneous updates by different tasks.
The sole interface between the Carrier Driver and the IPAC driver is
through the ipac_carrier_t typedef structure given in the header
file. Note that this has changed slightly since version 2.0 with the addition
of the carrier parameter to the initialise routine, and the new intConnect
routine. This is defined as follows:
typedef struct {
char *carrierType; /* String describing carrier board type */
ushort_t numberSlots; /* Number of IPAC devices this carrier can hold */
int (*initialise)(char *cardParams, void **cPrivate, ushort_t carrier);
/* Initialise carrier and return *cPrivate */
char *(*report)(void *cPrivate, ushort_t slot);
/* Return string giving status of this slot */
void *(*baseAddr)(void *cPrivate, ushort_t slot, ipac_addr_t space);
/* Return base addresses for this slot */
int (*irqCmd)(void *cPrivate, ushort_t slot, ushort_t irqNumber,
ipac_irqCmd_t cmd); /* Interrupt control */
int (*intConnect)(void *cPrivate, ushort_t slot, ushort_t vecNum,
void (*routine)(int parameter), int parameter);
/* Connect routine to interrupt vector */
} ipac_carrier_t;
The first two structure members provide fixed information about the carrier
to the IPAC driver. carrierType is a string which is printed by
the report function to identify the type of carrier board, and
numberSlots
indicates how many IPAC slots this particular type of carrier provides.
The remaining structure members are function pointers to the routines
which control the carrier board. The cPrivate parameter passed
to these functions points to a structure which must be defined and allocated
by the carrier driver and can be used to hold information about a particular
carrier board (for example the board base address). The IPAC driver stores
the cPrivate pointer which is returned by the initialise
routine, and passes the same pointer back to the other routines when referring
to that particular carrier board at a later stage.
The intConnect function pointer may be set to NULL if the standard
vxWorks intConnect provides all the funtionality needed to install an interrupt
vector - this is probably only needed for ISA bus carriers.
The other parameters to the routines are identical to those described
in detail in the corresponding IPAC Driver routines above. The IPAC Driver
performs parameter checking on the slot and irqNumber
parameters before calling the Carrier Driver routine, so these values can
be used with confidence.
The simplest way to write a carrier driver is to copy the VIPC310 or
MVME162 driver and modify one of these for the new board type. The MVME162
driver interfaces to the IPIC chip and can be used as a basis for carrier
drivers which provide extensive control over the IPAC slots. The VIPC310
board is totally dumb, and thus provides the simplest possible example
of a carrier driver.
Andrew Johnson <anjohnson@iee.org>
drvIpac/drvVipc310.c 0100644 0000621 0000620 00000016225 06747665042 013461 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvVipc310.c
Description:
IPAC Carrier Driver for the GreenSpring VIPC310 Dual IndustryPack
Carrier VME board, provides the interface between IPAC driver and the
hardware. This carrier is 3U high, and thus cannot support 32-bit
accesses to dual-slot IP modules.
Author:
Andrew Johnson
Created:
5 July 1995
Version:
drvVipc310.c,v 1.3 1999/07/28 20:37:54 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/* Characteristics of the card */
#define SLOTS 2
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
/* Offsets from base address in VME A16 */
#define REGS_A 0x0000
#define PROM_A 0x0080
#define REGS_B 0x0100
#define PROM_B 0x0180
/* VME Interrupt levels */
#define IRQ_A0 4
#define IRQ_A1 5
#define IRQ_B0 2
#define IRQ_B1 1
/* Carrier Private structure type, one instance per board */
typedef void* private_t[IPAC_ADDR_SPACES][SLOTS];
/*******************************************************************************
Routine:
initialise
Purpose:
Creates new private table for VIPC310 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional size of the memory space for the modules. If both the I/O and
memory base addresses can be reached from the CPU, a private table is
created for this board. The private table is a 2-D array of pointers
to the base addresses of the various accessible parts of the IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at the
start is optional) optionally followed by a comma and a decimal integer.
The first number is the I/O base address of the card in the VME A16
address space (the factory default is 0x6000). If present the second
number gives the memory space in Kbytes allocated to each IP module.
The memory base address of the VIPC310 card is set using the same jumpers
as the I/O base address and is always 256 times the I/O base address,
but in the VME A24 address space. The factory default fot the memory
base address is thus 0x600000. If the memory size parameter is omitted
or set to zero then neither IP module provides any memory space. Legal
memory size values are 0, 64, 128, 256, 512, 1024 or 2048. The memory
size interacts with the memory base address such that it is possible to
set the existance of memory in either slot independently with suitable
adjustment of the base address.
Examples:
"0x6000"
This indicates that the carrier has its I/O base set to 0x6000, and
neither slot uses any memory space.
"1000,512"
Here the I/O base is set to 0x1000, and there is 512 Kbytes of
memory on each module, with the IP module A memory at 0x100000
and module B at 0x180000.
"0xfe00, 128"
The I/O base is at 0xfe00, and hence the carrier memory base is
0xfe0000. However because the memory size is set to give each module
128 Kbytes of memory space, module A cannot be selected (128 K =
0x020000, so the module is decoded at 0xfc0000 but can't be accessed
because this is below the memory base).
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
int params, status1 = OK, status2 = OK, mSize = 0;
long ioBase, mOrig, mBase;
ushort_t space, slot;
private_t *private;
static const int offset[IO_SPACES][SLOTS] = {
{ PROM_A, PROM_B },
{ REGS_A, REGS_B }
};
if (cardParams == NULL ||
strlen(cardParams) == 0) {
ioBase = 0x6000;
} else {
params = sscanf(cardParams, "%p,%i", (void **) &ioBase, &mSize);
if (params < 1 || params > 2 ||
ioBase > 0xfe00 || ioBase & 0x01ff ||
mSize < 0 || mSize > 2048 || mSize & 63) {
return S_IPAC_badAddress;
}
}
mBase = ioBase << 8; /* Fixed by VIPC310 card */
status1 = sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO,
(char *) ioBase, (char **) &ioBase);
if (mSize > 0) {
status2 = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,
(char *) mBase, (char **) &mBase);
}
if (status1 || status2) {
return S_IPAC_badAddress;
}
mSize = mSize << 10; /* Convert size from K to Bytes */
mOrig = mBase & ~(mSize * SLOTS - 1);
private = malloc(sizeof (private_t));
for (space = 0; space < IO_SPACES; space++) {
for (slot = 0; slot < SLOTS; slot++) {
(*private)[space][slot] = (void *) (ioBase + offset[space][slot]);
}
}
(*private)[ipac_addrIO32][0] = NULL;
(*private)[ipac_addrIO32][1] = NULL;
if (mOrig == mBase) {
(*private)[ipac_addrMem][0] = (void *) mBase;
(*private)[ipac_addrMem][1] = (void *) (mBase + mSize);
} else {
(*private)[ipac_addrMem][0] = NULL;
(*private)[ipac_addrMem][1] = (void *) mBase;
}
*pprivate = private;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialise routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (*(private_t *) private)[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
VMEbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level (1, 2, 4 or 5),
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
static const int irqLevel[SLOTS][IPAC_IRQS] = {
{ IRQ_A0, IRQ_A1 } ,
{ IRQ_B0, IRQ_B1 }
};
switch (cmd) {
case ipac_irqGetLevel:
return irqLevel[slot][irqNumber];
case ipac_irqEnable:
sysIntEnable(irqLevel[slot][irqNumber]);
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t vipc310 = {
"GreenSpring VIPC310",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvIpac/drvVipc610.c 0100644 0000621 0000620 00000016755 06747665044 013476 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvVipc610.c
Description:
IPAC Carrier Driver for the GreenSpring VIPC610 Quad IndustryPack
Carrier VME board, provides the interface between IPAC driver and the
hardware. This carrier is 6U high, but cannot support 32-bit accesses
to dual-slot IP modules, or Extended mode addresses.
Author:
Andrew Johnson
Created:
19 July 1995
Version:
drvVipc610.c,v 1.4 1999/07/28 20:37:56 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/* Characteristics of the card */
#define SLOTS 4
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
/* Offsets from base address in VME A16 */
#define REGS_A 0x0000
#define PROM_A 0x0080
#define REGS_B 0x0100
#define PROM_B 0x0180
#define REGS_C 0x0200
#define PROM_C 0x0280
#define REGS_D 0x0300
#define PROM_D 0x0380
/* VME Interrupt levels */
#define IRQ_A0 1
#define IRQ_A1 2
#define IRQ_B0 3
#define IRQ_B1 4
#define IRQ_C0 5
#define IRQ_C1 6
#define IRQ_D0 7
#define IRQ_D1 0
/* Carrier Private structure type, one instance per board */
typedef void* private_t[IPAC_ADDR_SPACES][SLOTS];
/*******************************************************************************
Routine:
initialise
Purpose:
Creates new private table for VIPC610 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional size of the memory space for the modules. If both the I/O and
memory base addresses can be reached from the CPU, a private table is
created for this board. The private table is a 2-D array of pointers
to the base addresses of the various accessible parts of the IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at the
start is optional) optionally followed by a comma and a decimal integer.
The first number is the I/O base address of the card in the VME A16
address space (the factory default is 0x6000). If present the second
number gives the memory space in Kbytes allocated to each IP module.
The memory base address of the VIPC610 card is set using the same jumpers
as the I/O base address and is always 256 times the I/O base address,
but in the VME A24 address space. The factory default for the memory
base address is thus 0x600000. If the memory size parameter is omitted
or set to zero then none of the IP modules on the carrier provide any
memory space. Legal memory size values are 0, 64?, 128, 256, 512, 1024
or 2048. The memory size interacts with the memory base address such
that it is possible to exclude memory from the lower slots while still
providing access to memory in the later slots by adjusting the base
address suitably.
Examples:
"0x6000"
This indicates that the carrier board has its I/O base set to
0x6000, and none of the slots provide memory space.
"1000,128"
Here the I/O base is set to 0x1000, and there is 128Kbytes of
memory on each module, with the IP module A memory at 0x100000,
module B at 0x120000, module C at 0x140000 and D at 0x160000.
"7000,1024"
The I/O base is at 0x7000, and hence the carrier memory base is
0x700000. However because the memory size is set to 1024 Kbytes,
modules A, B and C cannot be selected (1024 K = 0x100000, so they
are decoded at 0x400000, 0x500000 and 0x600000 but can't be accessed
because these are below the base address).
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
int params, status1 = OK, status2 = OK, mSize = 0;
ulong_t ioBase, mOrig, mBase, addr;
ushort_t space, slot;
private_t *private;
static const int offset[IO_SPACES][SLOTS] = {
{ PROM_A, PROM_B, PROM_C, PROM_D },
{ REGS_A, REGS_B, REGS_C, REGS_D }
};
if (cardParams == NULL ||
strlen(cardParams) == 0) {
ioBase = 0x6000;
} else {
params = sscanf(cardParams, "%p,%i", (void **) &ioBase, &mSize);
if (params < 1 || params > 2 ||
ioBase > 0xfc00 || ioBase & 0x01ff ||
mSize < 0 || mSize > 2048 || mSize & 63) {
return S_IPAC_badAddress;
}
}
mBase = ioBase << 8; /* Fixed by the VIPC610 card */
ioBase = ioBase & 0xfc00; /* Clear A09 */
status1 = sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO,
(char *) ioBase, (char **) &ioBase);
if (mSize > 0) {
status2 = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,
(char *) mBase, (char **) &mBase);
}
if (status1 || status2) {
return S_IPAC_badAddress;
}
mSize = mSize << 10; /* Convert size from K to Bytes */
mOrig = mBase & ~(mSize * SLOTS - 1);
private = malloc(sizeof (private_t));
for (space = 0; space < IO_SPACES; space++) {
for (slot = 0; slot < SLOTS; slot++) {
(*private)[space][slot] = (void *) (ioBase + offset[space][slot]);
}
}
for (slot = 0; slot < SLOTS; slot++) {
(*private)[ipac_addrIO32][slot] = NULL;
addr = mOrig + (mSize * slot);
if (addr < mBase) {
(*private)[ipac_addrMem][slot] = NULL;
} else {
(*private)[ipac_addrMem][slot] = (void *) addr;
}
}
*pprivate = private;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialise routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (*(private_t *) private)[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
VMEbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level,
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
static const int irqLevel[SLOTS][IPAC_IRQS] = {
{ IRQ_A0, IRQ_A1 },
{ IRQ_B0, IRQ_B1 },
{ IRQ_C0, IRQ_C1 },
{ IRQ_D0, IRQ_D1 }
};
switch (cmd) {
case ipac_irqGetLevel:
return irqLevel[slot][irqNumber];
case ipac_irqEnable:
sysIntEnable(irqLevel[slot][irqNumber]);
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t vipc610 = {
"GreenSpring VIPC610",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvIpac/drvVipc610_01.c 0100644 0000621 0000620 00000017160 06747665045 013766 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvVipc610_01.c
Description:
IPAC Carrier Driver for the GreenSpring VIPC610-01 Quad IndustryPack
Carrier VME board, provides the interface between IPAC driver and the
hardware. This carrier is 6U high, but cannot support 32-bit accesses
to dual-slot IP modules. Note the VIPC610-01 fixes the IRQ levels to
be equivalent to two VIPC310 carriers, different to the VIPC610.
Author:
Andrew Johnson
Created:
19 July 1995
Version:
drvVipc610_01.c,v 1.3 1999/07/28 20:37:57 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/* Characteristics of the card */
#define SLOTS 4
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
/* Offsets from base address in VME A16 */
#define REGS_A 0x0000
#define PROM_A 0x0080
#define REGS_B 0x0100
#define PROM_B 0x0180
#define REGS_C 0x0200
#define PROM_C 0x0280
#define REGS_D 0x0300
#define PROM_D 0x0380
/* VME Interrupt levels for -01 option */
#define IRQ_A0 4
#define IRQ_A1 5
#define IRQ_B0 2
#define IRQ_B1 1
#define IRQ_C0 4
#define IRQ_C1 5
#define IRQ_D0 2
#define IRQ_D1 1
/* Carrier Private structure type, one instance per board */
typedef void* private_t[IPAC_ADDR_SPACES][SLOTS];
/*******************************************************************************
Routine:
initialise
Purpose:
Creates new private table for VIPC610 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional size of the memory space for the modules. If both the I/O and
memory base addresses can be reached from the CPU, a private table is
created for this board. The private table is a 2-D array of pointers
to the base addresses of the various accessible parts of the IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at the
start is optional) optionally followed by a comma and a decimal integer.
The first number is the I/O base address of the card in the VME A16
address space (the factory default is 0x6000). If present the second
number gives the memory space in Kbytes allocated to each IP module.
The memory base address of the VIPC610 card is set using the same jumpers
as the I/O base address and is always 256 times the I/O base address,
but in the VME A24 address space. The factory default for the memory
base address is thus 0x600000. If the memory size parameter is omitted
or set to zero then none of the IP modules on the carrier provide any
memory space. Legal memory size values are 0, 64?, 128, 256, 512, 1024
or 2048. The memory size interacts with the memory base address such
that it is possible to exclude memory from the lower slots while still
providing access to memory in the later slots by adjusting the base
address suitably.
Examples:
"0x6000"
This indicates that the carrier board has its I/O base set to
0x6000, and none of the slots provide memory space.
"1000,128"
Here the I/O base is set to 0x1000, and there is 128Kbytes of
memory on each module, with the IP module A memory at 0x100000,
module B at 0x120000, module C at 0x140000 and D at 0x160000.
"7000,1024"
The I/O base is at 0x7000, and hence the carrier memory base is
0x700000. However because the memory size is set to 1024 Kbytes,
modules A, B and C cannot be selected (1024 K = 0x100000, so they
are decoded at 0x400000, 0x500000 and 0x600000 but can't be accessed
because these are below the base address).
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
int params, status1 = OK, status2 = OK, mSize = 0;
ulong_t ioBase, mOrig, mBase, addr;
ushort_t space, slot;
private_t *private;
static const int offset[IO_SPACES][SLOTS] = {
{ PROM_A, PROM_B, PROM_C, PROM_D },
{ REGS_A, REGS_B, REGS_C, REGS_D }
};
if (cardParams == NULL ||
strlen(cardParams) == 0) {
ioBase = 0x6000;
} else {
params = sscanf(cardParams, "%p,%i", (void **) &ioBase, &mSize);
if (params < 1 || params > 2 ||
ioBase > 0xfc00 || ioBase & 0x01ff ||
mSize < 0 || mSize > 2048 || mSize & 63) {
return S_IPAC_badAddress;
}
}
mBase = ioBase << 8; /* Fixed by the VIPC610 card */
ioBase = ioBase & 0xfc00; /* Clear A09 */
status1 = sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO,
(char *) ioBase, (char **) &ioBase);
if (mSize > 0) {
status2 = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,
(char *) mBase, (char **) &mBase);
}
if (status1 || status2) {
return S_IPAC_badAddress;
}
mSize = mSize << 10; /* Convert size from K to Bytes */
mOrig = mBase & ~(mSize * SLOTS - 1);
private = malloc(sizeof (private_t));
for (space = 0; space < IO_SPACES; space++) {
for (slot = 0; slot < SLOTS; slot++) {
(*private)[space][slot] = (void *) (ioBase + offset[space][slot]);
}
}
for (slot = 0; slot < SLOTS; slot++) {
(*private)[ipac_addrIO32][slot] = NULL;
addr = mOrig + (mSize * slot);
if (addr < mBase) {
(*private)[ipac_addrMem][slot] = NULL;
} else {
(*private)[ipac_addrMem][slot] = (void *) addr;
}
}
*pprivate = private;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialise routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (*(private_t *) private)[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
VMEbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level (1, 2, 4 or 5),
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
static const int irqLevel[SLOTS][IPAC_IRQS] = {
{ IRQ_A0, IRQ_A1 },
{ IRQ_B0, IRQ_B1 },
{ IRQ_C0, IRQ_C1 },
{ IRQ_D0, IRQ_D1 }
};
switch (cmd) {
case ipac_irqGetLevel:
return irqLevel[slot][irqNumber];
case ipac_irqEnable:
sysIntEnable(irqLevel[slot][irqNumber]);
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t vipc610_01 = {
"GreenSpring VIPC610-01",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvIpac/drvVipc616.c 0100644 0000621 0000620 00000020404 06747665047 013471 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvVipc616.c
Description:
IPAC Carrier Driver for the GreenSpring VIPC616 Quad IndustryPack
Carrier VME board, provides the interface between IPAC driver and the
hardware. This carrier is 6U high and can support VME Extended mode
addresses, but not 32-bit access to dual-slot IP modules.
Author:
Andrew Johnson
Created:
17 October 1997
Version:
drvVipc616.c,v 1.4 1999/07/28 20:37:59 anj Exp
(c) 1997 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/* Characteristics of the card */
#define SLOTS 4
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
/* Offsets from base address in VME A16 */
#define REGS_A 0x0000
#define PROM_A 0x0080
#define REGS_B 0x0100
#define PROM_B 0x0180
#define REGS_C 0x0200
#define PROM_C 0x0280
#define REGS_D 0x0300
#define PROM_D 0x0380
/* VME Interrupt levels */
#define IRQ_A0 1
#define IRQ_A1 2
#define IRQ_B0 3
#define IRQ_B1 4
#define IRQ_C0 5
#define IRQ_C1 6
#define IRQ_D0 7
#define IRQ_D1 0
/* Carrier Private structure type, one instance per board */
typedef void* private_t[IPAC_ADDR_SPACES][SLOTS];
/*******************************************************************************
Routine:
initialise
Purpose:
Creates new private table for VIPC616 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional address and size of the card memory space. A private table
is created for this card which is a 2-D array of pointers to the base
addresses of the various accessible parts of each IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at
the start is optional), optionally followed by a comma and another
hex number, and possibly then another comma and a decimal integer.
The first number is the I/O base address of the card in the VME A16
address space (the factory default is 0x6000). If the second
number is present without the third number, this is the base
address of the module memory space in the VME A32 address space
(the address can then only have non-zero bits in A25 to A31; each
IP module is allocated 8Mb for memory in the VME A32 space). If a
third number is present, the second number is the card memory
address within the VME A24 address space and the third number is
the size in Kbytes allocated to each IP module. Legal memory size
values are 0, 64?, 128, 256, 512, 1024 or 2048. This memory size
interacts with the memory base address such that it is possible to
exclude memory from the lower slots while still providing access to
memory in the later slots by adjusting the base address suitably.
The factory default for the memory base address of the VIPC616 is
0xD0000000 (A32 space). If the memory address parameters are omitted
then none of the IP modules on the carrier provide any memory space.
Examples:
"0x6000"
This indicates that the carrier board has its I/O base set to
0x6000, and none of the slots provide memory space.
"1000,80000000"
Here the I/O base is set to 0x1000, and the module memory areas
are accessible in A32 space starting at 0x80000000, so IP module
A memory starts at 0x80000000, module B at 0x80800000, module C
at 0x81000000 and D at 0x81800000.
"6000,700000,1024"
The I/O base is at 0x6000, and the carrier memory base is in A24
space at 0x700000. However because the memory size is set to
1024 Kbytes, modules A, B and C cannot be selected (1024 K =
0x100000, so they are decoded at 0x400000, 0x500000 and 0x600000
but can't be accessed because these are below the memory base
address).
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
int params, ioStatus, memStatus = OK, mSize = 0;
ulong_t ioBase, mOrig, mBase, addr;
ushort_t space, slot;
private_t *private;
static const int offset[IO_SPACES][SLOTS] = {
{
PROM_A, PROM_B, PROM_C, PROM_D
}, {
REGS_A, REGS_B, REGS_C, REGS_D
}
};
if (cardParams == NULL ||
strlen(cardParams) == 0) {
/* No params or empty string, use manufacturers default settings */
ioBase = 0x6000;
mBase = 0xd0000000;
params = 2; /* Pretend, mBase is in A32 space */
} else {
params = sscanf(cardParams, "%p,%p,%i",
(void **) &ioBase, (void **) &mBase, &mSize);
if (params < 1 || params > 3 ||
ioBase > 0xfc00 || ioBase & 0x03ff ||
(params == 2 && mBase & 0x01ffffff) ||
(params == 3 && mBase & 0xff01ffff) ||
mSize < 0 || mSize > 2048 || mSize & 63) {
return S_IPAC_badAddress;
}
}
ioStatus = sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO,
(char *) ioBase, (char **) &ioBase);
if (params == 1) {
/* No memory, just the A16 I/O space */
mSize = 0;
mOrig = 0;
} else if (params == 2) {
/* A32 space, 8Mb allocated per module */
memStatus = sysBusToLocalAdrs(VME_AM_EXT_SUP_DATA,
(char *) mBase, (char **) &mBase);
mSize = 8 << 20;
mOrig = mBase;
} else {
/* A24 space, variable size per module */
memStatus = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,
(char *) mBase, (char **) &mBase);
mSize = mSize << 10; /* Convert size from K to Bytes */
mOrig = mBase & ~(mSize * SLOTS - 1);
}
if (ioStatus || memStatus) {
return S_IPAC_badAddress;
}
private = malloc(sizeof (private_t));
for (space = 0; space < IO_SPACES; space++) {
for (slot = 0; slot < SLOTS; slot++) {
(*private)[space][slot] = (void *) (ioBase + offset[space][slot]);
}
}
for (slot = 0; slot < SLOTS; slot++) {
(*private)[ipac_addrIO32][slot] = NULL;
addr = mOrig + (mSize * slot);
if ((mSize == 0) || (addr < mBase)) {
(*private)[ipac_addrMem][slot] = NULL;
} else {
(*private)[ipac_addrMem][slot] = (void *) addr;
}
}
*pprivate = private;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialise routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (*(private_t *) private)[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
VMEbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level,
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
static const int irqLevel[SLOTS][IPAC_IRQS] = {
{
IRQ_A0, IRQ_A1
}, {
IRQ_B0, IRQ_B1
}, {
IRQ_C0, IRQ_C1
}, {
IRQ_D0, IRQ_D1
}
};
switch (cmd) {
case ipac_irqGetLevel:
return irqLevel[slot][irqNumber];
case ipac_irqEnable:
sysIntEnable(irqLevel[slot][irqNumber]);
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t vipc616 = {
"GreenSpring VIPC616",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvIpac/ipacRelease.html 0100644 0000621 0000620 00000014506 06750344736 014555 0 ustar epics epics
IPAC Driver Release Notes
IPAC Driver Release Notes
Andrew Johnson
This document describes the changes which have been made to my vxWorks/EPICS
IndustryPack driver as it has evolved since first release. The earliest
version appears at the bottom, with more recent releases above it. These
release notes first appeared in version 1.1b, but I have attempted to describe
the differences in previous releases where I can remember or discover what they
were by examining the source code. After version 1.1c the CANbus driver was
split off as a separate package, hence the CANbus related comments up to and
including this release.
Version 2.1
Added:
- Jeff Hill's driver for the GreenSpring ATC40 ISAbus carrier board,
including the changes to the main drvIpac software and the other carrier
drivers which are necessary to support little-endian architectures. These
were:
- Make all accesses to the IP module registers & ID-Prom use short
(16-bit) read/writes, so the endian problem disappears. Adjust various
internal routines to allow for this.
- Provide a new function pointer (optional may be NULL) in the carrier table
which is called by ipmIntConnect. Module drivers should use ipmIntConnect
instead of vxWorks' intConnect routine to attach their interrupt routine. This
makes it possible to use identical module driver code on both VME and ISA bus,
although the module driver may have to be modified as discussed above to resolve
the endian issue.
- Some printf()<.TT>s in ipacAddCarrier so failures can be seen without
having to look at the return value.
- SRCS.c entries to Makefile.Vx so gnumake depends works.
Bug fixed:
- Finally tested the MVME162 carrier driver myself - this also works on the
MVME172. Changed the checking of m= addresses to use unsigned arithmetic.
Version 2.0
First version since I joined APS, now restructured as an EPICS R3.13.1
<supporttop> application. CANbus (drvTip810) and other drivers can be
built in separate application directories inside this <supporttop>.
Added:
- Peregrine McGehee's GreenSpring Octal Serial Driver from CFHT.
Bug fixed:
- Gcc compilation warnings attended to.
Version 1.1c
Final release from the RGO. CVS repository transferred to APS.
Bugs fixed, courtesy Carl Lionberger at LBL:
- Reinstated Bus Errors causing records to go into alarm state with an
INVALID_ALARM severity. A Bus Off transition is very rarely if ever
seen, even pulling the cable only causes a Bus Error.
Version 1.1b
Added:
- Carl Lionberger's code to canIoParse which allows message IDs to
be calculated as the sum of several numbers. This makes it possible to use
templates and text substitution to generate IDs for many device types.
- These release notes.
Bugs fixed:
- Changed the alarm severities for Bus Off events and Timeouts to use the
EPICS standard INVALID_ALARM severity. A Bus Error now does not affect record
alarm state or severities at all, but now all Bus Status transitions cause a
message to be logged giving the bus name and the new status.
- ao, bo, mbbo and mbboDirect records which have a constant value in their
DOL field will now initialise properly to this value - the DOL values copied
into the VAL field by the record initialisation routines were being overwritten
as the CAN device support init_record routine was returning
OK => 0 => CONVERT, rather than the correct
DO_NOT_CONVERT => 2.
Version 1.1a
Bugs fixed:
- Removed a change introduced in V1.1 to when input records are processed on
the receipt of interrupts. Carl Lionberger discovered this change had been a
mistake...
- VIPC616 carrier driver address parameter handling fixed, changes courtesy
of Christoper Estes.
Version 1.1
Added:
- devBoTip810 device support to allow CANbus status bits to be tested from
within the EPICS database.
- Support files for EPICS R3.13 (.dbd, LIBOBJS). Not tested though.
- canMsgDelete routine which allows message callbacks to be
unregistered.
- canBusReset, canBusStop and canBusRestart
routines to allow resetting and halting bus operations.
- Usage instructions output by canTest if no bus name given.
- IPAC Prom structure declared volatile to prevent gcc optimiser from
generating long word accesses to it. Only affected some carrier boards.
- Separate support for both the GreenSpring VIPC610 and VIPC610-01 carrier
boards, which have different interrupt levels for the different slot
interrupts. From now on the correct carrier table vipc610 or
vipc610_01 should be used.
- Support for the the GreenSpring VIPC616 carrier board, table name
vipc616.
- HTML Documentation distributed in the release.
Bugs fixed:
- Significantly reduced the number of callbacks used in the event of a Bus
Error or Bus Off event, by only using one per device support type rather than
one per record. This solves the large number of "callback ring buffer full"
messages which would occur whenever a CANbus with a lot of I/O on it was
disconnected.
- RTR messages are now sent with length 8 rather than 0 as some CAN devices
use the RTR length field to indicate how long a message to return.
- Tip810 Interrupt was not enabled using correct drvIpac call and would not
work on some carrier boards.
- A Bus-Off event sometimes caused all further transmissions to be disabled
forever because transmit semaphore was not released when resetting the error.
Version 1.0
First full release.
drvIpac/ipic.h 0100644 0000621 0000620 00000003172 06352262367 012541 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
ipic.h
Description:
IndustryPack Interface Controller ASIC header file, giving the register
layout and programming model for the IPIC chip used on the MVME162.
Author:
Andrew Johnson
Created:
6 July 1995
Version:
ipic.h,v 1.2 1997/06/19 16:57:27 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef INCipicH
#define INCipicH
#include
#ifdef __cplusplus
extern "C" {
#endif
/* Chip Registers */
#define IPIC_CHIP_ID 0x23
#define IPIC_CHIP_REVISION 0x00
/* Interrupt Control Register bits */
#define IPIC_INT_LEVEL 0x07
#define IPIC_INT_ICLR 0x08
#define IPIC_INT_IEN 0x10
#define IPIC_INT_INT 0x20
#define IPIC_INT_EDGE 0x40
#define IPIC_INT_PLTY 0x80
/* General Control Registers bits */
#define IPIC_GEN_MEN 0x01
#define IPIC_GEN_WIDTH 0x0c
#define IPIC_GEN_WIDTH_8 0x04
#define IPIC_GEN_WIDTH_16 0x08
#define IPIC_GEN_WIDTH_32 0x00
#define IPIC_GEN_RT 0x30
#define IPIC_GEN_RT_0 0x00
#define IPIC_GEN_RT_2 0x10
#define IPIC_GEN_RT_4 0x20
#define IPIC_GEN_RT_8 0x30
#define IPIC_GEN_ERR 0x80
/* IP Reset register bits */
#define IPIC_IP_RESET 0x01
/* Chip Structure */
typedef volatile struct {
uchar_t chipId;
uchar_t chipRevision;
uchar_t reserved1[2];
ushort_t memBase[4];
uchar_t memSize[4];
uchar_t intCtrl[4][2];
uchar_t genCtrl[4];
uchar_t reserved2[3];
uchar_t ipReset;
} ipic_t;
#ifdef __cplusplus
}
#endif
#endif /* INCipicH */
drvIpac/drvVipc616_01.c 0100600 0000621 0000620 00000020566 07271622521 013751 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvVipc616_01.c
Description:
IPAC Carrier Driver for the GreenSpring VIPC616-01 Quad IndustryPack
Carrier VME board, provides the interface between IPAC driver and the
hardware. This carrier is 6U high and can support VME Extended mode
addresses, but not 32-bit access to dual-slot IP modules. Note the
VIPC616-01 fixes the IRQ levels to be equivalent to two VIPC310
carriers, different to the VIPC616.
Author:
Andrew Johnson
Created:
8 March 2001
Version:
$Id: drvVipc616_01.c,v 1.1 2001/03/08 19:43:39 anj Exp $
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include "drvIpac.h"
/* Characteristics of the card */
#define SLOTS 4
#define IO_SPACES 2 /* Address spaces in A16 */
#define IPAC_IRQS 2 /* Interrupts per module */
/* Offsets from base address in VME A16 */
#define REGS_A 0x0000
#define PROM_A 0x0080
#define REGS_B 0x0100
#define PROM_B 0x0180
#define REGS_C 0x0200
#define PROM_C 0x0280
#define REGS_D 0x0300
#define PROM_D 0x0380
/* VME Interrupt levels for -01 option */
#define IRQ_A0 4
#define IRQ_A1 5
#define IRQ_B0 2
#define IRQ_B1 1
#define IRQ_C0 4
#define IRQ_C1 5
#define IRQ_D0 2
#define IRQ_D1 1
/* Carrier Private structure type, one instance per board */
typedef void* private_t[IPAC_ADDR_SPACES][SLOTS];
/*******************************************************************************
Routine:
initialise
Purpose:
Creates new private table for VIPC616 at addresses given by cardParams
Description:
Checks the parameter string for the address of the card I/O space and
optional address and size of the card memory space. A private table
is created for this card which is a 2-D array of pointers to the base
addresses of the various accessible parts of each IP module.
Parameters:
The parameter string should comprise a hex number (the 0x or 0X at
the start is optional), optionally followed by a comma and another
hex number, and possibly then another comma and a decimal integer.
The first number is the I/O base address of the card in the VME A16
address space (the factory default is 0x6000). If the second
number is present without the third number, this is the base
address of the module memory space in the VME A32 address space
(the address can then only have non-zero bits in A25 to A31; each
IP module is allocated 8Mb for memory in the VME A32 space). If a
third number is present, the second number is the card memory
address within the VME A24 address space and the third number is
the size in Kbytes allocated to each IP module. Legal memory size
values are 0, 64?, 128, 256, 512, 1024 or 2048. This memory size
interacts with the memory base address such that it is possible to
exclude memory from the lower slots while still providing access to
memory in the later slots by adjusting the base address suitably.
The factory default for the memory base address of the VIPC616 is
0xD0000000 (A32 space). If the memory address parameters are omitted
then none of the IP modules on the carrier provide any memory space.
Examples:
"0x6000"
This indicates that the carrier board has its I/O base set to
0x6000, and none of the slots provide memory space.
"1000,80000000"
Here the I/O base is set to 0x1000, and the module memory areas
are accessible in A32 space starting at 0x80000000, so IP module
A memory starts at 0x80000000, module B at 0x80800000, module C
at 0x81000000 and D at 0x81800000.
"6000,700000,1024"
The I/O base is at 0x6000, and the carrier memory base is in A24
space at 0x700000. However because the memory size is set to
1024 Kbytes, modules A, B and C cannot be selected (1024 K =
0x100000, so they are decoded at 0x400000, 0x500000 and 0x600000
but can't be accessed because these are below the memory base
address).
Returns:
0 = OK,
S_IPAC_badAddress = Parameter string error, or address not reachable
*/
LOCAL int initialise (
char *cardParams,
void **pprivate,
ushort_t carrier
) {
int params, ioStatus, memStatus = OK, mSize = 0;
ulong_t ioBase, mOrig, mBase, addr;
ushort_t space, slot;
private_t *private;
static const int offset[IO_SPACES][SLOTS] = {
{
PROM_A, PROM_B, PROM_C, PROM_D
}, {
REGS_A, REGS_B, REGS_C, REGS_D
}
};
if (cardParams == NULL ||
strlen(cardParams) == 0) {
/* No params or empty string, use manufacturers default settings */
ioBase = 0x6000;
mBase = 0xd0000000;
params = 2; /* Pretend, mBase is in A32 space */
} else {
params = sscanf(cardParams, "%p,%p,%i",
(void **) &ioBase, (void **) &mBase, &mSize);
if (params < 1 || params > 3 ||
ioBase > 0xfc00 || ioBase & 0x03ff ||
(params == 2 && mBase & 0x01ffffff) ||
(params == 3 && mBase & 0xff01ffff) ||
mSize < 0 || mSize > 2048 || mSize & 63) {
return S_IPAC_badAddress;
}
}
ioStatus = sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO,
(char *) ioBase, (char **) &ioBase);
if (params == 1) {
/* No memory, just the A16 I/O space */
mSize = 0;
mOrig = 0;
} else if (params == 2) {
/* A32 space, 8Mb allocated per module */
memStatus = sysBusToLocalAdrs(VME_AM_EXT_SUP_DATA,
(char *) mBase, (char **) &mBase);
mSize = 8 << 20;
mOrig = mBase;
} else {
/* A24 space, variable size per module */
memStatus = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,
(char *) mBase, (char **) &mBase);
mSize = mSize << 10; /* Convert size from K to Bytes */
mOrig = mBase & ~(mSize * SLOTS - 1);
}
if (ioStatus || memStatus) {
return S_IPAC_badAddress;
}
private = malloc(sizeof (private_t));
for (space = 0; space < IO_SPACES; space++) {
for (slot = 0; slot < SLOTS; slot++) {
(*private)[space][slot] = (void *) (ioBase + offset[space][slot]);
}
}
for (slot = 0; slot < SLOTS; slot++) {
(*private)[ipac_addrIO32][slot] = NULL;
addr = mOrig + (mSize * slot);
if ((mSize == 0) || (addr < mBase)) {
(*private)[ipac_addrMem][slot] = NULL;
} else {
(*private)[ipac_addrMem][slot] = (void *) addr;
}
}
*pprivate = private;
return OK;
}
/*******************************************************************************
Routine:
baseAddr
Purpose:
Returns the base address for the requested slot & address space
Description:
Because we did all that hard work in the initialise routine, this
routine only has to do a table lookup in the private array.
Note that no parameter checking is required - the IPAC driver which
calls this routine handles that.
Returns:
The requested address, or NULL if the module has no memory.
*/
LOCAL void *baseAddr (
void *private,
ushort_t slot,
ipac_addr_t space
) {
return (*(private_t *) private)[space][slot];
}
/*******************************************************************************
Routine:
irqCmd
Purpose:
Handles interrupter commands and status requests
Description:
The GreenSpring board is limited to fixed interrupt levels, and has
no control over interrupts. The only commands thus supported are
a request of the interrupt level associated with a particular slot
and interrupt number, or to enable interrupts by making sure the
VMEbus interrupter is listening on the necessary level.
Returns:
ipac_irqGetLevel returns the interrupt level,
ipac_irqEnable returns 0 = OK,
other calls return S_IPAC_notImplemented.
*/
LOCAL int irqCmd (
void *private,
ushort_t slot,
ushort_t irqNumber,
ipac_irqCmd_t cmd
) {
static const int irqLevel[SLOTS][IPAC_IRQS] = {
{
IRQ_A0, IRQ_A1
}, {
IRQ_B0, IRQ_B1
}, {
IRQ_C0, IRQ_C1
}, {
IRQ_D0, IRQ_D1
}
};
switch (cmd) {
case ipac_irqGetLevel:
return irqLevel[slot][irqNumber];
case ipac_irqEnable:
sysIntEnable(irqLevel[slot][irqNumber]);
return OK;
default:
return S_IPAC_notImplemented;
}
}
/******************************************************************************/
/* IPAC Carrier Table */
ipac_carrier_t vipc616_01 = {
"GreenSpring VIPC616-01",
SLOTS,
initialise,
NULL,
baseAddr,
irqCmd,
NULL
};
drvTip810/Makefile 0100644 0000621 0000620 00000000112 06671026116 013176 0 ustar epics epics TOP=..
include $(TOP)/config/CONFIG_APP
include $(TOP)/config/RULES_ARCHS
drvTip810/Makefile.Host 0100644 0000621 0000620 00000000342 06671026117 014120 0 ustar epics epics # Makefile.Host
TOP = ../..
include $(TOP)/config/CONFIG_APP
DBDINSTALL += devTip810.dbd
INC += canBus.h
HTMLS_DIR = .
HTMLS += devCan.html
HTMLS += drvTip810.html
HTMLS += canRelease.html
include $(TOP)/config/RULES.Host
drvTip810/Makefile.Vx 0100644 0000621 0000620 00000001156 06747665342 013621 0 ustar epics epics # Makefile.Vx
TOP = ../..
include $(TOP)/config/CONFIG_APP
SRCS.c += ../devAiCan.c
SRCS.c += ../devAoCan.c
SRCS.c += ../devBiCan.c
SRCS.c += ../devBoCan.c
SRCS.c += ../devMbbiCan.c
SRCS.c += ../devMbboCan.c
SRCS.c += ../devMbbiDirectCan.c
SRCS.c += ../devMbboDirectCan.c
SRCS.c += ../devBiTip810.c
SRCS.c += ../drvTip810.c
LIBOBJS += devAiCan.o
LIBOBJS += devAoCan.o
LIBOBJS += devBiCan.o
LIBOBJS += devBoCan.o
LIBOBJS += devMbbiCan.o
LIBOBJS += devMbboCan.o
LIBOBJS += devMbbiDirectCan.o
LIBOBJS += devMbboDirectCan.o
LIBOBJS += devBiTip810.o
LIBOBJS += drvTip810.o
LIBNAME = tip810Lib
include $(TOP)/config/RULES.Vx
drvTip810/canBus.h 0100644 0000621 0000620 00000004270 06533542272 013136 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
canBus.h
Description:
CANBUS specific constants
Author:
Andrew Johnson
Created:
25 July 1995
Version:
canBus.h,v 1.5 1998/05/29 14:22:50 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef INCcanBusH
#define INCcanBusH
#define CAN_IDENTIFIERS 2048
#define CAN_DATA_SIZE 8
#define CAN_BUS_OK 0
#define CAN_BUS_ERROR 1
#define CAN_BUS_OFF 2
#ifndef M_can
#define M_can (811<<16)
#endif
#define S_can_badMessage (M_can| 1) /*illegal CAN message contents*/
#define S_can_badAddress (M_can| 2) /*CAN address syntax error*/
#define S_can_noDevice (M_can| 3) /*CAN bus name does not exist*/
#define S_can_noMessage (M_can| 4) /*no matching CAN message callback*/
typedef struct {
ushort_t identifier; /* 0 .. 2047 with holes! */
enum {
SEND = 0, RTR = 1
} rtr; /* Remote Transmission Request */
uchar_t length; /* 0 .. 8 */
uchar_t data[CAN_DATA_SIZE];
} canMessage_t;
typedef struct {
char *busName;
int timeout;
ushort_t identifier;
ushort_t offset;
signed int parameter;
void *canBusID;
} canIo_t;
typedef void canMsgCallback_t(void *pprivate, canMessage_t *pmessage);
typedef void canSigCallback_t(void *pprivate, int status);
extern int canSilenceErrors; /* Really meant for EPICS use only */
extern int canOpen(const char *busName, void **pcanBusID);
extern int canBusReset(const char *busName);
extern int canBusStop(const char *busName);
extern int canBusRestart(const char *busName);
extern int canRead(void *canBusID, canMessage_t *pmessage, int timeout);
extern int canWrite(void *canBusID, canMessage_t *pmessage, int timeout);
extern int canMessage(void *canBusID, ushort_t identifier,
canMsgCallback_t callback, void *pprivate);
extern int canMsgDelete(void *canBusID, ushort_t identifier,
canMsgCallback_t callback, void *pprivate);
extern int canSignal(void *canBusID, canSigCallback_t callback, void *pprivate);
extern int canIoParse(char *canString, canIo_t *pcanIo);
#endif /* INCcanBusH */
drvTip810/canRelease.html 0100644 0000621 0000620 00000013076 06751406040 014500 0 ustar epics epics
CANbus Driver Release Notes
CANbus Driver Release Notes
Andrew Johnson
This document describes the changes which have been made to my vxWorks/EPICS
CANbus driver as it has evolved since first release. The earliest version
appears at the bottom, with more recent releases above it. These release notes
first appeared in version 1.1b, but I have attempted to describe the
differences in previous releases where I can remember or discover what they
were by examining the source code. After version 1.1c the IPAC driver was
split off as a separate package (although this is still required for use by the
Tip810 driver), hence the IPAC related comments up to and including this
release.
Version 2.1
Incorporated changes from Carl Lionberger that move the receive message
callbacks into a fairly high priority task, rather than doing them in the ISR
itself. This allows a higher peak message rate to be handled, although the
average rate should be essentially unchanged.
Added:
- ISR funnels incoming CAN packets into a Message queue rather than run the
canMessage callbacks directly. NB: The canSignal callbacks
are still run from the ISR so must be interrupt-safe.
Version 2.0
First version since I joined APS, now restructured in an EPICS R3.13.1
<supporttop> application. drvIpac module and carrier drivers were split
off into a separate application directory.
Bug fixed:
- Gcc compilation warnings attended to.
Version 1.1c
Final release from the RGO. CVS repository transferred to APS.
Bugs fixed, courtesy Carl Lionberger at LBL:
- Reinstated Bus Errors causing records to go into alarm state with an
INVALID_ALARM severity. A Bus Off transition is very rarely if ever
seen, even pulling the cable only causes a Bus Error.
Version 1.1b
Added:
- Carl Lionberger's code to canIoParse which allows message IDs to
be calculated as the sum of several numbers. This makes it possible to use
templates and text substitution to generate IDs for many device types.
- These release notes.
Bugs fixed:
- Changed the alarm severities for Bus Off events and Timeouts to use the
EPICS standard INVALID_ALARM severity. A Bus Error now does not affect record
alarm state or severities at all, but now all Bus Status transitions cause a
message to be logged giving the bus name and the new status.
- ao, bo, mbbo and mbboDirect records which have a constant value in their
DOL field will now initialise properly to this value - the DOL values copied
into the VAL field by the record initialisation routines were being overwritten
as the CAN device support init_record routine was returning
OK => 0 => CONVERT, rather than the correct
DO_NOT_CONVERT => 2.
Version 1.1a
Bugs fixed:
- Removed a change introduced in V1.1 to when input records are processed on
the receipt of interrupts. Carl Lionberger discovered this change had been a
mistake...
- VIPC616 carrier driver address parameter handling fixed, changes courtesy
of Christoper Estes.
Version 1.1
Added:
- devBoTip810 device support to allow CANbus status bits to be tested from
within the EPICS database.
- Support files for EPICS R3.13 (.dbd, LIBOBJS). Not tested though.
- canMsgDelete routine which allows message callbacks to be
unregistered.
- canBusReset, canBusStop and canBusRestart
routines to allow resetting and halting bus operations.
- Usage instructions output by canTest if no bus name given.
- IPAC Prom structure declared volatile to prevent gcc optimiser from
generating long word accesses to it. Only affected some carrier boards.
- Separate support for both the GreenSpring VIPC610 and VIPC610-01 carrier
boards, which have different interrupt levels for the different slot
interrupts. From now on the correct carrier table vipc610 or
vipc610_01 should be used.
- Support for the the GreenSpring VIPC616 carrier board, table name
vipc616.
- HTML Documentation distributed in the release.
Bugs fixed:
- Significantly reduced the number of callbacks used in the event of a Bus
Error or Bus Off event, by only using one per device support type rather than
one per record. This solves the large number of "callback ring buffer full"
messages which would occur whenever a CANbus with a lot of I/O on it was
disconnected.
- RTR messages are now sent with length 8 rather than 0 as some CAN devices
use the RTR length field to indicate how long a message to return.
- Tip810 Interrupt was not enabled using correct drvIpac call and would not
work on some carrier boards.
- A Bus-Off event sometimes caused all further transmissions to be disabled
forever because transmit semaphore was not released when resetting the error.
Version 1.0
First full release.
drvTip810/devAiCan.c 0100644 0000621 0000620 00000024346 06671026117 013374 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devAiCan.c
Description:
CANBUS Analogue Input device support
Author:
Andrew Johnson
Created:
8 August 1995
Version:
devAiCan.c,v 1.8 1999/03/08 20:00:15 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CONVERT 0
#define DO_NOT_CONVERT 2
typedef struct aiCanPrivate_s {
CALLBACK callback; /* This *must* be first member */
struct aiCanPrivate_s *nextPrivate;
WDOG_ID wdId;
IOSCANPVT ioscanpvt;
struct aiRecord *prec;
canIo_t inp;
ulong_t mask;
ulong_t sign;
long data;
int status;
} aiCanPrivate_t;
typedef struct aiCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct aiCanBus_s *nextBus;
aiCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} aiCanBus_t;
LOCAL long init_ai(struct aiRecord *prec);
LOCAL long get_ioint_info(int cmd, struct aiRecord *prec, IOSCANPVT *ppvt);
LOCAL long read_ai(struct aiRecord *prec);
LOCAL long special_linconv(struct aiRecord *prec, int after);
LOCAL void aiProcess(aiCanPrivate_t *pcanAi);
LOCAL void aiMessage(aiCanPrivate_t *pcanAi, canMessage_t *pmessage);
LOCAL void busSignal(aiCanBus_t *pbus, int status);
LOCAL void busCallback(aiCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_ai;
DEVSUPFUN special_linconv;
} devAiCan = {
6,
NULL,
NULL,
init_ai,
get_ioint_info,
read_ai,
special_linconv
};
LOCAL aiCanBus_t *firstBus;
LOCAL long init_ai (
struct aiRecord *prec
) {
aiCanPrivate_t *pcanAi;
aiCanBus_t *pbus;
int status;
ulong_t fsd;
if (prec->inp.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devAiCan (init_record) Illegal INP field");
return S_db_badField;
}
pcanAi = (aiCanPrivate_t *) malloc(sizeof(aiCanPrivate_t));
if (pcanAi == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanAi;
pcanAi->prec = prec;
pcanAi->ioscanpvt = NULL;
pcanAi->status = NO_ALARM;
/* Convert the address string into members of the canIo structure */
status = canIoParse(prec->inp.value.instio.string, &pcanAi->inp);
if (status) {
if (canSilenceErrors) {
pcanAi->inp.canBusID = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devAiCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("aiCan %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanAi->inp.busName, pcanAi->inp.identifier,
pcanAi->inp.offset, pcanAi->inp.parameter);
#endif
/* For ai records, the final parameter specifies the raw input size.
eg 0xff or 0x100 specify an 8-bit unsigned value. -ve numbers
specify a signed value, eg -4095 means a 12-bit signed value.
The range does not have to be a power of two, eg 99 is legal. */
fsd = abs(pcanAi->inp.parameter);
if (fsd > 0) {
if ((fsd & (fsd-1)) == 0) {
fsd--;
}
/* Make a mask to contain only the valid input bits based on fsd */
pcanAi->mask = 1;
while (pcanAi->mask < fsd) {
pcanAi->mask <<= 1;
}
pcanAi->mask--;
if (pcanAi->inp.parameter < 0) {
/* signed: rval = sign-extend(data & mask) */
pcanAi->sign = (pcanAi->mask >> 1) + 1;
} else {
/* unsigned: rval = data & mask */
pcanAi->sign = 0;
}
if (prec->linr == 1) {
prec->roff = pcanAi->sign;
prec->eslo = (prec->eguf - prec->egul) / fsd;
} else {
prec->roff = 0;
}
} else {
pcanAi->mask = pcanAi->sign = 0;
}
#ifdef DEBUG
printf(" fsd=%d, eslo=%g, roff = %d, mask=%#x, sign=%d\n",
fsd, prec->eslo, prec->roff, pcanAi->mask, pcanAi->sign);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanAi->inp.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (aiCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanAi->inp.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanAi->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanAi;
/* Set the callback parameters for asynchronous processing */
callbackSetCallback((VOIDFUNCPTR) aiProcess, &pcanAi->callback);
callbackSetPriority(prec->prio, &pcanAi->callback);
/* and create a watchdog for CANbus RTR timeouts */
pcanAi->wdId = wdCreate();
if (pcanAi->wdId == NULL) {
return S_dev_noMemory;
}
/* Register the message handler with the Canbus driver */
canMessage(pcanAi->inp.canBusID, pcanAi->inp.identifier,
(canMsgCallback_t *) aiMessage, pcanAi);
return OK;
}
LOCAL long get_ioint_info (
int cmd,
struct aiRecord *prec,
IOSCANPVT *ppvt
) {
aiCanPrivate_t *pcanAi = (aiCanPrivate_t *) prec->dpvt;
if (pcanAi->ioscanpvt == NULL) {
scanIoInit(&pcanAi->ioscanpvt);
}
#ifdef DEBUG
printf("canAi %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanAi->ioscanpvt;
return OK;
}
LOCAL long read_ai (
struct aiRecord *prec
) {
aiCanPrivate_t *pcanAi = (aiCanPrivate_t *) prec->dpvt;
if (pcanAi->inp.canBusID == NULL) {
return DO_NOT_CONVERT;
}
#ifdef DEBUG
printf("canAi %s: read_ai status=%#x\n", prec->name, pcanAi->status);
#endif
switch (pcanAi->status) {
case TIMEOUT_ALARM:
case COMM_ALARM:
recGblSetSevr(prec, pcanAi->status, INVALID_ALARM);
pcanAi->status = NO_ALARM;
return DO_NOT_CONVERT;
case NO_ALARM:
if (prec->pact || prec->scan == SCAN_IO_EVENT) {
#ifdef DEBUG
printf("canAi %s: message id=%#x, data=%#x\n",
prec->name, pcanAi->inp.identifier, pcanAi->data);
#endif
prec->rval = pcanAi->data & pcanAi->mask;
if (pcanAi->sign & prec->rval) {
prec->rval |= ~pcanAi->mask;
}
return CONVERT;
} else {
canMessage_t message;
message.identifier = pcanAi->inp.identifier;
message.rtr = RTR;
message.length = 8;
#ifdef DEBUG
printf("canAi %s: RTR, id=%#x\n",
prec->name, pcanAi->inp.identifier);
#endif
prec->pact = TRUE;
pcanAi->status = TIMEOUT_ALARM;
callbackSetPriority(prec->prio, &pcanAi->callback);
wdStart(pcanAi->wdId, pcanAi->inp.timeout,
(FUNCPTR) callbackRequest, (int) pcanAi);
canWrite(pcanAi->inp.canBusID, &message, pcanAi->inp.timeout);
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanAi->status = NO_ALARM;
return DO_NOT_CONVERT;
}
}
LOCAL long special_linconv (
struct aiRecord *prec,
int after
) {
if (after) {
if (prec->linr == 1) {
ulong_t fsd;
aiCanPrivate_t *pcanAi = (aiCanPrivate_t *) prec->dpvt;
fsd = abs(pcanAi->inp.parameter);
if (fsd > 0) {
if ((fsd & (fsd-1)) == 0) {
fsd--;
}
prec->roff = pcanAi->sign;
prec->eslo = (prec->eguf - prec->egul) / fsd;
}
} else {
prec->roff = 0;
}
}
return 0;
}
LOCAL void aiProcess (
aiCanPrivate_t *pcanAi
) {
dbScanLock((struct dbCommon *) pcanAi->prec);
(*((struct rset *) pcanAi->prec->rset)->process)(pcanAi->prec);
dbScanUnlock((struct dbCommon *) pcanAi->prec);
}
LOCAL void aiMessage (
aiCanPrivate_t *pcanAi,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pmessage->rtr == RTR) {
return; /* Ignore RTRs */
}
if (pcanAi->mask == 0) {
pcanAi->data = 0;
} else if (pcanAi->mask <= 0xff) {
pcanAi->data = pmessage->data[pcanAi->inp.offset+0];
} else if (pcanAi->mask <= 0xffff) {
pcanAi->data = pmessage->data[pcanAi->inp.offset+0] << 8 |
pmessage->data[pcanAi->inp.offset+1];
} else if (pcanAi->mask <= 0xffffff) {
pcanAi->data = pmessage->data[pcanAi->inp.offset+0] << 16 |
pmessage->data[pcanAi->inp.offset+1] << 8 |
pmessage->data[pcanAi->inp.offset+2];
} else {
pcanAi->data = pmessage->data[pcanAi->inp.offset+0] << 24 |
pmessage->data[pcanAi->inp.offset+1] << 16 |
pmessage->data[pcanAi->inp.offset+2] << 8 |
pmessage->data[pcanAi->inp.offset+3];
}
if (pcanAi->prec->scan == SCAN_IO_EVENT) {
pcanAi->status = NO_ALARM;
scanIoRequest(pcanAi->ioscanpvt);
} else if (pcanAi->status == TIMEOUT_ALARM) {
pcanAi->status = NO_ALARM;
wdCancel(pcanAi->wdId);
callbackRequest(&pcanAi->callback);
}
}
LOCAL void busSignal (
aiCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devAiCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devAiCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devAiCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
aiCanBus_t *pbus
) {
aiCanPrivate_t *pcanAi = pbus->firstPrivate;
while (pcanAi != NULL) {
pcanAi->status = pbus->status;
aiProcess(pcanAi);
pcanAi = pcanAi->nextPrivate;
}
}
drvTip810/devAoCan.c 0100644 0000621 0000620 00000022303 06671026117 013371 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devAoCan.c
Description:
CANBUS Analogue Output device support
Author:
Andrew Johnson
Created:
9 August 1995
Version:
devAoCan.c,v 1.6 1999/03/08 20:00:15 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DO_NOT_CONVERT 2
typedef struct aoCanPrivate_s {
struct aoCanPrivate_s *nextPrivate;
IOSCANPVT ioscanpvt;
struct aoRecord *prec;
canIo_t out;
ulong_t mask;
ulong_t sign;
long data;
int status;
} aoCanPrivate_t;
typedef struct aoCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct aoCanBus_s *nextBus;
aoCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} aoCanBus_t;
LOCAL long init_ao(struct aoRecord *prec);
LOCAL long get_ioint_info(int cmd, struct aoRecord *prec, IOSCANPVT *ppvt);
LOCAL long write_ao(struct aoRecord *prec);
LOCAL long special_linconv(struct aoRecord *prec, int after);
LOCAL void aoMessage(aoCanPrivate_t *pcanAo, canMessage_t *pmessage);
LOCAL void busSignal(aoCanBus_t *pbus, int status);
LOCAL void busCallback(aoCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN write_ao;
DEVSUPFUN special_linconv;
} devAoCan = {
6,
NULL,
NULL,
init_ao,
get_ioint_info,
write_ao,
special_linconv
};
LOCAL aoCanBus_t *firstBus;
LOCAL long init_ao (
struct aoRecord *prec
) {
aoCanPrivate_t *pcanAo;
aoCanBus_t *pbus;
int status;
ulong_t fsd;
if (prec->out.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devAoCan (init_record) Illegal OUT field");
return S_db_badField;
}
pcanAo = (aoCanPrivate_t *) malloc(sizeof(aoCanPrivate_t));
if (pcanAo == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanAo;
pcanAo->prec = prec;
pcanAo->ioscanpvt = NULL;
pcanAo->status = NO_ALARM;
/* Convert the parameter string into members of the canIo structure */
status = canIoParse(prec->out.value.instio.string, &pcanAo->out);
if (status) {
if (canSilenceErrors) {
pcanAo->out.canBusID = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devAoCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("canAo %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanAo->out.busName, pcanAo->out.identifier,
pcanAo->out.offset, pcanAo->out.parameter);
#endif
/* For ao records, the final parameter specifies the raw output size.
eg 0xfff or 0x1000 specify a 12-bit unsigned value. -ve numbers
specify a signed value, eg -256 means an 8-bit signed value. */
fsd = abs(pcanAo->out.parameter);
if (fsd > 0) {
if ((fsd & (fsd-1)) == 0) {
fsd--;
}
/* Make a mask to contain only the valid out bits based on fsd */
pcanAo->mask = 1;
while (pcanAo->mask < fsd) {
pcanAo->mask <<= 1;
}
pcanAo->mask--;
if (pcanAo->out.parameter < 0) {
/* signed data */
pcanAo->sign = (pcanAo->mask >> 1) + 1;
} else {
pcanAo->sign = 0;
}
if (prec->linr == 1) {
prec->roff = pcanAo->sign;
prec->eslo = (prec->eguf - prec->egul) / fsd;
} else {
prec->roff = 0;
}
} else {
pcanAo->mask = pcanAo->sign = 0;
}
#ifdef DEBUG
printf(" fsd=%d, eslo=%g, roff=%d, mask=%#x, sign=%d\n",
fsd, prec->eslo, prec->roff, pcanAo->mask, pcanAo->sign);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanAo->out.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (aoCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanAo->out.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanAo->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanAo;
/* Register the message handler with the Canbus driver */
canMessage(pcanAo->out.canBusID, pcanAo->out.identifier,
(canMsgCallback_t *) aoMessage, pcanAo);
return DO_NOT_CONVERT;
}
LOCAL long get_ioint_info (
int cmd,
struct aoRecord *prec,
IOSCANPVT *ppvt
) {
aoCanPrivate_t *pcanAo = (aoCanPrivate_t *) prec->dpvt;
if (pcanAo->ioscanpvt == NULL) {
scanIoInit(&pcanAo->ioscanpvt);
}
#ifdef DEBUG
printf("aoCan %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanAo->ioscanpvt;
return OK;
}
LOCAL long write_ao (
struct aoRecord *prec
) {
aoCanPrivate_t *pcanAo = (aoCanPrivate_t *) prec->dpvt;
if (pcanAo->out.canBusID == NULL) {
return ERROR;
}
#ifdef DEBUG
printf("aoCan %s: write_ao status=%#x\n", prec->name, pcanAo->status);
#endif
switch (pcanAo->status) {
case COMM_ALARM:
recGblSetSevr(prec, pcanAo->status, INVALID_ALARM);
pcanAo->status = NO_ALARM;
return ERROR;
case NO_ALARM:
{
canMessage_t message;
int status;
message.identifier = pcanAo->out.identifier;
message.rtr = SEND;
pcanAo->data = prec->rval & pcanAo->mask;
if (pcanAo->mask == 0) {
message.length = 0;
} else if (pcanAo->mask <= 0xff) {
message.data[0] = (pcanAo->data ) & 0xff;
message.length = 1;
} else if (pcanAo->mask <= 0xffff) {
message.data[0] = (pcanAo->data >> 8) & 0xff;
message.data[1] = (pcanAo->data ) & 0xff;
message.length = 2;
} else if (pcanAo->mask <= 0xffffff) {
message.data[0] = (pcanAo->data >> 16) & 0xff;
message.data[1] = (pcanAo->data >> 8) & 0xff;
message.data[2] = (pcanAo->data ) & 0xff;
message.length = 3;
} else {
message.data[0] = (pcanAo->data >> 24) & 0xff;
message.data[1] = (pcanAo->data >> 16) & 0xff;
message.data[2] = (pcanAo->data >> 8) & 0xff;
message.data[3] = (pcanAo->data ) & 0xff;
message.length = 4;
}
#ifdef DEBUG
printf("canAo %s: SEND id=%#x, length=%d, data=%#x\n",
prec->name, message.identifier, message.length,
pcanAo->data);
#endif
status = canWrite(pcanAo->out.canBusID, &message,
pcanAo->out.timeout);
if (status) {
#ifdef DEBUG
printf("canAo %s: canWrite status=%#x\n", status);
#endif
recGblSetSevr(prec, TIMEOUT_ALARM, INVALID_ALARM);
return ERROR;
}
return OK;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanAo->status = NO_ALARM;
return ERROR;
}
}
LOCAL long special_linconv (
struct aoRecord *prec,
int after
) {
if (after) {
if (prec->linr == 1) {
ulong_t fsd;
aoCanPrivate_t *pcanAo = (aoCanPrivate_t *) prec->dpvt;
fsd = abs(pcanAo->out.parameter);
if (fsd > 0) {
if ((fsd & (fsd-1)) == 0) {
fsd--;
}
prec->roff = pcanAo->sign;
prec->eslo = (prec->eguf - prec->egul) / fsd;
}
} else {
prec->roff = 0;
}
}
return 0;
}
LOCAL void aoMessage (
aoCanPrivate_t *pcanAo,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pcanAo->prec->scan == SCAN_IO_EVENT &&
pmessage->rtr == RTR) {
pcanAo->status = NO_ALARM;
scanIoRequest(pcanAo->ioscanpvt);
}
}
LOCAL void busSignal (
aoCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devAoCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devAoCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devAoCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
aoCanBus_t *pbus
) {
aoCanPrivate_t *pcanAo = pbus->firstPrivate;
while (pcanAo != NULL) {
pcanAo->status = pbus->status;
dbScanLock((struct dbCommon *) pcanAo->prec);
(*((struct rset *) pcanAo->prec->rset)->process)(pcanAo->prec);
dbScanUnlock((struct dbCommon *) pcanAo->prec);
pcanAo = pcanAo->nextPrivate;
}
}
drvTip810/devBiCan.c 0100644 0000621 0000620 00000020232 06671026117 013363 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devBiCan.c
Description:
CANBUS Binary Input device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devBiCan.c,v 1.8 1999/03/08 20:00:15 anj Exp
(c) 1995,1997 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CONVERT 0
#define DO_NOT_CONVERT 2
typedef struct biCanPrivate_s {
CALLBACK callback; /* This *must* be first member */
struct biCanPrivate_s *nextPrivate;
WDOG_ID wdId;
IOSCANPVT ioscanpvt;
struct biRecord *prec;
canIo_t inp;
long data;
int status;
} biCanPrivate_t;
typedef struct biCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct biCanBus_s *nextBus;
biCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} biCanBus_t;
LOCAL long init_bi(struct biRecord *prec);
LOCAL long get_ioint_info(int cmd, struct biRecord *prec, IOSCANPVT *ppvt);
LOCAL long read_bi(struct biRecord *prec);
LOCAL void biProcess(biCanPrivate_t *pcanBi);
LOCAL void biMessage(biCanPrivate_t *pcanBi, canMessage_t *pmessage);
LOCAL void busSignal(biCanBus_t *pbus, int status);
LOCAL void busCallback(biCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_bi;
} devBiCan = {
5,
NULL,
NULL,
init_bi,
get_ioint_info,
read_bi
};
LOCAL biCanBus_t *firstBus;
LOCAL long init_bi (
struct biRecord *prec
) {
biCanPrivate_t *pcanBi;
biCanBus_t *pbus;
int status;
if (prec->inp.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devBiCan (init_record) Illegal INP field");
return S_db_badField;
}
pcanBi = (biCanPrivate_t *) malloc(sizeof(biCanPrivate_t));
if (pcanBi == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanBi;
pcanBi->prec = prec;
pcanBi->ioscanpvt = NULL;
pcanBi->status = NO_ALARM;
/* Convert the address string into members of the canIo structure */
status = canIoParse(prec->inp.value.instio.string, &pcanBi->inp);
if (status ||
pcanBi->inp.parameter < 0 ||
pcanBi->inp.parameter > 7) {
if (canSilenceErrors) {
pcanBi->inp.canBusID = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devBiCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("biCan %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanBi->inp.busName, pcanBi->inp.identifier,
pcanBi->inp.offset, pcanBi->inp.parameter);
#endif
/* For bi records, the final parameter specifies the input bit number,
with offset specifying the message byte number. */
prec->mask = 1 << pcanBi->inp.parameter;
#ifdef DEBUG
printf(" bit=%d, mask=%#x\n",
pcanBi->inp.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanBi->inp.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (biCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanBi->inp.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanBi->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanBi;
/* Set the callback parameters for asynchronous processing */
callbackSetCallback((VOIDFUNCPTR) biProcess, &pcanBi->callback);
callbackSetPriority(prec->prio, &pcanBi->callback);
/* and create a watchdog for CANbus RTR timeouts */
pcanBi->wdId = wdCreate();
if (pcanBi->wdId == NULL) {
return S_dev_noMemory;
}
/* Register the message handler with the Canbus driver */
canMessage(pcanBi->inp.canBusID, pcanBi->inp.identifier,
(canMsgCallback_t *) biMessage, pcanBi);
return OK;
}
LOCAL long get_ioint_info (
int cmd,
struct biRecord *prec,
IOSCANPVT *ppvt
) {
biCanPrivate_t *pcanBi = (biCanPrivate_t *) prec->dpvt;
if (pcanBi->ioscanpvt == NULL) {
scanIoInit(&pcanBi->ioscanpvt);
}
#ifdef DEBUG
printf("canBi %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanBi->ioscanpvt;
return OK;
}
LOCAL long read_bi (
struct biRecord *prec
) {
biCanPrivate_t *pcanBi = (biCanPrivate_t *) prec->dpvt;
if (pcanBi->inp.canBusID == NULL) {
return DO_NOT_CONVERT;
}
#ifdef DEBUG
printf("canBi %s: read_bi status=%#x\n", prec->name, pcanBi->status);
#endif
switch (pcanBi->status) {
case TIMEOUT_ALARM:
case COMM_ALARM:
recGblSetSevr(prec, pcanBi->status, INVALID_ALARM);
pcanBi->status = NO_ALARM;
return DO_NOT_CONVERT;
case NO_ALARM:
if (prec->pact || prec->scan == SCAN_IO_EVENT) {
#ifdef DEBUG
printf("canBi %s: message id=%#x, data=%#x\n",
prec->name, pcanBi->inp.identifier, pcanBi->data);
#endif
prec->rval = pcanBi->data & prec->mask;
return CONVERT;
} else {
canMessage_t message;
message.identifier = pcanBi->inp.identifier;
message.rtr = RTR;
message.length = 8;
#ifdef DEBUG
printf("canBi %s: RTR, id=%#x\n",
prec->name, pcanBi->inp.identifier);
#endif
prec->pact = TRUE;
pcanBi->status = TIMEOUT_ALARM;
callbackSetPriority(prec->prio, &pcanBi->callback);
wdStart(pcanBi->wdId, pcanBi->inp.timeout,
(FUNCPTR) callbackRequest, (int) pcanBi);
canWrite(pcanBi->inp.canBusID, &message, pcanBi->inp.timeout);
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanBi->status = NO_ALARM;
return DO_NOT_CONVERT;
}
}
LOCAL void biProcess (
biCanPrivate_t *pcanBi
) {
dbScanLock((struct dbCommon *) pcanBi->prec);
(*((struct rset *) pcanBi->prec->rset)->process)(pcanBi->prec);
dbScanUnlock((struct dbCommon *) pcanBi->prec);
}
LOCAL void biMessage (
biCanPrivate_t *pcanBi,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pmessage->rtr == RTR) {
return; /* Ignore RTRs */
}
pcanBi->data = pmessage->data[pcanBi->inp.offset];
if (pcanBi->prec->scan == SCAN_IO_EVENT) {
pcanBi->status = NO_ALARM;
scanIoRequest(pcanBi->ioscanpvt);
} else if (pcanBi->status == TIMEOUT_ALARM) {
pcanBi->status = NO_ALARM;
wdCancel(pcanBi->wdId);
callbackRequest(&pcanBi->callback);
}
}
LOCAL void busSignal (
biCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devBiCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devBiCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devBiCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
biCanBus_t *pbus
) {
biCanPrivate_t *pcanBi = pbus->firstPrivate;
while (pcanBi != NULL) {
pcanBi->status = pbus->status;
biProcess(pcanBi);
pcanBi = pcanBi->nextPrivate;
}
}
drvTip810/devBiTip810.c 0100644 0000621 0000620 00000005641 06671026117 013656 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devBiTip810.c
Description:
TIP810 Status Binary Input device support
Author:
Andrew Johnson
Created:
3 April 1997
Version:
devBiTip810.c,v 1.3 1999/03/08 20:00:15 anj Exp
(c) 1997 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "canBus.h"
#include "drvTip810.h"
#include "pca82c200.h"
/* Create the dset for devBiTip810 */
LOCAL long init_bi(struct biRecord *prec);
LOCAL long read_bi(struct biRecord *prec);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_bi;
}devBiTip810={
5,
NULL,
NULL,
init_bi,
NULL,
read_bi};
LOCAL long init_bi(
struct biRecord *prec
) {
static struct {
char *string;
unsigned long mask;
} tipState[] = {
{ "BUS_OFF", PCA_SR_BS },
{ "BUS_ERROR", PCA_SR_ES },
{ "DATA_OVERRUN", PCA_SR_DO },
{ "RECEIVING", PCA_SR_RS },
{ "RECEIVED", PCA_SR_RBS },
{ "SENDING", PCA_SR_TS },
{ "SENT", PCA_SR_TCS },
{ "OK_TO_SEND", PCA_SR_TBS },
{ NULL, NULL }
};
char *canString;
char *name;
char separator;
int i;
long status;
/* bi.inp must be an INST_IO */
if (prec->inp.type != INST_IO) goto error;
canString = ((struct instio *)&(prec->inp.value))->string;
/* Strip leading whitespace & non-alphanumeric chars */
while (!isalnum(*canString)) {
if (*canString++ == '\0') goto error;
}
/* First part of string is the bus name */
name = canString;
/* find the end of the busName */
canString = strpbrk(canString, "/:");
if (canString == NULL || *canString == '\0') goto error;
/* Temporarily truncate string after name and look up t810 device */
separator = *canString;
*canString = '\0';
status = canOpen(name, &prec->dpvt);
*canString++ = separator;
if (status) goto error;
/* After the bus name comes the name of the status bit we're after */
prec->mask=0;
for (i=0; tipState[i].string != NULL; i++)
if (strcmp(canString, tipState[i].string) == 0)
prec->mask=tipState[i].mask;
if (prec->mask == 0) {
goto error;
}
return 0;
error:
if (canSilenceErrors) {
prec->dpvt = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_db_badField,(void *)prec,
"devBiTip810: Bad INP field type or value");
return S_db_badField;
}
}
LOCAL long read_bi(struct biRecord *prec)
{
if (prec->dpvt == NULL || prec->mask == 0) {
prec->pact = TRUE;
return S_dev_noDevice;
}
prec->rval = t810Status(prec->dpvt) & prec->mask;
return 0;
}
drvTip810/devBoCan.c 0100644 0000621 0000620 00000016430 06671026120 013370 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devBoCan.c
Description:
CANBUS Binary Output device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devBoCan.c,v 1.6 1999/03/08 20:00:16 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DO_NOT_CONVERT 2
typedef struct boCanPrivate_s {
struct boCanPrivate_s *nextPrivate;
IOSCANPVT ioscanpvt;
struct boRecord *prec;
canIo_t out;
long data;
int status;
} boCanPrivate_t;
typedef struct boCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct boCanBus_s *nextBus;
boCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} boCanBus_t;
LOCAL long init_bo(struct boRecord *prec);
LOCAL long get_ioint_info(int cmd, struct boRecord *prec, IOSCANPVT *ppvt);
LOCAL long write_bo(struct boRecord *prec);
LOCAL void boMessage(boCanPrivate_t *pcanBo, canMessage_t *pmessage);
LOCAL void busSignal(boCanBus_t *pbus, int status);
LOCAL void busCallback(boCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN write_bo;
} devBoCan = {
5,
NULL,
NULL,
init_bo,
get_ioint_info,
write_bo
};
LOCAL boCanBus_t *firstBus;
LOCAL long init_bo (
struct boRecord *prec
) {
boCanPrivate_t *pcanBo;
boCanBus_t *pbus;
int status;
if (prec->out.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devBoCan (init_record) Illegal OUT field");
return S_db_badField;
}
pcanBo = (boCanPrivate_t *) malloc(sizeof(boCanPrivate_t));
if (pcanBo == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanBo;
pcanBo->prec = prec;
pcanBo->ioscanpvt = NULL;
pcanBo->status = NO_ALARM;
/* Convert the parameter string into members of the canIo structure */
status = canIoParse(prec->out.value.instio.string, &pcanBo->out);
if (status ||
pcanBo->out.parameter < 0 ||
pcanBo->out.parameter > 7) {
if (canSilenceErrors) {
pcanBo->out.canBusID = NULL;
prec->pact = TRUE;
return DO_NOT_CONVERT;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devBoCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("canBo %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanBo->out.busName, pcanBo->out.identifier,
pcanBo->out.offset, pcanBo->out.parameter);
#endif
/* For bo records, the final parameter specifies the output bit number,
with the offset specifying the message byte number. */
prec->mask = 1 << pcanBo->out.parameter;
#ifdef DEBUG
printf(" bit=%d, mask=%#x\n", out.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanBo->out.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (boCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanBo->out.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanBo->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanBo;
/* Register the message handler with the Canbus driver */
canMessage(pcanBo->out.canBusID, pcanBo->out.identifier,
(canMsgCallback_t *) boMessage, pcanBo);
return DO_NOT_CONVERT;
}
LOCAL long get_ioint_info (
int cmd,
struct boRecord *prec,
IOSCANPVT *ppvt
) {
boCanPrivate_t *pcanBo = (boCanPrivate_t *) prec->dpvt;
if (pcanBo->ioscanpvt == NULL) {
scanIoInit(&pcanBo->ioscanpvt);
}
#ifdef DEBUG
printf("boCan %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanBo->ioscanpvt;
return OK;
}
LOCAL long write_bo (
struct boRecord *prec
) {
boCanPrivate_t *pcanBo = (boCanPrivate_t *) prec->dpvt;
if (pcanBo->out.canBusID == NULL) {
return ERROR;
}
#ifdef DEBUG
printf("boCan %s: write_bo status=%#x\n", prec->name, pcanBo->status);
#endif
switch (pcanBo->status) {
case COMM_ALARM:
recGblSetSevr(prec, pcanBo->status, INVALID_ALARM);
pcanBo->status = NO_ALARM;
return ERROR;
case NO_ALARM:
{
canMessage_t message;
int status;
message.identifier = pcanBo->out.identifier;
message.rtr = SEND;
pcanBo->data = prec->rval & prec->mask;
message.data[pcanBo->out.offset] = pcanBo->data;
message.length = pcanBo->out.offset + 1;
#ifdef DEBUG
printf("canBo %s: SEND id=%#x, length=%d, data=%#x\n",
prec->name, message.identifier, message.length,
pcanBo->data);
#endif
status = canWrite(pcanBo->out.canBusID, &message,
pcanBo->out.timeout);
if (status) {
#ifdef DEBUG
printf("canBo %s: canWrite status=%#x\n", status);
#endif
recGblSetSevr(prec, TIMEOUT_ALARM, INVALID_ALARM);
return ERROR;
}
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanBo->status = NO_ALARM;
return ERROR;
}
}
LOCAL void boMessage (
boCanPrivate_t *pcanBo,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pcanBo->prec->scan == SCAN_IO_EVENT &&
pmessage->rtr == RTR) {
pcanBo->status = NO_ALARM;
scanIoRequest(pcanBo->ioscanpvt);
}
}
LOCAL void busSignal (
boCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devBoCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devBoCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devBoCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
boCanBus_t *pbus
) {
boCanPrivate_t *pcanBo = pbus->firstPrivate;
while (pcanBo != NULL) {
pcanBo->status = pbus->status;
dbScanLock((struct dbCommon *) pcanBo->prec);
(*((struct rset *) pcanBo->prec->rset)->process)(pcanBo->prec);
dbScanUnlock((struct dbCommon *) pcanBo->prec);
pcanBo = pcanBo->nextPrivate;
}
}
drvTip810/devCan.html 0100644 0000621 0000620 00000034570 06604226575 013652 0 ustar epics epics
devCan - CAN Bus Device Support
devCan - CAN Bus Device Support
Developed for Gemini and UKIRT
CANbus Device Support for EPICS
Version 1.1b
Andrew Johnson
<anjohnson@iee.org>
1. Introduction
This document describes the device support provided with the
Gemini/
UKIRT
CANbus driver under EPICS. There are two related documents which describe
the lower levels of software which are also required to provide the interface
to the CANbus: drvTip810 - CAN Bus Driver
and drvIpac - Industry Pack Driver.
Device Support routines have been provided for the following record
types:
- Analogue Records (ai, ao)
- Binary Records (bi, bo)
- Multi-Bit Binary Records (mbbi, mbbo)
- Multi-Bit Binary Direct Records (mbbiDirect, mbboDirect)
The device support behaviour for these record types is substantially
the same. The differences are discussed individually for each record type
in section 3 below.
Also available with version 2 of the driver software is the ability
to check the status of the Tip810 CANbus interface, using:
- Binary Input Records to access the TIP810 module status (bi)
The support for this record type is significantly different to the others
so is described seperately in section 4.
2. Common Issues
All communication with the CANbus hardware is performed using the routines
defined in the generic header file canBus.h, thus these device support
routines can be used with hardware other than just the Tews TIP810 Industry-Pack
module if a suitable driver is written.
Each record is associated with a single message identifier on a particular
CANbus, although an identifier can be used for more than one record simultaneously.
This allows several items of input data to be encoded into one message
and each record just picks out the relevant part of the message data for
its own use. This technique cannot be used for output records however,
for obvious reasons.
Hardware Addressing
Records should have their Device Type (DTYP) field set
to "CANbus" for which the hardware address field
(INP or OUT) is an Instrument I/O type
(INST_IO), which provides for a single address string.
This contains a number of elements, some of which are optional, and is
formatted as follows:
@busName[/timeout]:identifier[+n..][.offset]parameter
The first element after the "@" is the bus
name, which should consist of alphanumeric characters only. The name starts
with the first non-white-space character, and is terminated immediately
before the first "/" or ":"
character in the string.
An oblique stroke after the bus name introduces an optional timeout
element, which is an integer number of milli-seconds to wait for a response
from the CANbus device. If the timeout element is omitted from the address
string, an indefinite delay will be permitted. If the CANbus goes into
a Bus Off state and there are output or RTR records without a timeout specified,
the scan tasks which process these records will be halted until the bus
recovers.
The CANbus message identifier is preceded by a colon, and must result in one
of the legal CANbus identifiers in the range 0 through 2047 (with holes). The
identifier itself can be specified as a single number, or in several parts
separated by plus signs, which are all summed. The numbers here can be given
in decimal, hex or octal as desired using the standard 'C' syntax.
If the identifier is followed by a decimal point, the following element
is an optional byte offset into the CANbus message where the data may be
found. The offset is a number from zero to seven, and defaults to zero
if the element is omitted. Note that for output records this offset is
not used.
The final parameter element is a signed integer introduced by a space
or tab character, and is used in different ways by the various different
record types (see below).
Asynchronous Processing
All input record types support asynchronous processing. This is necessary
because there may be a significant delay between sending a Remote Transmission
Request message out on the CANbus and the addressed device returning a value.
With asynchronous processing support this delay will not hold up other EPICS
activities which do not depend on the results returned to the input record
(i.e. records in a different lock-set). The timeout address field is used to
set a watchdog timer, and if no response is received within the given period
the record is set to the TIMEOUT_ALARM status with a severity
of INVALID_ALARM.
Record Scan Types
The CANbus messages handled and generated by the device support routines
depend on the setting of the SCAN field of the particular
record. When a passive or periodically scanned input record is processed,
a CANbus Remote Transmission Request message is sent and record processing
is suspended until the relevant reply is received. A passive or periodically
scanned output record causes a CANbus message to be generated each time
it is processed.
All record types support the I/O Interrupt scan type. Records which
use this scan mechanism will be processed whenever a CANbus message is
received with the relevant identifier. For an output record, the message
received must be a Remote Transmission Request, and an EPICS processing
chain can be used to obtain the value to be returned in response to this
request.
It is obviously desirable to avoid unnecessary CANbus message traffic,
thus if several input data items are encoded in the same CANbus message
identifier which are destined for several input records, all of the records
except one should be set to I/O Interrupt scanning. When it is processed
this record will generate a single CANbus RTR message to ask for the data
from the remote node. The reply will be distributed to all of the records
waiting on this particular message identifier.
Alarm Status
Records will be placed in an alarm state in the event of the CANbus
interface signalling a Bus Error or Bus Off event, or if the remote CAN device
does not respond to an RTR message within the specified timeout interval (Bus
Error alarms were disabled in Version 1.1b but have been reinstated in V1.1c).
The alarm status and severity meanings are given in the following table:
Status |
Severity |
Description |
COMM_ALARM |
INVALID_ALARM |
CAN Bus Error or Bus Off event |
TIMEOUT_ALARM |
INVALID_ALARM |
Device or RTR Timeout |
UDF_ALARM |
INVALID_ALARM |
Internal Error |
3. Record-Specific Behaviour
The major difference between the various record types supported is in
the use of the miscellaneous address parameter which defines how the CANbus
message data is interpreted.
Analogue Records
For Analogue records, the address parameter is a signed number which
specifies the maximum value of the raw data within the CANbus message.
Raw data values with arbitrary widths up to 32 bits can be handled, but
the data LSB must be aligned to the LSB of a message byte. If the address
parameter is negative the raw data values are taken to be 2s-complement
numbers with the given range and centred about zero, otherwise the value
is taken to be unsigned, ranging from zero up to the value given. In both
cases if the parameter value is a power of two it will be rounded down
by one (64 becomes 63, 0x800 becomes 0x7ff etc). The range is not limited
to just powers of two however - e.g. the value 1999 might be used if the
data originated from a 3½ digit BCD number (although it must have
been converted from BCD to 2s-complement binary before being transmitted
in the CAN message).
Analogue records include the ability to perform an automatic engineering
units conversion, depending on the setting of the record's LINR
field. If this is set to "NO CONVERSION" the record
value will be identical to the raw value read from the CAN message. When set to
"LINEAR" the input range specified by the address
parameter will be mapped directly to the range given by the
EGUL and EGUF fields. This applies even if the
input range is 2s-complement such that the most negative input will give return
the value given by EGUL and the most positive will return
EGUF.
Binary Records
For binary records, the address parameter specifies the bit number within
the byte which holds the binary value, where 0 refers to the least significant
bit and 7 the most significant. The address offset parameter must be used
to select which byte in the message is to be examined, although this byte
offset is ignored by the binary output record support.
Multi-Bit Binary Records
For the various multi-bit binary records, the address parameter specifies
the bit number (0 to 7) of the least significant bit in the bit-field (the
number of bits is specified in the record's NOBT field).
The address offset parameter is used to select which byte in the message
is used, although this is ignored by the output record types. It is not
possible for the bit-field to cross a byte boundary, thus the record behaviour
is undefined when the sum of the address parameter and NOBT
exceeds 8.
4. Tip810 Module Status Records
Version 1.1 of the CANbus driver software adds the ability to monitor
the status bits of the Tip810 CANbus interface chip using Binary Input
records. I do not regard these records as necessarily applying to any type
of CANbus interface which is why I have made them specifically named for
the Tip810. The main use of this device support will be for the Bus Status,
Error Status and Data Overrun bits, although all of the status bits can
be accessed. [Note: if there is sufficient demand, the various message
counters could also be made available via the database; at present these
can only be seen by using the t810Report shell command.]
The Device Type (DTYP) field of the Binary Input records
should be set to "Tip810" for which the hardware
address (INP) field is an Instrument I/O type
(INST_IO) and provides addressing information in the following
format:
@busName:signalName
The busName identifies the Tip810 card which is to be monitored, as
used for the other CANbus record types described above.
The signalName element after the ":" character
should be one of the following strings which identify the status bit which
this record is to read:
signalName |
Meaning when Set (1) |
BUS_OFF |
Device is disconnected from CANbus |
BUS_ERROR |
Messages from this node are being flagged with errors |
DATA_OVERRUN |
An incoming message was lost |
RECEIVING |
A message is being received |
RECEIVED |
A message was received and is available for reading |
SENDING |
A message is being transmitted |
SENT |
Message was transmitted Ok. |
OK_TO_SEND |
Transmit buffer is free to accept another message |
I/O Interrupt scanning is not supported by this device support, thus
these records will probably be processed periodically to monitor the status
of the bus. Note that the BUS_OFF signal may never be seen
as the Tip810 driver software automatically resets the CAN chip when it
goes into the Bus Off state. If it does appear it probably means that the
bus is unterminated or disconnected completely, or that there are no other
devices connected on it.
Andrew Johnson
<anjohnson@iee.org>
drvTip810/devMbbiCan.c 0100644 0000621 0000620 00000020736 06671026120 013705 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devMbbiCan.c
Description:
CANBUS Multi-Bit Binary Input device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devMbbiCan.c,v 1.8 1999/03/08 20:00:16 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CONVERT 0
#define DO_NOT_CONVERT 2
typedef struct mbbiCanPrivate_s {
CALLBACK callback; /* This *must* be first member */
struct mbbiCanPrivate_s *nextPrivate;
WDOG_ID wdId;
IOSCANPVT ioscanpvt;
struct mbbiRecord *prec;
canIo_t inp;
long data;
int status;
} mbbiCanPrivate_t;
typedef struct mbbiCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct mbbiCanBus_s *nextBus;
mbbiCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} mbbiCanBus_t;
LOCAL long init_mbbi(struct mbbiRecord *prec);
LOCAL long get_ioint_info(int cmd, struct mbbiRecord *prec, IOSCANPVT *ppvt);
LOCAL long read_mbbi(struct mbbiRecord *prec);
LOCAL void mbbiProcess(mbbiCanPrivate_t *pcanMbbi);
LOCAL void mbbiMessage(mbbiCanPrivate_t *pcanMbbi, canMessage_t *pmessage);
LOCAL void busSignal(mbbiCanBus_t *pbus, int status);
LOCAL void busCallback(mbbiCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_mbbi;
} devMbbiCan = {
5,
NULL,
NULL,
init_mbbi,
get_ioint_info,
read_mbbi
};
LOCAL mbbiCanBus_t *firstBus;
LOCAL long init_mbbi (
struct mbbiRecord *prec
) {
mbbiCanPrivate_t *pcanMbbi;
mbbiCanBus_t *pbus;
int status;
if (prec->inp.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devMbbiCan (init_record) Illegal INP field");
return S_db_badField;
}
pcanMbbi = (mbbiCanPrivate_t *) malloc(sizeof(mbbiCanPrivate_t));
if (pcanMbbi == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanMbbi;
pcanMbbi->prec = prec;
pcanMbbi->ioscanpvt = NULL;
pcanMbbi->status = NO_ALARM;
/* Convert the address string into members of the canIo structure */
status = canIoParse(prec->inp.value.instio.string, &pcanMbbi->inp);
if (status ||
pcanMbbi->inp.parameter < 0 ||
pcanMbbi->inp.parameter > 7) {
if (canSilenceErrors) {
pcanMbbi->inp.canBusID = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devMbbiCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("mbbiCan %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanMbbi->inp.busName, pcanMbbi->inp.identifier,
pcanMbbi->inp.offset, pcanMbbi->inp.parameter);
#endif
/* For mbbi records, the final parameter specifies the input bit shift,
with offset specifying the message byte number. */
prec->shft = pcanMbbi->inp.parameter;
prec->mask <<= pcanMbbi->inp.parameter;
#ifdef DEBUG
printf(" shft=%d, mask=%#x\n",
pcanMbbi->inp.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanMbbi->inp.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (mbbiCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanMbbi->inp.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanMbbi->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanMbbi;
/* Set the callback parameters for asynchronous processing */
callbackSetCallback((VOIDFUNCPTR) mbbiProcess, &pcanMbbi->callback);
callbackSetPriority(prec->prio, &pcanMbbi->callback);
/* and create a watchdog for CANbus RTR timeouts */
pcanMbbi->wdId = wdCreate();
if (pcanMbbi->wdId == NULL) {
return S_dev_noMemory;
}
/* Register the message handler with the Canbus driver */
canMessage(pcanMbbi->inp.canBusID, pcanMbbi->inp.identifier,
(canMsgCallback_t *) mbbiMessage, pcanMbbi);
return OK;
}
LOCAL long get_ioint_info (
int cmd,
struct mbbiRecord *prec,
IOSCANPVT *ppvt
) {
mbbiCanPrivate_t *pcanMbbi = (mbbiCanPrivate_t *) prec->dpvt;
if (pcanMbbi->ioscanpvt == NULL) {
scanIoInit(&pcanMbbi->ioscanpvt);
}
#ifdef DEBUG
printf("canMbbi %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanMbbi->ioscanpvt;
return OK;
}
LOCAL long read_mbbi (
struct mbbiRecord *prec
) {
mbbiCanPrivate_t *pcanMbbi = (mbbiCanPrivate_t *) prec->dpvt;
if (pcanMbbi->inp.canBusID == NULL) {
return DO_NOT_CONVERT;
}
#ifdef DEBUG
printf("canMbbi %s: read_mbbi status=%#x\n", prec->name, pcanMbbi->status);
#endif
switch (pcanMbbi->status) {
case TIMEOUT_ALARM:
case COMM_ALARM:
recGblSetSevr(prec, pcanMbbi->status, INVALID_ALARM);
pcanMbbi->status = NO_ALARM;
return DO_NOT_CONVERT;
case NO_ALARM:
if (prec->pact || prec->scan == SCAN_IO_EVENT) {
#ifdef DEBUG
printf("canMbbi %s: message id=%#x, data=%#x\n",
prec->name, pcanMbbi->inp.identifier, pcanMbbi->data);
#endif
prec->rval = pcanMbbi->data & prec->mask;
return CONVERT;
} else {
canMessage_t message;
message.identifier = pcanMbbi->inp.identifier;
message.rtr = RTR;
message.length = 8;
#ifdef DEBUG
printf("canMbbi %s: RTR, id=%#x\n",
prec->name, pcanMbbi->inp.identifier);
#endif
prec->pact = TRUE;
pcanMbbi->status = TIMEOUT_ALARM;
callbackSetPriority(prec->prio, &pcanMbbi->callback);
wdStart(pcanMbbi->wdId, pcanMbbi->inp.timeout,
(FUNCPTR) callbackRequest, (int) pcanMbbi);
canWrite(pcanMbbi->inp.canBusID, &message, pcanMbbi->inp.timeout);
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanMbbi->status = NO_ALARM;
return DO_NOT_CONVERT;
}
}
LOCAL void mbbiProcess (
mbbiCanPrivate_t *pcanMbbi
) {
dbScanLock((struct dbCommon *) pcanMbbi->prec);
(*((struct rset *) pcanMbbi->prec->rset)->process)(pcanMbbi->prec);
dbScanUnlock((struct dbCommon *) pcanMbbi->prec);
}
LOCAL void mbbiMessage (
mbbiCanPrivate_t *pcanMbbi,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pmessage->rtr == RTR) {
return; /* Ignore RTRs */
}
pcanMbbi->data = pmessage->data[pcanMbbi->inp.offset];
if (pcanMbbi->prec->scan == SCAN_IO_EVENT) {
pcanMbbi->status = NO_ALARM;
scanIoRequest(pcanMbbi->ioscanpvt);
} else if (pcanMbbi->status == TIMEOUT_ALARM) {
pcanMbbi->status = NO_ALARM;
wdCancel(pcanMbbi->wdId);
callbackRequest(&pcanMbbi->callback);
}
}
LOCAL void busSignal (
mbbiCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devMbbiCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devMbbiCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devMbbiCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
mbbiCanBus_t *pbus
) {
mbbiCanPrivate_t *pcanMbbi = pbus->firstPrivate;
while (pcanMbbi != NULL) {
pcanMbbi->status = pbus->status;
mbbiProcess(pcanMbbi);
pcanMbbi = pcanMbbi->nextPrivate;
}
}
drvTip810/devMbbiDirectCan.c 0100644 0000621 0000620 00000022422 06671026120 015032 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devMbbiDirectCan.c
Description:
CANBUS Multi-Bit Binary Input Direct device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devMbbiDirectCan.c,v 1.8 1999/03/08 20:00:16 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CONVERT 0
#define DO_NOT_CONVERT 2
typedef struct mbbiDirectCanPrivate_s {
CALLBACK callback; /* This *must* be first member */
struct mbbiDirectCanPrivate_s *nextPrivate;
WDOG_ID wdId;
IOSCANPVT ioscanpvt;
struct mbbiDirectRecord *prec;
canIo_t inp;
long data;
int status;
} mbbiDirectCanPrivate_t;
typedef struct mbbiDirectCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct mbbiDirectCanBus_s *nextBus;
mbbiDirectCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} mbbiDirectCanBus_t;
LOCAL long init_mbbiDirect(struct mbbiDirectRecord *prec);
LOCAL long get_ioint_info(int cmd, struct mbbiDirectRecord *prec, IOSCANPVT *ppvt);
LOCAL long read_mbbiDirect(struct mbbiDirectRecord *prec);
LOCAL void mbbiDirectProcess(mbbiDirectCanPrivate_t *pcanMbbiDirect);
LOCAL void mbbiDirectMessage(mbbiDirectCanPrivate_t *pcanMbbiDirect, canMessage_t *pmessage);
LOCAL void busSignal(mbbiDirectCanBus_t *pbus, int status);
LOCAL void busCallback(mbbiDirectCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_mbbiDirect;
} devMbbiDirectCan = {
5,
NULL,
NULL,
init_mbbiDirect,
get_ioint_info,
read_mbbiDirect
};
LOCAL mbbiDirectCanBus_t *firstBus;
LOCAL long init_mbbiDirect (
struct mbbiDirectRecord *prec
) {
mbbiDirectCanPrivate_t *pcanMbbiDirect;
mbbiDirectCanBus_t *pbus;
int status;
if (prec->inp.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devMbbiDirectCan (init_record) Illegal INP field");
return S_db_badField;
}
pcanMbbiDirect = (mbbiDirectCanPrivate_t *) malloc(sizeof(mbbiDirectCanPrivate_t));
if (pcanMbbiDirect == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanMbbiDirect;
pcanMbbiDirect->prec = prec;
pcanMbbiDirect->ioscanpvt = NULL;
pcanMbbiDirect->status = NO_ALARM;
/* Convert the address string into members of the canIo structure */
status = canIoParse(prec->inp.value.instio.string, &pcanMbbiDirect->inp);
if (status ||
pcanMbbiDirect->inp.parameter < 0 ||
pcanMbbiDirect->inp.parameter > 7) {
if (canSilenceErrors) {
pcanMbbiDirect->inp.canBusID = NULL;
prec->pact = TRUE;
return OK;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devMbbiDirectCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("mbbiDirectCan %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanMbbiDirect->inp.busName, pcanMbbiDirect->inp.identifier,
pcanMbbiDirect->inp.offset, pcanMbbiDirect->inp.parameter);
#endif
/* For mbbiDirect records, the final parameter specifies the input bit shift,
with offset specifying the message byte number. */
prec->shft = pcanMbbiDirect->inp.parameter;
prec->mask <<= pcanMbbiDirect->inp.parameter;
#ifdef DEBUG
printf(" shft=%d, mask=%#x\n",
pcanMbbiDirect->inp.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanMbbiDirect->inp.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (mbbiDirectCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanMbbiDirect->inp.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanMbbiDirect->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanMbbiDirect;
/* Set the callback parameters for asynchronous processing */
callbackSetCallback((VOIDFUNCPTR) mbbiDirectProcess, &pcanMbbiDirect->callback);
callbackSetPriority(prec->prio, &pcanMbbiDirect->callback);
/* and create a watchdog for CANbus RTR timeouts */
pcanMbbiDirect->wdId = wdCreate();
if (pcanMbbiDirect->wdId == NULL) {
return S_dev_noMemory;
}
/* Register the message handler with the Canbus driver */
canMessage(pcanMbbiDirect->inp.canBusID, pcanMbbiDirect->inp.identifier,
(canMsgCallback_t *) mbbiDirectMessage, pcanMbbiDirect);
return OK;
}
LOCAL long get_ioint_info (
int cmd,
struct mbbiDirectRecord *prec,
IOSCANPVT *ppvt
) {
mbbiDirectCanPrivate_t *pcanMbbiDirect = (mbbiDirectCanPrivate_t *) prec->dpvt;
if (pcanMbbiDirect->ioscanpvt == NULL) {
scanIoInit(&pcanMbbiDirect->ioscanpvt);
}
#ifdef DEBUG
printf("canMbbiDirect %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanMbbiDirect->ioscanpvt;
return OK;
}
LOCAL long read_mbbiDirect (
struct mbbiDirectRecord *prec
) {
mbbiDirectCanPrivate_t *pcanMbbiDirect = (mbbiDirectCanPrivate_t *) prec->dpvt;
if (pcanMbbiDirect->inp.canBusID == NULL) {
return DO_NOT_CONVERT;
}
#ifdef DEBUG
printf("canMbbiDirect %s: read_mbbiDirect status=%#x\n", prec->name, pcanMbbiDirect->status);
#endif
switch (pcanMbbiDirect->status) {
case TIMEOUT_ALARM:
case COMM_ALARM:
recGblSetSevr(prec, pcanMbbiDirect->status, INVALID_ALARM);
pcanMbbiDirect->status = NO_ALARM;
return DO_NOT_CONVERT;
case NO_ALARM:
if (prec->pact || prec->scan == SCAN_IO_EVENT) {
#ifdef DEBUG
printf("canMbbiDirect %s: message id=%#x, data=%#x\n",
prec->name, pcanMbbiDirect->inp.identifier, pcanMbbiDirect->data);
#endif
prec->rval = pcanMbbiDirect->data & prec->mask;
return CONVERT;
} else {
canMessage_t message;
message.identifier = pcanMbbiDirect->inp.identifier;
message.rtr = RTR;
message.length = 8;
#ifdef DEBUG
printf("canMbbiDirect %s: RTR, id=%#x\n",
prec->name, pcanMbbiDirect->inp.identifier);
#endif
prec->pact = TRUE;
pcanMbbiDirect->status = TIMEOUT_ALARM;
callbackSetPriority(prec->prio, &pcanMbbiDirect->callback);
wdStart(pcanMbbiDirect->wdId, pcanMbbiDirect->inp.timeout,
(FUNCPTR) callbackRequest, (int) pcanMbbiDirect);
canWrite(pcanMbbiDirect->inp.canBusID, &message, pcanMbbiDirect->inp.timeout);
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanMbbiDirect->status = NO_ALARM;
return DO_NOT_CONVERT;
}
}
LOCAL void mbbiDirectProcess (
mbbiDirectCanPrivate_t *pcanMbbiDirect
) {
dbScanLock((struct dbCommon *) pcanMbbiDirect->prec);
(*((struct rset *) pcanMbbiDirect->prec->rset)->process)(pcanMbbiDirect->prec);
dbScanUnlock((struct dbCommon *) pcanMbbiDirect->prec);
}
LOCAL void mbbiDirectMessage (
mbbiDirectCanPrivate_t *pcanMbbiDirect,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pmessage->rtr == RTR) {
return; /* Ignore RTRs */
}
pcanMbbiDirect->data = pmessage->data[pcanMbbiDirect->inp.offset];
if (pcanMbbiDirect->prec->scan == SCAN_IO_EVENT) {
pcanMbbiDirect->status = NO_ALARM;
scanIoRequest(pcanMbbiDirect->ioscanpvt);
} else if (pcanMbbiDirect->status == TIMEOUT_ALARM) {
pcanMbbiDirect->status = NO_ALARM;
wdCancel(pcanMbbiDirect->wdId);
callbackRequest(&pcanMbbiDirect->callback);
}
}
LOCAL void busSignal (
mbbiDirectCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devMbbiDirectCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devMbbiDirectCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devMbbiDirectCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->inp.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
mbbiDirectCanBus_t *pbus
) {
mbbiDirectCanPrivate_t *pcanMbbiDirect = pbus->firstPrivate;
while (pcanMbbiDirect != NULL) {
pcanMbbiDirect->status = pbus->status;
mbbiDirectProcess(pcanMbbiDirect);
pcanMbbiDirect = pcanMbbiDirect->nextPrivate;
}
}
drvTip810/devMbboCan.c 0100644 0000621 0000620 00000017053 06671026120 013711 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devMbboCan.c
Description:
CANBUS Multi-Bit Binary Output device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devMbboCan.c,v 1.6 1999/03/08 20:00:16 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DO_NOT_CONVERT 2
typedef struct mbboCanPrivate_s {
struct mbboCanPrivate_s *nextPrivate;
IOSCANPVT ioscanpvt;
struct mbboRecord *prec;
canIo_t out;
long data;
int status;
} mbboCanPrivate_t;
typedef struct mbboCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct mbboCanBus_s *nextBus;
mbboCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} mbboCanBus_t;
LOCAL long init_mbbo(struct mbboRecord *prec);
LOCAL long get_ioint_info(int cmd, struct mbboRecord *prec, IOSCANPVT *ppvt);
LOCAL long write_mbbo(struct mbboRecord *prec);
LOCAL void mbboMessage(mbboCanPrivate_t *pcanMbbo, canMessage_t *pmessage);
LOCAL void busSignal(mbboCanBus_t *pbus, int status);
LOCAL void busCallback(mbboCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN write_mbbo;
} devMbboCan = {
5,
NULL,
NULL,
init_mbbo,
get_ioint_info,
write_mbbo
};
LOCAL mbboCanBus_t *firstBus;
LOCAL long init_mbbo (
struct mbboRecord *prec
) {
mbboCanPrivate_t *pcanMbbo;
mbboCanBus_t *pbus;
int status;
if (prec->out.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devMbboCan (init_record) Illegal OUT field");
return S_db_badField;
}
pcanMbbo = (mbboCanPrivate_t *) malloc(sizeof(mbboCanPrivate_t));
if (pcanMbbo == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanMbbo;
pcanMbbo->prec = prec;
pcanMbbo->ioscanpvt = NULL;
pcanMbbo->status = NO_ALARM;
/* Convert the parameter string into members of the canIo structure */
status = canIoParse(prec->out.value.instio.string, &pcanMbbo->out);
if (status ||
pcanMbbo->out.parameter < 0 ||
pcanMbbo->out.parameter > 7) {
if (canSilenceErrors) {
pcanMbbo->out.canBusID = NULL;
prec->pact = TRUE;
return DO_NOT_CONVERT;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devMbboCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("canMbbo %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanMbbo->out.busName, pcanMbbo->out.identifier,
pcanMbbo->out.offset, pcanMbbo->out.parameter);
#endif
/* For mbbo records, the final parameter specifies the output bit shift,
with the offset specifying the message byte number. */
prec->shft = pcanMbbo->out.parameter;
prec->mask <<= pcanMbbo->out.parameter;
#ifdef DEBUG
printf(" bit=%d, mask=%#x\n", out.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanMbbo->out.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (mbboCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanMbbo->out.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanMbbo->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanMbbo;
/* Register the message handler with the Canbus driver */
canMessage(pcanMbbo->out.canBusID, pcanMbbo->out.identifier,
(canMsgCallback_t *) mbboMessage, pcanMbbo);
return DO_NOT_CONVERT;
}
LOCAL long get_ioint_info (
int cmd,
struct mbboRecord *prec,
IOSCANPVT *ppvt
) {
mbboCanPrivate_t *pcanMbbo = (mbboCanPrivate_t *) prec->dpvt;
if (pcanMbbo->ioscanpvt == NULL) {
scanIoInit(&pcanMbbo->ioscanpvt);
}
#ifdef DEBUG
printf("mbboCan %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanMbbo->ioscanpvt;
return OK;
}
LOCAL long write_mbbo (
struct mbboRecord *prec
) {
mbboCanPrivate_t *pcanMbbo = (mbboCanPrivate_t *) prec->dpvt;
if (pcanMbbo->out.canBusID == NULL) {
return ERROR;
}
#ifdef DEBUG
printf("mbboCan %s: write_mbbo status=%#x\n", prec->name, pcanMbbo->status);
#endif
switch (pcanMbbo->status) {
case COMM_ALARM:
recGblSetSevr(prec, pcanMbbo->status, INVALID_ALARM);
pcanMbbo->status = NO_ALARM;
return ERROR;
case NO_ALARM:
{
canMessage_t message;
int status;
message.identifier = pcanMbbo->out.identifier;
message.rtr = SEND;
pcanMbbo->data = prec->rval & prec->mask;
message.data[pcanMbbo->out.offset] = pcanMbbo->data;
message.length = pcanMbbo->out.offset + 1;
#ifdef DEBUG
printf("canMbbo %s: SEND id=%#x, length=%d, data=%#x\n",
prec->name, message.identifier, message.length,
pcanMbbo->data);
#endif
status = canWrite(pcanMbbo->out.canBusID, &message,
pcanMbbo->out.timeout);
if (status) {
#ifdef DEBUG
printf("canMbbo %s: canWrite status=%#x\n", status);
#endif
recGblSetSevr(prec, TIMEOUT_ALARM, INVALID_ALARM);
return ERROR;
}
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanMbbo->status = NO_ALARM;
return ERROR;
}
}
LOCAL void mbboMessage (
mbboCanPrivate_t *pcanMbbo,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pcanMbbo->prec->scan == SCAN_IO_EVENT &&
pmessage->rtr == RTR) {
pcanMbbo->status = NO_ALARM;
scanIoRequest(pcanMbbo->ioscanpvt);
}
}
LOCAL void busSignal (
mbboCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devMbboCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devMbboCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devMbboCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
mbboCanBus_t *pbus
) {
mbboCanPrivate_t *pcanMbbo = pbus->firstPrivate;
while (pcanMbbo != NULL) {
pcanMbbo->status = pbus->status;
dbScanLock((struct dbCommon *) pcanMbbo->prec);
(*((struct rset *) pcanMbbo->prec->rset)->process)(pcanMbbo->prec);
dbScanUnlock((struct dbCommon *) pcanMbbo->prec);
pcanMbbo = pcanMbbo->nextPrivate;
}
}
drvTip810/devMbboDirectCan.c 0100644 0000621 0000620 00000020336 06671026120 015042 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
devMbboDirectCan.c
Description:
CANBUS Multi-Bit Binary Output Direct device support
Author:
Andrew Johnson
Created:
14 August 1995
Version:
devMbboDirectCan.c,v 1.6 1999/03/08 20:00:16 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DO_NOT_CONVERT 2
typedef struct mbboDirectCanPrivate_s {
struct mbboDirectCanPrivate_s *nextPrivate;
IOSCANPVT ioscanpvt;
struct mbboDirectRecord *prec;
canIo_t out;
long data;
int status;
} mbboDirectCanPrivate_t;
typedef struct mbboDirectCanBus_s {
CALLBACK callback; /* This *must* be first member */
struct mbboDirectCanBus_s *nextBus;
mbboDirectCanPrivate_t *firstPrivate;
void *canBusID;
int status;
} mbboDirectCanBus_t;
LOCAL long init_mbboDirect(struct mbboDirectRecord *prec);
LOCAL long get_ioint_info(int cmd, struct mbboDirectRecord *prec, IOSCANPVT *ppvt);
LOCAL long write_mbboDirect(struct mbboDirectRecord *prec);
LOCAL void mbboDirectMessage(mbboDirectCanPrivate_t *pcanMbboDirect, canMessage_t *pmessage);
LOCAL void busSignal(mbboDirectCanBus_t *pbus, int status);
LOCAL void busCallback(mbboDirectCanBus_t *pbus);
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN write_mbboDirect;
} devMbboDirectCan = {
5,
NULL,
NULL,
init_mbboDirect,
get_ioint_info,
write_mbboDirect
};
LOCAL mbboDirectCanBus_t *firstBus;
LOCAL long init_mbboDirect (
struct mbboDirectRecord *prec
) {
mbboDirectCanPrivate_t *pcanMbboDirect;
mbboDirectCanBus_t *pbus;
int status;
if (prec->out.type != INST_IO) {
recGblRecordError(S_db_badField, (void *) prec,
"devMbboDirectCan (init_record) Illegal OUT field");
return S_db_badField;
}
pcanMbboDirect = (mbboDirectCanPrivate_t *) malloc(sizeof(mbboDirectCanPrivate_t));
if (pcanMbboDirect == NULL) {
return S_dev_noMemory;
}
prec->dpvt = pcanMbboDirect;
pcanMbboDirect->prec = prec;
pcanMbboDirect->ioscanpvt = NULL;
pcanMbboDirect->status = NO_ALARM;
/* Convert the parameter string into members of the canIo structure */
status = canIoParse(prec->out.value.instio.string, &pcanMbboDirect->out);
if (status ||
pcanMbboDirect->out.parameter < 0 ||
pcanMbboDirect->out.parameter > 7) {
if (canSilenceErrors) {
pcanMbboDirect->out.canBusID = NULL;
prec->pact = TRUE;
return DO_NOT_CONVERT;
} else {
recGblRecordError(S_can_badAddress, (void *) prec,
"devMbboDirectCan (init_record) bad CAN address");
return S_can_badAddress;
}
}
#ifdef DEBUG
printf("canMbboDirect %s: Init bus=%s, id=%#x, off=%d, parm=%d\n",
prec->name, pcanMbboDirect->out.busName, pcanMbboDirect->out.identifier,
pcanMbboDirect->out.offset, pcanMbboDirect->out.parameter);
#endif
/* For mbboDirect records, the final parameter specifies the output bit shift,
with the offset specifying the message byte number. */
prec->shft = pcanMbboDirect->out.parameter;
prec->mask <<= pcanMbboDirect->out.parameter;
#ifdef DEBUG
printf(" bit=%d, mask=%#x\n", out.parameter, prec->mask);
#endif
/* Find the bus matching this record */
for (pbus = firstBus; pbus != NULL; pbus = pbus->nextBus) {
if (pbus->canBusID == pcanMbboDirect->out.canBusID) break;
}
/* If not found, create one */
if (pbus == NULL) {
pbus = malloc(sizeof (mbboDirectCanBus_t));
if (pbus == NULL) return S_dev_noMemory;
/* Fill it in */
pbus->firstPrivate = NULL;
pbus->canBusID = pcanMbboDirect->out.canBusID;
callbackSetCallback((VOIDFUNCPTR) busCallback, &pbus->callback);
callbackSetPriority(priorityMedium, &pbus->callback);
/* and add it to the list of busses we know about */
pbus->nextBus = firstBus;
firstBus = pbus;
/* Ask driver for error signals */
canSignal(pbus->canBusID, (canSigCallback_t *) busSignal, pbus);
}
/* Insert private record structure into linked list for this CANbus */
pcanMbboDirect->nextPrivate = pbus->firstPrivate;
pbus->firstPrivate = pcanMbboDirect;
/* Register the message handler with the Canbus driver */
canMessage(pcanMbboDirect->out.canBusID, pcanMbboDirect->out.identifier,
(canMsgCallback_t *) mbboDirectMessage, pcanMbboDirect);
return DO_NOT_CONVERT;
}
LOCAL long get_ioint_info (
int cmd,
struct mbboDirectRecord *prec,
IOSCANPVT *ppvt
) {
mbboDirectCanPrivate_t *pcanMbboDirect = (mbboDirectCanPrivate_t *) prec->dpvt;
if (pcanMbboDirect->ioscanpvt == NULL) {
scanIoInit(&pcanMbboDirect->ioscanpvt);
}
#ifdef DEBUG
printf("mbboDirectCan %s: get_ioint_info %d\n", prec->name, cmd);
#endif
*ppvt = pcanMbboDirect->ioscanpvt;
return OK;
}
LOCAL long write_mbboDirect (
struct mbboDirectRecord *prec
) {
mbboDirectCanPrivate_t *pcanMbboDirect = (mbboDirectCanPrivate_t *) prec->dpvt;
if (pcanMbboDirect->out.canBusID == NULL) {
return ERROR;
}
#ifdef DEBUG
printf("mbboDirectCan %s: write_mbboDirect status=%#x\n", prec->name, pcanMbboDirect->status);
#endif
switch (pcanMbboDirect->status) {
case COMM_ALARM:
recGblSetSevr(prec, pcanMbboDirect->status, INVALID_ALARM);
pcanMbboDirect->status = NO_ALARM;
return ERROR;
case NO_ALARM:
{
canMessage_t message;
int status;
message.identifier = pcanMbboDirect->out.identifier;
message.rtr = SEND;
pcanMbboDirect->data = prec->rval & prec->mask;
message.data[pcanMbboDirect->out.offset] = pcanMbboDirect->data;
message.length = pcanMbboDirect->out.offset + 1;
#ifdef DEBUG
printf("canMbboDirect %s: SEND id=%#x, length=%d, data=%#x\n",
prec->name, message.identifier, message.length,
pcanMbboDirect->data);
#endif
status = canWrite(pcanMbboDirect->out.canBusID, &message,
pcanMbboDirect->out.timeout);
if (status) {
#ifdef DEBUG
printf("canMbboDirect %s: canWrite status=%#x\n", status);
#endif
recGblSetSevr(prec, TIMEOUT_ALARM, INVALID_ALARM);
return ERROR;
}
return 0;
}
default:
recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM);
pcanMbboDirect->status = NO_ALARM;
return ERROR;
}
}
LOCAL void mbboDirectMessage (
mbboDirectCanPrivate_t *pcanMbboDirect,
canMessage_t *pmessage
) {
if (!interruptAccept) return;
if (pcanMbboDirect->prec->scan == SCAN_IO_EVENT &&
pmessage->rtr == RTR) {
pcanMbboDirect->status = NO_ALARM;
scanIoRequest(pcanMbboDirect->ioscanpvt);
}
}
LOCAL void busSignal (
mbboDirectCanBus_t *pbus,
int status
) {
if (!interruptAccept) return;
switch(status) {
case CAN_BUS_OK:
logMsg("devMbboDirectCan: Bus Ok event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = NO_ALARM;
break;
case CAN_BUS_ERROR:
logMsg("devMbboDirectCan: Bus Error event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
case CAN_BUS_OFF:
logMsg("devMbboDirectCan: Bus Off event from %s\n",
(int) pbus->firstPrivate->out.busName, 0, 0, 0, 0, 0);
pbus->status = COMM_ALARM;
callbackRequest(&pbus->callback);
break;
}
}
LOCAL void busCallback (
mbboDirectCanBus_t *pbus
) {
mbboDirectCanPrivate_t *pcanMbboDirect = pbus->firstPrivate;
while (pcanMbboDirect != NULL) {
pcanMbboDirect->status = pbus->status;
dbScanLock((struct dbCommon *) pcanMbboDirect->prec);
(*((struct rset *) pcanMbboDirect->prec->rset)->process)(pcanMbboDirect->prec);
dbScanUnlock((struct dbCommon *) pcanMbboDirect->prec);
pcanMbboDirect = pcanMbboDirect->nextPrivate;
}
}
drvTip810/devTip810.dbd 0100644 0000621 0000620 00000001102 06671026120 013670 0 ustar epics epics # CANbus device support
device(ai,INST_IO,devAiCan,"CANbus")
device(ao,INST_IO,devAoCan,"CANbus")
device(bi,INST_IO,devBiCan,"CANbus")
device(bo,INST_IO,devBoCan,"CANbus")
device(mbbi,INST_IO,devMbbiCan,"CANbus")
device(mbbo,INST_IO,devMbboCan,"CANbus")
device(mbbiDirect,INST_IO,devMbbiDirectCan,"CANbus")
device(mbboDirect,INST_IO,devMbboDirectCan,"CANbus")
# Tip810 bus status device support
device(bi,INST_IO,devBiTip810,"Tip810")
# CANbus driver support for the TEWS Tip810 IP module...
driver(drvTip810)
# ... which depends on the drvIpac driver
include "drvIpac.dbd"
drvTip810/drvTip810.c 0100644 0000621 0000620 00000103153 06747674160 013430 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvTip810.c
Description:
CAN Bus driver for TEWS TIP810 Industry-Pack Module.
Author:
Andrew Johnson
Created:
20 July 1995
Version:
drvTip810.c,v 1.10 1999/07/28 21:38:56 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#include "drvTip810.h"
#include "drvIpac.h"
#include "pca82c200.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Some local magic numbers */
#define T810_INT_VEC_BASE 0x60
#define T810_MAGIC_NUMBER 81001
#define RECV_TASK_PRIO 55 /* vxWorks task priority */
#define RECV_TASK_STACK 20000 /* task stack size */
#define RECV_Q_SIZE 1000 /* Num messages to buffer */
/* These are the IPAC IDs for this module */
#define IP_MANUFACTURER_TEWS 0xb3
#define IP_MODEL_TEWS_TIP810 0x01
#ifndef NO_EPICS
#include
/* EPICS Driver Support Entry Table */
struct drvet drvTip810 = {
3,
(DRVSUPFUN) t810Report,
(DRVSUPFUN) t810Initialise,
NULL
};
#endif /* NO_EPICS */
typedef void callback_t(void *pprivate, long parameter);
typedef struct callbackTable_s {
struct callbackTable_s *pnext; /* linked list ... */
void *pprivate; /* reference for callback routine */
callback_t *pcallback; /* registered routine */
} callbackTable_t;
typedef struct t810Dev_s {
struct t810Dev_s *pnext; /* To next device. Must be first member */
int magicNumber; /* device pointer confirmation */
char *pbusName; /* Bus identification */
ushort_t card; /* Industry Pack address */
ushort_t slot; /* " " " */
uint_t busRate; /* bit rate of bus in Kbits/sec */
pca82c200_t *pchip; /* controller registers */
uchar_t *pintVec; /* interrupt vector register */
SEM_ID txSem; /* Transmit buffer protection */
uint_t txCount; /* messages transmitted */
uint_t rxCount; /* messages received */
uint_t overCount; /* overrun - lost messages */
uint_t unusedCount; /* messages without callback */
ushort_t unusedId; /* last ID received without a callback */
uint_t errorCount; /* Times entered Error state */
uint_t busOffCount; /* Times entered Bus Off state */
SEM_ID readSem; /* canRead task Mutex */
canMessage_t *preadBuffer; /* canRead destination buffer */
SEM_ID rxSem; /* canRead message arrival signal */
callbackTable_t *pmsgHandler[CAN_IDENTIFIERS]; /* message callbacks */
callbackTable_t *psigHandler; /* error signal callbacks */
} t810Dev_t;
typedef struct {
t810Dev_t *pdevice;
canMessage_t message;
} t810Receipt_t;
LOCAL t810Dev_t *pt810First = NULL;
int canSilenceErrors = FALSE; /* Really for EPICS device support use */
LOCAL MSG_Q_ID receiptQueue = 0;
int t810maxQueued = 0; /* not static so may be reset by operator */
/*******************************************************************************
Routine:
t810Status
Purpose:
Return status of given t810 device
Description:
Returns the status of the t810 device identified by the input parameter,
or -1 if not a device ID.
Returns:
Bit-pattern (0..255) or -1.
*/
int t810Status (
void *canBusID
) {
t810Dev_t *pdevice = (t810Dev_t *)canBusID;
if (canBusID != 0 &&
pdevice->magicNumber == T810_MAGIC_NUMBER) {
return pdevice->pchip->status;
} else {
return -1;
}
}
/*******************************************************************************
Routine:
t810Report
Purpose:
Report status of all t810 devices
Description:
Prints a list of all the t810 devices created, their IP carrier &
slot numbers and the bus name string. For interest > 0 it gives
additional information about each device.
Returns:
OK, or
S_t810_badDevice if device list corrupted.
*/
int t810Report (
int interest
) {
t810Dev_t *pdevice = pt810First;
ushort_t id, printed;
uchar_t status;
if (interest > 0) {
printf(" Receive queue holds %d messages, max %d = %d %% used.\n",
RECV_Q_SIZE, t810maxQueued,
(100 * t810maxQueued) / RECV_Q_SIZE);
}
while (pdevice != NULL) {
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
printf("t810 device list is corrupt\n");
return S_t810_badDevice;
}
printf(" '%s' : IP Carrier %hd Slot %hd, Bus rate %d Kbits/sec\n",
pdevice->pbusName, pdevice->card, pdevice->slot,
pdevice->busRate);
switch (interest) {
case 1:
printf("\tMessages Sent : %5d\n", pdevice->txCount);
printf("\tMessages Received : %5d\n", pdevice->rxCount);
printf("\tMessage Overruns : %5d\n", pdevice->overCount);
printf("\tDiscarded Messages : %5d\n", pdevice->unusedCount);
if (pdevice->unusedCount > 0) {
printf("\tLast Discarded ID : %#5x\n", pdevice->unusedId);
}
printf("\tError Interrupts : %5d\n", pdevice->errorCount);
printf("\tBus Off Events : %5d\n", pdevice->busOffCount);
break;
case 2:
printed = 0;
printf("\tCallbacks registered: ");
for (id=0; id < CAN_IDENTIFIERS; id++) {
if (pdevice->pmsgHandler[id] != NULL) {
if (printed % 10 == 0) {
printf("\n\t ");
}
printf("0x%-3hx ", id);
printed++;
}
}
if (printed == 0) {
printf("None.");
}
printf("\n\tcanRead Status : %s\n",
pdevice->preadBuffer ? "Active" : "Idle");
break;
case 3:
printf(" pca82c200 Chip Status:\n");
status = pdevice->pchip->status;
printf("\tBus Status : %s\n",
status & PCA_SR_BS ? "Bus-Off" : "Bus-On");
printf("\tError Status : %s\n",
status & PCA_SR_ES ? "Error" : "Ok");
printf("\tData Overrun : %s\n",
status & PCA_SR_DO ? "Overrun" : "Ok");
printf("\tReceive Status : %s\n",
status & PCA_SR_RS ? "Receiving" : "Idle");
printf("\tReceive Buffer Status : %s\n",
status & PCA_SR_RBS ? "Full" : "Empty");
printf("\tTransmit Status : %s\n",
status & PCA_SR_TS ? "Transmitting" : "Idle");
printf("\tTransmission Complete : %s\n",
status & PCA_SR_TCS ? "Complete" : "Incomplete");
printf("\tTransmit Buffer Access : %s\n",
status & PCA_SR_TBS ? "Released" : "Locked");
break;
}
pdevice = pdevice->pnext;
}
return OK;
}
/*******************************************************************************
Routine:
t810Create
Purpose:
Register a new TIP810 device
Description:
Checks that the given name and card/slot numbers are unique, then
creates a new device table, initialises it and adds it to the end
of the linked list.
Returns:
Example:
t810Create "CAN1", 0, 0, 500
*/
int t810Create (
char *pbusName, /* Unique Identifier for this device */
ushort_t card, /* Ipac Driver card .. */
ushort_t slot, /* .. and slot number */
uint_t busRate /* in Kbits/sec */
) {
static const struct {
uint_t rate;
uchar_t busTiming0;
uchar_t busTiming1;
} rateTable[] = {
{ 5, PCA_BTR0_5K, PCA_BTR1_5K },
{ 10, PCA_BTR0_10K, PCA_BTR1_10K },
{ 20, PCA_BTR0_20K, PCA_BTR1_20K },
{ 50, PCA_BTR0_50K, PCA_BTR1_50K },
{ 100, PCA_BTR0_100K, PCA_BTR1_100K },
{ 125, PCA_BTR0_125K, PCA_BTR1_125K },
{ 250, PCA_BTR0_250K, PCA_BTR1_250K },
{ 500, PCA_BTR0_500K, PCA_BTR1_500K },
{ 1000, PCA_BTR0_1M0, PCA_BTR1_1M0 },
{ 1600, PCA_BTR0_1M6, PCA_BTR1_1M6 },
{ -125, PCA_KVASER_125K, PCA_BTR1_KVASER},
{ -250, PCA_KVASER_250K, PCA_BTR1_KVASER},
{ -500, PCA_KVASER_500K, PCA_BTR1_KVASER},
{ -1000,PCA_KVASER_1M0, PCA_BTR1_KVASER},
{ 0, 0, 0 }
};
t810Dev_t *pdevice, *plist = (t810Dev_t *) &pt810First;
int status, rateIndex, id;
status = ipmValidate(card, slot, IP_MANUFACTURER_TEWS,
IP_MODEL_TEWS_TIP810);
if (status) {
return status;
}
/* Slot contains a real TIP810 module */
if (busRate == 0) {
return S_t810_badBusRate;
}
for (rateIndex = 0; rateTable[rateIndex].rate != busRate; rateIndex++) {
if (rateTable[rateIndex].rate == 0) {
return S_t810_badBusRate;
}
}
/* Bus rate is legal and we now know the right chip settings */
while (plist->pnext != NULL) {
plist = plist->pnext;
if (strcmp(plist->pbusName, pbusName) == 0 ||
(plist->card == card &&
plist->slot == slot)) {
return S_t810_duplicateDevice;
}
}
/* plist now points to the last item in the list */
pdevice = malloc(sizeof (t810Dev_t));
if (pdevice == NULL) {
return errno;
}
/* pdevice is our new device table */
pdevice->pnext = NULL;
pdevice->magicNumber = T810_MAGIC_NUMBER;
pdevice->pbusName = pbusName;
pdevice->card = card;
pdevice->slot = slot;
pdevice->busRate = busRate;
pdevice->pchip = (pca82c200_t *) ipmBaseAddr(card, slot, ipac_addrIO);
pdevice->pintVec = 0x41 + (uchar_t *) pdevice->pchip;
pdevice->preadBuffer = NULL;
pdevice->psigHandler = NULL;
for (id=0; idpmsgHandler[id] = NULL;
}
pdevice->txSem = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
pdevice->rxSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
pdevice->readSem = semMCreate(SEM_Q_PRIORITY |
SEM_INVERSION_SAFE |
SEM_DELETE_SAFE);
if (pdevice->txSem == NULL ||
pdevice->rxSem == NULL ||
pdevice->readSem == NULL) {
free(pdevice); /* Ought to free those semaphores, but... */
return errno;
}
plist->pnext = pdevice;
/* device table interface stuff filled in and added to list */
pdevice->pchip->control = PCA_CR_RR; /* Reset state */
pdevice->pchip->acceptanceCode = 0;
pdevice->pchip->acceptanceMask = 0xff;
pdevice->pchip->busTiming0 = rateTable[rateIndex].busTiming0;
pdevice->pchip->busTiming1 = rateTable[rateIndex].busTiming1;
pdevice->pchip->outputControl = PCA_OCR_OCM_NORMAL |
PCA_OCR_OCT0_PUSHPULL |
PCA_OCR_OCT1_PUSHPULL;
/* chip now initialised, but held in the Reset state */
return OK;
}
/*******************************************************************************
Routine:
t810Shutdown
Purpose:
Reboot hook routine
Description:
Stops interrupts and resets the CAN controller chip.
Returns:
void
*/
int t810Shutdown (
int startType
) {
t810Dev_t *pdevice = pt810First;
while (pdevice != NULL) {
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
/* Whoops! */
return S_t810_badDevice;
}
pdevice->pchip->control = PCA_CR_RR; /* Reset, interrupts off */
pdevice = pdevice->pnext;
}
return OK;
}
/*******************************************************************************
Routine:
getRxMessage
Purpose:
Copy a received message from chip to memory
Description:
Reads a message from the chip receive buffer into the message buffer
and flags to chip to release the buffer for further input.
Returns:
void
*/
LOCAL void getRxMessage (
pca82c200_t *pchip,
canMessage_t *pmessage
) {
uchar_t desc0, desc1, i;
desc0 = pchip->rxBuffer.descriptor0;
desc1 = pchip->rxBuffer.descriptor1;
pmessage->identifier = (desc0 << PCA_MSG_ID0_RSHIFT) |
((desc1 & PCA_MSG_ID1_MASK) >> PCA_MSG_ID1_LSHIFT);
pmessage->length = desc1 & PCA_MSG_DLC_MASK;
if (desc1 & PCA_MSG_RTR) {
pmessage->rtr = RTR;
} else {
pmessage->rtr = SEND;
for (i=0; ilength; i++) {
pmessage->data[i] = pchip->rxBuffer.data[i];
}
}
pchip->command = PCA_CMR_RRB; /* Finished with chip buffer */
}
/*******************************************************************************
Routine:
putTxMessage
Purpose:
Copy a message from memory to the chip
Description:
Copies a message from the message buffer into the chip receive buffer
and flags to chip to transmit the message.
Returns:
void
*/
LOCAL void putTxMessage (
pca82c200_t *pchip,
canMessage_t *pmessage
) {
uchar_t desc0, desc1, i;
desc0 = pmessage->identifier >> PCA_MSG_ID0_RSHIFT;
desc1 = (pmessage->identifier << PCA_MSG_ID1_LSHIFT) & PCA_MSG_ID1_MASK;
desc1 |= pmessage->length & PCA_MSG_DLC_MASK;
if (pmessage->rtr == SEND) {
for (i=0; ilength; i++) {
pchip->txBuffer.data[i] = pmessage->data[i];
}
} else {
desc1 |= PCA_MSG_RTR;
}
pchip->txBuffer.descriptor0 = desc0;
pchip->txBuffer.descriptor1 = desc1;
pchip->command = PCA_CMR_TR;
}
/*******************************************************************************
Routine:
doCallbacks
Purpose:
calls all routines in the given list
Description:
Returns:
void
*/
LOCAL void doCallbacks (
callbackTable_t *phandler,
long parameter
) {
while (phandler != NULL) {
(*phandler->pcallback)(phandler->pprivate, parameter);
phandler = phandler->pnext;
}
}
/*******************************************************************************
Routine:
t810ISR
Purpose:
Interrupt Service Routine
Description:
Returns:
void
*/
LOCAL void t810ISR (
t810Dev_t *pdevice
) {
uchar_t intSource = pdevice->pchip->interrupt;
if (intSource & PCA_IR_OI) { /* Overrun Interrupt */
pdevice->overCount++;
canBusStop(pdevice->pbusName); /* Reset the chip but not */
canBusRestart(pdevice->pbusName); /* all the counters */
intSource = pdevice->pchip->interrupt; /* Rescan interrupts */
}
if (intSource & PCA_IR_RI) { /* Receive Interrupt */
t810Receipt_t qmsg;
/* Take a local copy of the message */
qmsg.pdevice = pdevice;
getRxMessage(pdevice->pchip, &qmsg.message);
/* Send it to the servicing task */
if (msgQSend(receiptQueue, (char *)&qmsg, sizeof(t810Receipt_t),
NO_WAIT, MSG_PRI_NORMAL) == ERROR)
logMsg("Warning: CANbus receive queue overflow\n", 0, 0, 0, 0, 0, 0);
}
if (intSource & PCA_IR_EI) { /* Error Interrupt */
callbackTable_t *phandler = pdevice->psigHandler;
ushort_t status;
switch (pdevice->pchip->status & (PCA_SR_ES | PCA_SR_BS)) {
case PCA_SR_ES:
status = CAN_BUS_ERROR;
pdevice->errorCount++;
break;
case PCA_SR_BS:
case PCA_SR_BS | PCA_SR_ES:
status = CAN_BUS_OFF;
pdevice->busOffCount++;
semGive(pdevice->txSem); /* Release transmit */
pdevice->pchip->control &= ~PCA_CR_RR; /* Clear Reset state */
break;
default:
status = CAN_BUS_OK;
break;
}
doCallbacks(phandler, status);
}
if (intSource & PCA_IR_TI) { /* Transmit Interrupt */
pdevice->txCount++;
semGive(pdevice->txSem);
}
if (intSource & PCA_IR_WUI) { /* Wake-up Interrupt */
logMsg("Wake-up Interrupt from CANbus '%s'\n",
(int) pdevice->pbusName, 0, 0, 0, 0, 0);
}
}
/*******************************************************************************
Routine:
t810RecvTask
Purpose:
Receive task
Description:
This routine is a background task started by t810Initialise. It
takes messages out of the receive queue one by one and runs the
callbacks registered against the relevent message ID.
Returns:
int
*/
LOCAL int t810RecvTask() {
t810Receipt_t rmsg;
callbackTable_t *phandler;
int numQueued;
if (receiptQueue == 0) {
fprintf(stderr, "CANbus Receive queue does not exist, task exiting.\n");
return ERROR;
}
printf("CANbus receive task started\n");
while (TRUE) {
numQueued = msgQNumMsgs(receiptQueue);
if (numQueued > t810maxQueued) t810maxQueued = numQueued;
msgQReceive(receiptQueue, (char *)&rmsg, sizeof(t810Receipt_t),
WAIT_FOREVER);
rmsg.pdevice->rxCount++;
/* Look up the message ID and do the message callbacks */
phandler = rmsg.pdevice->pmsgHandler[rmsg.message.identifier];
if (phandler == NULL) {
rmsg.pdevice->unusedId = rmsg.message.identifier;
rmsg.pdevice->unusedCount++;
} else {
doCallbacks(phandler, (long) &rmsg.message);
}
/* If canRead is waiting for this ID, give it the message and kick it */
if (rmsg.pdevice->preadBuffer != NULL &&
rmsg.pdevice->preadBuffer->identifier == rmsg.message.identifier) {
memcpy(rmsg.pdevice->preadBuffer, &rmsg.message,
sizeof(canMessage_t));
rmsg.pdevice->preadBuffer = NULL;
semGive(rmsg.pdevice->rxSem);
}
}
}
/*******************************************************************************
Routine:
t810Initialise
Purpose:
Initialise driver and all registered hardware
Description:
Under EPICS this routine is called by iocInit, which must occur
after all t810Create calls in the startup script. It completes the
initialisation of the CAN controller chip and interrupt vector
registers for all known TIP810 devices and starts the chips
running. The receive queue is created and its processing task is
started to handle incoming data. A reboot hook is used to make
sure all interrupts are turned off if the OS is shut down.
Returns:
int
*/
int t810Initialise (
void
) {
uchar_t intVec = T810_INT_VEC_BASE;
t810Dev_t *pdevice = pt810First;
int status = OK;
rebootHookAdd(t810Shutdown);
receiptQueue = msgQCreate(RECV_Q_SIZE, sizeof(t810Receipt_t), MSG_Q_FIFO);
if ((receiptQueue == NULL) ||
taskSpawn("canRecvTask", RECV_TASK_PRIO, VX_FP_TASK, RECV_TASK_STACK,
t810RecvTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == ERROR) {
return errno;
}
while (pdevice != NULL) {
pdevice->txCount = 0;
pdevice->rxCount = 0;
pdevice->overCount = 0;
pdevice->unusedCount = 0;
pdevice->errorCount = 0;
pdevice->busOffCount = 0;
if (intConnect(INUM_TO_IVEC(intVec), t810ISR, (int) pdevice)) {
status = errno;
}
*(pdevice->pintVec) = intVec++;
ipmIrqCmd(pdevice->card, pdevice->slot, 0, ipac_irqEnable);
pdevice->pchip->control = PCA_CR_OIE |
PCA_CR_EIE |
PCA_CR_TIE |
PCA_CR_RIE;
pdevice = pdevice->pnext;
}
return status;
}
/*******************************************************************************
Routine:
canOpen
Purpose:
Return device pointer for given CAN bus name
Description:
Searches through the linked list of known t810 devices for one
which matches the name given, and returns the device pointer
associated with the relevant device table.
Returns:
OK, or S_can_noDevice if no match found.
Example:
void *can1;
status = canOpen("CAN1", &can1);
*/
int canOpen (
const char *pbusName,
void **ppdevice
) {
t810Dev_t *pdevice = pt810First;
while (pdevice != NULL) {
if (strcmp(pdevice->pbusName, pbusName) == 0) {
*ppdevice = pdevice;
return OK;
}
pdevice = pdevice->pnext;
}
return S_can_noDevice;
}
/*******************************************************************************
Routine:
canBusReset
Purpose:
Reset named CANbus
Description:
Resets the chip and connected to the named bus and all counters
Returns:
OK, or S_can_noDevice if no match found.
Example:
status = canBusReset("CAN1");
*/
int canBusReset (
const char *pbusName
) {
t810Dev_t *pdevice;
int status = canOpen(pbusName, (void **) &pdevice);
if (status) return status;
pdevice->pchip->control |= PCA_CR_RR; /* Reset the chip */
pdevice->txCount = 0;
pdevice->rxCount = 0;
pdevice->overCount = 0;
pdevice->unusedCount = 0;
pdevice->errorCount = 0;
pdevice->busOffCount = 0;
semGive(pdevice->txSem);
pdevice->pchip->control = PCA_CR_OIE |
PCA_CR_EIE |
PCA_CR_TIE |
PCA_CR_RIE;
return OK;
}
/*******************************************************************************
Routine:
canBusStop
Purpose:
Stop I/O on named CANbus
Description:
Holds the chip for the named bus in Reset state
Returns:
OK, or S_can_noDevice if no match found.
Example:
status = canBusStop("CAN1");
*/
int canBusStop (
const char *pbusName
) {
t810Dev_t *pdevice;
int status = canOpen(pbusName, (void **) &pdevice);
if (status) return status;
pdevice->pchip->control |= PCA_CR_RR; /* Reset the chip */
return OK;
}
/*******************************************************************************
Routine:
canBusRestart
Purpose:
Restart I/O on named CANbus
Description:
Restarts the chip for the named bus after a canBusStop
Returns:
OK, or S_can_noDevice if no match found.
Example:
status = canBusRestart("CAN1");
*/
int canBusRestart (
const char *pbusName
) {
t810Dev_t *pdevice;
int status = canOpen(pbusName, (void **) &pdevice);
if (status) return status;
semGive(pdevice->txSem);
pdevice->pchip->control = PCA_CR_OIE |
PCA_CR_EIE |
PCA_CR_TIE |
PCA_CR_RIE;
return OK;
}
/*******************************************************************************
Routine:
strdupn
Purpose:
duplicate n characters of a string and return pointer to new substring
Description:
Copies n characters from the input string to a newly malloc'ed memory
buffer, and adds a trailing '\0', then returns the new string pointer.
Returns:
char *newString, or NULL if malloc failed.
*/
LOCAL char* strdupn (
const char *ct,
size_t n
) {
char *duplicate;
duplicate = malloc(n+1);
if (duplicate == NULL) {
return NULL;
}
memcpy(duplicate, ct, n);
duplicate[n] = '\0';
return duplicate;
}
/*******************************************************************************
Routine:
canIoParse
Purpose:
Parse a CAN address string into a canIo_t structure
Description:
canString which must match the format below is converted by this routine
into the relevent fields of the canIo_t structure pointed to by pcanIo:
busname{/timeout}:id{+n}{.offset} parameter
where
busname is alphanumeric, all other fields are hex, decimal or octal
timeout is in milliseconds
id and any number of +n components are summed to give the CAN Id
offset is the byte offset into the message
parameter is available for use by device support
Returns:
OK, or
S_can_badAddress for illegal input strings,
S_can_noDevice for an unregistered bus name.
Example:
canIoParse("CAN1/20:0126+4+1.4 0xfff", &myIo);
*/
int canIoParse (
char *canString,
canIo_t *pcanIo
) {
char separator;
char *name;
pcanIo->canBusID = NULL;
if (canString == NULL ||
pcanIo == NULL) {
return S_can_badAddress;
}
/* Get rid of leading whitespace and non-alphanumeric chars */
while (!isalnum(*canString)) {
if (*canString++ == '\0') {
return S_can_badAddress;
}
}
/* First part of string is the bus name */
name = canString;
/* find the end of the busName */
canString = strpbrk(canString, "/:");
if (canString == NULL ||
*canString == '\0') {
return S_can_badAddress;
}
/* now we're at character after the end of the busName */
pcanIo->busName = strdupn(name, canString - name);
if (pcanIo->busName == NULL) {
return errno;
}
separator = *canString++;
/* Handle / if present, convert from ms to ticks */
if (separator == '/') {
pcanIo->timeout = strtol(canString, &canString, 0) * sysClkRateGet();
pcanIo->timeout = ((pcanIo->timeout + 500) / 1000);
separator = *canString++;
} else {
pcanIo->timeout = WAIT_FOREVER;
}
/* String must contain : */
if (separator != ':') {
return S_can_badAddress;
}
pcanIo->identifier = strtoul(canString, &canString, 0);
separator = *canString++;
/* Handle any number of optional + additions to the ID */
while (separator == '+') {
pcanIo->identifier += strtol(canString, &canString, 0);
separator = *canString++;
}
/* Handle . if present */
if (separator == '.') {
pcanIo->offset = strtoul(canString, &canString, 0);
if (pcanIo->offset >= CAN_DATA_SIZE) {
return S_can_badAddress;
}
separator = *canString++;
} else {
pcanIo->offset = 0;
}
/* Final parameter is separated by whitespace */
if (separator != ' ' &&
separator != '\t') {
return S_can_badAddress;
}
pcanIo->parameter = strtol(canString, &canString, 0);
/* Ok, finally look up the bus name */
return canOpen(pcanIo->busName, &pcanIo->canBusID);
}
/*******************************************************************************
Routine:
canWrite
Purpose:
writes a CAN message to the bus
Description:
Sends the message described by pmessage out through the bus identified by
canBusID. After some simple argument checks it obtains exclusive access to
the transmit registers, then copies the message to the chip. The timeout
value allows task recovery in the event that exclusive access is not
available within a the given number of vxWorks clock ticks.
Returns:
OK,
S_can_badMessage for bad identifier, message length or rtr value,
S_can_badDevice for bad device pointer,
S_objLib_OBJ_TIMEOUT indicates timeout,
S_t810_transmitterBusy indicates an internal error.
Example:
*/
int canWrite (
void *canBusID,
canMessage_t *pmessage,
int timeout
) {
t810Dev_t *pdevice = (t810Dev_t *) canBusID;
int status;
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
return S_t810_badDevice;
}
if (pmessage->identifier >= CAN_IDENTIFIERS ||
pmessage->length > CAN_DATA_SIZE ||
(pmessage->rtr != SEND && pmessage->rtr != RTR)) {
return S_can_badMessage;
}
status = semTake(pdevice->txSem, timeout);
if (status) {
return errno;
}
if (pdevice->pchip->status & PCA_SR_TBS) {
putTxMessage(pdevice->pchip, pmessage);
return OK;
} else {
semGive(pdevice->txSem);
return S_t810_transmitterBusy;
}
}
/*******************************************************************************
Routine:
canMessage
Purpose:
Register CAN message callback
Description:
Adds a new callback routine for the given CAN message ID on the
given device. There can be any number of callbacks for the same ID,
and all are called in turn when a message with this ID is
received. As a result, the callback routine must not change the
message at all - it is only permitted to examine it. The callback
is called from vxWorks Interrupt Context, thus there are several
restrictions in what the routine can perform (see vxWorks User
Guide for details of these). The callback routine should be
declared of type canMsgCallback_t
void callback(void *pprivate, can_Message_t *pmessage);
The pprivate value supplied to canMessage is passed to the callback
routine with each message to allow it to identify its context.
Returns:
OK,
S_can_badMessage for bad identifier or NULL callback routine,
S_t810_badDevice for bad device pointer.
Example:
*/
int canMessage (
void *canBusID,
ushort_t identifier,
canMsgCallback_t *pcallback,
void *pprivate
) {
t810Dev_t *pdevice = (t810Dev_t *) canBusID;
callbackTable_t *phandler, *plist;
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
return S_t810_badDevice;
}
if (identifier >= CAN_IDENTIFIERS ||
pcallback == NULL) {
return S_can_badMessage;
}
phandler = malloc(sizeof (callbackTable_t));
if (phandler == NULL) {
return errno;
}
phandler->pnext = NULL;
phandler->pprivate = pprivate;
phandler->pcallback = (callback_t *) pcallback;
plist = (callbackTable_t *) (&pdevice->pmsgHandler[identifier]);
while (plist->pnext != NULL) {
plist = plist->pnext;
}
/* plist now points to the last handler in the list */
plist->pnext = phandler;
return OK;
}
/*******************************************************************************
Routine:
canMsgDelete
Purpose:
Delete registered CAN message callback
Description:
Deletes an existing callback routine for the given CAN message ID
on the given device. The first matching callback found in the list
is deleted. To match, the parameters to canMsgDelete must be
identical to those given to canMessage.
Returns:
OK,
S_can_badMessage for bad identifier or NULL callback routine,
S_can_noMessage for no matching message callback,
S_t810_badDevice for bad device pointer.
Example:
*/
int canMsgDelete (
void *canBusID,
ushort_t identifier,
canMsgCallback_t *pcallback,
void *pprivate
) {
t810Dev_t *pdevice = (t810Dev_t *) canBusID;
callbackTable_t *phandler, *plist;
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
return S_t810_badDevice;
}
if (identifier >= CAN_IDENTIFIERS ||
pcallback == NULL) {
return S_can_badMessage;
}
plist = (callbackTable_t *) (&pdevice->pmsgHandler[identifier]);
while (plist->pnext != NULL) {
phandler = plist->pnext;
if (((canMsgCallback_t *)phandler->pcallback == pcallback) &&
(phandler->pprivate == pprivate)) {
plist->pnext = phandler->pnext;
phandler->pnext = NULL; /* Just in case... */
free(phandler);
return OK;
}
plist = phandler;
}
return S_can_noMessage;
}
/*******************************************************************************
Routine:
canSignal
Purpose:
Register CAN error signal callback
Description:
Adds a new callback routine for the CAN error reports. There can be
any number of error callbacks, and all are called in turn when the
controller chip reports an error or bus Off The callback is called
from vxWorks Interrupt Context, thus there are restrictions in what
the routine can perform (see vxWorks User Guide for details of
these). The callback routine should be declared a canSigCallback_t
void callback(void *pprivate, ushort_t status);
The pprivate value supplied to canSignal is passed to the callback
routine with the error status to allow it to identify its context.
Status values will be one of
CAN_BUS_OK,
CAN_BUS_ERROR or
CAN_BUS_OFF.
If the chip goes to the Bus Off state, the driver will attempt to
restart it.
Returns:
OK,
S_t810_badDevice for bad device pointer.
Example:
*/
int canSignal (
void *canBusID,
canSigCallback_t *pcallback,
void *pprivate
) {
t810Dev_t *pdevice = (t810Dev_t *) canBusID;
callbackTable_t *phandler, *plist;
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
return S_t810_badDevice;
}
phandler = malloc(sizeof (callbackTable_t));
if (phandler == NULL) {
return errno;
}
phandler->pnext = NULL;
phandler->pprivate = pprivate;
phandler->pcallback = (callback_t *) pcallback;
plist = (callbackTable_t *) (&pdevice->psigHandler);
while (plist->pnext != NULL) {
plist = plist->pnext;
}
/* plist now points to the last handler in the list */
plist->pnext = phandler;
return OK;
}
/*******************************************************************************
Routine:
canRead
Purpose:
read incoming CAN message, any ID number
Description:
The simplest way to implement this is have canRead take a message
ID in the buffer, send an RTR and look for the returned value of
this message. This is in keeping with the CAN philosophy and makes
it useful for simple software interfaces. More complex ones ought
to use the canMessage callback functions.
Returns:
OK, or
S_t810_badDevice for bad bus ID,
S_can_badMessage for bad message Identifier or length,
S_objLib_OBJ_TIMEOUT for timeout
Example:
canMessage_t myBuffer = {
139, // Can ID
0, // RTR
4 // Length
};
int status = canRead(canID, &myBuffer, WAIT_FOREVER);
*/
int canRead (
void *canBusID,
canMessage_t *pmessage,
int timeout
) {
t810Dev_t *pdevice = (t810Dev_t *) canBusID;
int status;
if (pdevice->magicNumber != T810_MAGIC_NUMBER) {
return S_t810_badDevice;
}
if (pmessage->identifier >= CAN_IDENTIFIERS ||
pmessage->length > CAN_DATA_SIZE) {
return S_can_badMessage;
}
/* This semaphore is so only one task canRead simultaneously */
status = semTake(pdevice->readSem, timeout);
if (status) {
return errno;
}
pdevice->preadBuffer = pmessage;
/* All set for the reply, now send the request */
pmessage->rtr = RTR;
status = canWrite(canBusID, pmessage, timeout);
if (status == OK) {
/* Wait for the message to be recieved */
status = semTake(pdevice->rxSem, timeout);
if (status) {
status = errno;
}
}
if (status) {
/* Problem (timeout) sending the RTR or receiving the reply */
pdevice->preadBuffer = NULL;
semTake(pdevice->rxSem, NO_WAIT); /* Must leave this EMPTY */
}
semGive(pdevice->readSem);
return status;
}
/*******************************************************************************
Routine:
canTest
Purpose:
Test routine, sends a single message to the named bus.
Description:
This routine is intended for use from the vxWorks shell.
Returns:
Ok, or ERROR
Example:
*/
int canTest (
char *pbusName,
ushort_t identifier,
ushort_t rtr,
uchar_t length,
char *data
) {
void *canBusID;
canMessage_t message;
int status;
if (pbusName == NULL) {
printf("Usage: canTest \"busname\", id, rtr, len, \"data\"\n");
return ERROR;
}
status = canOpen(pbusName, &canBusID);
if (status) {
printf("Error %d opening CAN bus '%s'\n", status, pbusName);
return ERROR;
}
message.identifier = identifier;
message.rtr = rtr ? RTR : SEND;
message.length = length;
if (rtr == 0) {
memcpy(&message.data[0], data, length);
}
status = canWrite(canBusID, &message, 0);
if (status) {
printf("Error %d writing message\n", status);
return ERROR;
}
return OK;
}
drvTip810/drvTip810.h 0100644 0000621 0000620 00000002241 06352262366 013422 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
drvTip810.h
Description:
Header file for TEWS TIP810 CAN Bus driver.
Author:
Andrew Johnson
Created:
20 July 1995
Version:
drvTip810.h,v 1.2 1997/06/19 16:57:26 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef INCdrvTip810H
#define INCdrvTip810H
#include
#include "canBus.h"
/* Error Numbers */
#ifndef M_t810
#define M_t810 (810<<16)
#endif
#define S_t810_duplicateDevice (M_t810| 1) /*duplicate t810 device definition*/
#define S_t810_badBusRate (M_t810| 2) /*CANbus bit rate not supported*/
#define S_t810_badDevice (M_t810| 3) /*device pointer is not for t810*/
#define S_t810_transmitterBusy (M_t810| 4) /*transmit buffer unexpectedly busy*/
extern int t810Status(void *canBusID);
extern int t810Report(int page);
extern int t810Create(char *busName, ushort_t card, ushort_t slot,
uint_t busRate);
extern int t810Shutdown(int starttype);
extern int t810Initialise(void);
#endif /* INCdrvTip810H */
drvTip810/drvTip810.html 0100644 0000621 0000620 00000103337 06747674161 014157 0 ustar epics epics
drvTip810 - CAN Bus Driver
drvTip810 - CAN Bus Driver
TEWS Tip810 Driver for EPICS
Version 2.1
Andrew Johnson
<anjohnson@iee.org>
1. Introduction
This document describes the software interface to the TEWS TIP810
Industry-Pack Module for vxWorks, written as part of an EPICS interface to
the CANbus for the Gemini and
UKIRT telescopes.
This software is an IPAC Module Driver and uses the services of the drvIpac
Industry Pack Driver to interface to the IPAC Carrier Board - see the
accompanying document drvIpac - Industry Pack
Driver for details of this driver software. The device support routines
provided for use with EPICS are described in the document
devCan - CAN Bus Device Support which briefly covers
the use of the various record types supported.
The following routines provided by this driver are described in detail
below:
2. Tip810 Driver Usage
The software provides an interface to higher level software which, other
than device initialisation, is not specific to the TIP810 but could be used
with a CANbus driver for a different hardware interface. The interface to the
higher level software is provided in two header files: canBus.h
contains all of the generic CANbus definitions and declarations, while
drvTip810.h incorporates the additional declarations and definitions
which are specific to the TIP810 module (and also #includes
canBus.h and the vxWorks ANSI header file types.h). The
routines which are specific to the TIP810 or meant for use from the vxWorks
shell are described individually in this section, while the generic CANbus
interface routines are described in section 3 below.
The TEWS TIP810 IP module contains a Philips pca82c200 stand-alone
CAN-controller chip which performs all of the CANbus interfacing functions.
The interface to this chip is defined in a separate header file
pca82c200.h which declares the register interface structure and the
bit-patterns for the various chip registers.
The driver uses an interrupt service routine with a different interrupt
vector for each TIP810 module it controls, starting with vector 0x60 and
increasing by one each time.
The driver can be built for use in a native vxWorks (i.e. non-EPICS)
application. To do this, the drvTip810.c file should be compiled with
the -DNO_EPICS switch to disable the EPICS-specific code.
t810Create
Registers a new TIP810 device with the driver.
int t810Create (char *pbusName, ushort_t card, ushort_t slot,
uint_t busRate);
Parameters
- char *pbusName
- String which comprises a unique identifier for this particular CAN Bus.
This must be a static string which should not be changed while the driver is
loaded and running (vxWorks shell string literals have this property).
While t810Create enforces no restrictions on the characters which
may be used in the name, if the canIoParse routine is used the name
should contain alphanumeric characters only.
- ushort_t card, ushort_t slot
- Ipac Driver carrier and slot numbers which identify the IPAC module,
for use with drvIpac.
- uint_t busRate
- CAN bus rate in Kbits/sec. This parameter can have one of the values
given in the following table. Other rates will require modifications to
the driver source code. The Kvaser standard uses different bit timings
which are not compatible with those used by Tews and so require special
bit-rate entries.
busRate |
CANbus bit rate |
5 |
5 Kbits/sec |
10 |
10 Kbits/sec |
20 |
20 Kbits/sec |
50 |
50 Kbits/sec |
100 |
100 Kbits/sec |
125 |
125 Kbits/sec |
250 |
250 Kbits/sec |
500 |
500 Kbits/sec |
1000 |
1000 Kbits/sec |
1600 |
1600 Kbits/sec |
-125 |
Kvaser 125 Kbits/sec |
-250 |
Kvaser 250 Kbits/sec |
-500 |
Kvaser 500 Kbits/sec |
-1000 |
Kvaser 1000 Kbits/sec |
Description
This routine will usually be called from the vxWorks (EPICS) start-up
script. It is used to inform the driver about each TIP810 module which
it is to control, providing information on how to find the module (the
IPAC carrier and slot numbers) and what CANbus bit rate is to be used.
Each module is given a name which is matched during calls to the canOpen
routine to identify the particular module again.
The code checks that the given name and card/slot numbers are unique
and point to a real Tews Tip810 module, then it creates a new device table
and initialises it and some of the chip registers. At this stage the device
is not activated but held in the reset state.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_t810_badBusRate |
Bus Rate not supported |
S_t810_duplicateDevice |
another TIP810 already using given name and/or IPAC address |
(drvIpac) |
errors returned by ipmValidate |
(vxWorks) |
errno from malloc, semBCreate, or semMCreate |
Example
-> t810Create("CAN1", 0, 1, 500)
Value = 0
t810Shutdown
Reboot hook routine, resets all devices to stop interrupts.
int t810Shutdown(int startType);
Parameters
- int startType
- This parameter is not used by the routine but is
required to allow it to be used as a vxWorks reboot hook routine,
when it differentiates between a cold and a warm restart.
Description
When t810Initialise is run, this routine will be connected
up to the vxWorks reboot hook to reset the CAN controller chips on all
known TIP810 modules when the system is rebooted. It can also be called
by applications programs to turn off all the module drivers in the event
of a catastrophic failure, but it may be necessary to reboot the system
to re-enable them afterwards.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_t810_badDevice |
Bad or corrupted internal device table found |
t810Initialise
Initialise driver and all registered hardware.
int t810Initialise(void);
Parameters
- void
- None.
Description
Under EPICS this routine is called during iocInit, which must occur
after all canCreate calls in the start-up script. It creates a message
queue to hold received messages and starts a task named canRecvTask to
distribute them to the routines that have asked to be informed about them.
Finally it completes the initialisation of the CAN controller chip and
interrupt vector registers for all known TIP810 devices and starts them
running.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
(vxWorks) |
errno from intConnect |
t810Report
Display report giving status of all TEWS Tip810 devices
int t810Report(int interest);
Parameters
- int interest
- Interest level, indicating how much detail is required and what information
to show.
Description
Outputs (to stdout) a list of all the Tip810 devices created, their
IP carrier & slot numbers and the bus name string. For interest=1
it adds message and error statistics; for interest=2 it lists
all CAN IDs for which a call-back has been registered; for interest=3
the status of the CAN controller chip is given.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_t810_badDevice |
Bad or corrupted internal device table found |
Examples
-> t810Report(1)
TEWS tip810 CANbus Ip Modules
'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
Messages Sent : 75
Messages Received : 43
Message Overruns : 0
Discarded Messages : 4
Last Discarded ID : 0x206
Error Interrupts : 0
Bus Off Events : 0
-> t810Report(2)
TEWS tip810 CANbus Ip Modules
'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
Callbacks registered:
0x1c 0x1d 0x1e 0x1f 0x200 0x202 0x204
canRead Status : Idle
-> t810Report(3)
TEWS tip810 CANbus Ip Modules
'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
pca82c200 Chip Status:
Bus Status : Bus-On
Error Status : Ok
Data Overrun : Ok
Receive Status : Idle
Receive Buffer Status : Empty
Transmit Status : Idle
Transmission Complete : Complete
Transmit Buffer Access : Released
canTest
Test routine, sends a single test message to the named CANbus.
int canTest (char *pbusName, ushort_t identifier, ushort_t rtr,
uchar_t length, char *data);
Parameters
- char *pbusName
- Device name to identify the particular T810 device to use.
- ushort_t identifier
- The CANbus message identifier to send.
- ushort_t rtr
- 0 for a normal CAN message, non-zero to send a Remote Transmission
Request packet.
- uchar_t length
- Number of bytes in the data field for this CANbus message type.
- char *data
- Pointer to the data to be sent. Ignored for RTR packets.
Description
This routine is provided as a diagnostic, to allow the system developer
to generate CANbus messages and RTR packets from the vxWorks shell. It
should not be used from within an application. It makes use of the canOpen
and canWrite routines, and responds to errors reported by those
routines by printing a message and returning -1.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
-1 |
ERROR |
Example
-> canTest "CAN1", 0x33, 0, 4, "STOP"
3. Routines for CANbus Applications
canOpen
Return device pointer for given CAN bus name.
int canOpen(char *busName, void **pcanBusID);
Parameters
- char *busName
- Device name to identify the particular Tip810 device to use.
- void **pcanBusID
- Pointer to a void* location to hold the device identifier.
Description
Searches through the list of registered Tip810 devices for one which
matches the name given, and returns a device identifier for it. This identifier
is a required parameter for all of the remaining can driver routines. String
searching in this manner is not particularly fast if several devices have
been registered so this routine is intended to be used mainly when an application
starts up. It may be used as often as desired however - there is no associated
canClose routine.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_noDevice |
No matching device name found. |
Example
void *can1;
if (canOpen("CAN1", &can1)) {
printf("Can't open CAN1\n");
return -1;
}
canIoParse
Parse a CAN address string into a canIo_t structure
int canIoParse(char *canString, canIo_t *pcanIo);
Parameters
- const char *canString
- Address string to be converted.
- canIo_t *pcanIo
- Pointer to a structure which will be used to return the converted address
information.
Description
canIoParse is used by the EPICS device support routines to
convert record hardware addresses, but can be used by any application with
similar requirements. It is intended to provide a standard way of converting
the parameters which are needed to specify a portion of a particular CANbus
message type from an ASCII string to their binary representation as a structure.
The canIo_t structure is defined as a typedef in the canBus.h
header as follows:
typedef struct {
char *busName;
int timeout;
ushort_t identifier;
ushort_t offset;
int parameter;
void *canBusID;
} canIo_t;
The address string passed to this routine consists of a series of elements,
some of which are optional.
busName[/timeout]:identifier[+n..][.offset]parameter
The first element is the bus name, which should consist of alphanumeric
characters only. The name is terminated immediately before the first "/"
or ":" character in the string, and after omitting any
leading white-space the characters forming the bus name are copied to a
newly allocated buffer, the address of which is placed in pcanIo->busName.
An oblique stroke ("/") after the bus name introduces
an optional timeout element, which is an integer number of milli-seconds
to wait for a response for this particular type of message. This is converted
into vxWorks system clock ticks and placed in pcanIo->timeout.
If no timeout element is included, the timeout is set to WAIT_FOREVER
(-1).
The CANbus message identifier is preceded by a colon
(":"), and must result in one of the legal CANbus
identifiers in the range 0 through 2047 (with holes). The identifier itself can
be specified as a single number, or in several parts separated by plus signs,
which are all summed. The numbers here can be given in any of the standard 'C'
formats as converted by strtol, so negative, hex or octal numbers may
be used as desired.
If the identifier is followed by a decimal point ("."),
the following element is an optional byte offset into the CANbus message.
The offset is an unsigned short integer (using strtoul again for
the conversion), although to remain within the limits of the message buffer
it should be restricted to a maximum value of seven. The converted value
is placed in pcanIo->offset, which defaults to zero if no offset
is given.
The final element is a general-purpose signed integer parameter, introduced
by a space or tab character. The value is converted using strtol
and placed in pcanIo->paramter.
If the string is successfully converted without errors, canIoParse will
also call canOpen to initialise the pcanIo->canBusID
bus identifier.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_badAddress |
illegal input string or NULL input parameters |
S_can_noDevice |
No matching device name found |
(vxWorks) |
errno from malloc |
Example
canIo_t myIo;
int status;
status = canIoParse("CAN1/20:0126.4 0xfff", &myIo)
if (status) {
printf("Address string rejected\n");
return -1;
}
canWrite
Writes a message to the given CANbus
int canWrite (void *canBusID, canMessage_t *pmessage, int timeout);
Parameters
- void *canBusID
- CANbus device identifier, obtained from canOpen
- canMessage_t *pmessage
- The message to be transmitted.
- int timeout
- Delay in vxWorks system clock ticks, indicating how long to wait for
exclusive access to the TIP810 hardware.
Description
This routine is called to transmit a message on a particular CANbus.
The canMessage_t type is defined as the following structure in
canBus.h:
typedef struct {
short_t identifier; /* 0 .. 2047 with holes! */
enum {
SEND = 0, RTR = 1
} rtr; /* Remote Transmission Request */
uchar_t length; /* 0 .. 8 */
uchar_t data[CAN_DATA_SIZE]; /* CAN_DATA_SIZE = 8 */
} canMessage_t;
When called, canWrite obtains exclusive access to the TIP810
transmission buffer, converts the message into the correct form for the
interface chip and copies it to the hardware registers. Finally it sends
a Transmit Message command to the chip. The exclusive access semaphore
will be released by the Interrupt Service Routine when it receives a notification
from the chip that the message has been transmitted successfully.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_t810_badDevice |
canBusID not valid |
S_can_badMessage |
invalid field in the message buffer |
S_t810_transmitterBusy |
system fault somewhere |
(vxWorks) |
errno from semTake |
Example
canIo_t myIo;
canMessage_t message;
char data[] = "STOP";
int status;
status = canIoParse("CAN1/20:0126 0xfff", &myIo);
if (status == OK) {
message.identifier = myIo.identifier;
message.rtr = SEND;
message.length = strlen(data);
memcpy(&message.data[0], data, message.length);
status = canWrite(myIo.canBusID, &message, myIo.timeout);
}
canMessage
Register CAN message call-back
int canMessage(void *canBusID, ushort_t identifier,
canMsgCallback_t *pcallback, void *pprivate);
Parameters
- void *canBusID
- CANbus device identifier, obtained from canOpen
- ushort_t identifier
- The CAN message identifier for which call-backs are required.
- canMsgCallback_t *pcallback
- The address of the routine to be called whenever matching messages
are received.
- void *pprivate
- A parameter which is passed to the call-back routine to help it identify
its context.
Description
This routine is used to add a call-back routine for a particular CAN message
identifier on the given CANbus. Call-backs can be registered for any CAN
message identifier, and there can be more than one call-back using the same ID
- all routines are called in turn when a message with the relevant identifier
is received. The call-back routine must not change the message at all, and
should copy any information it needs from the message buffer before returning.
The call-back used to be executed from vxWorks' Interrupt Context, thus there
were restrictions in what vxWorks routines the call-back could use (see the
vxWorks User Guide for details of these), however from release 2-1 a high
priority task is used instead. Processing should still be kept to a minimum
though as this task is run at a high priority. The call-back routine should be
of type canMsgCallback_t:
void callback(void *pprivate, const canMessage_t *pmessage);
The same pprivate value supplied when the call-back was registered
with canMessage is passed to the call-back routine with each message
to allow it to identify its context.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_badMessage |
bad identifier or NULL call-back routine |
S_can_badDevice |
bad device pointer |
(vxWorks) |
errno from malloc |
Example
void myCallback(short *pvalue, canMessage_t *pmessage) {
/* Update value whenever message arrives */
memcpy(pvalue, &pmessage->data[0], sizeof(short));
}
...
int status;
static short value;
status = canMessage(myIo.canBusID, myIo.identifier,
(canMsgCallback_t *) myCallback, &value);
canMsgDelete
Delete CAN message call-back
int canMsgDelete(void *canBusID, ushort_t identifier,
canMsgCallback_t *pcallback, void *pprivate);
Parameters
- void *canBusID
- CANbus device identifier, obtained from canOpen
- ushort_t identifier
- The CAN message identifier for which call-backs were registered.
- canMsgCallback_t *pcallback
- The address of the routine being called whenever matching messages
were received.
- void *pprivate
- The parameter passed to the call-back routine to help it identify
its context.
Description
This routine is used to remove a call-back routine already registerd for a particular CAN
message identifier on the given CANbus.
Exactly the same parameters given when the call-back was registered with
canMessage must be passed to canMsgDelete for it to be
successfully deleted.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_badMessage |
bad identifier or NULL call-back routine |
S_can_noMessage |
no matching call-back routine |
S_can_badDevice |
bad device pointer |
Example
To delete the call-back registered in the previous canMessage example:
status = canMsgDelete(myIo.canBusID, myIo.identifier,
(canMsgCallback_t *) myCallback, &value);
canSignal
Register CAN error signal call-back
int canSignal(void *canBusID, canSigCallback_t *pcallback,
void *pprivate);
Parameters
- void *canBusID
- CANbus device identifier, obtained from canOpen
- canSigCallback_t *pcallback
- The address of the routine to be called whenever there is a change
in the bus status.
- void *pprivate
- A parameter which is passed to the call-back routine to help it identify
its context.
Description
This routine is used to add a new call-back routine for CANbus error reports
from the given CANbus. There can be any number of error call-backs on each
device, and all are called in turn when the controller chip reports a Bus Error
or Bus Off event. The call-back routine is called from vxWorks' Interrupt
Context (Note this has not changed in release 2-1), thus there are
restrictions in which vxWorks routines can be used (see vxWorks User Guide for
details of these), and processing should be kept to an absolute minimum. The
call-back routine should be of type canSigCallback_t:
void callback(void *pprivate, ushort_t status);
The pprivate value supplied to canSignal is passed to the call-back
routine with the error status to allow it to identify its context. Status
values will be one of
- CAN_BUS_OK
- CAN_BUS_ERROR
- CAN_BUS_OFF
these being pre-processor macros defined in the header file. If the
chip goes to the Bus Off state, the driver will attempt to restart it,
thus a Bus Ok signal should follow almost immediately.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_badDevice |
bad device identifier |
(vxWorks) |
errno from malloc |
Example
int status;
status = canSignal(myIo.canBusID, logmsg, "CAN Signal, state %hd\n");
if (status) {
printf("Couldn't register CAN signal handler.\n");
}
canBusReset
Reset CAN chip and message and error counters
int canBusReset(char *busName);
Parameters
- char *busName
- Device name to identify the particular Tip810 device to be reset.
Description
Resets the pca82c200 chip identified by the given busName, and
clears all the counters associated with this device. This may clear some
bus-related errors. All registered callbacks will remain active as before,
although the chip may miss some incoming messages while resetting. This
routine may also be used to restart an interface after a call to
canBusStop.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_noDevice |
No matching device name found. |
Example
-> canBusReset "CAN1"
canBusStop
Stop CAN interface
int canBusStop(char *busName);
Parameters
- char *busName
- Device name to identify the particular Tip810 device to be stopped.
Description
Holds the pca82c200 chip identified by the given busName in the
reset state, which prevents it from sending or receiving messages, or from
sending message acknowledgements to other nodes. The interface can be
reactiviated using either canBusRestart or canBusReset.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_noDevice |
No matching device name found. |
Example
-> canBusStop "CAN1"
canBusRestart
Restart a stopped CAN interface
int canBusReset(char *busName);
Parameters
- char *busName
- Device name identifying the Tip810 device to be restarted.
Description
Restarts the pca82c200 chip identified by the given busName after
it has been stopped by a call to canBusStop. All registered
callbacks will remain active as before and the message and error counters
will continue to increment from their previous values.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_can_noDevice |
No matching device name found. |
Example
-> canBusRestart "CAN1"
canRead
Send Remote Transmission Request and wait for reply.
int canRead(void *canBusID, canMessage_t *pmessage, int timeout);
Parameters
- void *canBusID
- CANbus device identifier, obtained from canOpen
- canMessage_t *pmessage
- Message buffer to be used, with identifier and length fields set.
- int timeout
- Delay in vxWorks system clock ticks, indicating how long to wait for
a response, including the delay in canWrite to obtain exclusive
access to the transmit buffer. This value is not an absolute delay but
is used internally for three separate sequential semaphore time-outs, thus
there could be a total delay of over twice this period but the routine
could still succeed.
Description
Sends a CANbus Remote Transmission Request and waits for a reply on
the same message identifier, returning the message received in the given
buffer. On entry the message buffer must be initialised with the CANbus
message identifier and length of the expected reply in the relevant fields.
The canRead routine can be used along with canWrite
to create simple CANbus interface applications without the need to use
the message call-back system. This will work in situations where the vxWorks
system is the only device on the CANbus which initiates message traffic
and there are no long delays in responses to RTRs. More complex applications
which need to receive unsolicited messages will need to use the canMessage
call-back functions; these can be used at the same time as canRead.
Although the routine is safe to use in multi-tasking situations, the action
of sending an RTR and waiting for a returned message will only be performed
for one task at a time on each bus.
Returns
int
Symbol/Value |
Meaning |
0 |
OK |
S_t810_badDevice |
bad bus ID |
S_can_badMessage |
bad message Identifier or length |
(vxWorks) |
errno from semTake |
Example
int status;
canMessage_t myBuffer;
myBuffer.identifier = 139;
myBuffer.length = 4;
status = canRead(canID, &myBuffer, WAIT_FOREVER);
Andrew Johnson
<anjohnson@iee.org>
drvTip810/pca82c200.h 0100644 0000621 0000620 00000011633 06316464504 013226 0 ustar epics epics /*******************************************************************************
Project:
Gemini/UKIRT CAN Bus Driver for EPICS
File:
pca82c200.h
Description:
Philips Stand-alone CAN-controller chip header file, giving the register
layout and programming model for the chip used on the TIP810 IP module.
Author:
Andrew Johnson
Created:
19 July 1995
Version:
pca82c200.h,v 1.1.1.1 1997/03/27 12:34:12 anj Exp
(c) 1995 Royal Greenwich Observatory
*******************************************************************************/
#ifndef INCpca82c200H
#define INCpca82c200H
#include
#ifdef __cplusplus
extern "C" {
#endif
/***** Control Segment Bit Patterns *****/
/* Control Register */
#define PCA_CR_TM 0x80 /* Test Mode */
#define PCA_CR_S 0x40 /* Synch */
#define PCA_CR_OIE 0x10 /* Overrun Interrupt Enable */
#define PCA_CR_EIE 0x08 /* Error Interrupt Enable */
#define PCA_CR_TIE 0x04 /* Transmit Interrupt Enable */
#define PCA_CR_RIE 0x02 /* Receive Interrupt Enable */
#define PCA_CR_RR 0x01 /* Reset Request */
/* Command Register */
#define PCA_CMR_GTS 0x10 /* Goto Sleep */
#define PCA_CMR_COS 0x08 /* Clear Overrun Status */
#define PCA_CMR_RRB 0x04 /* Release Receive Buffer */
#define PCA_CMR_AT 0x02 /* Abort Transmission */
#define PCA_CMR_TR 0x01 /* Transmission Request */
/* Status Register */
#define PCA_SR_BS 0x80 /* Bus Status */
#define PCA_SR_ES 0x40 /* Error Status */
#define PCA_SR_TS 0x20 /* Transmit Status */
#define PCA_SR_RS 0x10 /* Receive Status */
#define PCA_SR_TCS 0x08 /* Transmission Complete Status */
#define PCA_SR_TBS 0x04 /* Transmit Buffer Status */
#define PCA_SR_DO 0x02 /* Data Overrun */
#define PCA_SR_RBS 0x01 /* Receive Buffer Status */
/* Interrupt Register */
#define PCA_IR_WUI 0x10 /* Wake-Up Interrupt */
#define PCA_IR_OI 0x08 /* Overrun Interrupt */
#define PCA_IR_EI 0x04 /* Error Interrupt */
#define PCA_IR_TI 0x02 /* Transmit Interrupt */
#define PCA_IR_RI 0x01 /* Receive Interrupt */
/* Bus Timing Register 0 */
#define PCA_BTR0_1M6 0x00 /* 1.6 Mbits/sec, 20 m */
#define PCA_BTR0_1M0 0x00 /* 1.0 Mbits/sec, 40 m */
#define PCA_BTR0_500K 0x00 /* 500 Kbits/sec, 130 m */
#define PCA_BTR0_250K 0x01 /* 250 Kbits/sec, 270 m */
#define PCA_BTR0_125K 0x03 /* 125 Kbits/sec, 530 m */
#define PCA_BTR0_100K 0x43 /* 100 Kbits/sec, 620 m */
#define PCA_BTR0_50K 0x47 /* 50 Kbits/sec, 1.3 km */
#define PCA_BTR0_20K 0x53 /* 20 Kbits/sec, 3.3 km */
#define PCA_BTR0_10K 0x67 /* 10 Kbits/sec, 6.7 km */
#define PCA_BTR0_5K 0x7f /* 5 Kbits/sec, 10 km */
#define PCA_KVASER_1M0 0x00 /* 1.0 Mbits/sec, 40 m -- Kvaser standard */
#define PCA_KVASER_500K 0x01 /* 500 Kbits/sec, 130 m -- Kvaser standard */
#define PCA_KVASER_250K 0x03 /* 250 Kbits/sec, 270 m -- Kvaser standard */
#define PCA_KVASER_125K 0x07 /* 125 Kbits/sec, 530 m -- Kvaser standard */
/* Bus Timing Register 1 */
#define PCA_BTR1_1M6 0x11 /* 1.6 Mbits/sec, 20 m */
#define PCA_BTR1_1M0 0x14 /* 1.0 Mbits/sec, 40 m */
#define PCA_BTR1_500K 0x1c /* 500 Kbits/sec, 130 m */
#define PCA_BTR1_250K 0x1c /* 250 Kbits/sec, 270 m */
#define PCA_BTR1_125K 0x1c /* 125 Kbits/sec, 530 m */
#define PCA_BTR1_100K 0x2f /* 100 Kbits/sec, 620 m */
#define PCA_BTR1_50K 0x2f /* 50 Kbits/sec, 1.3 km */
#define PCA_BTR1_20K 0x2f /* 20 Kbits/sec, 3.3 km */
#define PCA_BTR1_10K 0x2f /* 10 Kbits/sec, 6.7 km */
#define PCA_BTR1_5K 0x7f /* 5 Kbits/sec, 10 km */
#define PCA_BTR1_KVASER 0x23 /* All speeds -- Kvaser standard */
/* Output Control Register */
#define PCA_OCR_OCM_NORMAL 0x02
#define PCA_OCR_OCM_CLOCK 0x03
#define PCA_OCR_OCM_BIPHASE 0x00
#define PCA_OCR_OCM_TEST 0x01
#define PCA_OCR_OCT1_FLOAT 0x00
#define PCA_OCR_OCT1_PULLDOWN 0x40
#define PCA_OCR_OCT1_PULLUP 0x80
#define PCA_OCR_OCT1_PUSHPULL 0xc0
#define PCA_OCR_OCT0_FLOAT 0x00
#define PCA_OCR_OCT0_PULLDOWN 0x08
#define PCA_OCR_OCT0_PULLUP 0x10
#define PCA_OCR_OCT0_PUSHPULL 0x18
#define PCA_OCR_OCP1_INVERT 0x20
#define PCA_OCR_OCP0_INVERT 0x04
/* Message Buffers */
#define PCA_MSG_ID0_RSHIFT 3
#define PCA_MSG_ID1_LSHIFT 5
#define PCA_MSG_ID1_MASK 0xe0
#define PCA_MSG_RTR 0x10
#define PCA_MSG_DLC_MASK 0x0f
/***** Chip Structure *****/
/* Message Buffers */
typedef struct {
uchar_t pad0;
uchar_t descriptor0;
uchar_t pad1;
uchar_t descriptor1;
ushort_t data[8];
} msgBuffer_t;
/* Chip Registers */
typedef volatile struct {
uchar_t pad00;
uchar_t control;
uchar_t pad01;
uchar_t command;
uchar_t pad02;
uchar_t status;
uchar_t pad03;
uchar_t interrupt;
uchar_t pad04;
uchar_t acceptanceCode;
uchar_t pad05;
uchar_t acceptanceMask;
uchar_t pad06;
uchar_t busTiming0;
uchar_t pad07;
uchar_t busTiming1;
uchar_t pad08;
uchar_t outputControl;
uchar_t pad09;
uchar_t test;
msgBuffer_t txBuffer;
msgBuffer_t rxBuffer;
uchar_t pad31;
uchar_t clockDivider;
} pca82c200_t;
#ifdef __cplusplus
}
#endif
#endif /* INCpca82c200H */
tyGSOctal/Makefile 0100644 0000621 0000620 00000000112 06671302113 013340 0 ustar epics epics TOP=..
include $(TOP)/config/CONFIG_APP
include $(TOP)/config/RULES_ARCHS
tyGSOctal/Makefile.Host 0100644 0000621 0000620 00000000272 06671302113 014263 0 ustar epics epics # Makefile.Host
TOP = ../..
include $(TOP)/config/CONFIG_APP
#INC += tyGSOctal.h
HTMLS_DIR = .
HTMLS += tyGSOctal.html
HTMLS += tyGSOctalRelease.html
include $(TOP)/config/RULES.Host
tyGSOctal/Makefile.Vx 0100644 0000621 0000620 00000000212 06747674243 013761 0 ustar epics epics # Makefile.Vx
TOP = ../..
include $(TOP)/config/CONFIG_APP
SRCS.c += ../tyGSOctal.c
PROD += tyGSOctal.o
include $(TOP)/config/RULES.Vx
tyGSOctal/ip_modules.h 0100644 0000621 0000620 00000002221 06671302113 014214 0 ustar epics epics /**************************************************************************
Header: ip_modules.h
Author: Peregrine McGehee
Description: Manufacturer and Model Ids for GreenSpring serial I/O
modules. Based on the HiDEOS driver for the GreenSpring Ip_Octal 232,
422, and 485 serial I/O modules developed by Jim Kowalkowski of the
Advanced Photon Source.
History:
who when what
--- -------- ------------------------------------------------
PMM 18/11/96 Original
**********************************************************/
#ifndef __IP_MODULES_H
#define __IP_MODULES_H
/*
known id_prom values for Green Spring:
ascii_1='I' ascii_2='P' ascii_3='A' ascii_4='C'
manufacturer_id = 0xf0 (Green Spring)
model_id:
0x22 = IP-Octal232
0x2a = IP-Octal422
0x48 = IP-Octal485
*/
#define GREEN_SPRING_ID 0xf0
#define GSIP_OCTAL232 0x22
#define GSIP_OCTAL422 0x2a
#define GSIP_OCTAL485 0x48
#endif
/**************************************************************************
CVS/RCS Log information:
**************************************************************************/
tyGSOctal/scc2698.h 0100644 0000621 0000620 00000011127 06671302113 013162 0 ustar epics epics /**************************************************************************
Header: scc2698.h
Author: Peregrine McGehee
Description: SCC2698 UART management - based on the HiDEOS driver for
the GreenSpring Ip_Octal 232, 422, and 485 serial I/O modules
developed by Jim Kowalkowski of the Advanced Photon Source.
History:
who when what
--- -------- ------------------------------------------------
PMM 18/11/96 Original
**************************************************************************/
#ifndef __SCC2698_H
#define __SCC2698_H
/*
* SCC2698 UART MANAGEMENT
*
* (R)=read access, (W)=write access (R/W) read/write access
*
* This structure can be used to index to each port when the SCC is set
* up in the Quad or Octal configuation.
*/
struct scc2698_chan {
union {
struct {
UCHAR d0,mr; /* a mode register 1/2 (R/W) */
UCHAR d1,sr; /* a status (R), a clock select (W) */
UCHAR d2,r1; /* a command (W) */
UCHAR d3,rhr; /* a receiver hold (R), a transmitter
hold (W) */
UCHAR junk[8]; /* other stuff for block control */
} r;
struct {
UCHAR d0,mr; /* a mode register 1/2 (R/W) */
UCHAR d1,csr; /* a status (R), a clock select (W) */
UCHAR d2,cr; /* a command (W) */
UCHAR d3,thr; /* a receiver hold (R), a transmitter
hold (W) */
UCHAR junk[8]; /* other stuff for block control */
} w;
} u;
};
typedef volatile struct scc2698_chan SCC2698_CHAN;
/*
* This is the entire structure of the SCC. Note that there are really
* only four control blocks, each containing two ports.
*/
struct scc2698 {
union {
struct {
UCHAR d0,mra; /* a mode register 1/2 (R/W) */
UCHAR d1,sra; /* a status (R), a clock select (W) */
UCHAR d2,r1; /* reserved */
UCHAR d3,rhra; /* a receiver hold (R), a transmitter
hold (W) */
UCHAR d4,ipcr; /* a Aux cntl (W), a input port change
(R) */
UCHAR d5,isr; /* a Interrupt status (R), a Interrupt
mask (W) */
UCHAR d6,ctur; /* a Counter timer upper (R/W) */
UCHAR d7,ctlr; /* a Counter timer upper (R/W) */
UCHAR d8,mrb; /* b mode register 1/2 (R/W) */
UCHAR d9,srb; /* b status (R), b clock select (W) */
UCHAR da,r2; /* reserved */
UCHAR db,rhrb; /* b receiver hold (R), b transmitter
hold (W) */
UCHAR dc,r3; /* reserved */
UCHAR dd,ip; /* a output port conf (W), a input
port (R) */
UCHAR de,ctg; /* start counter timer a (R) */
UCHAR df,cts; /* stop counter timer a (R) */
} r;
struct {
UCHAR d0,mra; /* a mode register 1/2 (R/W) */
UCHAR d1,csra; /* a status (R), a clock select (W) */
UCHAR d2,cra; /* a command (W) */
UCHAR d3,thra; /* a receiver hold (R), a transmitter
hold (W) */
UCHAR d4,acr; /* a Aux cntl (W), a input port change
(R) */
UCHAR d5,imr; /* a Interrupt status (R), a Interrupt
mask (W) */
UCHAR d6,ctu; /* a Counter timer upper (R/W) */
UCHAR d7,ctl; /* a Counter timer upper (R/W) */
UCHAR d8,mrb; /* b mode register 1/2 (R/W) */
UCHAR d9,csrb; /* b status (R), b clock select (W) */
UCHAR da,crb; /* b command (W) */
UCHAR db,thrb; /* b receiver hold (R), b receiver
hold (W) */
UCHAR dc,r3; /* reserved */
UCHAR dd,opcr; /* a output port conf (W), a input
port (R) */
UCHAR de,r4; /* start counter timer a (R) */
UCHAR df,r5; /* start counter timer a (R) */
} w;
} u;
};
typedef volatile struct scc2698 SCC2698;
/*
* SCC 2698 ISR/IMR Bit definitions
*/
#define SCC_ISR_TXRDY_A 0x01
#define SCC_ISR_RXRDY_A 0x02
#define SCC_ISR_CBRK_A 0x04
#define SCC_ISR_CTRRDY 0x08
#define SCC_ISR_TXRDY_B 0x10
#define SCC_ISR_RXRDY_B 0x20
#define SCC_ISR_CBRK_B 0x40
#define SCC_ISR_MPI 0x80
#endif
/**************************************************************************
CVS/RCS Log information:
**************************************************************************/
tyGSOctal/tyGSOctal.c 0100644 0000621 0000620 00000057105 06750346761 013752 0 ustar epics epics /**************************************************************************
Header: tyGSOctal.c
Author: Peregrine M. McGehee
Description: Sourcefile for GreenSpring Ip_Octal 232, 422, and 485
serial I/O modules. This software is somewhat based on the HiDEOS
device driver developed by Jim Kowalkowski of the Advanced Photon Source.
**************************************************************************
USER-CALLABLE ROUTINES
Most of the routines in this driver are accessible only through the I/O
system. Two routines, however, must be called directly: tyGSOctalDrv() to
initialize the driver, and tyGSOctalDevCreate() to create devices.
Before the driver can be used, it must be initialized by calling
tyGSOctalDrv().
This routine should be called exactly once, before any reads, writes, or
calls to tyGSOctalModuleInit()/tyGSOctalDevCreate().
Before a terminal can be used, it must be created using
tyGSOctalModuleInit()/tyGSOctalDevCreate().
Each port to be used should have exactly one device associated with it by
calling this routine.
IOCTL FUNCTIONS
This driver responds to the same ioctl() codes as a normal tty driver; for
more information, see the manual entry for tyLib.
SEE ALSO
tyLib
Functions:
name description
---- -----------------------------------------------------------
What this function does.
History:
who when what
--- -------- ------------------------------------------------
PMM 18/11/96 Original
PMM 13/10/97 Recast as VxWorks device driver.
ANJ 09/03/99 Merged into ipac , fixed warnings.
**************************************************************************/
/*
* vxWorks includes
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ip_modules.h" /* GreenSpring IP modules */
#include "scc2698.h" /* SCC 2698 UART register map */
#include "tyGSOctal.h" /* Device driver includes */
#include "drvIpac.h" /* IP management (from drvIpac) */
QUAD_TABLE *tyGSOctalModules;
int tyGSOctalMaxModules;
int tyGSOctalLastModule;
int TYGSOCTAL_ISR_LOG = 0;
LOCAL int tyGSOctalDrvNum; /* driver number assigned to this driver */
/*
* forward declarations
*/
void tyGSOctalInt(int);
LOCAL void tyGSOctalInitChannel(QUAD_TABLE *, int);
LOCAL void tyGSOctalRS232(TY_GSOCTAL_DEV *);
LOCAL void tyGSOctalRS485(TY_GSOCTAL_DEV *);
/******************************************************************************
*
* tyGSOctalDrv - initialize the tty driver
*
* This routine initializes the serial driver, sets up interrupt vectors, and
* performs hardware initialization of the serial ports.
*
* This routine should be called exactly once, before any reads, writes, or
* calls to tyGSOctalDevCreate().
*
* This routine takes as an argument the maximum number of IP modules
* to support.
* For example:
* .CS
* int status;
* status = tyGSOctalDrv(4);
* .CE
*
* RETURNS: OK, or ERROR if the driver cannot be installed.
*
* SEE ALSO: tyGSOctalDevCreate()
*/
STATUS tyGSOctalDrv
(
int maxModules
)
{
static char *fn_nm = "tyGSOctalDrv";
/* check if driver already installed */
if (tyGSOctalDrvNum > 0)
return (OK);
tyGSOctalMaxModules = maxModules;
tyGSOctalLastModule = 0;
printf("allocating %d structures of %ld bytes\n", maxModules,
sizeof(QUAD_TABLE));
if (!(tyGSOctalModules =
(QUAD_TABLE *)malloc(maxModules*sizeof(QUAD_TABLE)))) {
logMsg("%s: Quad table allocation failed!",
(int)fn_nm,
NULL,NULL,NULL,NULL,NULL);
return (ERROR);
}
tyGSOctalDrvNum = iosDrvInstall (tyGSOctalOpen,
(FUNCPTR) NULL,
tyGSOctalOpen,
(FUNCPTR) NULL,
tyRead,
tyGSOctalWrite,
tyGSOctalIoctl);
return (tyGSOctalDrvNum == ERROR ? ERROR : OK);
}
void tyGSOctalReport()
{
QUAD_TABLE *qt;
int i, n;
TY_GSOCTAL_DEV *pty;
for (n = 0; n < tyGSOctalLastModule; n++) {
qt = &tyGSOctalModules[n];
printf("qt=%p carrier=%d module=%d\n",
qt, qt->carrier, qt->module);
for (i=0; i < 8; i++) {
pty = &qt->port[i];
if (pty->created) {
printf("port %d(%p)\t", i, pty);
printf("qt:%p\t", pty->qt);
printf("regs:%p chan:%p\n", pty->regs, pty->chan);
printf("drvNum:%d\t", pty->tyDev.devHdr.drvNum);
printf("%s\n", pty->tyDev.devHdr.name);
}
}
}
}
/******************************************************************************
* tyGSOctalModuleInit - initialize an IP module
*
* The routine initializes the specified IP module. Each module is
* characterized by its model name, interrupt vector, carrier board
* number, and module number on the board. No new setup is done if a
* QUAD_TABLE entry already exists with the same carrier and module
* numbers.
*
* For example:
* .CS
* int idx;
* idx = tyGSOctalModuleInit("GSIP_OCTAL232", 0x60, 0, 1);
* .CE
*
*
* RETURNS: Index into module table, or ERROR if the driver is not
* installed, the channel is invalid, or the device already exists.
*
* SEE ALSO: tyGSOctalDrv()
*/
int tyGSOctalModuleInit
(
char * type, /* IP module type */
int int_num, /* Interrupt number */
int carrier, /* which carrier board [0-n] */
int module /* module number on carrier [0-m] */
)
{
static char *fn_nm = "tyGSOctalModuleInit";
int modelID;
int status;
int i;
QUAD_TABLE *qt;
/*
* Check for the driver being installed.
*/
if (tyGSOctalDrvNum <= 0) {
errnoSet (S_ioLib_NO_DRIVER);
return (ERROR);
}
/*
* Check the IP module type.
*/
if (!strcmp(type, "GSIP_OCTAL232"))
modelID = GSIP_OCTAL232;
else if (!strcmp(type, "GSIP_OCTAL422"))
modelID = GSIP_OCTAL422;
else if (!strcmp(type, "GSIP_OCTAL485"))
modelID = GSIP_OCTAL485;
else {
logMsg("%s: Unsupported module type: %s",
(int)fn_nm, (int)type,
NULL,NULL,NULL,NULL);
return (ERROR);
}
/*
* Validate the IP module location and type.
*/
if ((status = ipmValidate(carrier, module, GREEN_SPRING_ID, modelID))
!= 0) {
logMsg("%s: Unable to validate IP module",
(int)fn_nm,
NULL,NULL,NULL,NULL,NULL);
logMsg("%s: carrier:%d module:%d modelID:%d",
(int)fn_nm, carrier, module, modelID,
NULL,NULL);
switch(status) {
case S_IPAC_badAddress:
logMsg("%s: Bad carrier or module number",
(int)fn_nm,
NULL,NULL,NULL,NULL,NULL);
break;
case S_IPAC_noModule:
logMsg("%s: No module installed",
(int)fn_nm,NULL,NULL,NULL,NULL,NULL);
break;
case S_IPAC_noIpacId:
logMsg("%s: IPAC identifier not found",
(int)fn_nm,NULL,NULL,NULL,NULL,NULL);
break;
case S_IPAC_badCRC:
logMsg("%s: CRC Check failed",
(int)fn_nm,NULL,NULL,NULL,NULL,NULL);
break;
case S_IPAC_badModule:
logMsg("%s: Manufacturer or model IDs wrong",
(int)fn_nm,NULL,NULL,NULL,NULL,NULL);
break;
default:
logMsg("%s: Bad error code: 0x%x",
(int)fn_nm, status,
NULL,NULL,NULL,NULL);
break;
}
return (ERROR);
}
/* See if the associated IP module has already been set up */
for (i = 0; i < tyGSOctalLastModule; i++) {
qt = &tyGSOctalModules[i];
if (qt->carrier == carrier && qt->module == module) break;
}
/* Create a new quad table entry if not there */
if (i >= tyGSOctalLastModule) {
void *addrIO;
uint16_t *addrMem;
SCC2698 *r;
SCC2698_CHAN *c;
int block;
if (tyGSOctalLastModule >= tyGSOctalMaxModules) {
logMsg("%s: Maximum module count exceeded!",
(int)fn_nm,
NULL,NULL,NULL,NULL,NULL);
return (ERROR);
}
qt = &tyGSOctalModules[tyGSOctalLastModule];
qt->carrier = carrier;
qt->module = module;
addrIO = ipmBaseAddr(carrier, module, ipac_addrIO);
r = (SCC2698 *) addrIO;
c = (SCC2698_CHAN *) addrIO;
for (i = 0; i < 8; i++) {
block = i/2;
qt->port[i].created = 0;
qt->port[i].qt = qt;
qt->port[i].regs = &r[block];
qt->port[i].chan = &c[i];
}
for (i = 0; i < 4; i++) qt->imr[i] = 0;
/* set up the single interrupt vector */
addrMem = (uint16_t *) ipmBaseAddr(carrier, module, ipac_addrMem);
if (addrMem == NULL) {
logMsg("%s: No memory allocated for carrier %d slot %d",
(int)fn_nm, carrier, module,
NULL,NULL,NULL);
return(ERROR);
}
*addrMem = int_num;
if (ipmIntConnect(carrier, module, int_num,
tyGSOctalInt, tyGSOctalLastModule)) {
logMsg("%s: Unable to connect ISR",
(int)fn_nm,
NULL,NULL,NULL,NULL,NULL);
return(ERROR);
}
ipmIrqCmd(carrier, module, 0, ipac_irqEnable);
ipmIrqCmd(carrier, module, 1, ipac_irqEnable);
}
return (tyGSOctalLastModule++);
}
/******************************************************************************
* tyGSOctalDevCreate - create a device for a serial port on an IP module
*
* This routine creates a device on a specified serial port. Each port
* to be used should have exactly one device associated with it by calling
* this routine.
*
* For instance, to create the device "/tyGSOctal/0/1/3", with buffer
* sizes of 512 bytes, the proper calls would be:
* .CS
* int idx, dev;
* idx = tyGSOctalModuleInit("GSIP_OCTAL232", 0x60, 0, 1);
* dev = tyGSOctalDevCreate ("/tyGSOctal/0/1/3", idx, 3, 512, 512);
* .CE
*
* RETURNS: Pointer to descriptor, or ERROR if the driver is not
* installed, the channel is invalid, or the device already exists.
*
* SEE ALSO: tyGSOctalDrv()
*/
TY_GSOCTAL_DEV *tyGSOctalDevCreate
(
char * name, /* name to use for this device */
int idx, /* index into module table */
int port, /* port on module for this device [0-7] */
int rdBufSize, /* read buffer size, in bytes */
int wrtBufSize /* write buffer size, in bytes */
)
{
TY_GSOCTAL_DEV *pTyGSOctalDv;
QUAD_TABLE *qt;
/* if this doesn't represent a valid module, don't do it */
if (idx < 0 || idx > tyGSOctalLastModule)
return ((TY_GSOCTAL_DEV *)ERROR);
/* if this doesn't represent a valid port, don't do it */
if (port < 0 || port > 7)
return ((TY_GSOCTAL_DEV *)ERROR);
qt = &tyGSOctalModules[idx];
pTyGSOctalDv = &qt->port[port];
/* if there is a device already on this channel, don't do it */
if (pTyGSOctalDv->created)
return ((TY_GSOCTAL_DEV *)ERROR);
/* initialize the ty descriptor */
if (tyDevInit (&pTyGSOctalDv->tyDev, rdBufSize, wrtBufSize,
(FUNCPTR) tyGSOctalStartup) != OK)
{
return ((TY_GSOCTAL_DEV *)ERROR);
}
/* initialize the channel hardware */
tyGSOctalInitChannel(qt, port);
/* mark the device as created, and add the device to the I/O system */
pTyGSOctalDv->created = TRUE;
if (iosDevAdd(&pTyGSOctalDv->tyDev.devHdr, name,
tyGSOctalDrvNum) != OK)
{
return ((TY_GSOCTAL_DEV *)ERROR);
}
return (pTyGSOctalDv);
}
/******************************************************************************
*
* tyGSOctalInitChannel - initialize a single channel
*/
LOCAL void tyGSOctalInitChannel
(
QUAD_TABLE *qt,
int port
)
{
TY_GSOCTAL_DEV *pTyGSOctalDv = &qt->port[port];
int block = port/2; /* 4 blocks per octal UART */
FAST int oldlevel; /* current interrupt level mask */
oldlevel = intLock (); /* disable interrupts during init */
pTyGSOctalDv->port = port;
pTyGSOctalDv->block = block;
pTyGSOctalDv->imr = ((port%2 == 0) ? SCC_ISR_TXRDY_A : SCC_ISR_TXRDY_B);
/* choose set 2 BRG */
pTyGSOctalDv->regs->u.w.acr = 0x80;
pTyGSOctalDv->chan->u.w.cr = 0x1a; /* disable trans/recv, reset pointer */
pTyGSOctalDv->chan->u.w.cr = 0x20; /* reset recv */
pTyGSOctalDv->chan->u.w.cr = 0x30; /* reset trans */
pTyGSOctalDv->chan->u.w.cr = 0x40 ; /* reset error status */
/*
* Set up the default port configuration:
* 9600 baud, no parity, 1 stop bit, 8 bits per char, no flow control
*/
tyGSOctalConfig(pTyGSOctalDv, 9600, 'N', 1, 8, 'N');
/*
* enable everything, really only Rx interrupts
*/
qt->imr[block] |= ((port%2) == 0 ? SCC_ISR_RXRDY_A : SCC_ISR_RXRDY_B);
pTyGSOctalDv->regs->u.w.imr = qt->imr[block]; /* enable RxRDY interrupt */
pTyGSOctalDv->chan->u.w.cr = 0x05; /* enable Tx,Rx */
intUnlock (oldlevel);
}
/******************************************************************************
*
* tyGSOctalOpen - open file to UART
*/
int tyGSOctalOpen
(
TY_GSOCTAL_DEV *pTyGSOctalDv,
char *name,
int mode
)
{
return ((int) pTyGSOctalDv);
}
/******************************************************************************
* tyGSOctalWrite - Outputs a specified number of characters on a serial port
*
*/
int
tyGSOctalWrite( TY_GSOCTAL_DEV *pTyGSOctalDv, /* device descriptor block */
char *write_bfr, /* ptr to an output buffer */
long write_size ) /* # bytes to write */
{
static char *fn_nm = "tyGSOctalWrite";
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
int nbytes;
/*
* verify that the device descriptor is valid
*/
if ( !pTyGSOctalDv ) {
logMsg( "%s: (%s) DEVICE DESCRIPTOR INVALID\n",
(int)fn_nm, (int)taskName( taskIdSelf() ),
NULL,NULL,NULL,NULL );
return (-1);
} else {
if (pTyGSOctalDv->mode == RS485)
/* disable recv, 1000=assert RTSN (low) */
chan->u.w.cr = 0x82;
/*
* Determine how much data exists in the write buffer
tyIoctl( &(pTyGSOctalDv->tyDev), FIONWRITE, (int)&nbytes);
*/
nbytes = tyWrite(&pTyGSOctalDv->tyDev, write_bfr, write_size);
if (pTyGSOctalDv->mode == RS485)
{
/* make sure all data sent */
while(!(chan->u.r.sr & 0x08)); /* TxEMT */
/* enable recv, 1001=negate RTSN high */
chan->u.w.cr = 0x91;
}
return nbytes;
}
}
/******************************************************************************
*
* tyGSOctalIoctl - special device control
*
* This routine handles FIOBAUDRATE requests and passes all others to
* tyIoctl().
*
* RETURNS: OK, or ERROR if invalid baud rate, or whatever tyIoctl() returns.
*/
STATUS tyGSOctalIoctl
(
TY_GSOCTAL_DEV *pTyGSOctalDv, /* device to control */
int request, /* request code */
int arg /* some argument */
)
{
FAST STATUS status;
switch (request)
{
case FIOBAUDRATE:
default:
status = tyIoctl (&pTyGSOctalDv->tyDev, request, arg);
break;
}
return (status);
}
/******************************************************************************
*
* tyGSOctalConfig - special device control (old version)
*
* This routine sets the baud rate, parity, stop bits, word size, and
* flow control for the specified port
*
*/
void tyGSOctalConfig
(
TY_GSOCTAL_DEV *pTyGSOctalDv,
unsigned int baud,
char parity,
int stop,
int bits,
char flow
)
{
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
QUAD_TABLE *qt = (QUAD_TABLE *)pTyGSOctalDv->qt;
UCHAR mr1, mr2;
/*
* mode registers
*/
chan->u.w.cr = 0x10; /* point MR to MR1 */
mr1 = 0x00; /* RxRTS=No, RxINT=RxRDY, Error=char */
mr2 = 0x00; /* normal, TxRTS=No, CTS=No, stop-bit-length=0.563 */
switch(parity) /* parity */
{
case 'E': break; /* leave zero for even parity */
case 'O': mr1|=0x04; break; /* odd parity */
case 'N':
default: mr1|=0x10; break; /* no parity is also default */
}
switch(bits) /* per character */
{
case 5: break; /* leave alone */
case 6: mr1|=0x01; break;
case 7: mr1|=0x02; break;
case 8:
default: mr1|=0x03; break; /* default is also 8 bits */
}
switch(stop) /* number of stop bits */
{
case 2: mr2|=0x0f; break;
case 1:
default: mr2|=0x07; break;
}
switch(flow) /* set up flow control */
{
case 'H': mr1|=0x80; mr2|=0x10; break;
case 'N':
default: break; /* do nothing */
}
pTyGSOctalDv->mr1 = mr1;
pTyGSOctalDv->mr2 = mr2;
chan->u.w.mr=mr1;
chan->u.w.mr=mr2;
switch(baud) /* clock select */
{
case 1200: chan->u.w.csr=0x66; break;
case 2400: chan->u.w.csr=0x88; break;
case 4800: chan->u.w.csr=0x99; break;
case 9600: chan->u.w.csr=0xbb; break;
case 38400: chan->u.w.csr=0x22; break;
default:
case 19200: chan->u.w.csr=0xcc; break;
}
pTyGSOctalDv->opcr = 0x80;
if(!ipmValidate(qt->carrier, qt->module,
GREEN_SPRING_ID, GSIP_OCTAL485))
tyGSOctalRS485(pTyGSOctalDv);
else
tyGSOctalRS232(pTyGSOctalDv);
}
LOCAL void tyGSOctalRS232
(
TY_GSOCTAL_DEV *pTyGSOctalDv
)
{
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
SCC2698 *regs = pTyGSOctalDv->regs;
UCHAR mr1 = pTyGSOctalDv->mr1;
UCHAR mr2 = pTyGSOctalDv->mr2;
pTyGSOctalDv->mode = RS232;
/*
* allow RTS (MPOa) to be turned on/off automatically
*/
regs->u.w.opcr = 0x87; /* out,MPOb=RTSN,MPOa=FIFO full */
chan->u.w.cr = 0x10 ; /* point MR to MR1 */
mr1 |= 0x80;
chan->u.w.mr = mr1; /* use RxRTS (auto mode) */
mr2 |= 0x20;
chan->u.w.mr = mr2; /* use TxRTS (auto mode),CTS enable Tx */
pTyGSOctalDv->mr1 = mr1;
pTyGSOctalDv->mr2 = mr2;
}
LOCAL void tyGSOctalRS485
(
TY_GSOCTAL_DEV *pTyGSOctalDv
)
{
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
SCC2698 *regs = pTyGSOctalDv->regs;
UCHAR mr1 = pTyGSOctalDv->mr1;
UCHAR mr2 = pTyGSOctalDv->mr2;
pTyGSOctalDv->mode = RS485;
/*
* allow RTS (MPOa) to be turned on/off manually through control reg
*/
regs->u.w.opcr = 0x80; /* out,MPOb=RTSN,MPOa=RTSN */
chan->u.w.cr = 0x10; /* point MR to MR1 */
mr1 &= 0x7f;
chan->u.w.mr = mr1; /* no auto RxRTS */
mr2 &= 0xcf;
chan->u.w.mr = mr2; /* no auto TxRTS and no CTS enable Tx */
pTyGSOctalDv->mr1 = mr1;
pTyGSOctalDv->mr2 = mr2;
}
void tyGSOctalSetcr(TY_GSOCTAL_DEV *pTyGSOctalDv, unsigned char crval)
{
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
chan->u.w.cr = crval;
}
void tyGSOctalSetopcr(TY_GSOCTAL_DEV *pTyGSOctalDv, unsigned char opcrval)
{
SCC2698 *regs = pTyGSOctalDv->regs;
regs->u.w.opcr = opcrval;
}
/*****************************************************************************
* tyGSOctalInt - interrupt level processing
*
* NOMANUAL
*/
void tyGSOctalInt
(
int idx
)
{
volatile unsigned char sr, isr;
unsigned int spin;
int i;
int level;
int vector;
int block;
char outChar;
char inChar;
QUAD_TABLE *pQt;
TY_GSOCTAL_DEV *pTyGSOctalDv;
SCC2698_CHAN *chan;
SCC2698 *regs;
pQt = &(tyGSOctalModules[idx]);
level = ipmIrqCmd(pQt->carrier, pQt->module, 0, ipac_irqGetLevel);
vector = sysBusIntAck(level);
for (spin=0; spin < MAX_SPIN_TIME; spin++)
{
/*
* check each port for work
*/
for (i = 0; i < 8; i++)
{
pTyGSOctalDv = &(pQt->port[i]);
if (!pTyGSOctalDv->created) continue;
block = i/2;
chan = pTyGSOctalDv->chan;
regs = pTyGSOctalDv->regs;
sr = chan->u.r.sr;
/* Only examine the active interrupts */
isr = regs->u.r.isr & pQt->imr[block];
/* Channel B interrupt data is on the upper nibble */
if ((i%2) == 1) isr >>= 4;
if (isr & 0x02) /* a byte needs to be read */
{
inChar = chan->u.r.rhr;
if (TYGSOCTAL_ISR_LOG)
logMsg("%d/%dR%02x %02x\n", idx, i, inChar,
isr, NULL, NULL);
if (tyIRd(&(pTyGSOctalDv->tyDev), inChar) != OK)
if (TYGSOCTAL_ISR_LOG)
logMsg("tyIRd failed!\n",
NULL,NULL,NULL,NULL,NULL, NULL);
}
if (isr & 0x01) /* a byte needs to be sent */
{
if (tyITx(&(pTyGSOctalDv->tyDev), &(outChar)) == OK) {
if (TYGSOCTAL_ISR_LOG)
logMsg("%d/%dT%02x %02x %lx = %d\n",
idx, i, outChar, isr,
(int)&(pTyGSOctalDv->tyDev.wrtState.busy),
pTyGSOctalDv->tyDev.wrtState.busy);
chan->u.w.thr = outChar;
}
else {
/* deactivate Tx INT and disable Tx INT */
pQt->imr[pTyGSOctalDv->block] &=
~pTyGSOctalDv->imr;
regs->u.w.imr = pQt->imr[pTyGSOctalDv->block];
if (TYGSOCTAL_ISR_LOG)
logMsg("TxInt disabled: %d/%d isr=%02x\n",
idx, i, isr,
NULL, NULL, NULL);
}
}
if (sr & 0xf0) /* error condition present */
{
if (TYGSOCTAL_ISR_LOG)
logMsg("%d/%dE% 02x\n",
idx, i,
sr, NULL, NULL, NULL);
/* reset error status */
chan->u.w.cr = 0x40;
}
}
}
}
/******************************************************************************
*
* tyGSOctalStartup - transmitter startup routine
*
* Call interrupt level character output routine.
*/
int tyGSOctalStartup
(
TY_GSOCTAL_DEV *pTyGSOctalDv /* ty device to start up */
)
{
static char *fn_nm = "tyGSOctalStartup";
char outChar;
QUAD_TABLE *qt = (QUAD_TABLE *)pTyGSOctalDv->qt;
SCC2698 *regs = pTyGSOctalDv->regs;
SCC2698_CHAN *chan = pTyGSOctalDv->chan;
int block = pTyGSOctalDv->block;
if (tyITx (&pTyGSOctalDv->tyDev, &outChar) == OK) {
if (chan->u.r.sr & 0x04)
chan->u.w.thr = outChar;
qt->imr[block] |= pTyGSOctalDv->imr; /* activate Tx interrupt */
regs->u.w.imr = qt->imr[block]; /* enable Tx interrupt */
}
else
logMsg("%s: tyITX ERROR, sr=%02x",
(int)fn_nm, chan->u.r.sr,
NULL, NULL, NULL, NULL);
return (0);
}
tyGSOctal/tyGSOctal.h 0100644 0000621 0000620 00000004667 06671302114 013746 0 ustar epics epics /**************************************************************************
Header: tyGSOctal.h
Author: Peregrine M. McGehee
Description: Header file for GreenSpring Ip_Octal 232, 422, and 485
serial I/O modules. This software is somewhat based on the HiDEOS
device driver developed by Jim Kowalkowski of the Advanced Photon Source.
History:
who when what
--- -------- ------------------------------------------------
PMM 18/11/96 Original.
PMM 12/12/96 Added node * client.
PMM 06/03/97 Increased number of delimiters to 5.
**************************************************************************/
#ifndef __OCTALUART_H
#define __OCTALUART_H
typedef enum { BYTE_FUNC_ISR=0, BYTE_FUNC_TASK=1 } BYTE_FUNC_TYPES;
typedef enum { SerialWriteTimeOut=-2, SerialReadTimeOut=-1 } SerialRC;
typedef enum { SerialNoWait=0, SerialWaitForever=-1 } SerialReadModes;
typedef enum { SerialNoReply=-2 } SerialWriteModes;
typedef enum { ByteFuncOK=0,ByteFuncEndRead=1,ByteFuncReject=-1 } ByteFuncRC;
enum { MAX_SPIN_TIME=2, CLOCK_HZ=3686400 };
typedef enum { QUAD, OCTAL } TYPE_SIZE;
typedef enum { RS485,RS232 } RSmode;
struct ty_gsoctal_dev
{
TY_DEV tyDev;
SCC2698* regs;
SCC2698_CHAN* chan;
int created;
UCHAR opcr,mr1,mr2;
UCHAR imr;
int port, block;
void * qt;
RSmode mode;
};
typedef struct ty_gsoctal_dev TY_GSOCTAL_DEV;
struct quadTable
{
TY_GSOCTAL_DEV port[8]; /* one per port */
ushort_t carrier;
ushort_t module;
UCHAR imr[4]; /* one per block */
};
typedef struct quadTable QUAD_TABLE;
STATUS tyGSOctalDrv(int);
int tyGSOctalModuleInit(char *, int, int, int);
TY_GSOCTAL_DEV *tyGSOctalDevCreate(char *, int, int, int, int);
void tyGSOctalReport(void);
void tyGSOctalConfig(TY_GSOCTAL_DEV *, unsigned int, char,
int, int, char);
void tyGSOctalSetcr(TY_GSOCTAL_DEV *, unsigned char);
void tyGSOctalSetopcr(TY_GSOCTAL_DEV *, unsigned char);
int tyGSOctalOpen(TY_GSOCTAL_DEV *, char *, int);
int tyGSOctalWrite(TY_GSOCTAL_DEV *, char *, long);
STATUS tyGSOctalIoctl(TY_GSOCTAL_DEV *, int, int);
int tyGSOctalStartup(TY_GSOCTAL_DEV *);
#endif
/**************************************************************************
CVS/RCS Log information:
**************************************************************************/
tyGSOctal/tyGSOctal.html 0100644 0000621 0000620 00000010341 06750137331 014452 0 ustar epics epics
GreenSpring OctalUart IP Module Support
GreenSpring OctalUart IP Module Support
Introduction
This software drives the GreenSpring OctalUart family of Industry Pack modules
to communicate with a number of RS-232, RS-422, and RS-485 devices. These IP
modules are mounted on a Greenpring VIPC-610 VMEbus carrier board.
The driver uses drvIpac for configuration of the
carrier board plus access routines for the SCC2698 octalUart used in the IP
modules. The driver is implemented as a standard vxWorks terminal driver
making use of the tyLib() system library.
Structure
The software structure used to support EPICS record processing is in three
layers:
EPICS device and driver support.
- devNode - simple support for stringin/out records with multiple delimiters
- drvNode - driver support called by devNode
EPICS STDIO support.
- drvSerial - EPICS STDIO Driver Support (for serial devices)
vxWorks device driver
- tyGSOctal - vxWorks device driver for the GreenSpring OctalUART
- drvIpac - IPAC management
Related Systems
Other present options for EPICS device/driver support that use drvSerial are:
- devAscii/drvAscii - support for ASCII commands and responses
- devAbDf1/drvAbDf1 - support for the Allen-Bradley DF1 protocol
drvSerial is designed to use any vxWorks serial device driver. Other hardware
supported includes:
- tyFs2 - vxWorks device driver for the FORCE SIO-2 VME card
Where to Find It!
EPICS device and driver support.
EPICS STDIO support.
vxWorks device driver
Driver Routines
The following is an example of setting up the device driver from within
a vxWorks startup script:
#####################
# Configuration Setup
#####################
# Initialize the IP module tasks
tyGSOctalDrv 4
ipacAddCarrier &vipc610, "0x6000"
# Init module on carrier 0, slot 0
MOD0 = tyGSOctalModuleInit("GSIP_OCTAL232", 0x80, 0, 0)
# Create devices
PORT00 = tyGSOctalDevCreate("/tyGS/0/0", MOD0, 0, 512, 512)
PORT02 = tyGSOctalDevCreate("/tyGS/0/2", MOD0, 2, 512, 512)
PORT04 = tyGSOctalDevCreate("/tyGS/0/4", MOD0, 4, 512, 512)
PORT06 = tyGSOctalDevCreate("/tyGS/0/6", MOD0, 6, 512, 512)
# Config ports
# baud, parity(N/E/O), stop, bits, flow(N/H)
tyGSOctalConfig PORT00, 9600, 'N', 1, 8, 'N'
tyGSOctalConfig PORT02, 9600, 'N', 1, 8, 'N'
tyGSOctalConfig PORT04, 9600, 'N', 1, 8, 'N'
tyGSOctalConfig PORT06, 9600, 'N', 1, 8, 'N'
#
# The following uses the EPICS drvNode/devNode support
#
# Create NODEs - device support uses these names
COMMON = drvNodeInit("/tyGS/0/0")
VILINK = drvNodeInit("/tyGS/0/2")
AOBG = drvNodeInit("/tyGS/0/4")
OSIS = drvNodeInit("/tyGS/0/6")
# Configure NODE delimiters
drvNodeSetDelim COMMON, 0, 2, "\015\012", 0x00
drvNodeSetDelim COMMON, 1, 2, "> ", 0x00
drvNodeSetDelim COMMON, 2, 3, ": ", 0x00
drvNodeSetDelim COMMON, 3, 3, ")? ", 0x00
drvNodeSetDelim COMMON, 4, 3, "): ", 0x00
#
drvNodeSetDelim VILINK, 0, 2, "\015\012", 0x00
#
drvNodeSetDelim AOBG, 0, 1, "\015", 0x00
#
drvNodeSetDelim OSIS, 0, 1, "\015", 0x00
tyGSOctal/tyGSOctalRelease.html 0100644 0000621 0000620 00000003531 06750347216 015763 0 ustar epics epics
GreenSpring Octal Serial Driver Release Notes
GreenSpring Octal Serial Driver Release Notes
Software by Peregrine McGehee
Release by Andrew Johnson
This document describes changes made to Peregrine McGehee's vxWorks driver
for the GreenSpring Octal RS232/RS422/RS485 IndustryPack modules since this
driver was merged into the unbundled EPICS ipac support area. Version numbers
refer to the release of the ipac support software as a whole. The earliest
version appears at the bottom, with more recent releases above it.
Version 2.1
Updated due to changes made to drvIpac, and also actually tried it out for
myself - got some interrupts but I haven't done a full test.
Changes:
- Use ipmIntConnect to attach the ISR.
Bugs fixed:
- Now works with the ipmv162 carrier driver - the interrupt vector register
was being accessed at an offset from the I/O space, which works on GreenSpring
carriers but doesn't meet the IPAC specification. The register is correctly
accessed in the IP memory space, which means that this must be mapped in.
Version 2.0
First version containing this driver, ipac support is now structured as an
EPICS R3.13.1 <supporttop> application.
Added:
- This driver software and these release notes.
Bugs fixed:
- Gcc compilation warnings attended to.