Interrupts¶
Interrupts provide a way to notify a remote application that a certain predefined condition, identified by a positive integer number, has occurred. Similarly to what happens for a memory segment, a SISCI interrupt is allocated on a node and it is connected to and used from another one.
The SISCI API includes two types of interrupts – with and without data forwarded as a part of the interrupt. Interrupts without data may have a less resource-consuming implementation, or have less latency on certain hardware implementations.
Managing SISCI interrupts involves two distinct types of resources:
A local interrupt is the resource available on the allocating node; it is represented by a descriptor of type
sci_local_interrupt, accessible via a handle of type sci_local_interrupt_t or sci_local_data_interrupt, accessible via a handle of type sci_local_data_interrupt_t.
A remote interrupt is the resource available on the importing node and corresponds to a local interrupt allocated on another node. A remote interrupt is represented by a descriptor of type sci_remote_interrupt, accessible via a handle of type sci_remote_interrupt_t or sci_remote_data_interrupt, accessible via a handle of type sci_remote_data_interrupt_t.
In this chapter, you will learn:
How to allocate an interrupt on the local node and make it available to other nodes.
How to wait synchronously for an interrupt.
How to connect to an interrupt available on a remote node.
How to trigger a remote application using an interrupt.
Managing local interrupts¶
Allocating an interrupt¶
An interrupt resource is allocated, initialized and made available to remote nodes calling the function SCICreateInterrupt():
sci_desc_t v_dev;
sci_local_interrupt_t local_interrupt;
unsigned int interrupt_no;
sci_error_t error;
SCIInitialize(...)
SCIOpen(&v_dev, ...);
/* possibly set interrupt_no */
SCICreateInterrupt(v_dev,
&local_interrupt,
ADAPTER_NO,
&interrupt_no,
NO_CALLBACK,
NO_ARG,
NO_FLAGS,
&error);
if (error == SCI_ERR_OK) {
/* the interrupt is available to remote applications */
} else {
/* manage error */
}
By default, the driver assigns an identifier to the allocated interrupt. A remote application can trigger the interrupt by using the same identifier. (You have to make the identifier known to the remote node in some way – e.g. by transferring it through shared memory). Alternatively, you can propose an identifier to the driver, setting in advance the parameter interrupt_no and using the flag SCI_FLAG_FIXED_INTNO when calling SCICreateInterrupt(). If the number is already in use, the error returned is SCI_ERR_INTNO_USED.
Example 6-2 demonstrates the use of SCI_FLAG_FIXED_INTNO to avoid implementing an additional mechanism to communicate the identifier to the application running on the other node.
Note that the SCICreateInterrupt() allocates and initializes the interrupt and makes it available to other nodes.
Deallocating an interrupt¶
Once an interrupt is no longer needed and all the remote nodes have disconnected from it, the local interrupt resource can be freed:
sci_error_t error;
sci_local_interrupt_t local_interrupt;
SCICreateInterrupt(..., &local_interrupt, ...);
/* use the interrupt */
SCIRemoveInterrupt(local_interrupt, NO_FLAGS, &error);
if (error == SCI_ERR_OK) {
/* the interrupt resource has been correctly freed */
} else {
/* manage error */
}
Waiting for an interrupt¶
The simplest way to wait for an interrupt to be triggered is to explicitly wait for that event.
sci_error_t error;
sci_local_interrupt_t local_interrupt;
SCICreateInterrupt(..., &local_interrupt, ...);
SCIWaitForInterrupt(local_interrupt, SCI_INFINITE_TIMEOUT, NO_FLAGS, &error);
if (error == SCI_ERR_OK) {
/* the interrupt has been triggered */
} else {
/* manage error */
}
The timeout is expressed in milliseconds. If an error occurs, it can be due to one of two reasons: the timeout has expired, or the interrupt has been removed in the meantime, presumably by another thread. It is alternatively possible to use a callback mechanism; this is addressed in the section Interrupts and callbacks.
Managing remote interrupts¶
Connecting to an interrupt¶
Like memory segments, the application has to “connect” to a remote interrupt to be able to trigger an interrupt on a remote node. The operation, achieved by calling SCIConnectInterrupt(), allocates and initializes a remote interrupt resource, providing a handle to it.
sci_desc_t v_dev;
unsigned int remote_interrupt_no;
sci_remote_interrupt_t remote_interrupt;
sci_error_t error;
SCIInitialize(...);
SCIOpen(&v_dev, ...);
/* set remote_interrupt_no */
SCIConnectInterrupt(v_dev, &remote_interrupt, RECEIVER_NODE_ID,
ADAPTER_NO, remote_interrupt_no,
SCI_INFINITE_TIMEOUT, NO_FLAGS, &error);
if (error == SCI_ERR_OK) {
/* the interrupt is available for triggering */
} else {
/* manage error */
}
A remote interrupt is identified by the node identifier of the exporting node and an integer number, the same one used to create it with SCICreateInterrupt(). This number may be assigned directly by the underlying driver software, in which case another mechanism must be foreseen to make this number available to other nodes.
Disconnecting from an interrupt¶
Once a remote interrupt resource isn’t needed anymore, it has to be released with SCIDisconnectInterrupt().
sci_remote_interrupt_t remote_interrupt;
sci_error_t error;
SCIConnectInterrupt(..., &remote_interrupt, ...)
/* use the remote interrupt */
SCIDisconnectInterrupt(remote_interrupt, NO_FLAGS, &error);
if (error == SCI_ERR_OK) {
/* the interrupt is not available any more */
} else {
/* manage error */
}
Triggering an interrupt¶
A remote interrupt is triggered using SCITriggerInterrupt().
sci_remote_interrupt_t remote_interrupt;
sci_error_t error;
SCIConnectInterrupt(..., &remote_interrupt, ...);
SCITriggerInterrupt(remote_interrupt, NO_FLAGS, &error);
if (error == SCI_ERR_OK) {
/* the remote interrupt has been successfully triggered */
} else {
/* manage error */
}
If the application that exported the interrupt is waiting, it should then wake up and proceed with its execution. If the application does not wait for interrupt, the interrupt will be lost.
Interrupt example¶
As in the previous examples, the example application is actually composed of two programs: a sender which connects to and triggers an interrupt exported by a receiver, which after receiving the interrupt prints “Hello, World!”.
Example 6-1 and Example 6-2 below show the operations performed by the sender and by the receiver respectively.
Example 6-1. A sender program based on interrupts
/* interrupt based sender program */
#include "sisci_error.h"
#include "sisci_api.h"
#define RECEIVER_NODE_ID 4
#define RECEIVER_INTERRUPT_NO 12345
#define ADAPTER_NO 0
#define NO_CALLBACK 0
#define NO_ARG 0
#define NO_FLAGS 0
int main(int argc, char* argv[])
{
sci_desc_t v_dev;
sci_error_t error;
sci_remote_interrupt_t remote_interrupt;
/* initialize the environment */
SCIInitialize(NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* create a virtual device */
SCIOpen(&v_dev, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* connect to the remote interrupt */
SCIConnectInterrupt(v_dev, &remote_interrupt, RECEIVER_NODE_ID,
ADAPTER_NO, RECEIVER_INTERRUPT_NO,
SCI_INFINITE_TIMEOUT, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* send the print command, i.e. trigger the interrupt */
SCITriggerInterrupt(remote_interrupt, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* cleanup */
SCIDisconnectInterrupt(remote_interrupt, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
SCIClose(v_dev, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
SCITerminate();
return 0;
}
Example 6-2. A receiver program based on interrupts
/* interrupt based receiver program */
#include "sisci_error.h"
#include "sisci_api.h"
#include <stdio.h>
#define RECEIVER_INTERRUPT_NO 12345
#define ADAPTER_NO 0
#define NO_CALLBACK 0
#define NO_ARG 0
#define NO_FLAGS 0
int main(int argc, char* argv[])
{
sci_desc_t v_dev;
sci_error_t error;
unsigned int interrupt_no;
sci_local_interrupt_t local_interrupt;
/* initialize the environment */
SCIInitialize(NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* create a virtual device */
SCIOpen(&v_dev, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
/* use a predefined interrupt number; assume it's not in use... */
interrupt_no = RECEIVER_INTERRUPT_NO;
/* allocate an interrupt and make it available */
SCICreateInterrupt(v_dev, &local_interrupt, ADAPTER_NO, &interrupt_no,
NO_CALLBACK, NO_ARG, SCI_FLAG_FIXED_INTNO, &error);
if (error != SCI_ERR_OK) return 1;
SCIWaitForInterrupt(local_interrupt, SCI_INFINITE_TIMEOUT, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
printf("Hello, World!");
/* cleanup */
SCIRemoveInterrupt(local_interrupt, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
SCIClose(v_dev, NO_FLAGS, &error);
if (error != SCI_ERR_OK) return 1;
SCITerminate();
return 0;
}