1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793
|
/*
Psychtoolbox3/PsychSourceGL/Source/Common/IOPort/PsychSerialUnixGlue.c
PROJECTS:
IOPort for now.
AUTHORS:
mario.kleiner.de@gmail.com mk
PLATFORMS:
All Unix (aka Posix) systems: OS/X and Linux.
HISTORY:
04/10/2008 mk Initial implementation.
04/03/2011 mk Audited (but not tested) to be 64-bit safe. Doesn't take advantage of > 2 GB
buffers yet. This will require some minor changes.
DESCRIPTION:
This is the operating system dependent "glue code" layer for access to serial ports for the
Unices, ie., for GNU/Linux and Apple MacOS/X. It is used by the higher-level serial port
routines to abstract out operating system dependencies.
The code is shared and #ifdef'ed if needed, because most of it is identical for Linux and OS/X.
*/
#include "Psych.h"
// Need to exclude this files source-code from compile on Win32:
#if PSYCH_SYSTEM != PSYCH_WINDOWS
#include "IOPort.h"
#if PSYCH_SYSTEM == PSYCH_LINUX
// Defines serial_struct and ASYNC_LOW_LATENCY
#include <linux/serial.h>
#endif
// Externally defined level of verbosity:
extern int verbosity;
// Map numeric baud rate to Posix constant:
static int BaudToConstant(int inint)
{
// Need to map from numeric values to predefined constants. On OS/X the
// constants are identical to the input values, but on Linux they aren't, so better safe
// than sorry, although it looks as if Linux accepts the "raw" numbers properly as well...
switch(inint) {
case 50: inint = B50; break;
case 75: inint = B75; break;
case 110: inint = B110; break;
case 134: inint = B134; break;
case 150: inint = B150; break;
case 200: inint = B200; break;
case 300: inint = B300; break;
case 600: inint = B600; break;
case 1200: inint = B1200; break;
case 1800: inint = B1800; break;
case 2400: inint = B2400; break;
case 4800: inint = B4800; break;
case 9600: inint = B9600; break;
case 19200: inint = B19200; break;
case 38400: inint = B38400; break;
case 57600: inint = B57600; break;
case 115200: inint = B115200; break;
case 230400: inint = B230400; break;
#if PSYCH_SYSTEM == PSYCH_OSX
// Only defined on OS/X:
case 7200: inint = B7200; break;
case 14400: inint = B14400; break;
case 28800: inint = B28800; break;
case 76800: inint = B76800; break;
#endif
default:
// Pass inint unchanged in the hope that the OS will accept it:
if (verbosity > 1) printf("IOPort: Non-standard BaudRate %i specified. We'll see if the OS likes this. Double-check the settings!\n", inint);
}
return(inint);
}
// Map Posix constant to numeric baud rate:
static int ConstantToBaud(int inint)
{
// Need to map predefined constants to numeric values On OS/X the
// constants are identical to the input values, but on Linux they aren't, so better safe
// than sorry, although it looks as if Linux accepts the "raw" numbers properly as well...
switch(inint) {
case B50: inint = 50; break;
case B75: inint = 75; break;
case B110: inint = 110; break;
case B134: inint = 134; break;
case B150: inint = 150; break;
case B200: inint = 200; break;
case B300: inint = 300; break;
case B600: inint = 600; break;
case B1200: inint = 1200; break;
case B1800: inint = 1800; break;
case B2400: inint = 2400; break;
case B4800: inint = 4800; break;
case B9600: inint = 9600; break;
case B19200: inint = 19200; break;
case B38400: inint = 38400; break;
case B57600: inint = 57600; break;
case B115200: inint = 115200; break;
case B230400: inint = 230400; break;
#if PSYCH_SYSTEM == PSYCH_OSX
// Only defined on OS/X:
case B7200: inint = 7200; break;
case B14400: inint = 14400; break;
case B28800: inint = 28800; break;
case B76800: inint = 76800; break;
#endif
default:
// Pass inint unchanged in the hope that the OS will accept it:
if (verbosity > 1) printf("IOPort: Non-standard BaudRate %i detected. Let's see if this makes sense...\n", inint);
}
return(inint);
}
int PsychSerialUnixGlueFcntl(PsychSerialDeviceRecord* device, int newVal)
{
int rc;
// Query current setting:
rc = fcntl(device->fileDescriptor, F_GETFL, 0);
// Only perform fcntl() call if current setting isn't already equal to requested new setting newVal,
// because fcntl() calls can be very time-consuming on some devices, e.g., USB-Serial ports:
if (rc != newVal) { rc = fcntl(device->fileDescriptor, F_SETFL, newVal); }
// Return new setting or error code:
return(rc);
}
int PsychSerialUnixGlueSetBlockingMinBytes(PsychSerialDeviceRecord* device, int minBytes)
{
struct termios options;
int rc;
// Retrieve current termios settings:
if (tcgetattr(device->fileDescriptor, &options) == -1)
{
if (verbosity > 0) fprintf(stderr, "IOPort: Error getting current serial port device settings for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
rc = minBytes;
// Clamp amount smaller or equal one to zero: Clamping negative values to zero is obviously needed
// to prevent passing illegal values (valid range 0-255). The amount == 1 --> amount = 0 case is special.
// Normally the interbyte timer VTIME will only get armed after reception of 1st byte, so asking for
// VMIN=1 for amount == 1 would effectively disable the VTIME interbyte timeout semantic that we want.
// However the special combination VTIME = someinterbytetimeout and VTIME = 0 will (see man termios)
// ask the system to wait until exactly 1 Byte is available or VTIME time elapses (absolute time, no
// interbyte timeout anymore!), whatever comes first. The net result is exactly the interbyte VTIME timeout
// semantics we want for a single byte receive.
if (minBytes <= 1) {
rc = (minBytes < 1) ? 0 : 1;
minBytes = 0;
}
// Clamp at max. 255 Bytes, the maximum for blocking reads: This is a bit inconvenient. Unix limits
// the maximum amount of Bytes one can wait for in a blocking receive to 255, because the VMIN
// parameter is only 1 Byte in size. We have to clamp to 255 if 255 is exceeded and just hope that
// this doesn't lead to undefined behaviour in usercode. Luckily, must data transfers in typical
// cognitive science applications of serial ports are smaller than 255 Bytes, e.g., 1 Byte for
// shoddy response boxes or for TTL trigger receivers, 1 - 8 Bytes for good response boxes, and
// maybe anywhere between 4 - 20 Bytes for eyetracker stuff and similar equipment. So i guess usercode
// would seldomly hit this special case. We output a warning anyway if a high level of verbosity is
// selected.
if (minBytes > 255) {
minBytes = 255;
rc = 255;
}
// Change the number of bytes that must be received for blocking read()'s before read() returns.
// read() will return if it can fetch at least that many bytes (we want 'minBytes' bytes at least),
// or if the user specified timeout occurs (as set in Open or Configure routine):
// N.B.: We do this lazily, only executing the call if the current and requested setting differ,
// as the tcsetattr() call may be expensive on some system/driver combinations...
if (options.c_cc[VMIN] != (unsigned char) minBytes) {
options.c_cc[VMIN] = (unsigned char) minBytes;
// Cause the new options to take effect immediately.
if (tcsetattr(device->fileDescriptor, TCSANOW, &options) == -1)
{
if (verbosity > 0) fprintf(stderr, "IOPort: Error setting new serial port configuration attributes for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
}
// Return really set value:
return(rc);
}
int PsychSerialUnixGlueAsyncReadbufferBytesAvailable(PsychSerialDeviceRecord* device)
{
int navail = 0;
// Lock data structure:
PsychLockMutex(&(device->readerLock));
// Compute amount of pending data for readout:
navail = (device->readerThreadWritePos - device->clientThreadReadPos);
// Unlock data structure:
PsychUnlockMutex(&(device->readerLock));
// Return it.
return(navail);
}
void* PsychSerialUnixGlueReaderThreadMain( void* deviceToCast)
{
int rc, nread, oldstate;
int navail;
int tmpcurpos, naccumread;
unsigned char lastcharacter, lineterminator;
double dt, oldt;
double t;
int doBlockingRead = 0;
// Get a handle to our device struct: These pointers must not be NULL!!!
PsychSerialDeviceRecord* device = (PsychSerialDeviceRecord*) deviceToCast;
// Assign a name to ourselves, for debugging:
PsychSetThreadName("IOPortSerialRd");
// Try to raise our priority: We ask to switch ourselves (NULL) to priority class 2 aka
// realtime scheduling, with a tweakPriority of +1, ie., raise the relative
// priority level by +1 wrt. to the current level:
if ((rc = PsychSetThreadPriority(NULL, 2, 1)) > 0) {
if (verbosity > 0) printf("PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): Failed to switch to realtime priority [%s]!\n", strerror(rc));
}
// Init reference timestamp of last scan:
PsychGetAdjustedPrecisionTimerSeconds(&oldt);
dt = 0;
// Main loop: Runs until external thread cancellation:
while (1) {
// Test for explicit cancellation by mother-thread:
PsychTestCancelThread(&(device->readerThread));
// Polling read requested?
doBlockingRead = device->isBlockingBackgroundRead;
if (doBlockingRead == 0) {
// Polling operation:
// Enough data available for read of requested granularity?
// If not, we sleep for some time, then retry:
ioctl(device->fileDescriptor, FIONREAD, &navail);
while(navail < device->readGranularity) {
PsychTestCancelThread(&(device->readerThread));
PsychWaitIntervalSeconds(device->pollLatency);
ioctl(device->fileDescriptor, FIONREAD, &navail);
}
}
else {
// Non-polling operation. We perform a blocking read on the device.
// Assuming the masterthread doesn't perform any non-blocking operations,
// which would screw us or it, this is safe. The read() call is a thread cancellation
// point, so we can be safely aborted by the masterthread even if blocked on
// input.
// Set filedescriptor to blocking mode:
// Clear the O_NONBLOCK flag so subsequent I/O will block.
// See fcntl(2) ("man 2 fcntl") for details.
// We do this in a cached, lazy way b'cause it can be expensive:
PsychSerialUnixGlueFcntl(device, 0);
}
// Zerofill the space we're gonna read in next read() request, so we have a nice
// clean buffersegment in case of a short-read, e.g., in cooked mode on end-of-line:
memset(&(device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)]), 0, device->readGranularity);
// Async linebuffered read op?
if (device->readFilterFlags & kPsychIOPortAsyncLineBufferFiltering) {
// Emulation of linebuffered readop, similar to Unix cooked, canonical input processing mode:
tmpcurpos = (device->readerThreadWritePos) % (device->readBufferSize);
lineterminator = device->lineTerminator;
lastcharacter = lineterminator + 1;
naccumread = 0;
t = DBL_MIN;
// Setup minimum byte counter for 1 Byte blocking reads:
if (doBlockingRead > 0) PsychSerialUnixGlueSetBlockingMinBytes(device, 1);
// Repeat reading until a maximum of readGranularity bytes has been read or until
// lineterminator character is encountered, whatever comes first:
while ( (naccumread < device->readGranularity) && (lastcharacter != lineterminator) ) {
// Read 1 Byte:
if ((nread = read(device->fileDescriptor, &lastcharacter, 1)) > 0) {
// Put it into readBuffer:
device->readBuffer[tmpcurpos++] = lastcharacter;
// Get representative timestamp of "start of line terminated new line" for first received byte:
if (0 == naccumread) PsychGetAdjustedPrecisionTimerSeconds(&t);
// Increment count of received bytes:
naccumread++;
}
else {
// 1 Byte read failed!
// Diagnostic:
if (nread == -1 && errno == EAGAIN) {
// Timeout.
if (verbosity > 5) fprintf(stderr, "PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): Linebuffered read: Failed to read 1 byte of data due to read-timeout at relative position %i! Padding...\n", naccumread);
}
else {
if (verbosity > 5) fprintf(stderr, "PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): Linebuffered read: Failed to read 1 byte of data for reason [%s] at relative position %i! Padding...\n", strerror(errno), naccumread);
}
// Break out of while() loop:
break;
}
}
// Done with this read quantum, either due to error, line-terminator reached, or readGranularity bytes stored.
// Increment serial bytes received counter:
device->asyncReadBytesCount += (naccumread > 0) ? naccumread : 0;
}
else {
// Standard non-linebuffered readop:
// How much data to read? We use the last 8-Bytes of a readGranularity quantum for our
// serial bytes counter and our dt to last scan time if kPsychIOPortCMUPSTFiltering is active:
naccumread = (device->readFilterFlags & kPsychIOPortCMUPSTFiltering) ? (device->readGranularity - 8) : device->readGranularity;
if (naccumread < 0) naccumread = 0;
// Setup minimum byte counter for 'naccumread' Bytes blocking reads:
if (doBlockingRead > 0) {
PsychSerialUnixGlueSetBlockingMinBytes(device, naccumread);
// More than 255 Bytes requested for one read() call?
if (naccumread > 255) {
// The Unix blocking VMIN/VTIME mechanism can only handle blocking/waiting
// for at most 255 bytes, ie., it can't handle this request. We need to resort
// to a polling wait for naccumread bytes :-(
// Enough data available for read of requested granularity?
// If not, we sleep for some time, then retry:
ioctl(device->fileDescriptor, FIONREAD, &navail);
while(navail < naccumread) {
PsychTestCancelThread(&(device->readerThread));
PsychWaitIntervalSeconds(device->pollLatency);
ioctl(device->fileDescriptor, FIONREAD, &navail);
}
// Ok, we've got our share of bytes...
}
}
// Enough data available. Read it!
if ((nread = read(device->fileDescriptor, &(device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)]), naccumread)) != naccumread) {
// Should not happen, unless device is in cooked (canonical) input processing mode, where any read() will
// at most return the content of a single line of buffered input, regardless of requested amount. In this
// case it is not only a perfectly valid and expected result, but also safe, because our memset() above will
// zero-fill the remainder of the buffer with a defined value. For this reason we only output an error
// if high verbosity level is selected for debug output.
// One special case: Zero bytes read due to read timeout exceeded. In this case we just skip and retry,
// instead of storing a complete zero-filled block of readGranularity bytes. Padding is nice, but returning
// completely empty data, which is indistinguishable from a sequence of zeros is not good. This caused serious
// pain when trying to receive one-byte scanner triggers, and it timed out due to scan not yet started and the
// valid scanner trigger bytes are value==zero bytes, indistinguishable by usercode from timeout. This injected
// "ghost triggers" from the usercodes perspective, very bad! Note that the actual timeout case is handled by
// the high level read code by independent checking for timeout. The high level code will hit timeout if we
// hit this "continue" too often, and will return a empty [] variable, reliably signalling to calling code that
// timeout occured - something that can be cleanly handled by caller.
if (nread == 0) continue;
if (verbosity > 5) fprintf(stderr, "PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): Failed to read %i bytes of data for unknown reason (Got only %i bytes)! Padding...\n", naccumread, nread);
}
// Take read completion timestamp:
PsychGetAdjustedPrecisionTimerSeconds(&t);
// Compute timedelta to last read() scan:
dt = t - oldt;
oldt = t;
// Increment serial bytes received counter:
device->asyncReadBytesCount += (nread > 0) ? nread : 0;
// Filtermode for filtering out CR and LF characters active (e.g., for UBW32-Bitwhacker with StickOS)?
if ((device->readFilterFlags & kPsychIOPortCRLFFiltering) &&
((device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)] == 10) ||
(device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)] == 13))) {
// Current read byte is code 10 or 13 aka CR or LF. Discard & Skip:
continue;
}
// Filtermode for CMU button box or PST button box enabled?
if (device->readFilterFlags & kPsychIOPortCMUPSTFiltering) {
// Special input data filter for the CMU button box and the PST button box.
// Both boxes are hillarious masterpieces of totally braindamaged protocol design.
// They send a continous stream of status bytes, at a rate of 1000 Hz (!?!), regardless
// if the status of the box has changed or not, instead of just sending a status update
// when actually something has changed. This creates a lot of load on the host computer
// and a s***load of redundant data. As these shoddy beasts are still sold to customers,
// and quite widespread, we implement special filtering. We check each received byte if
// it matches its predecessor. If so, we discard it, as it is redundant.
if ((device->readerThreadWritePos > 0) && (device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)] == lastcharacter)) {
// Current read byte value is identical to last stored value.
// --> No status change, therefore no reason to store this redundant value.
// We skip processing and wait for the next byte:
continue;
}
else {
// Store current character as "lastcharacter" reference for next iteration:
lastcharacter = device->readBuffer[(device->readerThreadWritePos) % (device->readBufferSize)];
// Store new counter as a 32-bit unsigned int, which may possibly be not 32-bit boundary aligned
// on the target architecture!
*((unsigned int*) &(device->readBuffer[(device->readerThreadWritePos+1) % (device->readBufferSize)])) = (unsigned int) device->asyncReadBytesCount;
// Store dt as a 32 bit unsigned int: It contains dt in microseconds - That resolution should be more than sufficient!
*((unsigned int*) &(device->readBuffer[(device->readerThreadWritePos+5) % (device->readBufferSize)])) = (unsigned int) (dt * 1e6);
}
}
} // End of regular non-linebuffered readop.
// Prevent our cancellation:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
// Store timestamp for this read chunk of data:
device->timeStamps[(device->readerThreadWritePos / device->readGranularity) % (device->readBufferSize / device->readGranularity)] = t;
// Try to lock, block until available if not available:
if ((rc=PsychLockMutex(&(device->readerLock)))) {
// This could potentially kill Matlab, as we're printing from outside the main interpreter thread.
// Use fprintf() instead of the overloaded printf() (aka mexPrintf()) in the hope that we don't
// wreak havoc -- maybe it goes to the system log, which should be safer...
fprintf(stderr, "PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): mutex_lock failed [%s].\n", strerror(rc));
// Commit suicide:
return(NULL);
}
// Update linear write pointer:
device->readerThreadWritePos += device->readGranularity;
// Need to unlock the mutex:
if ((rc=PsychUnlockMutex(&(device->readerLock)))) {
// This could potentially kill Matlab, as we're printing from outside the main interpreter thread.
// Use fprintf() instead of the overloaded printf() (aka mexPrintf()) in the hope that we don't
// wreak havoc -- maybe it goes to the system log, which should be safer...
fprintf(stderr, "PTB-ERROR: In IOPort:PsychSerialUnixGlueReaderThreadMain(): Last mutex_unlock in termination failed [%s].\n", strerror(rc));
// Commit suicide:
return(NULL);
}
// Reenable cancellation:
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
// Next iteration...
}
// Go and die peacefully...
return(NULL);
}
void PsychIOOSShutdownSerialReaderThread(PsychSerialDeviceRecord* device)
{
if (device->readerThread) {
// Cancel the thread:
PsychAbortThread(&(device->readerThread));
// Wait for it to die:
PsychDeleteThread(&(device->readerThread));
// Mark it as dead:
device->readerThread = (psych_thread) NULL;
// Release the mutex:
PsychDestroyMutex(&(device->readerLock));
// Release timestamp buffer:
free(device->timeStamps);
device->timeStamps = NULL;
}
return;
}
/* PsychSerialUnixGlueJLTriggerThreadMain() -- Timed trigger byte emission.
* This is a proof-of-concept prototype for the async-task API of IOPort,
* not for public use by regular users!
*
* Emits a 0xff triggerbyte over serial port at time device->triggerWhen.
*
*/
void* PsychSerialUnixGlueJLTriggerThreadMain( void* deviceToCast)
{
int rc;
unsigned char writedata = 0xff;
double timestamp[4];
char errmsg[256];
errmsg[0] = 0;
// Get a handle to our device struct: These pointers must not be NULL!!!
PsychSerialDeviceRecord* device = (PsychSerialDeviceRecord*) deviceToCast;
// Try to raise our priority: We ask to switch ourselves (NULL) to priority class 2 aka
// realtime scheduling, with a tweakPriority of +2, ie., raise the relative
// priority level by +2 wrt. to the current level:
if ((rc = PsychSetThreadPriority(NULL, 2, 2)) > 0) {
if (verbosity > 0) printf("PTB-ERROR: In IOPort:PsychSerialUnixGlueJLTriggerThreadMain(): Failed to switch to realtime priority [%s]!\n", strerror(rc));
}
// Detach ourselves, so parent thread doesn't need to pthread_join() us:
if (pthread_detach(pthread_self())) printf("PTB-ERROR: In IOPort:PsychSerialUnixGlueJLTriggerThreadMain(): Failed to detach!\n");
// Wait for our target time to come...
PsychWaitUntilSeconds(device->triggerWhen);
if (1 != PsychIOOSWriteSerialPort(device, &writedata, 1, 1, errmsg, timestamp)) {
printf("PTB-ERROR: In IOPort:PsychSerialUnixGlueJLTriggerThreadMain(): Failed to write triggerbyte!\n");
}
// Good enough?
if ((verbosity > 3) && (timestamp[0] - device->triggerWhen > 0.003)) printf("PTB-WARNING: In IOPort:PsychSerialUnixGlueJLTriggerThreadMain(): Trigger emission delayed by up to %f msecs wrt. to deadline!\n", (float) 1000.0 * (timestamp[0] - device->triggerWhen));
// Store write completion timestamp:
device->triggerWhen = timestamp[0];
// Go and die peacefully...
return(NULL);
}
/* PsychIOOSOpenSerialPort()
*
* Open a serial port device and configure it.
*
* portSpec - String with the port name / device name of the serial port device.
* configString - String with port configuration parameters.
* errmsg - Pointer to char[] buffer in which error messages should be returned, if any.
* On success, allocate a PsychSerialDeviceRecord with all relevant settings,
* return a pointer to it.
*
* Otherwise abort with error message.
*/
PsychSerialDeviceRecord* PsychIOOSOpenSerialPort(const char* portSpec, const char* configString, char* errmsg)
{
int fileDescriptor = -1;
struct termios options;
PsychSerialDeviceRecord* device = NULL;
psych_bool usererr = FALSE;
// Init errmsg error message to empty == no error:
errmsg[0] = 0;
// Open the serial port read/write, with no controlling terminal, and don't wait for a connection.
// The O_NONBLOCK flag also causes subsequent I/O on the device to be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(portSpec, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1) {
if (errno == ENOENT) {
sprintf(errmsg, "Error opening serial port device %s - No such serial port device exists! (%d) [ENOENT].\n", portSpec, errno);
usererr = TRUE;
}
else if (errno == EBUSY || errno == EPERM) {
sprintf(errmsg, "Error opening serial port device %s - The serial port is already open, close it first! %s(%d) [EBUSY EPERM]. Could be a permission problem as well.\n", portSpec, strerror(errno), errno);
usererr = TRUE;
}
else {
sprintf(errmsg, "Error opening serial port device %s - %s(%d).\n", portSpec, strerror(errno), errno);
}
goto error;
}
// Note that open() follows POSIX semantics: multiple open() calls to the same file will succeed
// unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned
// processes.
// See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details.
if (ioctl(fileDescriptor, TIOCEXCL) == -1) {
if (verbosity > 1) printf("IOPort-Warning: Error setting exclusive access (TIOCEXCL) on device %s - %s(%d).\n", portSpec, strerror(errno), errno);
// goto error;
}
// Create the device struct and init it:
device = calloc(1, sizeof(PsychSerialDeviceRecord));
device->fileDescriptor = -1;
device->readBuffer = NULL;
device->readBufferSize = 0;
device->readerThread = (psych_thread) NULL;
device->lineTerminator = _POSIX_VDISABLE;
// Get the current options and save them so we can restore the default settings later.
if (tcgetattr(fileDescriptor, &(device->OriginalTTYAttrs)) == -1) {
sprintf(errmsg, "Error getting original device settings for device %s - %s(%d).\n", portSpec, strerror(errno), errno);
goto error;
}
strncpy(device->portSpec, portSpec, sizeof(device->portSpec));
device->fileDescriptor = fileDescriptor;
options = device->OriginalTTYAttrs;
// Ok, the device is basically open. Call the reconfiguration routine with the setting
// string. It will do all further setup work:
if (PsychError_none != PsychIOOSConfigureSerialPort(device, configString)) {
// Something failed. Error handling...
sprintf(errmsg, "Error changing device settings for device %s - %s(%d).\n", portSpec, strerror(errno), errno);
usererr = TRUE;
goto error;
}
if (device->readBuffer == NULL) {
sprintf(errmsg, "Error for device %s - No InputBuffer allocated! You must specify the 'InputBuffer' size in the configuration.\n", portSpec);
usererr = TRUE;
goto error;
}
// printf("MAX_INPUT = %i / %i \n", (int) fpathconf(device->fileDescriptor, _PC_MAX_INPUT), (int) fpathconf(device->fileDescriptor, _PC_MAX_CANON));
// Success! Return Pointer to new device structure:
return(device);
// Failure path: Called on error with error message in errmsg:
error:
if (fileDescriptor != -1) {
// File open.
if (device) {
// Settings retrieved or made?
if (device->fileDescriptor != -1) {
// Have original settings available: Try to restore them:
if (tcsetattr(fileDescriptor, TCSANOW, &options) == -1) {
if (verbosity > 1) printf("WARNING: In error handling: Error restoring tty attributes %s - %s(%d).\n", portSpec, strerror(errno), errno);
}
}
// Release read buffer, if any:
if (device->readBuffer) free(device->readBuffer);
// Release device struct:
free(device);
}
// Close file:
close(fileDescriptor);
}
// Return with error message:
if (verbosity > 0) {
PsychErrorExitMsg(((usererr) ? PsychError_user : PsychError_system), errmsg);
}
return(NULL);
}
/* PsychIOOSCloseSerialPort()
*
* Close serial port connection/device referenced by given 'device' record.
* Free the device record afterwards.
*/
void PsychIOOSCloseSerialPort(PsychSerialDeviceRecord* device)
{
if (device == NULL) PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of valid device pointer!");
PsychIOOSShutdownSerialReaderThread(device);
// Drain all send-buffers:
// Block until all written output has been sent from the device.
// Note that this call is simply passed on to the serial device driver.
// See tcsendbreak(3) ("man 3 tcsendbreak") for details.
if ((!device->dontFlushOnWrite) && (tcdrain(device->fileDescriptor) == -1)) {
if (verbosity > 1) printf("IOPort: WARNING: While trying to close serial port: Error waiting for drain - %s(%d).\n", strerror(errno), errno);
}
// Traditionally it is good practice to reset a serial port back to
// the state in which you found it. This is why the original termios struct
// was saved.
if (tcsetattr(device->fileDescriptor, TCSANOW, &(device->OriginalTTYAttrs)) == -1) {
if (verbosity > 1) printf("IOPort: WARNING: While trying to close serial port: Could not restore original port settings - %s(%d).\n", strerror(errno), errno);
}
// Close device:
close(device->fileDescriptor);
// Release read buffer and/or bounceBuffer, if any:
if (device->readBuffer) free(device->readBuffer);
if (device->bounceBuffer) free(device->bounceBuffer);
// Release memory for device struct:
free(device);
// Done.
return;
}
/* PsychIOOSConfigureSerialPort()
*
* (Re-)configure serial port connection/device referenced by given 'device' record.
* The relevant parameters are stored in 'configString'.
*
* Returns success- or failure status.
*/
PsychError PsychIOOSConfigureSerialPort(PsychSerialDeviceRecord* device, const char* configString)
{
#if PSYCH_SYSTEM == PSYCH_LINUX
struct serial_struct serialstruct;
#endif
int rc;
struct termios options;
int handshake;
char* p;
float infloat;
int inint;
unsigned long mics = 0UL;
psych_bool updatetermios = FALSE;
// The serial port attributes such as timeouts and baud rate are set by modifying the termios
// structure and then calling tcsetattr() to cause the changes to take effect. Note that the
// changes will not become effective without the tcsetattr() call.
// See tcsetattr(4) ("man 4 tcsetattr") for details.
// Retrieve current settings:
if (tcgetattr(device->fileDescriptor, &options) == -1) {
if (verbosity > 0) printf("Error getting current serial port device settings for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(PsychError_system);
}
// Print the current input and output baud rates.
// See tcsetattr(4) ("man 4 tcsetattr") for details.
if (verbosity > 4) {
printf("IOPort-Info: Configuration for device %s:\n", device->portSpec);
printf("IOPort-Info: Current input baud rate is %d\n", ConstantToBaud((int) cfgetispeed(&options)));
printf("IOPort-Info: Current output baud rate is %d\n", ConstantToBaud((int) cfgetospeed(&options)));
}
// Set raw input (non-canonical) mode: No line-based processing or editing of
// received data via special control characters, unless a processing mode of
// cooked was explicitely requested:
if ((p=strstr(configString, "ProcessingMode="))!=NULL) {
if (strstr(p, "ProcessingMode=Cooked")) {
// Enable cooked mode:
device->cookedMode = 1;
updatetermios = TRUE;
// Disable any kind of output processing in cooked mode.
// We only use the input processing facilities of cooked mode!
options.c_oflag &= ~OPOST;
// If no lineTerminator is set, assign it to be the Unix default of ASCII 10 aka LF aka NL:
if (device->lineTerminator == _POSIX_VDISABLE) device->lineTerminator = (unsigned char) 10;
}
else if (strstr(p, "ProcessingMode=Raw")) {
// IOPort default setting must come last.
// Disable cooked mode, switch port to raw ops:
cfmakeraw(&options);
updatetermios = TRUE;
device->cookedMode = 0;
}
else {
// Invalid ProcessingMode:
if (verbosity > 0) printf("Invalid processing mode setting %s not accepted! (Valid are Cooked and Raw).\n", p);
return(PsychError_user);
}
}
// No minimum number of bytes to receive by default. This will get overriden
// in the synchronous and asynchronous (background thread driven) functions
// prior to read() system calls if blocking reads are requested:
options.c_cc[VMIN] = 0;
if ((p = strstr(configString, "ReceiveTimeout="))) {
// Set timeouts for read operations:
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") for details.
if ((1!=sscanf(p, "ReceiveTimeout=%f", &infloat)) || (infloat < 0)) {
if (verbosity > 0) printf("Invalid parameter for ReceiveTimeout set! Typo, or negative value provided.\n");
return(PsychError_user);
}
else {
// Set timeout: It is in granularity of 1/10th seconds, so we need to quantize to 10th of seconds:
inint = (int) (infloat * 10 + 0.5);
// Clamp to a minimum of 0.1 secs if a non-zero timeout is requested:
if ((inint <= 0) && (infloat > 0)) {
if (verbosity > 1) printf("IOPort: Warning: Requested per-byte 'ReceiveTimeout' value %f secs is positive but smaller than supported minimum of 0.1 secs on Unix. Changed value to 0.1 secs.\n", infloat);
infloat = 0.1;
inint = 1;
}
// Clamp to maximum of 25.5 seconds aka 255 10th of seconds. As Unix defines
// VTIME as 1 Byte, we can't go higher than that.
if (inint > 255) {
if (verbosity > 1) printf("IOPort: Warning: Requested per-byte 'ReceiveTimeout' value %f secs is bigger than supported maximum of 25.5 seconds on Unix. Changed value to 25.5 secs.\n", infloat);
infloat = 25.5;
inint = 255;
}
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned ReceiveTimeout= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
options.c_cc[VTIME] = inint;
device->readTimeout = infloat;
updatetermios = TRUE;
}
}
// Assign line terminator if any:
if ((p = strstr(configString, "Terminator="))) {
if (1!=sscanf(p, "Terminator=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for Terminator= set!\n");
return(PsychError_invalidIntegerArg);
}
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned Terminator= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
device->lineTerminator = (unsigned char) inint;
// We also assign the line terminator as VEOL, which will be effective in
// canonical input processing mode, and - maybe - as event character on
// OS/X et al. for FTDI serial converter chips: A negative value for
// LineTerminator disables the feature:
options.c_cc[VEOL] = (inint >= 0) ? (unsigned char) inint : _POSIX_VDISABLE;
// printf("ISNOW %x \n", options.c_cc[VEOL]);
updatetermios = TRUE;
}
// Assign receiver enable state, if any:
if ((p = strstr(configString, "ReceiverEnable="))) {
if (1!=sscanf(p, "ReceiverEnable=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for ReceiverEnable= set!\n");
return(PsychError_invalidIntegerArg);
}
if (inint > 0) {
// Enable receiver:
options.c_cflag |= CREAD;
}
else {
// Disable receiver:
options.c_cflag &= ~CREAD;
}
updatetermios = TRUE;
}
// Set common baud rate for send and receive:
if ((p = strstr(configString, "BaudRate="))) {
if (1!=sscanf(p, "BaudRate=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for BaudRate set!\n");
return(PsychError_invalidIntegerArg);
}
else {
// Set common speed for input- and output queues:
inint = BaudToConstant(inint);
if (-1==cfsetspeed(&options, inint)) {
if (verbosity > 0) printf("Invalid BaudRate %i not accepted! (%s)", inint, strerror(errno));
return(PsychError_invalidIntegerArg);
}
updatetermios = TRUE;
}
}
// Generation and handling of parity bits:
if ((p = strstr(configString, "Parity="))) {
// Clear all parity settings:
options.c_cflag &= ~(PARENB | PARODD);
options.c_iflag &= ~INPCK;
if (strstr(p, "Parity=Even")) {
options.c_cflag |= PARENB;
options.c_iflag |= INPCK;
}
else
if (strstr(p, "Parity=Odd")) {
options.c_cflag |= (PARENB | PARODD);
options.c_iflag |= INPCK;
}
else
if (strstr(p, "Parity=None")) {
// IOPort default setting must come last.
// Nothing to do.
}
else {
// Invalid parity spec:
if (verbosity > 0) printf("Invalid parity setting %s not accepted! (Valid are None, Even and Odd", p);
return(PsychError_user);
}
updatetermios = TRUE;
}
// Handling of Break conditions:
if ((p = strstr(configString, "BreakBehaviour="))) {
// Clear all break settings:
options.c_iflag &= ~(IGNBRK | BRKINT);
if (strstr(p, "BreakBehaviour=Zero")) {
// Don't change anything, ie. IGNBRK and BRKINT not set:
// A break signal will be output as single zero byte.
}
else
if (strstr(p, "BreakBehaviour=Flush")) {
// A break signal will flush the input- and output-queues:
options.c_iflag |= BRKINT;
}
else
if (strstr(p, "BreakBehaviour=Ignore")) {
// IOPort default setting must come last.
// Break signals will be ignored:
options.c_iflag |= IGNBRK;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid break behaviour %s not accepted (Valid: Ignore, Flush, or Zero)!", p);
return(PsychError_user);
}
updatetermios = TRUE;
}
// Handling of data bits:
if ((p = strstr(configString, "DataBits="))) {
// Clear all databit settings:
options.c_cflag &= ~(CS5 | CS6 | CS7 | CS8);
if (strstr(p, "DataBits=5")) {
options.c_cflag |= CS5;
}
else
if (strstr(p, "DataBits=6")) {
options.c_cflag |= CS6;
}
else
if (strstr(p, "DataBits=7")) {
options.c_cflag |= CS7;
}
else
if (strstr(p, "DataBits=8")) {
// IOPort default setting must come last.
options.c_cflag |= CS8;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for data bits %s not accepted! (Valid: 5, 6, 7 or 8 databits)", p);
return(PsychError_user);
}
updatetermios = TRUE;
}
// Handling of stop bits:
if ((p = strstr(configString, "StopBits="))) {
// Clear all stopbit settings:
options.c_cflag &= ~CSTOPB;
if (strstr(p, "StopBits=2")) {
options.c_cflag |= CSTOPB;
}
else
if (strstr(p, "StopBits=1")) {
// IOPort default setting must come last.
// Nothing to do, this is already set above.
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for stop bits %s not accepted! (Valid: 1 or 2 stopbits)", p);
return(PsychError_user);
}
updatetermios = TRUE;
}
// Handling of flow control:
if ((p = strstr(configString, "FlowControl="))) {
// Clear all flow control settings:
options.c_cflag &= ~CRTSCTS;
options.c_iflag &= ~(IXON | IXOFF);
if (strstr(p, "FlowControl=Software")) {
options.c_iflag |= (IXON | IXOFF);
}
else
if (strstr(p, "FlowControl=Hardware")) {
options.c_cflag |= CRTSCTS;
}
else
if (strstr(p, "FlowControl=None")) {
// IOPort default setting must come last.
// Set above already...
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for flow control %s not accepted! (Valid: None, Software, Hardware)", p);
return(PsychError_user);
}
updatetermios = TRUE;
}
// Print the new input and output baud rates. Note that the IOSSIOSPEED ioctl interacts with the serial driver
// directly bypassing the termios struct. This means that the following two calls will not be able to read
// the current baud rate if the IOSSIOSPEED ioctl was used but will instead return the speed set by the last call
// to cfsetspeed.
// Retrieve baudrate and check for equal rate on input- and output queue:
if (verbosity > 1) {
inint = cfgetispeed(&options);
if (inint != (int) cfgetospeed(&options)) printf("IOPort: Warning: Hmm, new input- and output baudrates %i vs. %i don't match!? May or may not be a problem...\n", ConstantToBaud((int) inint), ConstantToBaud((int) cfgetospeed(&options)));
}
// Output new baud rate:
if (verbosity > 4) printf("IOPort-Info: Baud rate changed to %d\n", (int) ConstantToBaud(inint));
// Cause the new options to take effect immediately.
if (updatetermios && (tcsetattr(device->fileDescriptor, TCSANOW, &options) == -1)) {
if (verbosity > 0) printf("Error setting new serial port configuration attributes for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(PsychError_system);
}
// To set the modem handshake lines, use the following ioctls.
// See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details.
// Retrieve current handshake state aka DTR, RTS, CTS, DSR et al.:
handshake = 0;
if (ioctl(device->fileDescriptor, TIOCMGET, &handshake) == -1) {
if (verbosity > 0) printf("Error getting lines status for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
if (strstr(configString, "Lenient") == NULL) return(PsychError_system);
}
else if (verbosity > 4) {
printf("IOPort-Info: Handshake lines currently set to %d : ", handshake);
printf("DTR=%i : DSR=%i : RTS=%i : CTS=%i\n", (handshake & TIOCM_DTR) ? 1:0, (handshake & TIOCM_DSR) ? 1:0, (handshake & TIOCM_RTS) ? 1:0, (handshake & TIOCM_CTS) ? 1:0);
}
// Handling of DTR :
if ((p = strstr(configString, "DTR="))) {
if (strstr(p, "DTR=0")) {
handshake&= ~TIOCM_DTR;
}
else
if (strstr(p, "DTR=1")) {
handshake|= TIOCM_DTR;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for DTR %s not accepted! (Valid: 1 or 0)", p);
return(PsychError_user);
}
}
// Handling of DSR :
if ((p = strstr(configString, "DSR="))) {
if (strstr(p, "DSR=0")) {
handshake&= ~TIOCM_DSR;
}
else
if (strstr(p, "DSR=1")) {
handshake|= TIOCM_DSR;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for DSR %s not accepted! (Valid: 1 or 0)", p);
return(PsychError_user);
}
}
// Handling of RTS :
if ((p = strstr(configString, "RTS="))) {
if (strstr(p, "RTS=0")) {
handshake&= ~TIOCM_RTS;
}
else
if (strstr(p, "RTS=1")) {
handshake|= TIOCM_RTS;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for RTS %s not accepted! (Valid: 1 or 0)", p);
return(PsychError_user);
}
}
// Handling of CTS :
if ((p = strstr(configString, "CTS="))) {
if (strstr(p, "CTS=0")) {
handshake&= ~TIOCM_CTS;
}
else
if (strstr(p, "CTS=1")) {
handshake|= TIOCM_CTS;
}
else {
// Invalid spec:
if (verbosity > 0) printf("Invalid setting for CTS %s not accepted! (Valid: 1 or 0)", p);
return(PsychError_user);
}
}
if (ioctl(device->fileDescriptor, TIOCMSET, &handshake) == -1) {
if (verbosity > 0) printf("Error setting handshake lines status for device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
if (strstr(configString, "Lenient") == NULL) return(PsychError_system);
}
#if PSYCH_SYSTEM == PSYCH_OSX
// OS/X specific implementation:
if ((p = strstr(configString, "ReceiveLatency="))) {
// Set receive latency for low-level driver: Minimum latency between dequeue and notify
// operation on reception of new input data. The granularity is microseconds...
if ((1!=sscanf(p, "ReceiveLatency=%f", &infloat)) || (infloat < 0)) {
if (verbosity > 0) printf("Invalid parameter for ReceiveLatency set! Typo, or negative value provided.\n");
return(PsychError_user);
}
else {
// Set timeout: It is in granularity microseconds, so we need to quantize to microseconds:
mics = (unsigned long) (infloat * 1000000 + 0.5);
// A value of zero would revert to operating system default, so we need to make sure
// the mics value is at least 1 microsecond:
if (mics == 0) mics = 1;
// Set the receive latency in microseconds. Serial drivers use this value to determine how often to
// dequeue characters received by the hardware. Most applications don't need to set this value: if an
// app reads lines of characters, the app can't do anything until the line termination character has been
// received anyway. The most common applications which are sensitive to read latency are MIDI and IrDA
// applications.
if (ioctl(device->fileDescriptor, IOSSDATALAT, &mics) == -1) {
if (verbosity > 0) printf("Error setting receive latency for device %s to %lu microseconds - %s(%d).\n", device->portSpec, mics, strerror(errno), errno);
if (strstr(configString, "Lenient") == NULL) return(PsychError_system);
}
}
}
#endif
#if PSYCH_SYSTEM == PSYCH_LINUX
// Linux specific implementation: Controls low-latency mode. If a value <= 0.001 seconds aka <= 1msec
// is requested, we set the low-latency flag, otherwise we clear it:
if ((p = strstr(configString, "ReceiveLatency="))) {
// Set receive latency for low-level driver: Minimum latency between dequeue and notify
// operation on reception of new input data. The granularity is microseconds...
if ((1!=sscanf(p, "ReceiveLatency=%f", &infloat)) || (infloat < 0)) {
if (verbosity > 0) printf("Invalid parameter for ReceiveLatency set! Typo, or negative value provided.\n");
return(PsychError_user);
}
else {
// Set timeout: It is in granularity microseconds, so we need to quantize to microseconds:
mics = (infloat <= 0.001) ? 1 : 0;
// Retrieve current settings:
if (ioctl(device->fileDescriptor, TIOCGSERIAL, &serialstruct) == -1) {
if (verbosity > 0) printf("Error setting receive latency for device %s to %s latency mode - %s(%d).\n", device->portSpec, ((mics) ? "low" : "normal"), strerror(errno), errno);
if (strstr(configString, "Lenient") == NULL) return(PsychError_system);
}
// Clear or set low-latency flag:
// Setting it enables whatever leads to low receive latency on
// a given driver. In the FTDI serial-converter driver (ftdi_sio.c)
// it forces the chips receive latency timer to its minimum value of
// 1 msec, overriding whatever sysfs value is set. Clearing it will
// enable use of the latency_timer value fro sysfs. Other drivers or
// the higher level subsystem may make other/additional optimizations.
if (mics) {
serialstruct.flags |= ASYNC_LOW_LATENCY;
} else {
serialstruct.flags &= ~ASYNC_LOW_LATENCY;
}
// Set updated settings:
if (ioctl(device->fileDescriptor, TIOCSSERIAL, &serialstruct) == -1) {
if (verbosity > 0) printf("Error setting receive latency for device %s to %s latency mode - %s(%d).\n", device->portSpec, ((mics) ? "low" : "normal"), strerror(errno), errno);
if (strstr(configString, "Lenient") == NULL) return(PsychError_system);
}
}
}
#endif
// Set input buffer size for receive ops:
if ((p = strstr(configString, "InputBufferSize="))) {
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned InputBufferSize= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
if ((1!=sscanf(p, "InputBufferSize=%i", &inint)) || (inint < 1)) {
if (verbosity > 0) printf("Invalid parameter for InputBufferSize set! Typo or requested buffer size smaller than 1 byte.\n");
return(PsychError_invalidIntegerArg);
}
else {
// Set InputBufferSize:
if (device->readBuffer) {
// Buffer already exists. Try to realloc:
p = (char*) realloc(device->readBuffer, inint);
if (p == NULL) {
// Realloc failed:
if (verbosity > 0) printf("Reallocation of Inputbuffer of device %s for new size %i failed! (%s)", device->portSpec, inint, strerror(errno));
return(PsychError_outofMemory);
}
// Worked. Assign new values:
device->readBuffer = (unsigned char*) p;
device->readBufferSize = (unsigned int) inint;
}
else {
// No Buffer yet. Allocate
p = malloc(inint);
if (p == NULL) {
// Realloc failed:
if (verbosity > 0) printf("Allocation of Inputbuffer of device %s of size %i failed! (%s)", device->portSpec, inint, strerror(errno));
return(PsychError_outofMemory);
}
// Worked. Assign new values:
device->readBuffer = (unsigned char*) p;
device->readBufferSize = (unsigned int) inint;
}
// Zerofill, so each page of memory gets touched once and we fault in
// all memory pages to improve realtime behaviour of rt reader thread by
// minimizing / avoiding page-faults:
memset(device->readBuffer, 0, device->readBufferSize);
}
}
if ((p = strstr(configString, "PollLatency="))) {
// Set polling latency for busy-waiting read operations:
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned PollLatency= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
if ((1!=sscanf(p, "PollLatency=%f", &infloat)) || (infloat < 0)) {
if (verbosity > 0) printf("Invalid parameter for PollLatency set! Typo, or negative value provided.\n");
return(PsychError_user);
}
else {
device->pollLatency = infloat;
}
}
if ((p = strstr(configString, "BlockingBackgroundRead="))) {
if (1!=sscanf(p, "BlockingBackgroundRead=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for BlockingBackgroundRead= set!\n");
return(PsychError_invalidIntegerArg);
}
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned BlockingBackgroundRead= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
device->isBlockingBackgroundRead = inint;
}
if ((p = strstr(configString, "ReadFilterFlags="))) {
if (1!=sscanf(p, "ReadFilterFlags=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for ReadFilterFlags= set!\n");
return(PsychError_invalidIntegerArg);
}
// Make sure we don't change non-mutex-protected variables behind the
// back of our readerThread by only allowing this function to be called
// with inactive thread:
if (device->readerThread) {
if (verbosity > 0) printf("Assigned ReadFilterFlags= while background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
device->readFilterFlags = (unsigned int) inint;
}
// Stop a background reader?
if ((p = strstr(configString, "StopBackgroundRead"))) {
PsychIOOSShutdownSerialReaderThread(device);
}
// Async background read via parallel thread requested?
if ((p = strstr(configString, "StartBackgroundRead="))) {
if (1!=sscanf(p, "StartBackgroundRead=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for StartBackgroundRead set!\n");
return(PsychError_invalidIntegerArg);
}
else {
// Set data-fetch granularity for single background read requests:
if (inint < 1) {
if (verbosity > 0) printf("Invalid StartBackgroundRead fetch granularity of %i bytes provided. Must be at least 1 byte!\n", inint);
return(PsychError_invalidIntegerArg);
}
if (device->readBufferSize < (unsigned int) inint) {
if (verbosity > 0) printf("Invalid StartBackgroundRead fetch granularity of %i bytes provided. Bigger than current Inputbuffer size of %i bytes!\n", inint, device->readBufferSize);
return(PsychError_invalidIntegerArg);
}
if ((device->readBufferSize % inint) != 0) {
if (verbosity > 0) printf("Invalid StartBackgroundRead fetch granularity of %i bytes provided. Inputbuffer size of %i bytes is not an integral multiple of fetch granularity as required!\n", inint, device->readBufferSize);
return(PsychError_invalidIntegerArg);
}
if (device->readerThread) {
if (verbosity > 0) printf("Called StartBackgroundRead, but background read operations are already enabled! Disable first via 'StopBackgroundRead'!\n");
return(PsychError_user);
}
// Setup data structures:
device->asyncReadBytesCount = 0;
device->readerThreadWritePos = 0;
device->clientThreadReadPos = 0;
device->readGranularity = inint;
// Warn user if readGranularity is possibly to high for system to handle properly without weird side-effects:
if ((device->readGranularity > 255) && (verbosity > 1)) printf("IOPort: WARNING: In call to 'StartBackgroundRead', requested read granularity of %i bytes exceeds maximum safe size of 255 Bytes.\nThis can cause malfunctions or unexpected behaviour/data loss on some systems with some device drivers!\n", device->readGranularity);
// Allocate sufficiently large timestamp buffer:
device->timeStamps = (double*) calloc(sizeof(double), device->readBufferSize / device->readGranularity);
// Create & Init the mutex:
if ((rc=PsychInitMutex(&(device->readerLock)))) {
printf("PTB-ERROR: In StartBackgroundRead(): Could not create readerLock mutex lock [%s].\n", strerror(rc));
return(PsychError_system);
}
// Perform lock->unlock mutex sequence to inject some memory ordering barriers here, so all our
// settings are picked up by the newborn thread:
if ((rc=PsychLockMutex(&(device->readerLock))) || (rc=PsychUnlockMutex(&(device->readerLock)))) {
printf("PTB-ERROR: In StartBackgroundRead(): Could not lock + unlock readerLock mutex lock [%s].\n", strerror(rc));
return(PsychError_system);
}
// Create and startup thread:
if ((rc=PsychCreateThread(&(device->readerThread), NULL, PsychSerialUnixGlueReaderThreadMain, (void*) device))) {
printf("PTB-ERROR: In StartBackgroundRead(): Could not create background reader thread [%s].\n", strerror(rc));
return(PsychError_system);
}
}
}
if ((p = strstr(configString, "DontFlushOnWrite="))) {
if (1!=sscanf(p, "DontFlushOnWrite=%i", &inint)) {
if (verbosity > 0) printf("Invalid parameter for DontFlushOnWrite= set!\n");
return(PsychError_invalidIntegerArg);
}
device->dontFlushOnWrite = inint;
}
// Proof-of-concept test code: Not for public use!
// Async triggerbyte emission via parallel thread requested?
if ((p = strstr(configString, "JLFireTrigger="))) {
if (1!=sscanf(p, "JLFireTrigger=%f", &infloat)) {
if (verbosity > 0) printf("Invalid parameter for JLFireTrigger set!\n");
return(PsychError_user);
}
else {
// Store target time in device struct:
device->triggerWhen = (double) infloat;
// Create and startup trigger thread: It will detach itself from us, do
// its job and then die lonely and forgotten without us caring:
psych_thread threadhandle;
if ((rc=PsychCreateThread(&threadhandle, NULL, PsychSerialUnixGlueJLTriggerThreadMain, (void*) device))) {
printf("PTB-ERROR: In JLFireTrigger(): Could not create background trigger thread [%s].\n", strerror(rc));
return(PsychError_system);
}
}
}
// Done.
return(PsychError_none);
}
/* PsychIOOSWriteSerialPort()
*
* Write data to serial port:
* writedata = Ptr. to data buffer of uint8 values to write.
* amount = Buffersize aka amount of bytes to write.
* blocking = 0 --> Async, 1 --> Flush buffer and wait for write completion.
* errmsg = Pointer to char array where error messages should be put to.
* timestamp = Pointer to a 4 element vectorof double values where write-completion timestamps should be put to.
* [0] = Final write completion timestamp.
* [1] = Timestamp immediately before write() submission.
* [2] = Timestamp immediately after write() submission.
* [3] = Timestamp of last check for write completion.
*
* Returns number of bytes written, or -1 on error.
*/
int PsychIOOSWriteSerialPort(PsychSerialDeviceRecord* device, void* writedata, unsigned int amount, int blocking, char* errmsg, double* timestamp)
{
int nwritten;
unsigned int lsr = 0; // Serial transmitter line status register.
int outqueue_pending; // Pending bytes in output queue.
// Nonblocking mode?
if (blocking <= 0) {
// Yep. Set filedescriptor to non-blocking mode:
// Set the O_NONBLOCK flag so subsequent I/O will not block.
// See fcntl(2) ("man 2 fcntl") for details.
if (PsychSerialUnixGlueFcntl(device, O_NONBLOCK) == -1) {
sprintf(errmsg, "Error setting O_NONBLOCK on device %s for non-blocking writes - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Write the data: Take pre- and postwrite timestamps.
PsychGetAdjustedPrecisionTimerSeconds(×tamp[1]);
if ((nwritten = write(device->fileDescriptor, writedata, amount)) == -1) {
sprintf(errmsg, "Error during write to device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
PsychGetAdjustedPrecisionTimerSeconds(×tamp[2]);
}
else {
// Nope. Set filedescriptor to blocking mode:
// Clear the O_NONBLOCK flag so subsequent I/O will block.
// See fcntl(2) ("man 2 fcntl") for details.
if (PsychSerialUnixGlueFcntl(device, 0) == -1) {
sprintf(errmsg, "Error clearing O_NONBLOCK on device %s for blocking writes - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Write the data: Take pre- and postwrite timestamps.
PsychGetAdjustedPrecisionTimerSeconds(×tamp[1]);
if ((nwritten = write(device->fileDescriptor, writedata, amount)) == -1) {
sprintf(errmsg, "Error during write to device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
PsychGetAdjustedPrecisionTimerSeconds(×tamp[2]);
// Special polling mode to wait for transmit completion instead of tcdrain() + wait?
if (blocking == 2) {
// Yes. Use a tight polling loop which spin-waits until the driver output queue is empty (contains zero pending bytes):
outqueue_pending = 1;
while (outqueue_pending > 0) {
// Take timestamp for this iteration:
PsychGetAdjustedPrecisionTimerSeconds(×tamp[3]);
// Poll:
ioctl(device->fileDescriptor, TIOCOUTQ, &outqueue_pending);
}
}
else if ((PSYCH_SYSTEM == PSYCH_LINUX) && (blocking == 3)) {
// Yes. Use a tight polling loop which spin-waits on the transmitter idle flag:
// TODO: This may be not what we want, because the transmitter might be temporarily
// idle although there is data pending in the higher-level write buffers. A more
// proper combo might be to have blocking==2 polling for empty queue followed by
// this call, so both conditions must be met.
#if PSYCH_SYSTEM == PSYCH_LINUX
// ioctl supported by device?
if (ioctl(device->fileDescriptor, TIOCSERGETLSR, &lsr)==-1) {
// Error! Can't use this method!
sprintf(errmsg, "Error during write to device %s while trying to use blocking mode 3 to wait for write completion - %s(%d).\nMaybe function unsupported by device??\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Seems to work. Go into the spin-waiting loop:
while (!(lsr & TIOCSER_TEMT)) {
// Take timestamp for this iteration:
PsychGetAdjustedPrecisionTimerSeconds(×tamp[3]);
// Poll:
ioctl(device->fileDescriptor, TIOCSERGETLSR, &lsr);
}
#else
(void) lsr;
#endif
}
else {
// Take timestamp for completeness although it doesn't make much sense in the blocking case:
PsychGetAdjustedPrecisionTimerSeconds(×tamp[3]);
// Flush the write buffer and wait for write completion on physical hardware:
if ((!device->dontFlushOnWrite) && (tcdrain(device->fileDescriptor) == -1)) {
sprintf(errmsg, "Error during write to device %s while draining the write buffers - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
}
}
// Write successfully completed if we reach this point. Take timestamp, clear error message, return:
PsychGetAdjustedPrecisionTimerSeconds(×tamp[0]);
errmsg[0] = 0;
return(nwritten);
}
int PsychIOOSReadSerialPort(PsychSerialDeviceRecord* device, void** readdata, unsigned int amount, int blocking, char* errmsg, double* timestamp)
{
double timeout;
int raPos, i;
int nread = 0;
int gotamount, reqamount;
unsigned char* tmpbuffer;
*readdata = NULL;
// Clamp 'amount' of data to be read to receive buffer size:
if (amount > device->readBufferSize) {
// Too much. Is amount unspecified aka INT_MAX? In that case,
// we'll clamp it to readbuffers size. Otherwise we abort with
// error.
if (amount == INT_MAX) {
// Clamp to buffersize:
amount = device->readBufferSize;
}
else {
// amount specified and impossible to satisfy request at
// current readbuffer size. Abort with error and tell
// user to resize readbuffer:
sprintf(errmsg, "Amount of requested data %i is more than device %s can satisfy, as its input buffer is too small (%i bytes).\nSet a bigger readbuffer size please.\n", amount, device->portSpec, device->readBufferSize);
return(-1);
}
}
// Nonblocking mode?
if (blocking <= 0) {
// Background read active?
if (device->readerThread) {
// Just return what is available in the internal buffer:
nread = PsychSerialUnixGlueAsyncReadbufferBytesAvailable(device);
}
else {
// Set filedescriptor to non-blocking mode:
// Set the O_NONBLOCK flag so subsequent I/O will not block.
// See fcntl(2) ("man 2 fcntl") for details.
if (PsychSerialUnixGlueFcntl(device, O_NONBLOCK) == -1) {
sprintf(errmsg, "Error setting O_NONBLOCK on device %s for non-blocking read - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Read the data, at most 'amount' bytes, nonblocking:
if ((nread = read(device->fileDescriptor, device->readBuffer, amount)) == -1) {
// Nothing to read? In nonblocking mode we simply return zero bytes read:
if (errno == EAGAIN) return(0);
// Some other error:
sprintf(errmsg, "Error during non-blocking read from device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
}
}
else {
// Nope. This is a blocking read request:
// Background read active?
if (device->readerThread) {
// Poll until requested amount of data is available:
// TODO FIXME: Could do this in a more friendly way by using condition
// variables, so we sleep until async reader thread signals availability
// of sufficient data...
PsychGetAdjustedPrecisionTimerSeconds(&timeout);
*timestamp = timeout;
timeout+=device->readTimeout;
while((*timestamp < timeout) && (PsychSerialUnixGlueAsyncReadbufferBytesAvailable(device) < (int) amount)) {
PsychGetAdjustedPrecisionTimerSeconds(timestamp);
PsychWaitIntervalSeconds(device->pollLatency);
}
// Return amount of available data:
nread = PsychSerialUnixGlueAsyncReadbufferBytesAvailable(device);
}
else {
// Set filedescriptor to blocking mode:
// Clear the O_NONBLOCK flag so subsequent I/O will block.
// See fcntl(2) ("man 2 fcntl") for details.
if (PsychSerialUnixGlueFcntl(device, 0) == -1) {
sprintf(errmsg, "Error clearing O_NONBLOCK on device %s for blocking read - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Init start position for read to store data to the start of readBuffer:
tmpbuffer = device->readBuffer;
// Perform repeated read() requests in small chunks until the full 'amount'
// of requested data has been read() (or error, timeout etc.). We need to
// perform polling/blocking reads() in relatively small chunks because the
// size of the serial port receive queue is limited to a few kilobytes at
// best - often less, so data can easily get lost or we can deadlock on
// reads() > maxqueuesize if we don't do it in < queuesize chunks:
while (amount > 0) {
// Perform pull iteration:
// Setup minimum byte counter for 'naccumread' Bytes blocking reads:
gotamount = PsychSerialUnixGlueSetBlockingMinBytes(device, amount);
if ((int) amount != gotamount) {
// Didn't get what we wanted: Error!
// Unless this is just the case where we clamp to the 255 bytes max on Unix:
if (!(gotamount == 255 && amount > 255)) {
sprintf(errmsg, "Error setting wanted minimum amount of bytes %i on device %s for blocking read - %s(%d). Got %i instead!\n", amount, device->portSpec, strerror(errno), errno, gotamount);
return(-1);
}
}
// Skip this polling for first byte if BlockingBackgroundRead is non-zero:
if (device->isBlockingBackgroundRead == 0) {
// Define timeout deadline for poll:
PsychGetAdjustedPrecisionTimerSeconds(&timeout);
*timestamp = timeout;
// Timeout is one interbyte timeout:
timeout += device->readTimeout;
// Wait polling until timeout or 1 byte available:
while((*timestamp < timeout) && (PsychIOOSBytesAvailableSerialPort(device) < 1)) {
PsychGetAdjustedPrecisionTimerSeconds(timestamp);
PsychWaitIntervalSeconds(device->pollLatency);
}
if (PsychIOOSBytesAvailableSerialPort(device) < 1) {
// Ok, first byte for this read() iteration didn't arrive within timeout period.
// Simply break out of while() loop to terminate this read.
break;
}
}
// Read the data, at most (and at least, unless timeout occurs) 'gotamount' bytes, blocking:
reqamount = gotamount;
if ((gotamount = read(device->fileDescriptor, tmpbuffer, reqamount)) == -1) {
sprintf(errmsg, "Error during blocking read from device %s - %s(%d).\n", device->portSpec, strerror(errno), errno);
return(-1);
}
// Successfully read nread >= 0 bytes.
// Advance read() storebuffer pointer and decrement count of pending data to read:
tmpbuffer+= gotamount;
amount -= gotamount;
nread += gotamount;
// Is a line-terminator set, the device in cooked input processing mode and the last read character is the lineterminator?
if ((gotamount > 0) && (device->cookedMode > 0) && (device->lineTerminator == *(tmpbuffer - 1))) {
// Line terminator detected in cooked mode: Break out of read() loop!
break;
}
// read() prematurely completed due to insufficient amount of data available and
// interbyte-timeout occured? If so, we break out of the pull-loop:
if (gotamount < reqamount) break;
// Next pull loop iteration...
} // Try to read() next chunk of data.
// Done with this blocking read.
// Reset minbytes back to zero:
PsychSerialUnixGlueSetBlockingMinBytes(device, 0);
}
}
// Read successfully completed if we reach this point. Clear error message:
errmsg[0] = 0;
// Was this a fetch-op from an active background read?
if (device->readerThread) {
// Yes.
// Check for buffer overflow:
if (nread > (int) device->readBufferSize) {
sprintf(errmsg, "Error: Readbuffer overflow for background read operation on device %s. Flushing buffer to recover. At least %i bytes of input data have been lost, expect data corruption!\n", device->portSpec, nread);
// Flush readBuffer - Try to get a fresh start...
PsychLockMutex(&(device->readerLock));
// Set read pointer to current write pointer, effectively emptying the buffer:
device->clientThreadReadPos = device->readerThreadWritePos;
// Unlock data structure:
PsychUnlockMutex(&(device->readerLock));
// Return error code:
return(-1);
}
// Clamp available amount to requested (or maximum allowable) amount:
nread = (nread > (int) amount) ? amount : nread;
// Compute startindex in buffer for readout operation:
raPos = (device->clientThreadReadPos) % (device->readBufferSize);
// Exceeding the end of the buffer?
if (raPos + nread - 1 >= (int) device->readBufferSize) {
// Yes: Need an intermediate bounce-buffer to make this safe:
if (device->bounceBufferSize < nread) {
free(device->bounceBuffer);
device->bounceBuffer = NULL;
device->bounceBufferSize = 0;
}
if (NULL == device->bounceBuffer) {
// (Re-)allocate bounceBuffer. Allocate at least 4096 Bytes, ie., one memory page.
// Anything smaller would be just plain inefficient speed-wise:
device->bounceBufferSize = (nread < 4096) ? 4096 : nread;
device->bounceBuffer = (unsigned char*) calloc(sizeof(unsigned char), device->bounceBufferSize);
}
// Copy data to bounce-buffer, take wraparound in readBuffer into account:
for(i = 0; i < nread; i++, raPos++) device->bounceBuffer[i] = device->readBuffer[raPos % (device->readBufferSize)];
*readdata = (void*) device->bounceBuffer;
}
else {
// No: Can directly copy from readBuffer, starting at raPos:
*readdata = (void*) &(device->readBuffer[raPos]);
}
// Retrieve timestamp for this read chunk of data:
*timestamp = device->timeStamps[(device->clientThreadReadPos / device->readGranularity) % (device->readBufferSize / device->readGranularity)];
// Update of read-pointer:
device->clientThreadReadPos += nread;
}
else {
// No. Standard read:
// Take timestamp of read completion:
PsychGetAdjustedPrecisionTimerSeconds(timestamp);
// Assign returned data:
*readdata = (void*) device->readBuffer;
}
return(nread);
}
int PsychIOOSBytesAvailableSerialPort(PsychSerialDeviceRecord* device)
{
int navail = 0;
if (device->readerThread) {
// Async reader poll: Calculate what is available in the internal buffer:
navail = PsychSerialUnixGlueAsyncReadbufferBytesAvailable(device);
}
else {
// Regular poll: Perform query to system:
if (ioctl(device->fileDescriptor, FIONREAD, &navail)!=0) {
if (verbosity > 0) printf("Error during 'BytesAvailable': ioctl - FIONREAD byte available query on device %s returned %s(%d)\n", device->portSpec, strerror(errno), errno);
return(-1);
}
}
return(navail);
}
void PsychIOOSFlushSerialPort(PsychSerialDeviceRecord* device)
{
if (tcdrain(device->fileDescriptor)!=0) {
if (verbosity > 0) printf("Error during 'Flush': tcdrain() on device %s returned %s(%d)\n", device->portSpec, strerror(errno), errno);
}
return;
}
void PsychIOOSPurgeSerialPort(PsychSerialDeviceRecord* device)
{
if (tcflush(device->fileDescriptor, TCIOFLUSH)!=0) {
if (verbosity > 0) printf("Error during 'Purge': tcflush(TCIFLUSH) on device %s returned %s(%d)\n", device->portSpec, strerror(errno), errno);
}
if (device->readerThread) {
// Purge the input buffer of async reader thread as well:
// Lock data structure:
PsychLockMutex(&(device->readerLock));
// Set read pointer to current write pointer, effectively emptying the buffer:
// It is important to not modify the write pointer, only the read pointer, otherwise
// we would have an ugly race-condition within the writer thread, as the writer thread
// does not mutex-lock during reading the writeposition pointer, only during updating
// the writeposition pointer!
device->clientThreadReadPos = device->readerThreadWritePos;
// Unlock data structure:
PsychUnlockMutex(&(device->readerLock));
}
return;
}
#endif
|