1
2#include <fcntl.h>
3#include <stdio.h>
4#include <string.h>
5#include <termios.h>
6#include <unistd.h>
7
8#include <sys/ioctl.h>
9
10#include <IOKit/serial/ioss.h>
11
12#include "IOSerialTestLib.h"
13
14int _testIOSSIOSPEEDIoctl(int fd, speed_t speed);
15int _modifyAttributes(int fd, struct termios *originalOptions);
16int _modifyModemLines(int fd);
17
18#pragma mark -
19
20int _testIOSSIOSPEEDIoctl(int fd, speed_t speed)
21{
22    struct termios options;
23
24    // The IOSSIOSPEED ioctl can be used to set arbitrary baud rates other than
25    // those specified by POSIX. The driver for the underlying serial hardware
26    // ultimately determines which baud rates can be used. This ioctl sets both
27    // the input and output speed.
28
29    if (ioctl(fd, IOSSIOSPEED, &speed) == -1) {
30        printf("[WARN] ioctl(..., IOSSIOSPEED, %lu).\n", speed);
31        goto fail;
32    }
33
34    // Check that speed is properly modified
35    if (tcgetattr(fd, &options) == -1) {
36        printf("[WARN] _modifyAttributes: tcgetattr failed\n");
37        goto fail;
38    }
39
40    if (cfgetispeed(&options) != speed ||
41        cfgetospeed(&options) != speed) {
42        printf("[WARN] _modifyAttributes: cfsetspeed failed, %lu, %lu.\n",
43               speed,
44               cfgetispeed(&options));
45        goto fail;
46    }
47
48    return 0;
49fail:
50    return -1;
51
52}
53
54int _modifyAttributes(int fd, struct termios *originalOptions)
55{
56    int result = 0;
57    unsigned long mics = 1UL;
58    struct termios options;
59
60    if (!originalOptions) {
61        printf("[FAIL] _modifyAttributes: NULL argument unexpected\n");
62        goto fail;
63    }
64
65    // prevent additional opens on the device, except from a root-owned process
66    if (ioctl(fd, TIOCEXCL) == -1) {
67        printf("[FAIL] _modifyAttributes: ioctl TIOCEXCL failed\n");
68        goto fail;
69    }
70
71    // clear the O_NONBLOCK flag so subsequent I/O will block
72    if (fcntl(fd, F_SETFL, 0) == -1) {
73        printf("[FAIL] _modifyAttributes: fcntl failed\n");
74        goto fail;
75    }
76
77    // snapshot the current terminal state in originalOptions
78    if (tcgetattr(fd, originalOptions) == -1) {
79        printf("[FAIL] _modifyAttributes: tcgetattr failed\n");
80        goto fail;
81    }
82
83    options = *originalOptions;
84
85    // Set raw input (non-canonical) mode
86    cfmakeraw(&options);
87
88    options.c_cc[VMIN] = 0;
89    options.c_cc[VTIME] = 10;
90
91    // Set 19200 baud
92    if (cfsetspeed(&options, B19200) == -1) {
93        printf("[FAIL] _modifyAttributes: cfsetspeed failed\n");
94        goto fail;
95    }
96
97    options.c_cflag |= (CS7        |    // Use 7 bit words
98                        PARENB     |    // Parity enable (even parity if PARODD not also set)
99                        CCTS_OFLOW |    // CTS flow control of output
100                        CRTS_IFLOW);    // RTS flow control of input
101
102    // Cause the new options to take effect immediately.
103    if (tcsetattr(fd, TCSANOW, &options) == -1) {
104        printf("[FAIL] _modifyAttributes: tcsetattr failed\n");
105        goto fail;
106    }
107
108    // check that the tcsetattr worked properly
109    if (tcgetattr(fd, &options) == -1) {
110        printf("[FAIL] _modifyAttributes: tcgetattr failed\n");
111        goto fail;
112    }
113    if ((options.c_cflag & (CS7 | PARENB | CCTS_OFLOW | CRTS_IFLOW)) !=
114        (CS7 | PARENB | CCTS_OFLOW | CRTS_IFLOW)) {
115        printf("[FAIL] _modifyAttributes: tcsetattr/tcgetattr failed\n");
116        goto fail;
117    }
118
119    // Check that speed is 19200 baud
120    if (cfgetispeed(&options) != B19200 ||
121        cfgetospeed(&options) != B19200) {
122        printf("[FAIL] _modifyAttributes: cfsetspeed failed\n");
123        goto fail;
124    }
125
126    // Set the receive latency in microseconds
127    if (ioctl(fd, IOSSDATALAT, &mics) == -1) {
128        // set latency to 1 microsecond
129        printf("[FAIL] _modifyAttributes: ioctl IOSSDATALAT failed\n");
130        goto fail;
131    }
132
133    return result;
134
135fail:
136    return -1;
137}
138
139int _modifyModemLines(int fd)
140{
141    int handshake;
142
143    // Assert Data Terminal Ready (DTR)
144    if (ioctl(fd, TIOCSDTR) == -1) {
145        printf("[FAIL] _modifyModemLines: ioctl TIOCSDTR failed\n");
146        goto fail;
147    }
148    // Clear Data Terminal Ready (DTR)
149    if (ioctl(fd, TIOCCDTR) == -1) {
150        printf("[FAIL] _modifyModemLines: ioctl TIOCCDTR failed\n");
151        goto fail;
152    }
153
154    // Set the modem lines depending on the bits set in handshake
155    handshake = TIOCM_DTR | TIOCM_RTS | TIOCM_CTS | TIOCM_DSR;
156    if (ioctl(fd, TIOCMSET, &handshake) == -1) {
157        printf("[FAIL] _modifyModemLines: ioctl TIOCMSET failed\n");
158        goto fail;
159    }
160
161    // Store the state of the modem lines in handshake
162    if (ioctl(fd, TIOCMGET, &handshake) == -1) {
163        printf("[FAIL] _modifyModemLines: ioctl TIOCMGET failed\n");
164        goto fail;
165    }
166
167    return 0;
168
169fail:
170    return -1;
171}
172
173#pragma mark -
174
175// open and close the serial connection
176int testOpenClose(const char *path)
177{
178    int fd = -1;
179    int result = 0;
180
181    fd = open(path, O_RDWR|O_NONBLOCK);
182    if (fd == -1) {
183        return -1;
184    }
185
186    result = close(fd);
187    if (result) {
188        return -1;
189    }
190
191    return 0;
192}
193
194// uses ioctl() and fcntl() to modify config of the tty session
195int testModifyConfig(const char *path)
196{
197    int fd = -1;
198    int result = 0;
199    speed_t speed;
200
201    struct termios originalTTYAttrs;
202
203    fd = open(path, O_RDWR|O_NOCTTY|O_NONBLOCK);
204    if (fd == -1) {
205        printf("[FAIL] testModifyConfig: open failed\n");
206        goto fail;
207    }
208
209    if (_modifyAttributes(fd, &originalTTYAttrs)) {
210        printf("[FAIL] testModifyConfig: config failed\n");
211        goto fail;
212    }
213
214    if (_modifyModemLines(fd)) {
215        printf("[FAIL] testModifyConfig: config failed\n");
216        goto fail;
217    }
218
219    // testing for some arbitrary non-standard values
220    speed = 40000;
221    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
222        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
223    }
224    speed = 58000;
225    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
226        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
227    }
228    speed = 250000;
229    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
230        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
231    }
232    speed = 10400;
233    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
234        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
235    }
236    speed = 8192;
237    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
238        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
239    }
240    speed = 128000;
241    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
242        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
243    }
244
245    // 31250: standard rate used for MIDI signaling, but isn't included in POSIX
246    speed = 31250;
247    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
248        printf("[WARN] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
249    }
250
251    // some standard values
252    speed = 38400;
253    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
254        printf("[FAIL] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
255        goto fail;
256    }
257    speed = 115200;
258    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
259        printf("[FAIL] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
260        goto fail;
261    }
262    speed = 19200;
263    if (_testIOSSIOSPEEDIoctl(fd, speed)) {
264        printf("[FAIL] testModifyConfig: IOSSIOSPEED ioctl with %lu failed\n", speed);
265        goto fail;
266    }
267
268    // resets tty attributes
269    tcsetattr(fd, TCSANOW, &originalTTYAttrs);
270
271    result = close(fd);
272    if (result == -1) {
273        printf("[FAIL] testModifyConfig: close failed\n");
274        goto fail;
275    }
276
277    return 0;
278
279fail:
280    if (fd != -1) close(fd);
281    return -1;
282}
283
284#define kHelloWorldString    "Hello World"
285
286int testReadWrite(const char *readPath, const char *writePath, const char *message)
287{
288    int readFd = -1;
289    int writeFd = -1;
290    int result = 0;
291    ssize_t numBytes;
292
293    char myMessage[256];
294    char buffer[256];    // Input buffer
295    char *bufPtr;        // Current char in buffer
296
297    struct termios originalReadTTYAttrs;
298    struct termios originalWriteTTYAttrs;
299
300    if (!message) {
301        // no message argument, using default value
302        if (strlcpy(myMessage, kHelloWorldString, sizeof(myMessage)) >=
303            sizeof(myMessage)) {
304            // message argument is too long, got truncated
305            myMessage[sizeof(myMessage)-1] = '\0';
306        }
307    }
308    else {
309        if (strlcpy(myMessage, message, sizeof(myMessage)) >= sizeof(myMessage)) {
310            // message argument is too long, got truncated
311            myMessage[sizeof(myMessage)-1] = '\0';
312        }
313    }
314
315    readFd = open(readPath, O_RDWR|O_NOCTTY|O_NONBLOCK);
316    if (readFd == -1) {
317        printf("[FAIL] testReadWrite: open failed\n");
318        goto fail;
319    }
320    if (_modifyAttributes(readFd, &originalReadTTYAttrs)) {
321        printf("[FAIL] testReadWrite: _modifyAttributes failed\n");
322        goto fail;
323    }
324
325    writeFd = open(writePath, O_RDWR|O_NOCTTY|O_NONBLOCK);
326    if (writeFd == -1) {
327        printf("[FAIL] testReadWrite: open failed\n");
328        goto fail;
329    }
330    if (_modifyAttributes(writeFd, &originalWriteTTYAttrs)) {
331        printf("[FAIL] testReadWrite: _modifyAttributes failed\n");
332        goto fail;
333    }
334
335    if (_modifyModemLines(readFd) ||
336        _modifyModemLines(writeFd)) {
337        printf("[FAIL] testReadWrite: _modifyModemLines failed\n");
338        goto fail;
339    }
340
341    numBytes = write(writeFd, myMessage, strnlen(myMessage, 256));
342
343    if (numBytes == -1) {
344        printf("[FAIL] write returned -1\n");
345        goto fail;
346    }
347    if ((size_t)numBytes < strnlen(myMessage, 256)) {
348        printf("[FAIL] write did not complete\n");
349        goto fail;
350    }
351
352    memset(buffer, 0, sizeof(buffer));
353
354    bufPtr = buffer;
355    do {
356        numBytes = read(readFd, bufPtr, &buffer[sizeof(buffer)] - bufPtr - 1);
357        if (numBytes > 0) {
358            bufPtr += numBytes;
359            if (*(bufPtr - 1) == '\n' || *(bufPtr - 1) == '\r') {
360                break;
361            }
362        }
363    } while (numBytes > 0);
364
365    if (strncmp(buffer, myMessage, sizeof(myMessage))) {
366        printf("[FAIL] testReadWrite: read string was: \"%s\", "
367               "expected: \"%s\"\n", buffer, myMessage);
368        goto fail;
369    }
370
371    tcsetattr(readFd, TCSANOW, &originalReadTTYAttrs);
372    tcsetattr(writeFd, TCSANOW, &originalWriteTTYAttrs);
373
374    result = close(readFd);
375    readFd = -1;
376    if (result == -1) {
377        printf("[FAIL] testReadWrite: close failed\n");
378        goto fail;
379    }
380    result = close(writeFd);
381    writeFd = -1;
382    if (result == -1) {
383        printf("[FAIL] testReadWrite: close failed\n");
384        goto fail;
385    }
386
387    return 0;
388
389fail:
390    if (readFd != -1) close(readFd);
391    if (writeFd != -1) close(writeFd);
392    return -1;
393}
394
395