Deck Control If You Have Real-Time Scheduling

By Chris Pirazzi.

Here are some tips for doing deck control on SGI platforms which support real-time IRIX process scheduling guarantees.

Before you read this, read the comments at the beginning of IRIX 6.3 O2 422 Deck Control Sample Code. You'll see that a large portion of that single-processor IRIX sample code exists only to work around the lack of IRIX process scheduling guarantees. If you could be guaranteed that your process would run, say, every millisecond, then there would be no need to schedule sony commands for transmission far in the future and receive responses which are potentially from far in the past.

On multi-processor SGI systems, including Onyx, Challenge, Onyx2, and Origin, you can do this. SGI lets you dedicate one or more processors to real-time tasks. This is accomplished with sysmp(MP_MUSTRUN) on the older machines, and newer and nicer interfaces such as the Frame Scheduler in the REACT/Pro package on the newer machines.

Once you set up such a real-time process, you will need to access the machine's serial ports in a low-latency fashion. The standard /dev/ttyd# STREAMS interface to serial ports is notorious for holding bytes which arrive at a serial port for an indeterminate amount of time before waking up your process, and (more rarely) holding bytes you have written for an indeterminate amount of time before transmitting them. This is because /dev/ttyd# is designed for high-bandwidth applications such as modems, where coalescing bytes into large packets is the best way to keep the port saturated, and where latencies on the order of tens or hundreds of milliseconds are an acceptable tradeoff.

But deck control rarely saturates the serial port, and the port runs at only 38400 baud. Low latency is much more important.

The /dev/ttyd# driver has a tunable parameter which you can set to reduce the amount of time it will hold an input byte while waiting for another one. On older systems, you must change the kernel tunable duart_rsrv_duration found in /var/sysgen/system/master.d/sduart and rebuild your kernel (or reboot). On newer systems, you can control this parameter using the SIOC_ITIMER ioctl() described in man serial(7). 0 is a good value to use for minimum latency. On some (especially older) systems, duart_rsrv_duration==0 could lead to dropped input bytes for high-speed modem applications, so use it carefully.

The /dev/ttyd# STREAMS interface also does some arcane UNIX-style processing of the byte stream. You don't want this for deck control. The code below helps push STREAMS out of the way by mimicking the behavior of 'stty raw.' This turns your serial port into a raw byte pipe. Other customers accomplished a similar thing by popping the STREAMS line discipline module off the stack with the I_POP ioctl() from streamio(7).

This code also uses the SIOC_RS422 ioctl() to switch your port into 422/Macintosh mode. You only need this one some of the high-end platforms, as described in Getting 422 in and out of Your SGI.

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <termios.h>
#include <sys/termio.h>
#include <sys/stermio.h>
#include <stropts.h>
#include <sys/z8530.h>

void main(int argc, char **argv)
{
  int fd; 
  struct termios t;

  /* --- open tty device */

  if (!(fd = open("/dev/ttyd2", O_RDWR)))
    perror("open /dev/ttyd2");

  /* --- shove it into RS422 mode */

  {
    struct strioctl str;
    int arg;
    str.ic_cmd = SIOC_RS422;
    str.ic_timout = 0;
    str.ic_len = 4;
    arg = RS422_ON;
    str.ic_dp = (char *)&arg;
    if (ioctl(fd, I_STR, &str) < 0) 
      perror("setting RS422 mode");
  }

  if (tcgetattr(fd, &t)) 
    perror("getattr");
  
  /* --- set 38400 baud */
  
  if (cfsetispeed(&t, B38400)) 
    perror("ispeed");
  if (cfsetospeed(&t, B38400)) 
    perror("ospeed");

  /* --- set 'raw' mode as it is set by "stty raw" */
  
  t.c_cflag &= ~(CSIZE);
  t.c_cflag |= CS8|PARENB|PARODD;
  t.c_iflag &= ~(-1);
  t.c_iflag |= 0;
  t.c_lflag &= ~(ISIG|ICANON|XCASE);
  t.c_lflag |= 0;
  t.c_oflag &= ~OPOST;
  t.c_oflag |= 0;
  t.c_cc[VMIN] = 1;
  t.c_cc[VTIME] = 1;
  
  /* turn off echo */
  t.c_lflag &= ~(ECHO);
  
  /* these three are technically the default, but what the heck */
  {
#if 0 /* this is only with termio, not termios (of course!) */
    /* new berkeley line discipline -- the 'default' */
    t.c_line = LDISC1;  
#endif
    
    /* make sure ALL oflags off even tho opost is supposed to do this */
    t.c_oflag &= ~(ONLCR|TAB3);
    
    /* make sure ALL lflags off even tho -icanon is supposed to */
    t.c_lflag &= ~(ECHOK);
  }

  if (tcsetattr(fd, TCSANOW, &t))  
    perror("setattr");
  
  /* --- useful debugging thing */

  printf("---------------- Serial Port Status:\n");
  system("stty -a < /dev/ttyd2");
  printf("----------------\n");

  /* --- do your serial I/O! */
}
We're sure there are hundreds more tips that we could put into this page. If you have one, send us some mail!