/************************************************************************** GTA PROJECT AT division Copyright, 1990, 1991, 1992 The Regents of the University of California Los Alamos National Laboratory $Id$ DESCRIPTION: Seq_tasks.c: Task creation and control for sequencer state sets. ENVIRONMENT: VxWorks HISTORY 04dec91,ajk Implemented linked list of state programs, eliminating task variables. 11dec91,ajk Made cosmetic changes and cleaned up comments. 19dec91,ajk Changed algoritm in seq_getTimeout(). 29apr92,ajk Implemented new event flag mode. 30apr92,ajk Periodically call ca_pend_event() to detect connection failures. 21may92,ajk In sprog_delete() wait for loggin semaphore before suspending tasks. Some minor changes in the way semaphores are deleted. 18feb92,ajk Changed to allow sharing of single CA task by all state programs. Added seqAuxTask() and removed ca_pend_event() from ss_entry(). 9aug93,ajk Added calls to taskwdInsert() & taskwdRemove(). ***************************************************************************/ #define ANSI #include "seq.h" #include /* Function declarations */ LOCAL VOID ss_task_init(SPROG *, SSCB *); LOCAL long seq_getTimeout(SSCB *); #define TASK_NAME_SIZE 10 #define MAX_DELAY (10000000) /* max delay time pending for events */ /* * sequencer() - Sequencer main task entry point. * */ sequencer(pSP, stack_size, ptask_name) SPROG *pSP; /* ptr to original (global) state program table */ int stack_size; /* stack size */ char *ptask_name; /* Parent task name */ { SSCB *pSS; STATE *pST; int nss, task_id, i; char task_name[TASK_NAME_SIZE+10]; extern VOID ss_entry(); extern int seqAuxTaskId; pSP->task_id = taskIdSelf(); /* my task id */ pSS = pSP->sscb; pSS->task_id = pSP->task_id; /* Add the program to the state program list */ seqAddProg(pSP); /* Import Channel Access context from the auxillary seq. task */ ca_import(seqAuxTaskId); /* Initiate connect & monitor requests to database channels. */ seq_connect(pSP); /* Additional state set task names are derived from the first ss */ if (strlen(ptask_name) > TASK_NAME_SIZE) ptask_name[TASK_NAME_SIZE] = 0; /* Create each additional state set task */ for (nss = 1, pSS = pSP->sscb + 1; nss < pSP->nss; nss++, pSS++) { /* Form task name from program name + state set number */ sprintf(task_name, "%s_%d", ptask_name, nss); /* Spawn the task */ task_id = taskSpawn( task_name, /* task name */ SPAWN_PRIORITY+pSS->task_priority, /* priority */ SPAWN_OPTIONS, /* task options */ stack_size, /* stack size */ (FUNCPTR)ss_entry, /* entry point */ (int)pSP, (int)pSS, 0,0,0,0,0,0,0,0); /* pass 2 parameters */ seq_log(pSP, "Spawning task %d: \"%s\"\n", task_id, task_name); } /* First state set jumps directly to entry point */ ss_entry(pSP, pSP->sscb); } /* * ss_entry() - Task entry point for all state sets. * Provides the main loop for state set processing. */ VOID ss_entry(pSP, pSS) SPROG *pSP; SSCB *pSS; { BOOL ev_trig; STATE *pST, *pStNext; long delay; char *pVar; LOCAL VOID seq_waitConnect(); /* Initialize all ss tasks */ ss_task_init(pSP, pSS); /* If "+c" option, wait for all channels to connect */ if (pSP->options & OPT_CONN) seq_waitConnect(pSP, pSS); /* Initilaize state set to enter the first state */ pST = pSS->states; pSS->current_state = 0; /* Use the event mask for this state */ pSS->pMask = (pST->eventMask); /* Local ptr to user variables (for reentrant code only) */ pVar = pSP->user_area; /* * ============= Main loop ============== */ while (1) { pSS->time = tickGet(); /* record time we entered this state */ /* Call delay function to set up delays */ pSS->ndelay = 0; pST->delay_func(pSP, pSS, pVar); /* Generate a phoney event. * This guarantees that a when() is always executed at * least once when a state is entered. */ semGive(pSS->syncSemId); /* * Loop until an event is triggered, i.e. when() returns TRUE */ do { /* Wake up on CA event, event flag, or expired time delay */ delay = seq_getTimeout(pSS); if (delay > 0) semTake(pSS->syncSemId, delay); /* Call the event function to check for an event trigger. * The statement inside the when() statement is executed. * Note, we lock out CA events while doing this. */ semTake(pSP->caSemId, WAIT_FOREVER); ev_trig = pST->event_func(pSP, pSS, pVar); /* check events */ if ( ev_trig && (pSP->options & OPT_NEWEF) == 0 ) { /* Clear all event flags (old mode only) */ register int i; for (i = 0; i < NWRDS; i++) pSP->events[i] = pSP->events[i] & !pSS->pMask[i]; } semGive(pSP->caSemId); } while (!ev_trig); /* * An event triggered: * execute the action statements and enter the new state. */ /* Change event mask ptr for next state */ pStNext = pSS->states + pSS->next_state; pSS->pMask = (pStNext->eventMask); /* Execute the action for this event */ pSS->action_complete = FALSE; pST->action_func(pSP, pSS, pVar); /* Flush any outstanding DB requests */ ca_flush_io(); /* Change to next state */ pSS->prev_state = pSS->current_state; pSS->current_state = pSS->next_state; pST = pSS->states + pSS->current_state; pSS->action_complete = TRUE; } } /* Initialize state-set tasks */ LOCAL VOID ss_task_init(pSP, pSS) SPROG *pSP; SSCB *pSS; { extern int seqAuxTaskId; /* Get this task's id */ pSS->task_id = taskIdSelf(); /* Import Channel Access context from the auxillary seq. task */ if (pSP->task_id != pSS->task_id) ca_import(seqAuxTaskId); /* Register this task with the EPICS watchdog (no callback function) */ taskwdInsert(pSS->task_id, (VOIDFUNCPTR)0, (VOID *)0); return; } /* Wait for all channels to connect */ LOCAL VOID seq_waitConnect(pSP, pSS) SPROG *pSP; SSCB *pSS; { STATUS status; long delay; delay = 600; /* 10, 20, 30, 40, 40,... sec */ while (pSP->conn_count < pSP->nchan) { status = semTake(pSS->syncSemId, delay); if ((status != OK) && (pSP-> task_id == pSS->task_id)) { logMsg("%d of %d channels connected\n", pSP->conn_count, pSP->nchan); } if (delay < 2400) delay = delay + 600; } } /* * seq_getTimeout() - return time-out for pending on events. * Returns number of tics to next expected timeout of a delay() call. * Returns MAX_DELAY if no delays pending */ LOCAL long seq_getTimeout(pSS) SSCB *pSS; { int ndelay; long timeout; /* expiration clock time (tics) */ long delay, delayMin; /* remaining & min. delay (tics) */ if (pSS->ndelay == 0) return MAX_DELAY; delayMin = MAX_DELAY; /* start with largest possible delay */ /* Find the minimum delay among all non-expired timeouts */ for (ndelay = 0; ndelay < pSS->ndelay; ndelay++) { timeout = pSS->timeout[ndelay]; if (timeout == 0) continue; /* already expired */ delay = timeout - tickGet(); /* convert timeout to remaining delay */ if (delay <= 0) { /* just expired */ delayMin = 0; pSS->timeout[ndelay] = 0; /* mark as expired */ } else if (delay < delayMin) { delayMin = delay; /* this is the min. delay so far */ } } return delayMin; } /* Set-up for delay() on entering a state. This routine is called by the state program for each delay in the "when" statement */ VOID seq_start_delay(pSP, pSS, delay_id, delay) SPROG *pSP; SSCB *pSS; int delay_id; /* delay id */ float delay; { int td_tics; /* Note: the following 2 lines could be combined, but doing so produces * the undefined symbol "Mund", which is not in VxWorks library */ td_tics = delay*60.0; pSS->timeout[delay_id] = pSS->time + td_tics; delay_id += 1; if (delay_id > pSS->ndelay) pSS->ndelay = delay_id; return; } /* Test for time-out expired */ BOOL seq_test_delay(pSP, pSS, delay_id) SPROG *pSP; SSCB *pSS; int delay_id; { if (pSS->timeout[delay_id] == 0) return TRUE; /* previously expired t-o */ if (tickGet() >= pSS->timeout[delay_id]) { pSS->timeout[delay_id] = 0; /* mark as expired */ return TRUE; } return FALSE; } /* * Delete all state set tasks and do general clean-up. * General procedure is: * 1. Suspend all state set tasks except self. * 2. Call the user program's exit routine. * 3. Disconnect all channels for this state program. * 4. Cancel the channel access context. * 5. Delete all state set tasks except self. * 6. Delete semaphores, close log file, and free allocated memory. * 7. Return, causing self to be deleted. * * This task is run whenever ANY task in the system is deleted. * Therefore, we have to check the task belongs to a state program. */ sprog_delete(tid) int tid; { int nss, tid_ss; SPROG *pSP, *seqFindProg(); SSCB *pSS; pSP = seqFindProg(tid); if (pSP == NULL) return -1; /* not a state program task */ logMsg("Delete %s: pSP=%d=0x%x, tid=%d\n", pSP->name, pSP, pSP, tid); /* Is this a real sequencer task? */ if (pSP->magic != MAGIC) { logMsg(" Not main state program task\n"); return -1; } /* Wait for log semaphore (in case a task is doing a write) */ semTake(pSP->logSemId, 600); /* Remove tasks' watchdog & suspend all state set tasks except self */ #ifdef DEBUG logMsg(" Suspending state set tasks:\n"); #endif /*DEBUG */ pSS = pSP->sscb; for (nss = 0; nss < pSP->nss; nss++, pSS++) { tid_ss = pSS->task_id; /* Remove the task from EPICS watchdog */ taskwdRemove(tid_ss); if ( (tid_ss != 0) && (tid != tid_ss) ) { #ifdef DEBUG logMsg(" suspend task: tid=%d\n", tid_ss); #endif /*DEBUG */ taskSuspend(tid_ss); } } /* Give back log semaphore */ semGive(pSP->logSemId); /* Call user exit routine (only if task has run) */ if (pSP->sscb->task_id != 0) { #ifdef DEBUG logMsg(" Call exit function\n"); #endif /*DEBUG */ pSP->exit_func(pSP, pSP->user_area); } /* Disconnect all channels */ seq_disconnect(pSP); /* Cancel the CA context for each state set task */ for (nss = 0, pSS = pSP->sscb; nss < pSP->nss; nss++, pSS++) { ca_import_cancel(pSS->task_id); } /* Close the log file */ if (pSP->logFd > 0 && pSP->logFd != ioGlobalStdGet(1)) { #ifdef DEBUG logMsg("Closing log fd=%d\n", pSP->logFd); #endif /*DEBUG */ close(pSP->logFd); pSP->logFd = ioGlobalStdGet(1); } /* Remove the state program from the state program list */ seqDelProg(pSP); /* Delete state set tasks (except self) & delete their semaphores */ pSS = pSP->sscb; for (nss = 0; nss < pSP->nss; nss++, pSS++) { tid_ss = pSS->task_id; if ( (tid != tid_ss) && (tid_ss != 0) ) { #ifdef DEBUG logMsg(" delete ss task: tid=%d\n", tid_ss); #endif /*DEBUG */ taskDelete(tid_ss); } if (pSS->syncSemId != 0) semDelete(pSS->syncSemId); if (pSS->getSemId != 0) semDelete(pSS->getSemId); } /* Delete program-wide semaphores */ semDelete(pSP->caSemId); semDelete(pSP->logSemId); /* Free the memory that was allocated for the task area */ #ifdef DEBUG logMsg("free pSP->dyn_ptr=0x%x\n", pSP->dyn_ptr); #endif /*DEBUG */ taskDelay(5); free(pSP->dyn_ptr); return 0; } /* * Sequencer auxillary task -- loops on ca_pend_event(). */ seqAuxTask() { extern int seqAuxTaskId; /* Set up so all state program tasks will use a common CA context */ ca_task_initialize(); seqAuxTaskId = taskIdSelf(); /* must follow ca_task_initialize() */ /* This loop allows CA to check for connect/disconnect on channels */ for (;;) { ca_pend_event(10.0); /* returns every 10 sec. */ } }