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
|
/*
* PsychToolbox/Source/Common/PsychHID/PsychHIDReceiveReports.c
*
* PROJECTS: PsychHID
*
* PLATFORMS: All
*
* AUTHORS:
*
* denis.pelli@nyu.edu dgp
* mario.kleiner.de@gmail.com mk
*
* HISTORY:
*
* 4/7/05 dgp Wrote it, based on PsychHIDGetReport.c
*
* READ:
* bugs in mac os x retrieval of reports.
* http://lists.apple.com/archives/usb/2004/Jul/msg00003.html
* $900 USB bus analyzer
* http://lists.apple.com/archives/usb/2003/Dec/msg00011.html
* guido kolber reports problem with getting port
* http://lists.apple.com/archives/usb/2004/Nov/msg00062.html
* usb mailing list:
* http://lists.apple.com/mailman/listinfo/usb
* header file with helpful comments:
* http://darwinsource.opendarwin.org/10.3.6/IOHIDFamily-86.21/IOHIDLib/IOHIDDeviceClass.h
* my query on the Apple usb list:
* http://lists.apple.com/archives/usb/2005/Apr/msg00050.html
* HID documentation:
* http://developer.apple.com/documentation/DeviceDrivers/Conceptual/HID/
* How to set up notification callback when a device is attached
* or removed:
* file:///Developer/Examples/IOKit/usb/Another%20USB%20Notification%20Example/USBNotificationExample.c
* http://developer.apple.com/samplecode/USBPrivateDataSample/USBPrivateDataSample.html
*
* NOTE ON GETREPORT:
* 4 April 2005. Implementing SetReport, to send a report to the
* device, was straightforward, just a matter of calling Apple's HID
* Utilities function HIDSetReport. Implementing "GetReport", to
* receive a report from the device, was arduous. I quickly
* implemented a call to Apple's HIDGetReport, and it worked with my
* keyboard and mouse, but it never worked with my PMD-1208FS. The
* GetReport request always resulted in a "stall", indicating that the
* PMD-1208FS was rejecting the request. This baffled me. Last friday,
* the firmware engineer, Nick, who programmed the PMD-1208FS,
* explained to me that I shouldn't use the GetReport command because
* that goes out on the USB bus, making a request to the device, which
* is too slow a way of communicating, so the device doesn't support
* it. This surprised me. I had supposed until then that GetReport
* merely grabbed whatever report had already been received by the
* host. Following a suggestion of Allen Ingling's I looked again in
* Apple's HID Manager documentation and discovered that the command
* one should use is in fact setInterruptReportHandlerCallback.
* However, the text explaining what this does and how to call it is
* very skimpy, not enough for me to figure it out. Fortunately
* googling setInterruptReportHandlerCallback turned up several helpful
* discussions on apple's usb developer forum. Remarkably, last
* november, the CEO of Code Mercenaries, who make IOWarrior, presented
* an abbreviated listing of exactly the code I needed, about five
* lines, asking why it broke in Mac OS 10.3.6. With apple's answer I
* was able to finish writing my code. I then had to read about a Mach
* feature in Mac OS X called a CFRunLoop, since
* setInterruptReportHandlerCallback implements the callback by
* installing a "source" in that loop. After about a day's fiddling it
* now works.
*/
/*
* file:///Developer/ADC%20Reference%20Library/documentation/Darwin/Reference/IOKit/IOHIDLib/Classes/IOHIDDeviceInterface122/CompositePage.html#//apple_ref/doc/compositePage/c/func/setInterruptReportHandlerCallback
*
* IOHIDDeviceInterface::
*
* IOHIDReportCallbackFunction
*
* typedef void (*IOHIDReportCallbackFunction) (
* void * target,
* IOReturn result,
* void * refcon,
* void * sender,
* UInt32 bufferSize);
*
*
* Type and arguments of callout C function that is used when a completion routine is called, see IOHIDLib.h:setReport().
*
*
* Parameter Descriptions
*
* target
* void * pointer to your data, often a pointer to an object.
*
* result
* Completion result of desired operation.
*
* refcon
* void * pointer to more data.
*
* sender
* Interface instance sending the completion routine.
*
* bufferSize
* Size of the buffer received upon completion.
*/
#include "PsychHID.h"
// internal prototypes
PsychError ReceiveReportsStop(int deviceIndex); // function is below.
void CountReports(char *string);
typedef struct ReportStruct {
int deviceIndex;
long int error;
psych_uint32 bytes;
double time;
//int type; // 1=input, 2=output, 3=feature
struct ReportStruct *next;
psych_uint8 *report;
} ReportStruct;
static psych_bool firstTimeInit = TRUE; // TRUE at PsychHID load & init time and after shutdown. FALSE during operation.
// These are out here for easy access by several routines in this file.
static psych_bool oneShotRealloc = FALSE;
static psych_bool ready[MAXDEVICEINDEXS];
static psych_bool optionsPrintReportSummary = 0; // options.print: Enable diagnostic print of report by ReportCallback.
static psych_bool optionsPrintCrashers = 0; // options.printCrashers
static psych_bool optionsConsistencyChecks = 0; // options.consistencyChecks
static int optionsMaxReports = 10000; // options.maxReports
static int optionsMaxReportSize = 65; // options.maxReportSize
static double optionsSecs = 0.010; // options.secs
// These are out here for easy access by my report callback function: ReportCallback.
static ReportStruct *freeReportsPtr[MAXDEVICEINDEXS]; // Per device linked list of free hid input reports.
static ReportStruct *deviceReportsPtr[MAXDEVICEINDEXS]; // Per device linked list of filled hid input reports.
static ReportStruct *allocatedReports[MAXDEVICEINDEXS]; // Per device linked list storage - list is tightly packed in memory.
static psych_bool reportsHaveBeenAllocated[MAXDEVICEINDEXS]; // Allocated flag.
static int MaxDeviceReports[MAXDEVICEINDEXS]; // Per device number of total reports.
static int MaxDeviceReportSize[MAXDEVICEINDEXS]; // Per device max size of each report.
psych_uint8 * reportData[MAXDEVICEINDEXS]; // Per device buffer for all reports databuffers, tightly packed.
// Set by PsychHIDSetReport, read by ReportCallback solely for the optionsPrintReportSummary.
double AInScanStart = 0;
#if PSYCH_SYSTEM == PSYCH_OSX
#include <IOKit/HID/IOHIDLib.h>
void CheckRunLoopSource(int deviceIndex,char *caller,int line);
static CFRunLoopSourceRef source[MAXDEVICEINDEXS];
static CFStringRef myRunLoopMode=NULL; // CFSTR("myMode");
void ReportCallback(void *target,IOReturn result,void *refcon,void *sender,psych_uint32 bufferSize); // function is below.
void ReportCallback(void *target,IOReturn result,void *refcon,void *sender,psych_uint32 bufferSize)
{
int deviceIndex, i, n, m;
unsigned char *ptr;
ReportStruct *r;
CountReports("ReportCallback beginning.");
deviceIndex = (long int) refcon;
if (deviceIndex < 0 || deviceIndex >= MAXDEVICEINDEXS) {
printf("ReportCallback received out-of-range deviceIndex %d. Aborting.\n", deviceIndex);
return;
}
// take report from free list.
if (freeReportsPtr[deviceIndex] == NULL) {
// Darn. We're full. It might be elegant to discard oldest report, but for now, we'll just ignore the new one.
printf("ReportCallback warning. No more free reports. Discarding new report.\n");
return;
}
r = freeReportsPtr[deviceIndex];
freeReportsPtr[deviceIndex] = r->next;
r->next = NULL;
// install report into the device's list.
r->next = deviceReportsPtr[deviceIndex];
deviceReportsPtr[deviceIndex] = r;
// fill in the rest of the report struct
r->error = result;
r->bytes = bufferSize;
r->deviceIndex = deviceIndex;
ptr = target;
// Clamp amount of returned data to global and per-device limit:
if (bufferSize > MAXREPORTSIZE) bufferSize = MAXREPORTSIZE;
if (bufferSize > MaxDeviceReportSize[deviceIndex]) bufferSize = MaxDeviceReportSize[deviceIndex];
// Copy data:
for(i = 0; i < bufferSize; i++) r->report[i] = *(ptr+i);
PsychGetPrecisionTimerSeconds(&r->time);
if (optionsPrintReportSummary) {
// print diagnostic summary of the report
int serial;
serial=r->report[62]+256*r->report[63]; // 32-bit serial number at end of AInScan report from PMD-1208FS
printf("Got input report %4d: %2ld bytes, dev. %d, %4.0f ms. ",serial,(long)r->bytes,deviceIndex,1000*(r->time-AInScanStart));
if(r->bytes>0){
printf(" report ");
n=r->bytes;
if(n>6)n=6;
for(i=0;i<n;i++)printf("%3d ",(int)r->report[i]);
m=r->bytes-2;
if(m>i){
printf("... ");
i=m;
}
for(;i<r->bytes;i++)printf("%3d ",(int)r->report[i]);
}
printf("\n");
}
CountReports("ReportCallback end.");
return;
}
PsychError ReceiveReports(int deviceIndex)
{
long error=0;
pRecDevice device;
IOHIDDeviceInterface122** interface=NULL;
int reason; // kCFRunLoopRunFinished, kCFRunLoopRunStopped, kCFRunLoopRunTimedOut, kCFRunLoopRunHandledSource
PsychHIDVerifyInit();
if(deviceIndex < 0 || deviceIndex >= MAXDEVICEINDEXS) PrintfExit("Sorry. Can't cope with deviceNumber %d (more than %d). Please tell denis.pelli@nyu.edu",deviceIndex,(int)MAXDEVICEINDEXS-1);
// Allocate report buffers if needed:
PsychHIDAllocateReports(deviceIndex);
CountReports("ReceiveReports beginning.");
if (freeReportsPtr[deviceIndex] == NULL) PrintfExit("No free reports.");
device=PsychHIDGetDeviceRecordPtrFromIndex(deviceIndex);
if(!HIDIsValidDevice(device))PrintfExit("PsychHID: Invalid device.\n");
interface = PsychHIDGetDeviceInterfacePtrFromIndex(deviceIndex);
if(interface==NULL)PrintfExit("PsychHID: No interface for device.\n");
CheckRunLoopSource(deviceIndex,"ReceiveReports",__LINE__);
if (!ready[deviceIndex]) {
// setInterruptReportHandlerCallback
static unsigned char buffer[MAXREPORTSIZE];
psych_uint32 bufferSize=MAXREPORTSIZE;
psych_bool createSource;
createSource=(source[deviceIndex]==NULL);
if(createSource){
if(optionsPrintCrashers && createSource)printf("%d: createAsyncEventSource\n",deviceIndex);
error=(*interface)->createAsyncEventSource(interface,&(source[deviceIndex]));
if(error)PrintfExit("ReceiveReports - createAsyncEventSource error 0x%lx.",error);
if(0 && optionsPrintCrashers && createSource)
printf("%d: source %4.4lx validity %d, CFRunLoopContainsSource is %d.\n",deviceIndex,(unsigned long)source[deviceIndex]
,CFRunLoopSourceIsValid(source[deviceIndex])
,CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode));
}
if(optionsPrintCrashers && createSource)printf("%d: getAsyncEventSource\n",deviceIndex);
CheckRunLoopSource(deviceIndex,"ReceiveReports",__LINE__);
if(optionsPrintCrashers && createSource)printf("%d: CFRunLoopAddSource\n",deviceIndex);
CFRunLoopAddSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode);
if(0 && optionsPrintCrashers && createSource)printf("%d: source %4.4lx validity %d, CFRunLoopContainsSource is %d.\n",deviceIndex,(unsigned long)source[deviceIndex]
,CFRunLoopSourceIsValid(source[deviceIndex])
,CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode));
ready[deviceIndex]=1;
CheckRunLoopSource(deviceIndex,"ReceiveReports",__LINE__);
if(optionsPrintCrashers && createSource)printf("%d: setInterruptReportHandlerCallback\n",deviceIndex);
error=(*interface)->setInterruptReportHandlerCallback(interface,buffer,bufferSize,ReportCallback,buffer,(void *)(long int) deviceIndex);
if(error)PrintfExit("ReceiveReports - setInterruptReportHandlerCallback error 0x%lx.",error);
if(optionsPrintCrashers && createSource)printf("%d: CFRunLoopRunInMode.\n",deviceIndex);
}
//printf("%d: CFRunLoopRunInMode\n",deviceIndex);
reason=CFRunLoopRunInMode(myRunLoopMode,optionsSecs,false);
if(reason!=kCFRunLoopRunTimedOut && reason!=kCFRunLoopRunHandledSource){
char *s;
switch(reason){
case kCFRunLoopRunFinished: s="kCFRunLoopRunFinished"; break;
case kCFRunLoopRunStopped: s="kCFRunLoopRunStopped"; break;
case kCFRunLoopRunTimedOut: s="kCFRunLoopRunTimedOut"; break;
case kCFRunLoopRunHandledSource: s="kCFRunLoopRunHandledSource"; break;
default: s="of unknown reason.";
}
printf("RunLoop ended at %.3f s because %s.\n",CFAbsoluteTimeGetCurrent()-AInScanStart,s);
}
CountReports("ReceiveReports end.");
return error;
}
PsychError ReceiveReportsStop(int deviceIndex)
{
CheckRunLoopSource(deviceIndex,"ReceiveReportsStop",__LINE__);
if (ready[deviceIndex]) {
// Rob Yepez, at Apple, suggested that it might be better to call CFRunLoopRemoveSource than CFRunLoopSourceInvalidate.
// He's right. There's no problem in re-enabling the callback after using CFRunLoopRemoveSource.
// The source remains valid and can be added again to the run loop. Don't create it again.
if(myRunLoopMode==NULL)myRunLoopMode=CFSTR("myMode");; // kCFRunLoopDefaultMode
if(0 && optionsPrintCrashers)printf("%d: CFRunLoopRemoveSource\n",deviceIndex);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode);// kCFRunLoopDefaultMode
if(0 && optionsPrintCrashers)printf("%d: source %4.4lx validity %d, CFRunLoopContainsSource is %d.\n",deviceIndex,(unsigned long)source[deviceIndex]
,CFRunLoopSourceIsValid(source[deviceIndex])
,CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode));
ready[deviceIndex]=0;
}
CheckRunLoopSource(deviceIndex,"ReceiveReportsStop",__LINE__);
return 0;
}
/* PsychHIDReceiveReportsCleanup(void) -- Called at PsychHID shutdown time:
*
* 1. one must call CFRunLoopSourceInvalidate to kill the callback established by setInterruptReportHandlerCallback
* before calling HIDReleaseDeviceList.
*
* 2. after calling setInterruptReportHandlerCallback to enable the callback and CFRunLoopSourceInvalidate to disable
* it, it is then impossible to re-enable the callback with that source. To later re-enable, simply remove the source, instead of
* invalidating it. Once i've called CFRunLoopSourceInvalidate it appears that my only option is to release the interface by calling
* HIDReleaseDeviceList and start over.
*
*/
PsychError PsychHIDReceiveReportsCleanup(void)
{
int deviceIndex;
//printf("Clean up before PsychHID is flushed.\n");
for(deviceIndex=0;deviceIndex<MAXDEVICEINDEXS;deviceIndex++) if(source[deviceIndex]!=NULL) {
CheckRunLoopSource(deviceIndex,"PsychHIDReceiveReportsCleanup",__LINE__);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode);// kCFRunLoopDefaultMode
if (optionsPrintCrashers) printf("%d: CFRunLoopSourceInvalidate\n",deviceIndex);
CFRunLoopSourceInvalidate(source[deviceIndex]);
if(optionsPrintCrashers) printf("%d: source %4.4lx validity %d, CFRunLoopContainsSource is %d.\n",deviceIndex,(unsigned long)source[deviceIndex]
,CFRunLoopSourceIsValid(source[deviceIndex])
,CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode));
ready[deviceIndex]=0;
CheckRunLoopSource(deviceIndex,"PsychHIDReceiveReportsCleanup",__LINE__);
source[deviceIndex]=NULL;
}
// Release all report linked lists, memory buffers etc.:
PsychHIDReleaseAllReportMemory();
return 0;
}
void CheckRunLoopSource(int deviceIndex,char *caller,int line){
CFRunLoopSourceRef currentSource;
pRecDevice device;
IOHIDDeviceInterface122** interface = NULL;
// Skip this function if hardcore debugging is not enabled:
if (!optionsPrintCrashers) return;
device=PsychHIDGetDeviceRecordPtrFromIndex(deviceIndex);
if(!HIDIsValidDevice(device))PrintfExit("PsychHID: Invalid device.\n");
interface = PsychHIDGetDeviceInterfacePtrFromIndex(deviceIndex);
if(interface==NULL)PrintfExit("PsychHID: No interface for device.\n");
currentSource=(*interface)->getAsyncEventSource(interface);
if(source[deviceIndex] != currentSource)printf("%s (%d): source[%d] %4.4lx != current source %4.4lx.\n"
,caller,line,(int)deviceIndex,(unsigned long)source[deviceIndex],(unsigned long)currentSource);
if(ready[deviceIndex] && (source[deviceIndex]!=NULL) && !CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode))
printf("%d: %s(%d): \"ready\" but source not in CFRunLoop.\n",(int)deviceIndex,caller,line);
if(!ready[deviceIndex] && (source[deviceIndex]!=NULL) && CFRunLoopContainsSource(CFRunLoopGetCurrent(),source[deviceIndex],myRunLoopMode))
printf("%d: %s(%d): \"!ready\" yet source is in CFRunLoop.\n",(int)deviceIndex,caller,line);
}
#else
// NON OSX CODE (Linux, Windows):
// ==============================
extern hid_device* source[MAXDEVICEINDEXS];
extern hid_device* last_hid_device;
/* Do all the report processing for all devices: Iterates in a fetch loop
* until error condition, or a maximum allowable processing time of
* optionSecs seconds has been exceeded.
*
* Calls hidlib function hid_read() to get reports, one at a time, enqueues
* it in our own reports lists for later retrieval by 'GiveMeReports' or
* 'GiveMeReport'.
*
*/
PsychError ReceiveReports(int deviceIndex)
{
int rateLimit[MAXDEVICEINDEXS] = { 0 };
double deadline, now;
pRecDevice device;
int n, m;
unsigned int i;
ReportStruct *r;
long error = 0;
PsychHIDVerifyInit();
if(deviceIndex < 0 || deviceIndex >= MAXDEVICEINDEXS) PrintfExit("Sorry. Can't cope with deviceNumber %d (more than %d). Please tell denis.pelli@nyu.edu",deviceIndex, (int) MAXDEVICEINDEXS-1);
// Allocate report buffers if needed:
PsychHIDAllocateReports(deviceIndex);
CountReports("ReceiveReports beginning.");
if (freeReportsPtr[deviceIndex] == NULL) PrintfExit("No free reports.");
// Enable this device for hid report reception:
ready[deviceIndex] = TRUE;
PsychGetAdjustedPrecisionTimerSeconds(&now);
deadline = now + optionsSecs;
// Iterate until deadline reached or no more pending reports to process:
while ((error == 0) && (now <= deadline)) {
// Iterate over all active devices:
for (deviceIndex = 0; deviceIndex < MAXDEVICEINDEXS; deviceIndex++) {
// Test for timeout:
PsychGetAdjustedPrecisionTimerSeconds(&now);
if (now > deadline) break;
// Skip this device if it isn't enabled to receive hid reports:
if (!ready[deviceIndex]) continue;
// Free target report buffers?
if(freeReportsPtr[deviceIndex] == NULL) {
// Darn. We're full. It might be elegant to discard oldest report, but for now, we'll just ignore the new one.
if (!rateLimit[deviceIndex]) printf("PsychHID: WARNING! ReportCallback warning. No more free reports for deviceIndex %i. Discarding new report.\n", deviceIndex);
rateLimit[deviceIndex] = 1;
continue;
}
// Handle one report for this device:
CountReports("ReportCallback beginning.");
device = PsychHIDGetDeviceRecordPtrFromIndex(deviceIndex);
last_hid_device = (hid_device*) device->interface;
// Get a report struct to fill in:
r = freeReportsPtr[deviceIndex];
// Fetch the actual data: Bytes fetched, or zero for no reports available, or
// -1 for error condition.
r->error = hid_read((hid_device*) device->interface, &(r->report[0]), MaxDeviceReportSize[deviceIndex]);
// Skip remainder if no data received:
if (r->error == 0) continue;
// Ok, we got something, even if it is only an error code. Need
// to move the (r)eport from the free list to the received list:
freeReportsPtr[deviceIndex] = r->next;
r->next=NULL;
// install report into the device's list.
r->next = deviceReportsPtr[deviceIndex];
deviceReportsPtr[deviceIndex] = r;
// fill in the rest of the report struct
r->deviceIndex = deviceIndex;
// Timestamp processing:
PsychGetPrecisionTimerSeconds(&r->time);
// Success or error?
if (r->error > 0) {
// Success: Reset error, assign size of retrieved report:
r->bytes = r->error;
r->error = 0;
}
else {
// Error: No data assigned.
r->bytes = 0;
// Signal error return code -1:
error = -1;
// Abort fetch loop:
break;
}
if (optionsPrintReportSummary) {
// print diagnostic summary of the report
int serial;
serial = r->report[62] + 256 * r->report[63]; // 32-bit serial number at end of AInScan report from PMD-1208FS
printf("Got input report %4d: %2ld bytes, dev. %d, %4.0f ms. ", serial, (long) r->bytes, deviceIndex, 1000 * (r->time - AInScanStart));
if(r->bytes>0) {
printf(" report ");
n = r->bytes;
if (n > 6) n=6;
for(i=0; i < (unsigned int) n; i++) printf("%3d ", (int) r->report[i]);
m = r->bytes - 2;
if (m > (int) i) {
printf("... ");
i = m;
}
for(; i < r->bytes; i++) printf("%3d ", (int) r->report[i]);
}
printf("\n");
}
CountReports("ReportCallback end.");
}
}
CountReports("ReceiveReports end.");
return error;
}
PsychError ReceiveReportsStop(int deviceIndex)
{
pRecDevice device;
PsychHIDVerifyInit();
// Disable HID report reception:
ready[deviceIndex] = FALSE;
device = PsychHIDGetDeviceRecordPtrFromIndex(deviceIndex);
last_hid_device = (hid_device*) device->interface;
if (device->interface) hid_close((hid_device*) device->interface);
device->interface = NULL;
return 0;
}
PsychError PsychHIDReceiveReportsCleanup(void)
{
// Release all report linked lists, memory buffers etc.:
PsychHIDReleaseAllReportMemory();
return 0;
}
#endif
// OS INDEPENDENT CODE:
// ====================
void PsychHIDReleaseAllReportMemory(void)
{
int deviceIndex;
for(deviceIndex = 0; deviceIndex < MAXDEVICEINDEXS; deviceIndex++) {
if (!firstTimeInit && reportsHaveBeenAllocated[deviceIndex]) {
free(allocatedReports[deviceIndex]);
free(reportData[deviceIndex]);
}
// Reset all stuff that needs to be reset at PsychHID init and shutdown:
freeReportsPtr[deviceIndex] = NULL;
deviceReportsPtr[deviceIndex] = NULL;
allocatedReports[deviceIndex] = NULL;
reportData[deviceIndex] = NULL;
MaxDeviceReports[deviceIndex] = 0;
MaxDeviceReportSize[deviceIndex] = 0;
reportsHaveBeenAllocated[deviceIndex] = FALSE;
source[deviceIndex] = NULL;
ready[deviceIndex] = FALSE;
}
// Reset defaults:
optionsMaxReports = 10000; // options.maxReports
optionsMaxReportSize = 65; // options.maxReportSize
optionsSecs = 0.010; // options.secs
oneShotRealloc = FALSE;
// Toggle firstTimeInit - If this was a shutdown, next call will be init,
// if it was an init, next call will be a shutdown:
firstTimeInit = !firstTimeInit;
}
void PsychHIDAllocateReports(int deviceIndex)
{
int i;
ReportStruct *r;
// Reallocation of report buffers requested by caller?
if (oneShotRealloc) {
// Yes. Reset flag, as this is a one-shot query:
oneShotRealloc = FALSE;
// Anything allocated that needs to be reallocated?
if(reportsHaveBeenAllocated[deviceIndex]) {
// Yes. Device stopped? Otherwise this is a no-go:
if (ready[deviceIndex]) {
// No-No:
printf("PTB-WARNING:PsychHID:ReceiveReports: Tried to set new option.maxReportSize or option.maxReports on deviceIndex %i while report\n", deviceIndex);
printf("PTB-WARNING:PsychHID:ReceiveReports: processing is active. Call PsychHID('ReceiveReportsStop', %i); first to release old reports!\n", deviceIndex);
} else {
// Release all databuffers, so they get reallocated below:
free(allocatedReports[deviceIndex]);
free(reportData[deviceIndex]);
freeReportsPtr[deviceIndex] = NULL;
deviceReportsPtr[deviceIndex] = NULL;
allocatedReports[deviceIndex] = NULL;
reportData[deviceIndex] = NULL;
MaxDeviceReports[deviceIndex] = 0;
MaxDeviceReportSize[deviceIndex] = 0;
// Done. Code below will realloc with current settings:
reportsHaveBeenAllocated[deviceIndex] = FALSE;
}
}
}
// Reports for device already allocated? Do so if not:
if (!reportsHaveBeenAllocated[deviceIndex]) {
// Initial set up. Allocate free reports.
if (optionsMaxReports < 1)
optionsMaxReports = 1;
// Allocate common buffer to store linked list of all
// ReportStruct's, tightly packed:
allocatedReports[deviceIndex] = (ReportStruct*) calloc(optionsMaxReports, sizeof(ReportStruct));
if (NULL == allocatedReports[deviceIndex]) PsychErrorExitMsg(PsychError_outofMemory, "Out of memory while trying to allocate hid reports!");
// Allocate common buffer to store actual report data
// referenced by ReportStruct's, tightly packed:
reportData[deviceIndex] = (psych_uint8*) calloc(optionsMaxReports, optionsMaxReportSize);
if (NULL == reportData[deviceIndex]) {
// Failed. Free previous allocations:
free(allocatedReports[deviceIndex]);
allocatedReports[deviceIndex] = NULL;
PsychErrorExitMsg(PsychError_outofMemory, "Out of memory while trying to allocate hid report data buffers!");
}
// Ok, we have memory allocated. Set it up:
// Store max number of reports and max size of reports
// for this device, as defined and allocated from global setting:
MaxDeviceReports[deviceIndex] = optionsMaxReports;
MaxDeviceReportSize[deviceIndex] = optionsMaxReportSize;
// Setup pointer mappings to create the linked-list in the memory buffers:
freeReportsPtr[deviceIndex] = allocatedReports[deviceIndex];
r = &(allocatedReports[deviceIndex][0]);
for(i = 0; i < optionsMaxReports; i++) {
// Setup linked-list pointers for linked list of ReportStruct's
// insie the allocatedReports[deviceIndex] buffer:
r=&(allocatedReports[deviceIndex][i]);
r->next=&(allocatedReports[deviceIndex][i+1]);
// Setup pointer to associated actual HID report data buffer
// inside the reportData[deviceIndex] buffer:
r->report = &(reportData[deviceIndex][i * optionsMaxReportSize]);
}
r->next=NULL;
reportsHaveBeenAllocated[deviceIndex] = TRUE;
}
}
void CountReports(char *string)
{
int i, l1, l2;
ReportStruct *r;
// First time init at first invocation after PsycHID load time:
#if PSYCH_SYSTEM == PSYCH_OSX
if (myRunLoopMode==NULL) myRunLoopMode=CFSTR("myMode"); // kCFRunLoopDefaultMode
#endif
// Optional consistency check, disabled by default. Do the numbers of
// reports enqueued in the different device lists and the free list
// sum up to the total number of allocated reports? Print warning and
// current numbers if this is not the case:
if (optionsConsistencyChecks > 0) {
for(i = 0; i < MAXDEVICEINDEXS; i++) {
r = deviceReportsPtr[i];
l1 = 0;
while(r != NULL) {
r = r->next;
l1++;
}
r = freeReportsPtr[i];
l2 = 0;
while(r != NULL) {
r = r->next;
l2++;
}
if((l1 + l2) != MaxDeviceReports[i]) {
printf("%s", string);
printf(" device:reports. free:%3d, %2d:%3d",l2, i,l1);
printf("\n");
}
}
}
}
// GiveMeReports is called solely by PsychHIDGiveMeReports, but the code resides here
// in PsychHIDReceiveReports because it uses the typedefs and static variables that
// are defined solely in this file. The linked lists of reports are unknown outside of this file.
PsychError GiveMeReports(int deviceIndex,int reportBytes)
{
PsychGenericScriptType *outReports;
const char *fieldNames[] = {"report", "device", "time"};
ReportStruct *r, *rTail = NULL;
PsychGenericScriptType *fieldValue;
unsigned char *reportBuffer = NULL;
int i, n;
unsigned int j;
long error = 0;
double now;
CountReports("GiveMeReports beginning.");
r = deviceReportsPtr[deviceIndex];
n = 0;
while (r != NULL) {
n++;
rTail = r;
r = r->next;
}
PsychAllocOutStructArray(1, kPsychArgRequired, n, 3, fieldNames, &outReports);
r = deviceReportsPtr[deviceIndex];
PsychGetPrecisionTimerSeconds(&now);
for (i = n-1; i >= 0; i--) {
if (r->error)
error = r->error;
//printf("%2d: r->bytes %2d, reportBytes %4d, -%4.1f s\n",i,(int)r->bytes,(int)reportBytes, now-r->time);
if (r->bytes > (unsigned int) reportBytes)
r->bytes = reportBytes;
reportBuffer = NULL;
PsychAllocateNativeUnsignedByteMat(1, r->bytes, 1, (psych_uint8**) &reportBuffer, &fieldValue);
for(j = 0; j < r->bytes; j++)
reportBuffer[j] = r->report[j];
PsychSetStructArrayNativeElement("report", i, fieldValue, outReports);
PsychSetStructArrayDoubleElement("device", i, (double) r->deviceIndex, outReports);
PsychSetStructArrayDoubleElement("time", i, r->time, outReports);
r = r->next;
}
if (deviceReportsPtr[deviceIndex] != NULL && rTail) {
// transfer all these now-obsolete reports to the free list
rTail->next = freeReportsPtr[deviceIndex];
freeReportsPtr[deviceIndex] = deviceReportsPtr[deviceIndex];
deviceReportsPtr[deviceIndex] = NULL;
}
CountReports("GiveMeReports end.");
return error;
}
// Called solely by PsychHIDGetReport, but resides here in order to access the linked list of reports.
PsychError GiveMeReport(int deviceIndex,psych_bool *reportAvailablePtr,unsigned char *reportBuffer,psych_uint32 *reportBytesPtr,double *reportTimePtr)
{
ReportStruct *r,*rOld;
long error;
unsigned int i;
CountReports("GiveMeReport beginning.");
r=deviceReportsPtr[deviceIndex];
if(r!=NULL){ // report available?
// grab the oldest report for this device
*reportAvailablePtr=1;
if(r->next==NULL){
deviceReportsPtr[deviceIndex]=NULL;
}else{
while(r->next->next!=NULL)r=r->next;
rOld=r;
r=r->next;
rOld->next=NULL;
}
if(*reportBytesPtr > r->bytes)*reportBytesPtr=r->bytes;
for(i=0;i<*reportBytesPtr;i++)reportBuffer[i]=r->report[i];
*reportTimePtr=r->time;
error=r->error;
// add it to the free list
r->next=freeReportsPtr[deviceIndex];
freeReportsPtr[deviceIndex]=r;
}else{
*reportAvailablePtr=0;
*reportBytesPtr=0;
*reportTimePtr=0.0;
error=0;
}
CountReports("GiveMeReport end.");
return error;
}
/* OS independent entry point from PsychHID(): */
static char useString[] = "err=PsychHID('ReceiveReports',deviceNumber[,options])";
static char synopsisString[] =
"Receive and save, internally, all reports from the specified USB HID device, now and forever more (unless stopped).\n"
"Some parameters are persistent. When you don't explicitly supply a value they retain whatever value they had last time. "
"They do have an initial default (built-in) that is present when PsychHID is first called after being CLEARed. "
"Non-persistent parameters have a fixed default that applies every time you call PsychHID.\n"
"The returned value \"err.n\" is zero upon success and a nonzero error code upon failure, "
"as spelled out by \"err.name\" and \"err.description\". "
"\"deviceNumber\" specifies which device.\n"
"\"options.print\" =1 (initial default 0) enables diagnostic printing of a summary of each report when our callback routine receives it.\n"
"\"options.printCrashers\" =1 (initial default 0) enables diagnostic printing of the creation of the callback source and its addition to the CFRunLoop.\n"
"\"options.consistencyChecks\" =1 (initial default 0) enables diagnostic printing of the consistency of all report structs. Very time consuming!\n"
"\"options.maxReports\" (initial default 10000) allocate space for at least this many reports for the given device.\n"
"\"options.maxReportSize\" (initial default 65) allocate this many bytes per report. Most HID devices don't send HID reports of more than "
"64 Bytes, so allowing for one extra byte for the reportID, a default of 65 Bytes is usually sufficient. If you need more, you can increase "
"this value up to 8192 Bytes. If you need even more, contact us, because likely you are doing something wrong. Smaller values than 65 may "
"make sense if you are very tight on memory.\n"
"\"options.secs\" (initial default 0.010 s) is how long to allow the function to process reports received from all active HID devices. "
"The operating system receives reports all the time after the first call to 'ReceiveReports' or 'GetReport'. "
"It has a small buffer capacity, discarding the oldest received reports if its small buffer is full. When requested by PsychHID, the OS "
"tranfers reports to PsychHID (for all devices for which ReceiveReports is still active). "
"Thus reports are received from the OS only during your call to ReceiveReports or GetReport (GetReport implies an automatic call to ReceiveReports). "
"You should call ReceiveReports frequently to avoid losing reports. "
"Reports can be received from multiple devices during a single call to ReceiveReports. "
"Calling ReceiveReports enables callbacks (forever) for the incoming reports from that device; "
"call ReceiveReportsStop to halt acquisition of further reports for a device; "
" you can resume acquisition for a device by calling ReceiveReports again. "
"Call GiveMeReports to get all the received reports and empty PsychHID's internal store for a device. "
"PsychHID can hold up to options.maxReports reports, and discards new incoming reports when it has no room to hold them. "
"For prolonged data acquisition you may need to call GiveMeReports periodically, emptying PsychHID's store before it becomes full.\n"
"PsychHID was enhanced by adding HID commands to send and receive HID reports to support the PMD-1208FS. "
"PsychHID is likely to work with other HID-compliant USB devices as well.\n"
"The device-specific programming for the PMD-1208FS is entirely in the MATLAB M files of the Daq Toolbox; "
"what is specific to the different operating systems is (hopefully) entirely in PsychHID. "
"PsychHID is entirely generic, following the HID standard; none of the internal code is specific to any device, "
"but we tested it primarily with the PMD-1208FS, "
"as well as keyboards, mice, and gamepads, so working with new devices may be an adventure. ";
static char seeAlsoString[] = "SetReport, ReceiveReportsStop, GiveMeReports";
PsychError PSYCHHIDReceiveReports(void)
{
int deviceIndex;
long error = 0;
const PsychGenericScriptType *mxOptions, *mx;
PsychPushHelp(useString, synopsisString, seeAlsoString);
if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };
PsychErrorExit(PsychCapNumOutputArgs(1));
PsychErrorExit(PsychCapNumInputArgs(2));
PsychCopyInIntegerArg(1, TRUE, &deviceIndex);
if (deviceIndex < 0 || deviceIndex >= MAXDEVICEINDEXS)
PrintfExit("Sorry. Can't cope with deviceNumber %d (more than %d). Please tell denis.pelli@nyu.edu", deviceIndex, (int) MAXDEVICEINDEXS - 1);
/*
* "\"options.print\" =1 (default 0) enables diagnostic printing of a summary of each report when our callback routine receives it. "
* "\"options.printCrashers\" =1 (default 0) enables diagnostic printing of the creation of the callback source and its addition to the CFRunLoop. "
* "\"options.maxReports\" (default 10000) allocate space for at least this many reports, shared among all devices. "
* "\"options.maxReportSize\" (default 65) allocate this many bytes per report. "
*/
// optionsPrintReportSummary = 0; // options.print
// optionsPrintCrashers = 0; // options.printCrashers
// optionsMaxReports = 10000; // options.maxReports
// optionsMaxReportSize = 65; // options.maxReportSize
// optionsSecs = 0.010; // options.secs
mxOptions = PsychGetInArgPtr(2);
if (mxOptions != NULL) {
if (PsychGetArgType(2) != PsychArgType_structArray)
PsychErrorExitMsg(PsychError_user, "PsychHID ReceiveReports: 'options' arg is not a struct, as required.");
mx = mxGetField(mxOptions, 0, "print");
if (mx != NULL)
optionsPrintReportSummary = (psych_bool) mxGetScalar(mx);
mx = mxGetField(mxOptions, 0, "printCrashers");
if (mx != NULL)
optionsPrintCrashers = (psych_bool) mxGetScalar(mx);
mx = mxGetField(mxOptions, 0, "secs");
if (mx != NULL) optionsSecs = mxGetScalar(mx);
mx = mxGetField(mxOptions, 0, "consistencyChecks");
if (mx != NULL) optionsConsistencyChecks = (psych_bool) mxGetScalar(mx);
// Changing maxReports or maxReportSize triggers a reallocation of
// buffer memory:
mx = mxGetField(mxOptions, 0, "maxReports");
if (mx != NULL) {
oneShotRealloc = TRUE;
optionsMaxReports = (int) mxGetScalar(mx);
}
mx = mxGetField(mxOptions, 0, "maxReportSize");
if (mx != NULL) {
oneShotRealloc = TRUE;
optionsMaxReportSize = (int) mxGetScalar(mx);
}
}
// Sanity check:
if (optionsMaxReports < 1)
PsychErrorExitMsg(PsychError_user, "PsychHID ReceiveReports: Sorry, requested maxReports count must be at least 1!");
if (optionsMaxReportSize < 1)
PsychErrorExitMsg(PsychError_user, "PsychHID ReceiveReports: Sorry, requested maxReportSize must be at least 1 byte!");
if (optionsMaxReportSize > MAXREPORTSIZE) {
printf("PsychHID ReceiveReports: Sorry, requested maximum report size %d bytes exceeds built-in maximum of %d bytes.\n", optionsMaxReportSize, (int) MAXREPORTSIZE);
PsychErrorExitMsg(PsychError_user, "Invalid option.maxReportSize provided!");
}
// Start reception of reports: This will also allocate memory for the reports
// on first invocation for this deviceIndex:
error = ReceiveReports(deviceIndex);
{
PsychGenericScriptType *outErr;
char *name = "", *description = "";
const char *fieldNames[] = {"n", "name", "description"};
PsychHIDErrors(NULL, error, &name, &description); // Get error name and description, if available.
PsychAllocOutStructArray(1, kPsychArgOptional, -1, 3, fieldNames, &outErr);
PsychSetStructArrayDoubleElement("n", 0, (double) error, outErr);
PsychSetStructArrayStringElement("name", 0, name, outErr);
PsychSetStructArrayStringElement("description", 0, description, outErr);
}
return(PsychError_none);
}
|