Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/winpr/libwinpr/comm/comm_serial_sys.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Serial Communication API
4
 *
5
 * Copyright 2011 O.S. Systems Software Ltda.
6
 * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
7
 * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
8
 * Copyright 2014 Hewlett-Packard Development Company, L.P.
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 *     http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22
23
#include <winpr/assert.h>
24
#include <errno.h>
25
#include <fcntl.h>
26
#include <sys/ioctl.h>
27
#include <termios.h>
28
#include <unistd.h>
29
30
#include "comm_serial_sys.h"
31
#include "comm.h"
32
33
#include <winpr/crt.h>
34
#include <winpr/wlog.h>
35
36
/* Undocumented flag, not supported everywhere.
37
 * Provide a sensible fallback to avoid compilation problems. */
38
#ifndef CMSPAR
39
#define CMSPAR 010000000000
40
#endif
41
42
/* hard-coded in N_TTY */
43
0
#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
44
0
#define TTY_THRESHOLD_UNTHROTTLE 128
45
0
#define N_TTY_BUF_SIZE 4096
46
47
0
#define BAUD_TABLE_END 0010020 /* __MAX_BAUD + 1 */
48
49
/* 0: B* (Linux termios)
50
 * 1: CBR_* or actual baud rate
51
 * 2: BAUD_* (identical to SERIAL_BAUD_*)
52
 */
53
static const speed_t BAUD_TABLE[][3] = {
54
#ifdef B0
55
  { B0, 0, 0 }, /* hang up */
56
#endif
57
#ifdef B50
58
  { B50, 50, 0 },
59
#endif
60
#ifdef B75
61
  { B75, 75, BAUD_075 },
62
#endif
63
#ifdef B110
64
  { B110, CBR_110, BAUD_110 },
65
#endif
66
#ifdef B134
67
  { B134, 134, 0 /*BAUD_134_5*/ },
68
#endif
69
#ifdef B150
70
  { B150, 150, BAUD_150 },
71
#endif
72
#ifdef B200
73
  { B200, 200, 0 },
74
#endif
75
#ifdef B300
76
  { B300, CBR_300, BAUD_300 },
77
#endif
78
#ifdef B600
79
  { B600, CBR_600, BAUD_600 },
80
#endif
81
#ifdef B1200
82
  { B1200, CBR_1200, BAUD_1200 },
83
#endif
84
#ifdef B1800
85
  { B1800, 1800, BAUD_1800 },
86
#endif
87
#ifdef B2400
88
  { B2400, CBR_2400, BAUD_2400 },
89
#endif
90
#ifdef B4800
91
  { B4800, CBR_4800, BAUD_4800 },
92
#endif
93
/* {, ,BAUD_7200} */
94
#ifdef B9600
95
  { B9600, CBR_9600, BAUD_9600 },
96
#endif
97
/* {, CBR_14400, BAUD_14400}, /\* unsupported on Linux *\/ */
98
#ifdef B19200
99
  { B19200, CBR_19200, BAUD_19200 },
100
#endif
101
#ifdef B38400
102
  { B38400, CBR_38400, BAUD_38400 },
103
#endif
104
/* {, CBR_56000, BAUD_56K}, /\* unsupported on Linux *\/ */
105
#ifdef B57600
106
  { B57600, CBR_57600, BAUD_57600 },
107
#endif
108
#ifdef B115200
109
  { B115200, CBR_115200, BAUD_115200 },
110
#endif
111
/* {, CBR_128000, BAUD_128K}, /\* unsupported on Linux *\/ */
112
/* {, CBR_256000, BAUD_USER}, /\* unsupported on Linux *\/ */
113
#ifdef B230400
114
  { B230400, 230400, BAUD_USER },
115
#endif
116
#ifdef B460800
117
  { B460800, 460800, BAUD_USER },
118
#endif
119
#ifdef B500000
120
  { B500000, 500000, BAUD_USER },
121
#endif
122
#ifdef B576000
123
  { B576000, 576000, BAUD_USER },
124
#endif
125
#ifdef B921600
126
  { B921600, 921600, BAUD_USER },
127
#endif
128
#ifdef B1000000
129
  { B1000000, 1000000, BAUD_USER },
130
#endif
131
#ifdef B1152000
132
  { B1152000, 1152000, BAUD_USER },
133
#endif
134
#ifdef B1500000
135
  { B1500000, 1500000, BAUD_USER },
136
#endif
137
#ifdef B2000000
138
  { B2000000, 2000000, BAUD_USER },
139
#endif
140
#ifdef B2500000
141
  { B2500000, 2500000, BAUD_USER },
142
#endif
143
#ifdef B3000000
144
  { B3000000, 3000000, BAUD_USER },
145
#endif
146
#ifdef B3500000
147
  { B3500000, 3500000, BAUD_USER },
148
#endif
149
#ifdef B4000000
150
  { B4000000, 4000000, BAUD_USER }, /* __MAX_BAUD */
151
#endif
152
  { BAUD_TABLE_END, 0, 0 }
153
};
154
155
static const char* get_modem_flag_str(int flag)
156
0
{
157
0
  if (flag & TIOCM_LE)
158
0
    return "DSR";
159
0
  if (flag & TIOCM_DTR)
160
0
    return "DTR";
161
0
  if (flag & TIOCM_RTS)
162
0
    return "RTS";
163
0
  if (flag & TIOCM_ST)
164
0
    return "Secondary TXD";
165
0
  if (flag & TIOCM_SR)
166
0
    return "Secondary RXD";
167
0
  if (flag & TIOCM_CTS)
168
0
    return "CTS";
169
0
  if (flag & TIOCM_CAR)
170
0
    return "DCD";
171
0
  if (flag & TIOCM_CD)
172
0
    return "CD";
173
0
  if (flag & TIOCM_RNG)
174
0
    return "RNG";
175
0
  if (flag & TIOCM_RI)
176
0
    return "RI";
177
0
  if (flag & TIOCM_DSR)
178
0
    return "DSR";
179
0
  return "UNKNOWN";
180
0
}
181
182
static const char* get_modem_status_str(int status, char* buffer, size_t size)
183
0
{
184
0
  const int flags[] = { TIOCM_LE,  TIOCM_DTR, TIOCM_RTS, TIOCM_ST, TIOCM_SR, TIOCM_CTS,
185
0
                      TIOCM_CAR, TIOCM_CD,  TIOCM_RNG, TIOCM_RI, TIOCM_DSR };
186
0
  winpr_str_append("{", buffer, size, "");
187
188
0
  const char* sep = "";
189
0
  for (size_t x = 0; x < ARRAYSIZE(flags); x++)
190
0
  {
191
0
    const int flag = flags[x];
192
0
    if (status & flag)
193
0
    {
194
0
      winpr_str_append(get_modem_flag_str(flag), buffer, size, sep);
195
0
      sep = "|";
196
0
    }
197
0
  }
198
199
0
  char number[32] = { 0 };
200
0
  (void)_snprintf(number, sizeof(number), "}[0x%08x]", (unsigned)status);
201
0
  winpr_str_append(number, buffer, size, "");
202
0
  return buffer;
203
0
}
204
205
static BOOL get_properties(WINPR_ATTR_UNUSED WINPR_COMM* pComm, COMMPROP* pProperties)
206
0
{
207
0
  WINPR_ASSERT(pComm);
208
  /* http://msdn.microsoft.com/en-us/library/windows/hardware/jj680684%28v=vs.85%29.aspx
209
   * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363189%28v=vs.85%29.aspx
210
   */
211
212
  /* FIXME: properties should be better probed. The current
213
   * implementation just relies on the Linux' implementation.
214
   */
215
0
  WINPR_ASSERT(pProperties);
216
0
  if (pProperties->dwProvSpec1 != COMMPROP_INITIALIZED)
217
0
  {
218
0
    ZeroMemory(pProperties, sizeof(COMMPROP));
219
0
    pProperties->wPacketLength = sizeof(COMMPROP);
220
0
  }
221
222
0
  pProperties->wPacketVersion = 2;
223
224
0
  pProperties->dwServiceMask = SERIAL_SP_SERIALCOMM;
225
226
  /* pProperties->Reserved1; not used */
227
228
  /* FIXME: could be implemented on top of N_TTY */
229
0
  pProperties->dwMaxTxQueue = N_TTY_BUF_SIZE;
230
0
  pProperties->dwMaxRxQueue = N_TTY_BUF_SIZE;
231
232
  /* FIXME: to be probe on the device? */
233
0
  pProperties->dwMaxBaud = BAUD_USER;
234
235
  /* FIXME: what about PST_RS232? see also: serial_struct */
236
0
  pProperties->dwProvSubType = PST_UNSPECIFIED;
237
238
  /* TODO: to be finalized */
239
0
  pProperties->dwProvCapabilities =
240
0
      /*PCF_16BITMODE |*/ PCF_DTRDSR | PCF_INTTIMEOUTS | PCF_PARITY_CHECK | /*PCF_RLSD |*/
241
0
      PCF_RTSCTS | PCF_SETXCHAR | /*PCF_SPECIALCHARS |*/ PCF_TOTALTIMEOUTS | PCF_XONXOFF;
242
243
  /* TODO: double check SP_RLSD */
244
0
  pProperties->dwSettableParams = SP_BAUD | SP_DATABITS | SP_HANDSHAKING | SP_PARITY |
245
0
                                  SP_PARITY_CHECK | /*SP_RLSD |*/ SP_STOPBITS;
246
247
0
  pProperties->dwSettableBaud = 0;
248
0
  for (int i = 0; BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
249
0
  {
250
0
    pProperties->dwSettableBaud |= BAUD_TABLE[i][2];
251
0
  }
252
253
0
  pProperties->wSettableData =
254
0
      DATABITS_5 | DATABITS_6 | DATABITS_7 | DATABITS_8 /*| DATABITS_16 | DATABITS_16X*/;
255
256
0
  pProperties->wSettableStopParity = STOPBITS_10 | /*STOPBITS_15 |*/ STOPBITS_20 | PARITY_NONE |
257
0
                                     PARITY_ODD | PARITY_EVEN | PARITY_MARK | PARITY_SPACE;
258
259
  /* FIXME: additional input and output buffers could be implemented on top of N_TTY */
260
0
  pProperties->dwCurrentTxQueue = N_TTY_BUF_SIZE;
261
0
  pProperties->dwCurrentRxQueue = N_TTY_BUF_SIZE;
262
263
  /* pProperties->ProvSpec1; see above */
264
  /* pProperties->ProvSpec2; ignored */
265
  /* pProperties->ProvChar[1]; ignored */
266
267
0
  return TRUE;
268
0
}
269
270
static BOOL set_baud_rate(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate)
271
0
{
272
0
  speed_t newSpeed = 0;
273
0
  struct termios futureState = { 0 };
274
275
0
  WINPR_ASSERT(pComm);
276
0
  WINPR_ASSERT(pBaudRate);
277
278
0
  if (tcgetattr(pComm->fd, &futureState) <
279
0
      0) /* NB: preserves current settings not directly handled by the Communication Functions */
280
0
  {
281
0
    SetLastError(ERROR_IO_DEVICE);
282
0
    return FALSE;
283
0
  }
284
285
0
  for (int i = 0; BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
286
0
  {
287
0
    if (BAUD_TABLE[i][1] == pBaudRate->BaudRate)
288
0
    {
289
0
      newSpeed = BAUD_TABLE[i][0];
290
0
      if (cfsetspeed(&futureState, newSpeed) < 0)
291
0
      {
292
0
        CommLog_Print(WLOG_WARN, "failed to set speed 0x%x (%" PRIu32 ")", newSpeed,
293
0
                      pBaudRate->BaudRate);
294
0
        return FALSE;
295
0
      }
296
297
0
      WINPR_ASSERT(cfgetispeed(&futureState) == newSpeed);
298
299
0
      if (comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0)
300
0
      {
301
0
        CommLog_Print(WLOG_WARN, "comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
302
0
                      GetLastError());
303
0
        return FALSE;
304
0
      }
305
306
0
      return TRUE;
307
0
    }
308
0
  }
309
310
0
  CommLog_Print(WLOG_WARN, "could not find a matching speed for the baud rate %" PRIu32 "",
311
0
                pBaudRate->BaudRate);
312
0
  SetLastError(ERROR_INVALID_DATA);
313
0
  return FALSE;
314
0
}
315
316
static BOOL get_baud_rate(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate)
317
0
{
318
0
  speed_t currentSpeed = 0;
319
0
  struct termios currentState = { 0 };
320
321
0
  WINPR_ASSERT(pComm);
322
0
  WINPR_ASSERT(pBaudRate);
323
324
0
  if (tcgetattr(pComm->fd, &currentState) < 0)
325
0
  {
326
0
    SetLastError(ERROR_IO_DEVICE);
327
0
    return FALSE;
328
0
  }
329
330
0
  currentSpeed = cfgetispeed(&currentState);
331
332
0
  for (int i = 0; BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
333
0
  {
334
0
    if (BAUD_TABLE[i][0] == currentSpeed)
335
0
    {
336
0
      pBaudRate->BaudRate = BAUD_TABLE[i][1];
337
0
      return TRUE;
338
0
    }
339
0
  }
340
341
0
  CommLog_Print(WLOG_WARN, "could not find a matching baud rate for the speed 0x%x",
342
0
                currentSpeed);
343
0
  SetLastError(ERROR_INVALID_DATA);
344
0
  return FALSE;
345
0
}
346
347
/**
348
 * NOTE: Only XonChar and XoffChar are plenty supported with the Linux
349
 *       N_TTY line discipline.
350
 *
351
 * ERRORS:
352
 *   ERROR_IO_DEVICE
353
 *   ERROR_INVALID_PARAMETER when Xon and Xoff chars are the same;
354
 *   ERROR_NOT_SUPPORTED
355
 */
356
static BOOL set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars)
357
0
{
358
0
  BOOL result = TRUE;
359
0
  struct termios upcomingTermios = { 0 };
360
361
0
  WINPR_ASSERT(pComm);
362
0
  WINPR_ASSERT(pSerialChars);
363
364
0
  if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
365
0
  {
366
0
    SetLastError(ERROR_IO_DEVICE);
367
0
    return FALSE;
368
0
  }
369
370
  /* termios(3): (..) above symbolic subscript values are all
371
   * different, except that VTIME, VMIN may have the same value
372
   * as VEOL, VEOF, respectively. In noncanonical mode the
373
   * special character meaning is replaced by the timeout
374
   * meaning.
375
   *
376
   * EofChar and c_cc[VEOF] are not quite the same, prefer to
377
   * don't use c_cc[VEOF] at all.
378
   *
379
   * FIXME: might be implemented during read/write I/O
380
   */
381
0
  if (pSerialChars->EofChar != '\0')
382
0
  {
383
0
    CommLog_Print(WLOG_WARN, "EofChar %02" PRIX8 " cannot be set\n", pSerialChars->EofChar);
384
0
    SetLastError(ERROR_NOT_SUPPORTED);
385
0
    result = FALSE; /* but keep on */
386
0
  }
387
388
  /* According the Linux's n_tty discipline, characters with a
389
   * parity error can only be let unchanged, replaced by \0 or
390
   * get the prefix the prefix \377 \0
391
   */
392
393
  /* FIXME: see also: set_handflow() */
394
0
  if (pSerialChars->ErrorChar != '\0')
395
0
  {
396
0
    CommLog_Print(WLOG_WARN, "ErrorChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
397
0
                  pSerialChars->ErrorChar, (char)pSerialChars->ErrorChar);
398
0
    SetLastError(ERROR_NOT_SUPPORTED);
399
0
    result = FALSE; /* but keep on */
400
0
  }
401
402
  /* FIXME: see also: set_handflow() */
403
0
  if (pSerialChars->BreakChar != '\0')
404
0
  {
405
0
    CommLog_Print(WLOG_WARN, "BreakChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
406
0
                  pSerialChars->BreakChar, (char)pSerialChars->BreakChar);
407
0
    SetLastError(ERROR_NOT_SUPPORTED);
408
0
    result = FALSE; /* but keep on */
409
0
  }
410
411
0
  if (pSerialChars->EventChar != '\0')
412
0
  {
413
0
    pComm->eventChar = pSerialChars->EventChar;
414
0
  }
415
416
0
  upcomingTermios.c_cc[VSTART] = pSerialChars->XonChar;
417
418
0
  upcomingTermios.c_cc[VSTOP] = pSerialChars->XoffChar;
419
420
0
  if (comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
421
0
  {
422
0
    CommLog_Print(WLOG_WARN, "comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
423
0
                  GetLastError());
424
0
    return FALSE;
425
0
  }
426
427
0
  return result;
428
0
}
429
430
static BOOL get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars)
431
0
{
432
0
  struct termios currentTermios = { 0 };
433
434
0
  WINPR_ASSERT(pComm);
435
0
  WINPR_ASSERT(pSerialChars);
436
437
0
  if (tcgetattr(pComm->fd, &currentTermios) < 0)
438
0
  {
439
0
    SetLastError(ERROR_IO_DEVICE);
440
0
    return FALSE;
441
0
  }
442
443
0
  ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS));
444
445
  /* EofChar unsupported */
446
447
  /* ErrorChar unsupported */
448
449
  /* BreakChar unsupported */
450
451
  /* FIXME: see also: set_serial_chars() */
452
  /* EventChar */
453
454
0
  pSerialChars->XonChar = currentTermios.c_cc[VSTART];
455
456
0
  pSerialChars->XoffChar = currentTermios.c_cc[VSTOP];
457
458
0
  return TRUE;
459
0
}
460
461
static BOOL set_line_control(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl)
462
0
{
463
0
  BOOL result = TRUE;
464
0
  struct termios upcomingTermios = { 0 };
465
466
0
  WINPR_ASSERT(pComm);
467
0
  WINPR_ASSERT(pLineControl);
468
469
  /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx
470
   *
471
   * The use of 5 data bits with 2 stop bits is an invalid
472
   * combination, as is 6, 7, or 8 data bits with 1.5 stop bits.
473
   *
474
   * FIXME: preferred to let the underlying driver to deal with
475
   * this issue. At least produce a warning message?
476
   */
477
478
0
  if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
479
0
  {
480
0
    SetLastError(ERROR_IO_DEVICE);
481
0
    return FALSE;
482
0
  }
483
484
  /* FIXME: use of a COMMPROP to validate new settings? */
485
486
0
  switch (pLineControl->StopBits)
487
0
  {
488
0
    case STOP_BIT_1:
489
0
      upcomingTermios.c_cflag &= (uint32_t)~CSTOPB;
490
0
      break;
491
492
0
    case STOP_BITS_1_5:
493
0
      CommLog_Print(WLOG_WARN, "Unsupported one and a half stop bits.");
494
0
      break;
495
496
0
    case STOP_BITS_2:
497
0
      upcomingTermios.c_cflag |= CSTOPB;
498
0
      break;
499
500
0
    default:
501
0
      CommLog_Print(WLOG_WARN, "unexpected number of stop bits: %" PRIu8 "\n",
502
0
                    pLineControl->StopBits);
503
0
      result = FALSE; /* but keep on */
504
0
      break;
505
0
  }
506
507
0
  switch (pLineControl->Parity)
508
0
  {
509
0
    case NO_PARITY:
510
0
      upcomingTermios.c_cflag &= (uint32_t)~(PARENB | PARODD | CMSPAR);
511
0
      break;
512
513
0
    case ODD_PARITY:
514
0
      upcomingTermios.c_cflag &= (uint32_t)~CMSPAR;
515
0
      upcomingTermios.c_cflag |= PARENB | PARODD;
516
0
      break;
517
518
0
    case EVEN_PARITY:
519
0
      upcomingTermios.c_cflag &= (uint32_t)~(PARODD | CMSPAR);
520
0
      upcomingTermios.c_cflag |= PARENB;
521
0
      break;
522
523
0
    case MARK_PARITY:
524
0
      upcomingTermios.c_cflag |= PARENB | PARODD | CMSPAR;
525
0
      break;
526
527
0
    case SPACE_PARITY:
528
0
      upcomingTermios.c_cflag &= (uint32_t)~PARODD;
529
0
      upcomingTermios.c_cflag |= PARENB | CMSPAR;
530
0
      break;
531
532
0
    default:
533
0
      CommLog_Print(WLOG_WARN, "unexpected type of parity: %" PRIu8 "\n",
534
0
                    pLineControl->Parity);
535
0
      result = FALSE; /* but keep on */
536
0
      break;
537
0
  }
538
539
0
  switch (pLineControl->WordLength)
540
0
  {
541
0
    case 5:
542
0
      upcomingTermios.c_cflag &= (uint32_t)~CSIZE;
543
0
      upcomingTermios.c_cflag |= CS5;
544
0
      break;
545
546
0
    case 6:
547
0
      upcomingTermios.c_cflag &= (uint32_t)~CSIZE;
548
0
      upcomingTermios.c_cflag |= CS6;
549
0
      break;
550
551
0
    case 7:
552
0
      upcomingTermios.c_cflag &= (uint32_t)~CSIZE;
553
0
      upcomingTermios.c_cflag |= CS7;
554
0
      break;
555
556
0
    case 8:
557
0
      upcomingTermios.c_cflag &= (uint32_t)~CSIZE;
558
0
      upcomingTermios.c_cflag |= CS8;
559
0
      break;
560
561
0
    default:
562
0
      CommLog_Print(WLOG_WARN, "unexpected number od data bits per character: %" PRIu8 "\n",
563
0
                    pLineControl->WordLength);
564
0
      result = FALSE; /* but keep on */
565
0
      break;
566
0
  }
567
568
0
  if (comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
569
0
  {
570
0
    CommLog_Print(WLOG_WARN, "comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
571
0
                  GetLastError());
572
0
    return FALSE;
573
0
  }
574
575
0
  return result;
576
0
}
577
578
static BOOL get_line_control(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl)
579
0
{
580
0
  struct termios currentTermios = { 0 };
581
582
0
  WINPR_ASSERT(pComm);
583
0
  WINPR_ASSERT(pLineControl);
584
585
0
  if (tcgetattr(pComm->fd, &currentTermios) < 0)
586
0
  {
587
0
    SetLastError(ERROR_IO_DEVICE);
588
0
    return FALSE;
589
0
  }
590
591
0
  pLineControl->StopBits = (currentTermios.c_cflag & CSTOPB) ? STOP_BITS_2 : STOP_BIT_1;
592
593
0
  if (!(currentTermios.c_cflag & PARENB))
594
0
  {
595
0
    pLineControl->Parity = NO_PARITY;
596
0
  }
597
0
  else if (currentTermios.c_cflag & CMSPAR)
598
0
  {
599
0
    pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? MARK_PARITY : SPACE_PARITY;
600
0
  }
601
0
  else
602
0
  {
603
    /* PARENB is set */
604
0
    pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? ODD_PARITY : EVEN_PARITY;
605
0
  }
606
607
0
  switch (currentTermios.c_cflag & CSIZE)
608
0
  {
609
0
    case CS5:
610
0
      pLineControl->WordLength = 5;
611
0
      break;
612
0
    case CS6:
613
0
      pLineControl->WordLength = 6;
614
0
      break;
615
0
    case CS7:
616
0
      pLineControl->WordLength = 7;
617
0
      break;
618
0
    default:
619
0
      pLineControl->WordLength = 8;
620
0
      break;
621
0
  }
622
623
0
  return TRUE;
624
0
}
625
626
static BOOL set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow)
627
0
{
628
0
  BOOL result = TRUE;
629
0
  struct termios upcomingTermios = { 0 };
630
631
0
  WINPR_ASSERT(pComm);
632
0
  WINPR_ASSERT(pHandflow);
633
634
0
  if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
635
0
  {
636
0
    SetLastError(ERROR_IO_DEVICE);
637
0
    return FALSE;
638
0
  }
639
640
  /* HUPCL */
641
642
  /* logical XOR */
643
0
  if ((!(pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
644
0
       (pHandflow->FlowReplace & SERIAL_RTS_CONTROL)) ||
645
0
      ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
646
0
       !(pHandflow->FlowReplace & SERIAL_RTS_CONTROL)))
647
0
  {
648
0
    CommLog_Print(WLOG_WARN,
649
0
                  "SERIAL_DTR_CONTROL:%s and SERIAL_RTS_CONTROL:%s cannot be different, HUPCL "
650
0
                  "will be set since it is claimed for one of the both lines.",
651
0
                  (pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ? "ON" : "OFF",
652
0
                  (pHandflow->FlowReplace & SERIAL_RTS_CONTROL) ? "ON" : "OFF");
653
0
  }
654
655
0
  if ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ||
656
0
      (pHandflow->FlowReplace & SERIAL_RTS_CONTROL))
657
0
  {
658
0
    upcomingTermios.c_cflag |= HUPCL;
659
0
  }
660
0
  else
661
0
  {
662
0
    upcomingTermios.c_cflag &= (uint32_t)~HUPCL;
663
664
    /* FIXME: is the DTR line also needs to be forced to a disable state according
665
     * SERIAL_DTR_CONTROL? */
666
    /* FIXME: is the RTS line also needs to be forced to a disable state according
667
     * SERIAL_RTS_CONTROL? */
668
0
  }
669
670
  /* CRTSCTS */
671
672
  /* logical XOR */
673
0
  if ((!(pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
674
0
       (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)) ||
675
0
      ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
676
0
       !(pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)))
677
0
  {
678
0
    CommLog_Print(WLOG_WARN,
679
0
                  "SERIAL_CTS_HANDSHAKE:%s and SERIAL_RTS_HANDSHAKE:%s cannot be different, "
680
0
                  "CRTSCTS will be set since it is claimed for one of the both lines.",
681
0
                  (pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ? "ON" : "OFF",
682
0
                  (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE) ? "ON" : "OFF");
683
0
  }
684
685
0
  if ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ||
686
0
      (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE))
687
0
  {
688
0
    upcomingTermios.c_cflag |= CRTSCTS;
689
0
  }
690
0
  else
691
0
  {
692
0
    upcomingTermios.c_cflag &= ~CRTSCTS;
693
0
  }
694
695
  /* ControlHandShake */
696
697
0
  if (pHandflow->ControlHandShake & SERIAL_DTR_HANDSHAKE)
698
0
  {
699
    /* DTR/DSR flow control not supported on Linux */
700
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DTR_HANDSHAKE feature.");
701
0
    SetLastError(ERROR_NOT_SUPPORTED);
702
0
    result = FALSE; /* but keep on */
703
0
  }
704
705
0
  if (pHandflow->ControlHandShake & SERIAL_DSR_HANDSHAKE)
706
0
  {
707
    /* DTR/DSR flow control not supported on Linux */
708
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_HANDSHAKE feature.");
709
0
    SetLastError(ERROR_NOT_SUPPORTED);
710
0
    result = FALSE; /* but keep on */
711
0
  }
712
713
0
  if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE)
714
0
  {
715
    /* DCD flow control not supported on Linux */
716
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DCD_HANDSHAKE feature.");
717
0
    SetLastError(ERROR_NOT_SUPPORTED);
718
0
    result = FALSE; /* but keep on */
719
0
  }
720
721
  // FIXME: could be implemented during read/write I/O
722
0
  if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY)
723
0
  {
724
    /* DSR line control not supported on Linux */
725
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_SENSITIVITY feature.");
726
0
    SetLastError(ERROR_NOT_SUPPORTED);
727
0
    result = FALSE; /* but keep on */
728
0
  }
729
730
  // FIXME: could be implemented during read/write I/O
731
0
  if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT)
732
0
  {
733
    /* Aborting operations on error not supported on Linux */
734
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_ERROR_ABORT feature.");
735
0
    SetLastError(ERROR_NOT_SUPPORTED);
736
0
    result = FALSE; /* but keep on */
737
0
  }
738
739
  /* FlowReplace */
740
741
0
  if (pHandflow->FlowReplace & SERIAL_AUTO_TRANSMIT)
742
0
  {
743
0
    upcomingTermios.c_iflag |= IXON;
744
0
  }
745
0
  else
746
0
  {
747
0
    upcomingTermios.c_iflag &= (uint32_t)~IXON;
748
0
  }
749
750
0
  if (pHandflow->FlowReplace & SERIAL_AUTO_RECEIVE)
751
0
  {
752
0
    upcomingTermios.c_iflag |= IXOFF;
753
0
  }
754
0
  else
755
0
  {
756
0
    upcomingTermios.c_iflag &= (uint32_t)~IXOFF;
757
0
  }
758
759
  // FIXME: could be implemented during read/write I/O, as of today ErrorChar is necessary '\0'
760
0
  if (pHandflow->FlowReplace & SERIAL_ERROR_CHAR)
761
0
  {
762
    /* errors will be replaced by the character '\0'. */
763
0
    upcomingTermios.c_iflag &= (uint32_t)~IGNPAR;
764
0
  }
765
0
  else
766
0
  {
767
0
    upcomingTermios.c_iflag |= IGNPAR;
768
0
  }
769
770
0
  if (pHandflow->FlowReplace & SERIAL_NULL_STRIPPING)
771
0
  {
772
0
    upcomingTermios.c_iflag |= IGNBRK;
773
0
  }
774
0
  else
775
0
  {
776
0
    upcomingTermios.c_iflag &= (uint32_t)~IGNBRK;
777
0
  }
778
779
  // FIXME: could be implemented during read/write I/O
780
0
  if (pHandflow->FlowReplace & SERIAL_BREAK_CHAR)
781
0
  {
782
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_BREAK_CHAR feature.");
783
0
    SetLastError(ERROR_NOT_SUPPORTED);
784
0
    result = FALSE; /* but keep on */
785
0
  }
786
787
  // FIXME: could be implemented during read/write I/O
788
0
  if (pHandflow->FlowReplace & SERIAL_XOFF_CONTINUE)
789
0
  {
790
    /* not supported on Linux */
791
0
    CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_XOFF_CONTINUE feature.");
792
0
    SetLastError(ERROR_NOT_SUPPORTED);
793
0
    result = FALSE; /* but keep on */
794
0
  }
795
796
  /* XonLimit */
797
798
0
  pComm->XOffLimit = pHandflow->XoffLimit;
799
0
  pComm->XOnLimit = pHandflow->XonLimit;
800
801
0
  if (comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
802
0
  {
803
0
    CommLog_Print(WLOG_WARN, "comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
804
0
                  GetLastError());
805
0
    return FALSE;
806
0
  }
807
808
0
  return result;
809
0
}
810
811
static BOOL get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow)
812
0
{
813
0
  struct termios currentTermios = { 0 };
814
815
0
  WINPR_ASSERT(pComm);
816
0
  WINPR_ASSERT(pHandflow);
817
818
0
  if (tcgetattr(pComm->fd, &currentTermios) < 0)
819
0
  {
820
0
    SetLastError(ERROR_IO_DEVICE);
821
0
    return FALSE;
822
0
  }
823
824
  /* ControlHandShake */
825
826
0
  pHandflow->ControlHandShake = 0;
827
828
0
  if (currentTermios.c_cflag & HUPCL)
829
0
    pHandflow->ControlHandShake |= SERIAL_DTR_CONTROL;
830
831
  /* SERIAL_DTR_HANDSHAKE unsupported */
832
833
0
  if (currentTermios.c_cflag & CRTSCTS)
834
0
    pHandflow->ControlHandShake |= SERIAL_CTS_HANDSHAKE;
835
836
  /* SERIAL_DSR_HANDSHAKE unsupported */
837
838
  /* SERIAL_DCD_HANDSHAKE unsupported */
839
840
  /* SERIAL_DSR_SENSITIVITY unsupported */
841
842
  /* SERIAL_ERROR_ABORT unsupported */
843
844
  /* FlowReplace */
845
846
0
  pHandflow->FlowReplace = 0;
847
848
0
  if (currentTermios.c_iflag & IXON)
849
0
    pHandflow->FlowReplace |= SERIAL_AUTO_TRANSMIT;
850
851
0
  if (currentTermios.c_iflag & IXOFF)
852
0
    pHandflow->FlowReplace |= SERIAL_AUTO_RECEIVE;
853
854
0
  if (!(currentTermios.c_iflag & IGNPAR))
855
0
    pHandflow->FlowReplace |= SERIAL_ERROR_CHAR;
856
857
0
  if (currentTermios.c_iflag & IGNBRK)
858
0
    pHandflow->FlowReplace |= SERIAL_NULL_STRIPPING;
859
860
  /* SERIAL_BREAK_CHAR unsupported */
861
862
0
  if (currentTermios.c_cflag & HUPCL)
863
0
    pHandflow->FlowReplace |= SERIAL_RTS_CONTROL;
864
865
0
  if (currentTermios.c_cflag & CRTSCTS)
866
0
    pHandflow->FlowReplace |= SERIAL_RTS_HANDSHAKE;
867
868
  /* SERIAL_XOFF_CONTINUE unsupported */
869
870
0
  pHandflow->XonLimit = pComm->XOnLimit;
871
0
  pHandflow->XoffLimit = pComm->XOffLimit;
872
873
0
  return TRUE;
874
0
}
875
876
static BOOL set_timeouts(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts)
877
0
{
878
0
  WINPR_ASSERT(pComm);
879
0
  WINPR_ASSERT(pTimeouts);
880
881
  /* NB: timeouts are applied on system during read/write I/O */
882
883
  /* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx */
884
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
885
0
      (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
886
0
  {
887
0
    CommLog_Print(
888
0
        WLOG_WARN,
889
0
        "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
890
0
    SetLastError(ERROR_INVALID_PARAMETER);
891
0
    return FALSE;
892
0
  }
893
894
0
  pComm->timeouts.ReadIntervalTimeout = pTimeouts->ReadIntervalTimeout;
895
0
  pComm->timeouts.ReadTotalTimeoutMultiplier = pTimeouts->ReadTotalTimeoutMultiplier;
896
0
  pComm->timeouts.ReadTotalTimeoutConstant = pTimeouts->ReadTotalTimeoutConstant;
897
0
  pComm->timeouts.WriteTotalTimeoutMultiplier = pTimeouts->WriteTotalTimeoutMultiplier;
898
0
  pComm->timeouts.WriteTotalTimeoutConstant = pTimeouts->WriteTotalTimeoutConstant;
899
900
0
  CommLog_Print(WLOG_DEBUG, "ReadIntervalTimeout %" PRIu32 "",
901
0
                pComm->timeouts.ReadIntervalTimeout);
902
0
  CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutMultiplier %" PRIu32 "",
903
0
                pComm->timeouts.ReadTotalTimeoutMultiplier);
904
0
  CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutConstant %" PRIu32 "",
905
0
                pComm->timeouts.ReadTotalTimeoutConstant);
906
0
  CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutMultiplier %" PRIu32 "",
907
0
                pComm->timeouts.WriteTotalTimeoutMultiplier);
908
0
  CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutConstant %" PRIu32 "",
909
0
                pComm->timeouts.WriteTotalTimeoutConstant);
910
911
0
  return TRUE;
912
0
}
913
914
static BOOL get_timeouts(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts)
915
0
{
916
0
  WINPR_ASSERT(pComm);
917
0
  WINPR_ASSERT(pTimeouts);
918
919
0
  pTimeouts->ReadIntervalTimeout = pComm->timeouts.ReadIntervalTimeout;
920
0
  pTimeouts->ReadTotalTimeoutMultiplier = pComm->timeouts.ReadTotalTimeoutMultiplier;
921
0
  pTimeouts->ReadTotalTimeoutConstant = pComm->timeouts.ReadTotalTimeoutConstant;
922
0
  pTimeouts->WriteTotalTimeoutMultiplier = pComm->timeouts.WriteTotalTimeoutMultiplier;
923
0
  pTimeouts->WriteTotalTimeoutConstant = pComm->timeouts.WriteTotalTimeoutConstant;
924
925
0
  return TRUE;
926
0
}
927
928
static BOOL set_lines(WINPR_COMM* pComm, UINT32 lines)
929
0
{
930
0
  WINPR_ASSERT(pComm);
931
932
0
  return CommIoCtl(pComm, TIOCMBIS, &lines);
933
0
}
934
935
static BOOL clear_lines(WINPR_COMM* pComm, UINT32 lines)
936
0
{
937
0
  WINPR_ASSERT(pComm);
938
939
0
  return CommIoCtl(pComm, TIOCMBIC, &lines);
940
0
}
941
942
static BOOL set_dtr(WINPR_COMM* pComm)
943
0
{
944
0
  SERIAL_HANDFLOW handflow = { 0 };
945
0
  WINPR_ASSERT(pComm);
946
947
0
  if (!get_handflow(pComm, &handflow))
948
0
    return FALSE;
949
950
  /* SERIAL_DTR_HANDSHAKE not supported as of today */
951
0
  WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
952
953
0
  if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
954
0
  {
955
0
    SetLastError(ERROR_INVALID_PARAMETER);
956
0
    return FALSE;
957
0
  }
958
959
0
  return set_lines(pComm, TIOCM_DTR);
960
0
}
961
962
static BOOL clear_dtr(WINPR_COMM* pComm)
963
0
{
964
0
  SERIAL_HANDFLOW handflow = { 0 };
965
0
  WINPR_ASSERT(pComm);
966
967
0
  if (!get_handflow(pComm, &handflow))
968
0
    return FALSE;
969
970
  /* SERIAL_DTR_HANDSHAKE not supported as of today */
971
0
  WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
972
973
0
  if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
974
0
  {
975
0
    SetLastError(ERROR_INVALID_PARAMETER);
976
0
    return FALSE;
977
0
  }
978
979
0
  return clear_lines(pComm, TIOCM_DTR);
980
0
}
981
982
static BOOL set_rts(WINPR_COMM* pComm)
983
0
{
984
0
  SERIAL_HANDFLOW handflow = { 0 };
985
0
  WINPR_ASSERT(pComm);
986
987
0
  if (!get_handflow(pComm, &handflow))
988
0
    return FALSE;
989
990
0
  if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
991
0
  {
992
0
    SetLastError(ERROR_INVALID_PARAMETER);
993
0
    return FALSE;
994
0
  }
995
996
0
  return set_lines(pComm, TIOCM_RTS);
997
0
}
998
999
static BOOL clear_rts(WINPR_COMM* pComm)
1000
0
{
1001
0
  SERIAL_HANDFLOW handflow = { 0 };
1002
0
  WINPR_ASSERT(pComm);
1003
0
  if (!get_handflow(pComm, &handflow))
1004
0
    return FALSE;
1005
1006
0
  if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
1007
0
  {
1008
0
    SetLastError(ERROR_INVALID_PARAMETER);
1009
0
    return FALSE;
1010
0
  }
1011
1012
0
  return clear_lines(pComm, TIOCM_RTS);
1013
0
}
1014
1015
static BOOL get_raw_modemstatus(WINPR_COMM* pComm, int* pRegister)
1016
0
{
1017
0
  WINPR_ASSERT(pComm);
1018
0
  WINPR_ASSERT(pRegister);
1019
1020
0
  const BOOL rc = CommIoCtl(pComm, TIOCMGET, pRegister);
1021
1022
0
  char buffer[128] = { 0 };
1023
0
  CommLog_Print(WLOG_DEBUG, "status %s",
1024
0
                get_modem_status_str(*pRegister, buffer, sizeof(buffer)));
1025
0
  return rc;
1026
0
}
1027
1028
static BOOL get_modemstatus(WINPR_COMM* pComm, ULONG* pRegister)
1029
0
{
1030
0
  int lines = 0;
1031
1032
0
  if (!get_raw_modemstatus(pComm, &lines))
1033
0
    return FALSE;
1034
1035
0
  *pRegister = 0;
1036
0
  if (lines & TIOCM_CTS)
1037
0
    *pRegister |= SERIAL_MSR_CTS | SERIAL_MSR_DCTS;
1038
0
  if (lines & TIOCM_DSR)
1039
0
    *pRegister |= SERIAL_MSR_DSR | SERIAL_MSR_DDSR;
1040
0
  if (lines & TIOCM_RI)
1041
0
    *pRegister |= SERIAL_MSR_RI | SERIAL_MSR_TERI;
1042
0
  if (lines & TIOCM_CD)
1043
0
    *pRegister |= SERIAL_MSR_DCD | SERIAL_MSR_DDCD;
1044
1045
0
  return TRUE;
1046
0
}
1047
1048
/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
1049
static const ULONG SERIAL_SYS_SUPPORTED_EV_MASK =
1050
    SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR |
1051
    SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING |
1052
    /* SERIAL_EV_PERR     | */
1053
    SERIAL_EV_RX80FULL /*|
1054
    SERIAL_EV_EVENT1   |
1055
    SERIAL_EV_EVENT2*/
1056
    ;
1057
1058
static BOOL is_wait_set(WINPR_COMM* pComm)
1059
0
{
1060
0
  WINPR_ASSERT(pComm);
1061
1062
0
  EnterCriticalSection(&pComm->EventsLock);
1063
0
  const BOOL isWaiting = (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING) != 0;
1064
0
  LeaveCriticalSection(&pComm->EventsLock);
1065
0
  return isWaiting;
1066
0
}
1067
1068
static BOOL set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
1069
0
{
1070
0
  ULONG possibleMask = 0;
1071
1072
0
  WINPR_ASSERT(pComm);
1073
0
  WINPR_ASSERT(pWaitMask);
1074
1075
  /* Stops pending IOCTL_SERIAL_WAIT_ON_MASK
1076
   * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
1077
   */
1078
0
  if (is_wait_set(pComm))
1079
0
  {
1080
    /* FIXME: any doubt on reading PendingEvents out of a critical section? */
1081
1082
0
    EnterCriticalSection(&pComm->EventsLock);
1083
0
    pComm->PendingEvents |= SERIAL_EV_WINPR_STOP;
1084
0
    LeaveCriticalSection(&pComm->EventsLock);
1085
1086
    /* waiting the end of the pending wait_on_mask() */
1087
0
    while (is_wait_set(pComm))
1088
0
      Sleep(10); /* 10ms */
1089
1090
0
    EnterCriticalSection(&pComm->EventsLock);
1091
0
    pComm->PendingEvents &= (uint32_t)~SERIAL_EV_WINPR_STOP;
1092
0
    LeaveCriticalSection(&pComm->EventsLock);
1093
0
  }
1094
1095
  /* NB: ensure to leave the critical section before to return */
1096
0
  EnterCriticalSection(&pComm->EventsLock);
1097
1098
0
  if (*pWaitMask == 0)
1099
0
  {
1100
    /* clearing pending events */
1101
0
    if (!CommUpdateIOCount(pComm, FALSE))
1102
0
    {
1103
0
      LeaveCriticalSection(&pComm->EventsLock);
1104
0
      return FALSE;
1105
0
    }
1106
1107
0
    pComm->PendingEvents = 0;
1108
0
  }
1109
1110
0
  possibleMask = *pWaitMask & SERIAL_SYS_SUPPORTED_EV_MASK;
1111
1112
0
  if (possibleMask != *pWaitMask)
1113
0
  {
1114
0
    CommLog_Print(WLOG_WARN,
1115
0
                  "Not all wait events supported (Serial.sys), requested events= 0x%08" PRIX32
1116
0
                  ", possible events= 0x%08" PRIX32 "",
1117
0
                  *pWaitMask, possibleMask);
1118
1119
    /* FIXME: shall we really set the possibleMask and return FALSE? */
1120
0
    pComm->WaitEventMask = possibleMask;
1121
1122
0
    LeaveCriticalSection(&pComm->EventsLock);
1123
0
    return FALSE;
1124
0
  }
1125
1126
0
  pComm->WaitEventMask = possibleMask;
1127
1128
0
  LeaveCriticalSection(&pComm->EventsLock);
1129
0
  return TRUE;
1130
0
}
1131
1132
static BOOL get_wait_mask(WINPR_COMM* pComm, ULONG* pWaitMask)
1133
0
{
1134
0
  WINPR_ASSERT(pComm);
1135
0
  WINPR_ASSERT(pWaitMask);
1136
1137
0
  *pWaitMask = pComm->WaitEventMask;
1138
0
  return TRUE;
1139
0
}
1140
1141
static BOOL set_queue_size(WINPR_ATTR_UNUSED WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize)
1142
0
{
1143
0
  WINPR_ASSERT(pComm);
1144
0
  WINPR_ASSERT(pQueueSize);
1145
1146
0
  if ((pQueueSize->InSize <= N_TTY_BUF_SIZE) && (pQueueSize->OutSize <= N_TTY_BUF_SIZE))
1147
0
    return TRUE; /* nothing to do */
1148
1149
  /* FIXME: could be implemented on top of N_TTY */
1150
1151
0
  if (pQueueSize->InSize > N_TTY_BUF_SIZE)
1152
0
    CommLog_Print(WLOG_WARN,
1153
0
                  "Requested an incompatible input buffer size: %" PRIu32
1154
0
                  ", keeping on with a %" PRIu32 " bytes buffer.",
1155
0
                  pQueueSize->InSize, N_TTY_BUF_SIZE);
1156
1157
0
  if (pQueueSize->OutSize > N_TTY_BUF_SIZE)
1158
0
    CommLog_Print(WLOG_WARN,
1159
0
                  "Requested an incompatible output buffer size: %" PRIu32
1160
0
                  ", keeping on with a %" PRIu32 " bytes buffer.",
1161
0
                  pQueueSize->OutSize, N_TTY_BUF_SIZE);
1162
1163
0
  SetLastError(ERROR_CANCELLED);
1164
0
  return FALSE;
1165
0
}
1166
1167
static BOOL purge(WINPR_COMM* pComm, const ULONG* pPurgeMask)
1168
0
{
1169
0
  WINPR_ASSERT(pComm);
1170
0
  WINPR_ASSERT(pPurgeMask);
1171
1172
0
  if ((*pPurgeMask & (uint32_t)~(SERIAL_PURGE_TXABORT | SERIAL_PURGE_RXABORT |
1173
0
                                 SERIAL_PURGE_TXCLEAR | SERIAL_PURGE_RXCLEAR)) > 0)
1174
0
  {
1175
0
    CommLog_Print(WLOG_WARN, "Invalid purge mask: 0x%" PRIX32 "\n", *pPurgeMask);
1176
0
    SetLastError(ERROR_INVALID_PARAMETER);
1177
0
    return FALSE;
1178
0
  }
1179
1180
  /* FIXME: currently relying too much on the fact the server
1181
   * sends a single IRP_MJ_WRITE or IRP_MJ_READ at a time
1182
   * (taking care though that one IRP_MJ_WRITE and one
1183
   * IRP_MJ_READ can be sent simultaneously) */
1184
1185
0
  if (*pPurgeMask & SERIAL_PURGE_TXABORT)
1186
0
  {
1187
    /* Purges all write (IRP_MJ_WRITE) requests. */
1188
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
1189
0
    if (eventfd_write(pComm->fd_write_event, WINPR_PURGE_TXABORT) < 0)
1190
0
    {
1191
0
      if (errno != EAGAIN)
1192
0
      {
1193
0
        char ebuffer[256] = { 0 };
1194
0
        CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
1195
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1196
0
      }
1197
1198
0
      WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_WRITE */
1199
0
    }
1200
0
#endif
1201
0
  }
1202
1203
0
  if (*pPurgeMask & SERIAL_PURGE_RXABORT)
1204
0
  {
1205
    /* Purges all read (IRP_MJ_READ) requests. */
1206
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
1207
0
    if (eventfd_write(pComm->fd_read_event, WINPR_PURGE_RXABORT) < 0)
1208
0
    {
1209
0
      if (errno != EAGAIN)
1210
0
      {
1211
0
        char ebuffer[256] = { 0 };
1212
0
        CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
1213
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1214
0
      }
1215
1216
0
      WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_READ */
1217
0
    }
1218
0
#endif
1219
0
  }
1220
1221
0
  if (*pPurgeMask & SERIAL_PURGE_TXCLEAR)
1222
0
  {
1223
    /* Purges the transmit buffer, if one exists. */
1224
1225
0
    if (tcflush(pComm->fd, TCOFLUSH) < 0)
1226
0
    {
1227
0
      char ebuffer[256] = { 0 };
1228
0
      CommLog_Print(WLOG_WARN, "tcflush(TCOFLUSH) failure, errno=[%d] %s", errno,
1229
0
                    winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1230
0
      SetLastError(ERROR_CANCELLED);
1231
0
      return FALSE;
1232
0
    }
1233
0
  }
1234
1235
0
  if (*pPurgeMask & SERIAL_PURGE_RXCLEAR)
1236
0
  {
1237
    /* Purges the receive buffer, if one exists. */
1238
1239
0
    if (tcflush(pComm->fd, TCIFLUSH) < 0)
1240
0
    {
1241
0
      char ebuffer[256] = { 0 };
1242
0
      CommLog_Print(WLOG_WARN, "tcflush(TCIFLUSH) failure, errno=[%d] %s", errno,
1243
0
                    winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1244
0
      SetLastError(ERROR_CANCELLED);
1245
0
      return FALSE;
1246
0
    }
1247
0
  }
1248
1249
0
  return TRUE;
1250
0
}
1251
1252
/* NB: get_commstatus also produces most of the events consumed by  wait_on_mask(). Exceptions:
1253
 *  - SERIAL_EV_RXFLAG: FIXME: once EventChar supported
1254
 *
1255
 */
1256
static BOOL get_commstatus(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus)
1257
0
{
1258
0
  BOOL rc = FALSE;
1259
  /* http://msdn.microsoft.com/en-us/library/jj673022%28v=vs.85%29.aspx */
1260
0
#if defined(WINPR_HAVE_COMM_COUNTERS)
1261
0
  struct serial_icounter_struct currentCounters = { 0 };
1262
0
#endif
1263
0
  WINPR_ASSERT(pComm);
1264
0
  WINPR_ASSERT(pCommstatus);
1265
1266
  /* NB: ensure to leave the critical section before to return */
1267
0
  EnterCriticalSection(&pComm->EventsLock);
1268
1269
0
  ZeroMemory(pCommstatus, sizeof(SERIAL_STATUS));
1270
1271
0
  int status = 0;
1272
0
  if (!get_raw_modemstatus(pComm, &status))
1273
0
    goto fail;
1274
1275
0
#if defined(WINPR_HAVE_COMM_COUNTERS)
1276
0
  if (!CommUpdateIOCount(pComm, FALSE))
1277
0
    goto fail;
1278
0
  currentCounters = pComm->counters;
1279
1280
  /* NB: preferred below (currentCounters.* != pComm->counters.*) over (currentCounters.* >
1281
   * pComm->counters.*) thinking the counters can loop */
1282
1283
  /* Errors */
1284
1285
0
  if (currentCounters.buf_overrun != pComm->counters.buf_overrun)
1286
0
  {
1287
0
    pCommstatus->Errors |= SERIAL_ERROR_QUEUEOVERRUN;
1288
0
  }
1289
1290
0
  if (currentCounters.overrun != pComm->counters.overrun)
1291
0
  {
1292
0
    pCommstatus->Errors |= SERIAL_ERROR_OVERRUN;
1293
0
    pComm->PendingEvents |= SERIAL_EV_ERR;
1294
0
  }
1295
1296
0
  if (currentCounters.brk != pComm->counters.brk)
1297
0
  {
1298
0
    pCommstatus->Errors |= SERIAL_ERROR_BREAK;
1299
0
    pComm->PendingEvents |= SERIAL_EV_BREAK;
1300
0
  }
1301
1302
0
  if (currentCounters.parity != pComm->counters.parity)
1303
0
  {
1304
0
    pCommstatus->Errors |= SERIAL_ERROR_PARITY;
1305
0
    pComm->PendingEvents |= SERIAL_EV_ERR;
1306
0
  }
1307
1308
0
  if (currentCounters.frame != pComm->counters.frame)
1309
0
  {
1310
0
    pCommstatus->Errors |= SERIAL_ERROR_FRAMING;
1311
0
    pComm->PendingEvents |= SERIAL_EV_ERR;
1312
0
  }
1313
0
#endif
1314
1315
  /* HoldReasons */
1316
1317
0
  if (status & TIOCM_CTS)
1318
0
    pComm->PendingEvents |= SERIAL_EV_CTS;
1319
1320
  /* TODO: SERIAL_TX_WAITING_FOR_XON */
1321
1322
  /* TODO: SERIAL_TX_WAITING_ON_BREAK, see LCR's bit 6 */
1323
1324
  /* TODO: SERIAL_TX_WAITING_XOFF_SENT */
1325
1326
0
  if (status & TIOCM_SR)
1327
0
    pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR;
1328
1329
  /* AmountInInQueue */
1330
0
  int available = 0;
1331
0
  if (!CommIoCtl(pComm, FIONREAD, &available))
1332
0
    goto fail;
1333
1334
0
#if defined(__linux__)
1335
0
  if (!CommIoCtl(pComm, TIOCINQ, &pCommstatus->AmountInInQueue))
1336
0
    goto fail;
1337
0
#endif
1338
1339
  /*  AmountInOutQueue */
1340
1341
0
  if (!CommIoCtl(pComm, TIOCOUTQ, &pCommstatus->AmountInOutQueue))
1342
0
    goto fail;
1343
1344
  /*  BOOLEAN EofReceived; FIXME: once EofChar supported */
1345
1346
  /*  BOOLEAN WaitForImmediate; TODO: once IOCTL_SERIAL_IMMEDIATE_CHAR fully supported */
1347
1348
  /* other events based on counters */
1349
1350
0
  if (available > 0)
1351
0
    pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR;
1352
1353
0
  if (pCommstatus->AmountInOutQueue == 0) /* output buffer is now empty */
1354
0
  {
1355
0
    pComm->PendingEvents |= SERIAL_EV_TXEMPTY;
1356
0
  }
1357
0
  else
1358
0
  {
1359
    /* FIXME: "now empty" from the specs is ambiguous, need to track previous completed
1360
     * transmission? */
1361
0
    pComm->PendingEvents &= (uint32_t)~SERIAL_EV_TXEMPTY;
1362
0
  }
1363
1364
0
#if defined(WINPR_HAVE_COMM_COUNTERS)
1365
0
  if (currentCounters.tx != pComm->counters.tx)
1366
0
  {
1367
0
    pComm->PendingEvents &= (uint32_t)~SERIAL_EV_TXEMPTY;
1368
0
  }
1369
0
  else
1370
0
  {
1371
0
    pComm->PendingEvents |= SERIAL_EV_TXEMPTY;
1372
0
  }
1373
1374
0
  if (currentCounters.rx != pComm->counters.rx)
1375
0
  {
1376
0
    pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR;
1377
0
  }
1378
1379
0
  if (currentCounters.cts != pComm->counters.cts)
1380
0
  {
1381
0
    pComm->PendingEvents |= SERIAL_EV_CTS;
1382
0
  }
1383
1384
0
  if (currentCounters.dsr != pComm->counters.dsr)
1385
0
  {
1386
0
    pComm->PendingEvents |= SERIAL_EV_DSR;
1387
0
  }
1388
1389
0
  if (currentCounters.dcd != pComm->counters.dcd)
1390
0
  {
1391
0
    pComm->PendingEvents |= SERIAL_EV_RLSD;
1392
0
  }
1393
1394
0
  if (currentCounters.rng != pComm->counters.rng)
1395
0
  {
1396
0
    pComm->PendingEvents |= SERIAL_EV_RING;
1397
0
  }
1398
0
  pComm->counters = currentCounters;
1399
0
#endif
1400
1401
0
  if (pCommstatus->AmountInInQueue > (0.8 * N_TTY_BUF_SIZE))
1402
0
  {
1403
0
    pComm->PendingEvents |= SERIAL_EV_RX80FULL;
1404
0
  }
1405
0
  else
1406
0
  {
1407
    /* FIXME: "is 80 percent full" from the specs is ambiguous, need to track when it previously
1408
     * * occurred? */
1409
0
    pComm->PendingEvents &= (uint32_t)~SERIAL_EV_RX80FULL;
1410
0
  }
1411
1412
0
  rc = TRUE;
1413
0
fail:
1414
0
  LeaveCriticalSection(&pComm->EventsLock);
1415
0
  return rc;
1416
0
}
1417
1418
static BOOL refresh_PendingEvents(WINPR_COMM* pComm)
1419
0
{
1420
0
  SERIAL_STATUS serialStatus = { 0 };
1421
1422
0
  WINPR_ASSERT(pComm);
1423
1424
  /* NB: also ensures PendingEvents to be up to date */
1425
0
  if (!get_commstatus(pComm, &serialStatus))
1426
0
  {
1427
0
    return FALSE;
1428
0
  }
1429
1430
0
  return TRUE;
1431
0
}
1432
1433
static void consume_event(WINPR_COMM* pComm, ULONG* pOutputMask, ULONG event)
1434
0
{
1435
0
  WINPR_ASSERT(pComm);
1436
0
  WINPR_ASSERT(pOutputMask);
1437
1438
0
  if ((pComm->WaitEventMask & event) && (pComm->PendingEvents & event))
1439
0
  {
1440
0
    pComm->PendingEvents &= ~event; /* consumed */
1441
0
    *pOutputMask |= event;
1442
0
  }
1443
0
}
1444
1445
static BOOL unlock_return(WINPR_COMM* pComm, BOOL res)
1446
0
{
1447
0
  EnterCriticalSection(&pComm->EventsLock);
1448
0
  pComm->PendingEvents &= (uint32_t)~SERIAL_EV_WINPR_WAITING;
1449
0
  LeaveCriticalSection(&pComm->EventsLock);
1450
0
  return res;
1451
0
}
1452
1453
/*
1454
 * NB: see also: set_wait_mask()
1455
 */
1456
static BOOL wait_on_mask(WINPR_COMM* pComm, ULONG* pOutputMask)
1457
0
{
1458
0
  WINPR_ASSERT(pComm);
1459
0
  WINPR_ASSERT(*pOutputMask == 0);
1460
1461
0
  EnterCriticalSection(&pComm->EventsLock);
1462
0
  pComm->PendingEvents |= SERIAL_EV_WINPR_WAITING;
1463
0
  LeaveCriticalSection(&pComm->EventsLock);
1464
1465
0
  while (TRUE)
1466
0
  {
1467
    /* NB: EventsLock also used by  refresh_PendingEvents() */
1468
0
    if (!refresh_PendingEvents(pComm))
1469
0
      return unlock_return(pComm, FALSE);
1470
1471
    /* NB: ensure to leave the critical section before to return */
1472
0
    EnterCriticalSection(&pComm->EventsLock);
1473
1474
0
    if (pComm->PendingEvents & SERIAL_EV_WINPR_STOP)
1475
0
    {
1476
      /* pOutputMask must remain empty but should
1477
       * not have been modified.
1478
       *
1479
       * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
1480
       */
1481
0
      WINPR_ASSERT(*pOutputMask == 0);
1482
1483
0
      LeaveCriticalSection(&pComm->EventsLock);
1484
0
      break;
1485
0
    }
1486
1487
0
    consume_event(pComm, pOutputMask, SERIAL_EV_RXCHAR);
1488
0
    consume_event(pComm, pOutputMask, SERIAL_EV_RXFLAG);
1489
0
    consume_event(pComm, pOutputMask, SERIAL_EV_TXEMPTY);
1490
0
    consume_event(pComm, pOutputMask, SERIAL_EV_DSR);
1491
0
    consume_event(pComm, pOutputMask, SERIAL_EV_RLSD);
1492
0
    consume_event(pComm, pOutputMask, SERIAL_EV_BREAK);
1493
0
    consume_event(pComm, pOutputMask, SERIAL_EV_ERR);
1494
0
    consume_event(pComm, pOutputMask, SERIAL_EV_RING);
1495
0
    consume_event(pComm, pOutputMask, SERIAL_EV_RX80FULL);
1496
1497
0
    LeaveCriticalSection(&pComm->EventsLock);
1498
1499
    /* NOTE: PendingEvents can be modified from now on but
1500
     * not pOutputMask */
1501
1502
0
    if (*pOutputMask != 0)
1503
0
      break;
1504
1505
    /* waiting for a modification of PendingEvents.
1506
     *
1507
     * NOTE: previously used a semaphore but used
1508
     * sem_timedwait() anyway. Finally preferred a simpler
1509
     * solution with Sleep() without the burden of the
1510
     * semaphore initialization and destroying.
1511
     */
1512
1513
0
    Sleep(100); /* 100 ms */
1514
0
  }
1515
1516
0
  return unlock_return(pComm, TRUE);
1517
0
}
1518
1519
static BOOL set_break_on(WINPR_COMM* pComm)
1520
0
{
1521
0
  WINPR_ASSERT(pComm);
1522
0
  return CommIoCtl(pComm, TIOCSBRK, NULL);
1523
0
}
1524
1525
static BOOL set_break_off(WINPR_COMM* pComm)
1526
0
{
1527
0
  WINPR_ASSERT(pComm);
1528
1529
0
  return CommIoCtl(pComm, TIOCCBRK, NULL);
1530
0
}
1531
1532
static BOOL set_xoff(WINPR_COMM* pComm)
1533
0
{
1534
0
  WINPR_ASSERT(pComm);
1535
  // NOLINTNEXTLINE(concurrency-mt-unsafe)
1536
0
  if (tcflow(pComm->fd, TCIOFF) < 0)
1537
0
  {
1538
0
    char ebuffer[256] = { 0 };
1539
0
    CommLog_Print(WLOG_WARN, "TCIOFF failure, errno=[%d] %s", errno,
1540
0
                  winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1541
0
    SetLastError(ERROR_IO_DEVICE);
1542
0
    return FALSE;
1543
0
  }
1544
1545
0
  return TRUE;
1546
0
}
1547
1548
static BOOL set_xon(WINPR_COMM* pComm)
1549
0
{
1550
0
  WINPR_ASSERT(pComm);
1551
  // NOLINTNEXTLINE(concurrency-mt-unsafe)
1552
0
  if (tcflow(pComm->fd, TCION) < 0)
1553
0
  {
1554
0
    char ebuffer[256] = { 0 };
1555
0
    CommLog_Print(WLOG_WARN, "TCION failure, errno=[%d] %s", errno,
1556
0
                  winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
1557
0
    SetLastError(ERROR_IO_DEVICE);
1558
0
    return FALSE;
1559
0
  }
1560
1561
0
  return TRUE;
1562
0
}
1563
1564
static BOOL get_dtrrts(WINPR_COMM* pComm, ULONG* pMask)
1565
0
{
1566
0
  int lines = 0;
1567
1568
0
  WINPR_ASSERT(pComm);
1569
0
  WINPR_ASSERT(pMask);
1570
1571
0
  if (!get_raw_modemstatus(pComm, &lines))
1572
0
    return FALSE;
1573
1574
0
  *pMask = 0;
1575
1576
0
  if (!(lines & TIOCM_DTR))
1577
0
    *pMask |= SERIAL_DTR_STATE;
1578
0
  if (!(lines & TIOCM_RTS))
1579
0
    *pMask |= SERIAL_RTS_STATE;
1580
1581
0
  return TRUE;
1582
0
}
1583
1584
static BOOL config_size(WINPR_ATTR_UNUSED WINPR_COMM* pComm, ULONG* pSize)
1585
0
{
1586
0
  WINPR_ASSERT(pComm);
1587
0
  WINPR_ASSERT(pSize);
1588
1589
  /* http://msdn.microsoft.com/en-us/library/ff546548%28v=vs.85%29.aspx */
1590
0
  if (!pSize)
1591
0
    return FALSE;
1592
1593
0
  *pSize = 0;
1594
0
  return TRUE;
1595
0
}
1596
1597
static BOOL immediate_char(WINPR_COMM* pComm, const UCHAR* pChar)
1598
0
{
1599
0
  BOOL result = 0;
1600
0
  DWORD nbBytesWritten = 0;
1601
1602
0
  WINPR_ASSERT(pComm);
1603
0
  WINPR_ASSERT(pChar);
1604
1605
  /* FIXME: CommWriteFile uses a critical section, shall it be
1606
   * interrupted?
1607
   *
1608
   * FIXME: see also get_commstatus()'s WaitForImmediate boolean
1609
   */
1610
1611
0
  result = CommWriteFile(pComm, pChar, 1, &nbBytesWritten, NULL);
1612
1613
0
  WINPR_ASSERT(nbBytesWritten == 1);
1614
1615
0
  return result;
1616
0
}
1617
1618
static BOOL reset_device(WINPR_ATTR_UNUSED WINPR_COMM* pComm)
1619
0
{
1620
  /* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx */
1621
0
  WINPR_ASSERT(pComm);
1622
1623
0
  pComm->XOnLimit = TTY_THRESHOLD_UNTHROTTLE;
1624
0
  pComm->XOffLimit = TTY_THRESHOLD_THROTTLE;
1625
1626
0
  (void)CommUpdateIOCount(pComm, TRUE);
1627
1628
0
  return CommIoCtl(pComm, TIOCMSET, 0);
1629
0
}
1630
1631
static const SERIAL_DRIVER SerialSys = {
1632
  .id = SerialDriverSerialSys,
1633
  .name = _T("Serial.sys"),
1634
  .set_baud_rate = set_baud_rate,
1635
  .get_baud_rate = get_baud_rate,
1636
  .get_properties = get_properties,
1637
  .set_serial_chars = set_serial_chars,
1638
  .get_serial_chars = get_serial_chars,
1639
  .set_line_control = set_line_control,
1640
  .get_line_control = get_line_control,
1641
  .set_handflow = set_handflow,
1642
  .get_handflow = get_handflow,
1643
  .set_timeouts = set_timeouts,
1644
  .get_timeouts = get_timeouts,
1645
  .set_dtr = set_dtr,
1646
  .clear_dtr = clear_dtr,
1647
  .set_rts = set_rts,
1648
  .clear_rts = clear_rts,
1649
  .get_modemstatus = get_modemstatus,
1650
  .set_wait_mask = set_wait_mask,
1651
  .get_wait_mask = get_wait_mask,
1652
  .wait_on_mask = wait_on_mask,
1653
  .set_queue_size = set_queue_size,
1654
  .purge = purge,
1655
  .get_commstatus = get_commstatus,
1656
  .set_break_on = set_break_on,
1657
  .set_break_off = set_break_off,
1658
  .set_xoff = set_xoff,
1659
  .set_xon = set_xon,
1660
  .get_dtrrts = get_dtrrts,
1661
  .config_size = config_size,
1662
  .immediate_char = immediate_char,
1663
  .reset_device = reset_device,
1664
};
1665
1666
const SERIAL_DRIVER* SerialSys_s(void)
1667
0
{
1668
0
  return &SerialSys;
1669
0
}