Copyright(c) 1992 Digi International, Inc. This document contains information proprietary and confiden- tial to Digi International, Inc. By accepting delivery, Recipient agrees to retain it in confidence and not disclose it to others nor use it for any purpose other than that for which it was delivered, and to promptly return it upon request. This document is an attempt to clarify the basic mechanisms of writing UNIX(tm) ioctls for various character devices, especially as they apply to DigiBoard products. I/O control calls are a basic mechanism provided for doing various control functions on character devices, hence: ioctl's. It is recommended that you carefully study the man pages for termio listed as termio(7) or as TERMIO(M) for SCO. It is further recommended that you have a working knowledge of read(2), write(2), open(2), close(2) and ioctl(2), also called READ(S), WRITE(S), OPEN(S), CLOSE(S) and IOCTL(S) for SCO. DISCLAIMER: This programming information is provided free of charge to our customers and other interested parties. It is provided "as is", without warranty of any kind, either express or implied. The contents and availability of this information is subject to change without notice and does not represent a commitment on the part of Digi- Board, Inc. The intent of the programming fragments provided is that they be basically correct, and that they are an example of how one may procede in making use of the ioctl func- tions in the Unix operating system. They may also con- tain typographical errors which would prevent them from compiling correctly and they do not necessarily do all of the error handling which should be a part of a pro- duction program. Digi International Technical Support cannot provide detailed programming assistance to individual customers. Section 1: The first variety of ioctl calls are those described under termio(7). The form of the ioctls will be: ioctl(fd, command, arg) int fd; /* file descriptor of the device specified */ int command; struct termio *arg; /* pointer to a termio structure */ One method of using these ioctls would be to include in your program. You must also include and other include files, depending upon what your desires are. EXAMPLE: #include #include { int fd; /* file descriptor of device */ struct termio arg; /* the termio structure */ char *ttyname = "/dev/tty"; /* * O_NDELAY may be used to prevent * hanging in the open while waiting for DCD. */ if ((fd = open(ttyname, O_RDWR|O_NDELAY)) == -1) { perror("tty open"); exit(1); } /* * Ioctl calls return '-1' to indicate failure. */ if (ioctl(fd, TCGETA, &arg) == -1) { perror("TCGETA"); exit(1); } /* * arg now contains the current setting of the device * name "/dev/tty". * * to make changes to the device, we will do another * ioctl call after changing the flags to reflect the * values we need. */ arg.c_lflag &= ~ICANON; /* turn off input processing */ arg.c_oflag &= ~OPOST; /* turn of output processing */ arg.c_cc[VMIN] = 1; /* wait for one character */ arg.c_cc[VTIME] = 0; /* time now has no effect */ /* * use the ioctl to copy the above changes * to the device * * the command could be TCSETA, TCSETAW, or TCSETAF * depending upon the desired result. * TCSETA will cause an immediate result, * TCSETAW will wait for the output to drain before the * changes take effect * TCSETAF will wait for the output to drain, then * flush the input before the changes take effect. */ if (ioctl(fd, TCSETA, &arg) == -1) { perror("TCSETA"); exit(1); } } There are 3 more ioctls described in termio(7). The only difference is that the third argument to the ioctl is an integer rather than a pointer to a termio structure. The commands are TCSBRK, TCXONC, TCFLSH and the following method could be used: The form of the ioctl is: ioctl(fd, command, arg) int fd; int command; int arg; EXAMPLE: { int fd; /* file descriptor of device */ int arg; /* use integer this time */ char *ttyname = "/dev/tty"; fd = open(ttyname, O_RDWR|O_NDELAY); /* * The following will cause a break to be sent. * note: if arg is non-zero this will not cause a break to be sent. */ arg = 0; if (ioctl(fd, TCSBRK, arg) == -1) { perror("TCSBRK"); exit(1); } /* * The following will restart output which has stopped. */ arg = 1; if (ioctl(fd, TCXONC, arg) == -1) { perror("TCXONC"); exit(1); } /* * the following will flush both input and output buffers. */ arg = 2; if (ioctl(fd, TCFLSH, arg) == -1) { perror("TCFLSH"); exit(1); } close(fd); exit(0); } In the above cases it is the responsibility of the driver to differentiate between the types of ioctls which it is receiving and to act upon the type of the arguments being passed to it. It is therefor the responsibility of the application which is performing the ioctl call to pass the driver the correct argument for the command which is to be performed. NOTE: If the definition of the command is that "arg" is an integer, pass an integer, not a pointer to an integer. In the same thought if the command is defined to use a certain structure do not use different structure, the results of such actions will be truly surprising. Section 2: The next class of ioctl's are those special functions sup- ported by the device drivers for DigiBoard intelligent pro- ducts. Some of these functions were borrowed from BSD derivations of Unix. Documentation for these may be found in termio(4). The form for these ioctl's is: ioctl(fd, command, arg) int fd; int command; int *arg; EXAMPLE: #include #include #include { int fd; /* file descriptor of device */ int modem; /* the integer we will use */ if ((fd = open("/dev/tty", O_RDWR|O_NDELAY)) == -1) { perror("open failed"); exit(1); } if (ioctl(fd, TIOCMGET, &modem) == -1) { perror("TIOCMGET"); exit(1); } /* * print out: +/-RTS +/-DTR +/- CTS +/- DSR +/- DCD * in a non-ambiguous manner. */ (void) printf("%cRTS %cDTR %cCTS %cDSR %cDCD\n", (modem & TIOCM_RTS) ? "+" : "-", (modem & TIOCM_DTR) ? "+" : "-", (modem & TIOCM_CTS) ? "+" : "-", (modem & TIOCM_DSR) ? "+" : "-", (modem & TIOCM_CD) ? "+" : "-"); /* * We can drop the output, iff that signal * is not used for flow control. */ modem &= ~TIOCM_RTS; /* to drop RTS */ if (ioctl(fd, TIOCMSET, &modem) == -1) { perror("TIOCMSET"); exit(1); } /* * at this time it may be wise to do another TIOCM_GET to * be sure that RTS had been dropped. That will be left as * an exercise for the user. */ close(fd); exit(0); } The ioctl for TIOCMBIS, TIOCMBIC and TIOCMODS use the same arguments as TIOCMGET and TIOCMSET. In all cases the only signals which can be set are the ouputs (RTS and DTR), the inputs are read-only. The ioctl's TIOCSDTR and TIOCCDTR ignore the third argument to the function. TIOCSDTR attempts to raise the DTR line and TIOCCDTR will attempt to clear (lower) the DTR line. Section 3: The next ioctl's are further extensions which are peculiar to the DigiBoard products, and as such are not likely to be recognized by any other devices. They are defined in digi.h and consist of: DIGI_GETA /* get all digiboard parameters */ DIGI_SETA /* set all digiboard parameters */ DIGI_SETAW /* set after wait for output to drain */ DIGI_SETAF /* set after ouput drains and flushing input */ DIGI_KME DIGI_GETFLOW /* get flow control characters */ DIGI_SETFLOW /* set flow control characters */ DIGI_GETAFLOW /* get auxilliary flow control characters */ DIGI_SETAFLOW /* set auxilliary flow control characters */ The structure used with ioctl commands for DIGI parameters. struct digi_struct { u_short digi_flags; /* Flags */ u_short digi_maxcps; /* Max printer CPS */ u_short digi_maxchar; /* Max chars in print queue */ u_short digi_bufsize; /* Buffer size */ u_char digi_onlen; /* Length of ON string */ u_char digi_offlen; /* Length of OFF string */ char digi_onstr[DIGI_PLEN]; /* Printer on string */ char digi_offstr[DIGI_PLEN]; /* Printer off string */ char digi_term[DIGI_TSIZ]; /* terminal string */ }; struct digiflow_struct { u_char startc; /* flow cntl start char */ u_char stopc; /* flow cntl stop char */ }; The digi_flags are: DIGI_IXON /* Let the FEP handle flow control */ DIGI_FAST /* Fast baud, ie: B50 => 57.6Kb, etc */ RTSPACE /* RTS is input flow control signal */ CTSPACE /* CTS is output flow control signal */ DSRPACE /* DSR is output flow control signal */ DCDPACE /* DCD is output flow control signal */ DTRPACE /* DTR is input flow control signal */ DIGI_COOK /* Let FEP do limited character processing */ DIGI_FORCEDCD /* DCD signal will be ignored */ DIGI_ALTPIN /* swap the functions of DCD and DSR */ DIGI_AIXON /* Auxilliary IXON is set */ The form for these ioctl's is: ioctl(fd, command, arg) int fd; int command; digi_t *arg; EXAMPLE: #include #include #include { int fd; /* file descriptor of device */ digi_t digiold; /* save the original settings */ digi_t diginew; /* structure used to change settings */ if ((fd = open("/dev/tty", O_RDWR|O_NDELAY)) == -1) { perror("open failed"); exit(1); } /* * this will return the current settings of the port. */ if (ioctl(fd, DIGI_GETA, &digiold) == -1) { perror("DIGI_GETA"); exit(1); } /* * save the old settings. */ diginew = digiold; /* * Turn off forcedcd, rtspace and ctspace. */ diginew.digi_flags &= ~(DIGI_FORCEDCD|RTSPACE|CTSPACE|DSRPACE); /* * Set dtrpacing and dcdpacing, this cannot be done * if forcedcd is set. */ diginew.digi_flags |= DTRPACE|DCDPACE; if (ioctl(fd, DIGI_SETAW, &diginew) == -1) { perror("DIGI_SETAW"); exit(1); } close(fd); exit(0); } Section 4: This section is meant for those who are working with the STREAMS driver on UNIX System V Release 4.0 and later. Changes have been incorporated into the operating system to facilitate the use of non-standard ioctl's. These changes are manifested in the method of passing arguments from the user to the driver. The operating system allows the use of the ioctls which con- form to the TIOC, LDIOC and AIOC types to be used as previ- ously. This means that code which runs on non-STEAMS drivers and uses these ioctls (TCGETA, TCSETA, LDSETT, AIOC- DOSMODE, etc) will continue to use the form of: ioctl(fd, command, arg) int fd; int command; struct termio *arg; or int *arg; The operating system converts these calls into a form which the driver expects. The rest of this section will deal with the necessary adjustments to make ioctl calls which are not recognized by STREAMS. These ioctl's use the command "I_STR" and rely on the strioctl struct defined in . The structure is defined as: struct strioctl { int ic_cmd; int id_timout; int ic_len; char *ic_dp }; ic_cmd is the command for the driver, such as TIOCM_GET, TCSETSW, and so on. ic_timout is the timeout for command completion. It can be "0", "-1", or some positive integer. 0 is the default, commonly 15 seconds. "-1" indi- cates infinite timeout and the positive value will be the number of seconds allowed for the command to complete. ic_len is the length of what ic_dp points to. *ic_db contains the information which will be sent to the driver or returned from the driver. The calls follow the form of: ioctl(fd, command, arg) int fd; /* file discriptor of the device */ int command; /* this will always be I_STR */ struct strioctl *arg; The file descriptor is obtained from an open of the device. The command will always be I_STR as defined in . The arg pointer to the strioctl structure will contain all the information needed by the system and driver to perform the functions needed. EXAMPLE: #include #include #include #include #include { int fd; struct stioctl s; digi_t digiold; digi_t diginew; int modem; char *ttyname = "/dev/term/i16b"; /* * The open is done in the same manner, * O_NONBLOCK or O_NDELAY may be used to prevent * hanging in the open while waiting for DCD. */ if ((fd = open(ttyname, O_NONBLOCK|O_RDWR)) == -1) { perror("open failed"); exit(1); } /* * First we will get the old setting of the device using DIGI_GETA */ s.ic_cmd = DIGI_GETA; s.timout = 0; /* default timeout = 15 sec */ s.ic_len = 0; /* driver sends information */ s.ic_dp = (caddr_t) &digiold; /* pointer to the structure */ if (ioctl(fd, I_STR, &s) == -1) { /* -1 is failure */ perror("DIGI_GETA"); exit(1); } diginew = digiold; /* * This example will use the same sequence to * do what the previous example on digi_flags did. * ie: turn off forcedcd, rtspace, and ctspace. */ diginew.digi_flags &= ~(DIGI_FORCEDCD|DIGI_RTSFLOW |DIGI_CTSFLOW|DIGI_DSRFLOW); diginew.digi_flags |= DIGI_DTRFLOW|DIGI_DCDFLOW; s.ic_cmd = DIGI_SETA; s.ic_timout = 0; s.ic_len = sizeof(digi_t); s.ic_dp = (caddr_t) &diginew; if (ioctl(fd, I_STR, &s) == -1) { perror("DIGI_SETA"); exit(1); } /* * After the preceding DIGI_SETA, we will * find the value of the modem registers. */ s.ic_cmd = TIOCMGET; s.ic_timout = 0; s.ic_len = sizeof(int); s.ic_dp = (caddr_t) &modem; if (ioctl(fd, I_STR, &s) == -1) { perror("TIOCMGET"); exit(1); } /* * Print these out in a meaningful manner. */ (void) printf( "DTR%c RTS%c CTS%c CD%c DSR%c RI%c\n", (modem & TIOCM_DTR) ? '+' : '-', (modem & TIOCM_RTS) ? '+' : '-', (modem & TIOCM_CTS) ? '+' : '-', (modem & TIOCM_CD) ? '+' : '-', (modem & TIOCM_DSR) ? '+' : '-', (modem & TIOCM_RI) ? '+' : '-'); close(fd); exit(0); } Addendum: There may be situations under which one the use of a "modem control device" is preferable. This is a device which is sensitive to the presence of DCD. When this type of device is opened and DCD is not present, the open will not suceed until DCD is asserted. One method of dealing with this is to first open the device with O_NDELAY, so the open will suceed without DCD. One method is as follows: EXAMPLE: #include #include #include { int fd; /* for the first open */ int fstat; /* for the fcntl() call */ struct termio oldtio; struct termio newtio; /* after setting CLOCAL */ char *ttyname = "/dev/ttyi1B"; /* a "modem control" port */ /* * First open the port with O_NDELAY. */ if ((fd = open(ttyname, O_NDELAY|O_RDWR)) < 0) { perror("open failed"); exit(1); } if (ioctl(fd, TCGETA, &oldtio) < 0) { perror("TCGETA"); exit(1); } newtio = oldtio; /* * Set CLOCAL, if necessary so the port * will no longer be sensitive to DCD. */ newtio.c_cflag |= CLOCAL; if (ioctl(fd, TCSETA, &newtio) == -1) { perror("TCSETA"); exit(1); } /* * Get the current state of the flags set in open. */ fstat = fcntl(fd, F_GETFL, 0); /* * We can now turn O_NDELAY off, if desired. * This will enable the use of a "blocking" read * if vmin and vtime are properly set. */ fstat &= ~O_NDELAY; fcntl(fd, F_SETFL, fstat); /* * now all operations are possible just as if * the port was a "non-modem control port". */ /* * When it has been determined that DCD is present it * will be necessary to turn off CLOCAL, if the loss * of DCD is supposed to cause a hangup. */ } You can effectively make the modem control port into a non- modem control port using this example. There are situations where one must open a modem control port before carrier is established. This is possible using the procedure outlined here, when DCD is detected, CLOCAL can be turned off and the device will once again be sensi- tive to the loss of carrier. An example of a use of this is a modem dialer script which opens a modem control port to initialize the modems. After it has set the modem and dialed the number it waits for a connection to be established. When the connection has been made the dialer can turn off CLOCAL so if DCD is lost the driver will send a SIGHUP to the process.