-
Notifications
You must be signed in to change notification settings - Fork 16
/
rd.rex
executable file
·2626 lines (2409 loc) · 94.9 KB
/
rd.rex
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
/*REXX*/
/* RDD! HID Report Descriptor Decoder v2.0
Copyright (c) 2011-2020, Andrew J. Armstrong
All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Author:
Andrew J. Armstrong <androidarmstrong@gmail.com>
*/
trace off
parse arg sCommandLine
numeric digits 16
g. = '' /* global variables */
k. = '' /* global constants */
o. = '' /* global options */
f. = '' /* global field names */
call Prolog sCommandLine
if getOption('--version')
then do
say getVersion()
return
end
if getOption('--codes')
then do
call showCodes
return
end
o.0HELP = getOption('--help')
if o.0HELP | sCommandLine = ''
then do
parse source . . sThis .
say getVersion()
say
say 'This will read a USB Human Interface Device (HID) report descriptor from the'
say 'specified input file then attempt to decode it and, optionally, create a'
say 'C language header file from it. It also does some minimal sanity checks'
say 'to verify that the report descriptor is valid. The input file can be a'
say 'binary file or a text file (for example, an existing C header file). If'
say 'it is a text file, it will concatenate all the printable-hex-like text'
say 'that it finds on each line (until the first non-hex sequence is found)'
say 'into a single string of hex digits, and then attempt to decode that string.'
say 'You can feed it an existing C header file and it will decode it as long'
say 'as you have all the hex strings (e.g. 0x0F, 0x0Fb2) at the beginning of'
say 'each line. Commas (,) and semicolons (;) are ignored. Specify the --right'
say 'command line option if the hex strings are on the rightmost part of each line.'
say
say 'Usage:'
say ' rexx' sThis '[-h format] [-i fileinc] [-o fileout] [-dsvxOrb] -f filein'
say ' or:'
say ' rexx' sThis '[-h format] [-i fileinc] [-o fileout] [-dsvxO] -c hex'
say
say 'Where:'
say ' filein = Input file path to be decoded'
say ' fileout = Output file (default is console)'
say ' fileinc = Include file of PAGE/USAGE definitions'
say ' hex = Printable hex to be decoded from command line'
say ' format = Type of output C header file format:'
say ' AVR - AVR style'
say ' MIKROC - MikroElektronika mikroC Pro for PIC style'
say ' MCHIP - Microchip C18 style'
do i = 1 to g.0OPTION_INDEX.0
say ' 'left(strip(g.0OPTION_SHORT.i g.0OPTION_LONG.i),16) '=' g.0OPTION_DESC.i
end
say ' -vv = Modifies --all so that even array field indices that'
say ' have blank usage descriptions are listed'
say
say 'Prerequisites:'
say ' You need a REXX interpreter installed, such as'
say ' 1. Regina REXX (http://regina-rexx.sourceforge.net)'
say ' 2. Open Object REXX (http://www.oorexx.org/)'
say
say 'Examples:'
say ' rexx' sThis '-d --hex 05010906 A1010508 19012903 15002501 75019503 91029505 9101 C0'
say ' ...decodes the given hex string. Spaces are not significant'
say
say ' rexx' sThis '-sc 05010906 A1010508 19012903 15002501 75019503 91029505 9101 C0'
say ' ...generates C structure declarations for the given hex string'
say
say ' rexx' sThis '-d -f myinputfile.h -o myoutputfile.txt'
say ' ...decodes the hex strings found in myinputfile.h into myoutputfile.txt'
say
say ' rexx' sThis 'myinputfile.h'
say ' ...generates C structure declarations for the hex strings found in myinputfile.h'
say
say ' rexx' sThis '--include mybuttonmap.txt myinputfile.h'
say ' ...generates C structure declarations for the hex strings found in myinputfile.h'
say ' using vendor-defined usages defined in mybuttonmap.txt'
say
say ' rexx' sThis '-dr usblyzer.txt'
say ' ...decodes the hex strings found on the rightmost side of each line of the'
say ' usblyzer.txt input file'
return
end
o.0BINARY = getOption('--binary')
o.0VERBOSITY = getOption('--verbose')
o.0STRUCT = getOption('--struct')
o.0DECODE = getOption('--decode')
o.0HEADER = toUpper(getOption('--header',1))
o.0DUMP = getOption('--dump')
o.0OUTPUT = getOption('--output',1)
o.0RIGHT = getOption('--right')
o.0ALL = getOption('--all')
o.0OPT = getOption('--opt')
if o.0OUTPUT <> ''
then do
if \openFile(o.0OUTPUT,'WRITE REPLACE')
then do
say 'Could not open output file: --output' sFileOut'. Will use console instead'
o.0OUTPUT = '' /* console */
end
end
if \(o.0DECODE | o.0STRUCT | o.0DUMP) /* If neither --decode nor --struct nor --dump was specified */
then o.0STRUCT = 1 /* then assume --struct was specified */
sData = ''
select
when getOptionCount('--file') > 0 then sFile = getOption('--file',1)
when getOptionCount('--hex') > 0 then sData = getOption('--hex',1)
otherwise sFile = g.0REST /* assume command line is the name of the input file */
end
xData = readDescriptor(sFile,sData)
if xData = ''
then do /* try reading hex values from the rightmost end of each line */
o.0RIGHT = 1 /* force the --right option */
xData = readDescriptor(sFile,sData,'quiet')
end
if o.0DUMP
then do
call emitHeading 'Report descriptor data in hex (length' length(xData)/2 'bytes)'
call say
call dumpHex xData
call say
end
featureField.0 = 0
inputField.0 = 0
outputField.0 = 0
sCollectionStack = ''
g.0INDENT = 0
sData = x2c(xData)
nIndent = 0
nByte = 1
do while nByte <= length(sData)
sItem = getNext(1)
sTag = bitand(sItem,'11110000'b)
sType = bitand(sItem,'00001100'b)
sSize = bitand(sItem,'00000011'b)
nSize = c2d(sSize)
if nSize = 3 then nSize = 4
select
when sSize = '00000000'b then sParm = ''
when sSize = '00000001'b then sParm = getNext(1)
when sSize = '00000010'b then sParm = getNext(2)
otherwise sParm = getNext(4)
end
xItem = c2x(sItem)
xParm = c2x(sParm)
sValue = reverse(sParm) /* 0xllhh --> 0xhhll */
sMeaning = ''
select
when sType = k.0TYPE.MAIN then call processMAIN
when sType = k.0TYPE.GLOBAL then call processGLOBAL
when sType = k.0TYPE.LOCAL then call processLOCAL
otherwise call emitDecode xItem,xParm,'ERROR',,,'<-- Error: Item ('xItem') is not a MAIN, GLOBAL or LOCAL item'
end
end
if sCollectionStack <> ''
then say 'Error: Missing END_COLLECTION MAIN tag (0xC0)'
call Epilog
return
getVersion: procedure
parse value sourceline(2) with . sVersion
return sVersion
readDescriptor: procedure expose g. k. o.
parse arg sFile,sData,sQuiet
if sData <> ''
then do
xData = space(sData,0)
if \isHex(xData)
then do
say 'Expecting printable hexadecimal data. Found:' sData
xData = ''
end
end
else do
xData = ''
if openFile(sFile)
then do
if o.0BINARY
then do
sData = charin(sFile, 1, chars(sFile))
xData = c2x(sData)
end
else do
do while chars(sFile) > 0
sLine = linein(sFile)
sLine = translate(sLine,'',',;${}') /* Ignore some special chars */
if o.0RIGHT
then do /* scan from right to left for hex */
xLine = ''
do i = words(sLine) to 1 by -1
sWord = word(sLine,i)
select
when left(sWord,2) = '0x' then sWord = substr(sWord,3)
when left(sWord,1) = "'" then do
sWord = strip(sWord,'BOTH',"'")
if isHex(sWord)
then sWord = c2x(sWord)
end
otherwise nop
end
if isHex(sWord)
then xLine = sWord || xLine /* prepend any hex data found */
else leave /* stop when the first non-hexadecimal value is found */
end
xData = xData || xLine
end
else do /* scan from left to right for hex */
parse var sLine sDefine sIdentifier sValue .
if sDefine = '#define' & isIdentifier(sIdentifier)
then select
when left(sValue,2) = '0x' then g.0DEFINE.sIdentifier = substr(sValue,3) /* 0xYYYY */
when isDec(sValue) then g.0DEFINE.sIdentifier = d2x(sValue,2) /* YYY */
when isChar(sValue) then g.0DEFINE.sIdentifier = c2x(substr(sValue,2,1)) /* 'Y' */
when isString(sValue) then g.0DEFINE.sIdentifier = strip(sValue,'BOTH','"') /* "YYYY" */
otherwise nop
end
else do i = 1 to words(sLine)
sWord = strip(word(sLine,i),'TRAILING',',')
select
when g.0DEFINE.sWord <> '' then sWord = g.0DEFINE.sWord
when left(sWord,2) = '0x' then sWord = substr(sWord,3)
when left(sWord,1) = "'" then do
sWord = strip(sWord,'BOTH',"'")
if isHex(sWord)
then sWord = c2x(sWord)
end
otherwise nop
end
if isHex(sWord)
then xData = xData || sWord /* append any hex data found */
else leave /* stop when the first non-hexadecimal value is found */
end
end
end
end
rc = closeFile(sFile)
end
else do
if sQuiet <> 'quiet'
then say 'Could not open input file: --file "'sFile'"'
end
end
return xData
isIdentifier: procedure
arg firstletter +1 0 name
bIsIdentifier = verify(firstletter,'ABCDEFGHIJKLMNOPQRSTUVWXYZ_','NOMATCH') = 0,
& verify(name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789','NOMATCH') = 0
return bIsIdentifier
processMAIN:
xValue = right(c2x(sValue),8,'0')
select
when sTag = k.0MAIN.INPUT then do
sFlags = getInputFlags()
call emitDecode xItem,xParm,'MAIN','INPUT',xValue,getDimension(g.0REPORT_COUNT, g.0REPORT_SIZE) sFlags getSanity(sValue)
if g.0IN_NAMED_ARRAY_COLLECTION = 1
then g.0USAGE = g.0NAMED_ARRAY_USAGE
n = inputField.0 + 1
inputField.n = xValue getGlobals()','getLocals()','g.0USAGES','sFlags','f.0COLLECTION_NAME
inputField.0 = n
call clearLocals
end
when sTag = k.0MAIN.OUTPUT then do
sFlags = getOutputFlags()
call emitDecode xItem,xParm,'MAIN','OUTPUT',xValue,getDimension(g.0REPORT_COUNT, g.0REPORT_SIZE) sFlags getSanity(sValue)
if g.0IN_NAMED_ARRAY_COLLECTION = 1
then g.0USAGE = g.0NAMED_ARRAY_USAGE
n = outputField.0 + 1
outputField.n = xValue getGlobals()','getLocals()','g.0USAGES','sFlags','f.0COLLECTION_NAME
outputField.0 = n
call clearLocals
end
when sTag = k.0MAIN.FEATURE then do
sFlags = getFeatureFlags()
call emitDecode xItem,xParm,'MAIN','FEATURE',xValue,getDimension(g.0REPORT_COUNT, g.0REPORT_SIZE) sFlags getSanity(sValue)
if g.0IN_NAMED_ARRAY_COLLECTION = 1
then g.0USAGE = g.0NAMED_ARRAY_USAGE
n = featureField.0 + 1
featureField.n = xValue getGlobals()','getLocals()','g.0USAGES','sFlags','f.0COLLECTION_NAME
featureField.0 = n
call clearLocals
end
when sTag = k.0MAIN.COLLECTION then do
g.0EXPECTED_COLLECTION_USAGE = ''
xExtendedUsage = g.0USAGE
sCollectionName = getUsageDesc(xExtendedUsage)
sCollectionType = getCollectionType(xValue)
f.0COLLECTION_NAME = strip(f.0COLLECTION_NAME sCollectionType':'space(sCollectionName,0))
nValue = c2d(sValue)
sCollectionStack = nValue sCollectionStack /* push onto collection stack */
select
when nValue > 127 then sMeaning = 'Vendor Defined'
when nValue > 6 then sMeaning = 'Reserved'
otherwise do
sUsageTypeCode = getUsageTypeCode(xExtendedUsage)
sMeaning = getCollectionDesc(xValue) '(Usage=0x'xExtendedUsage':',
'Page='getPageDesc(xExtendedUsage)',',
'Usage='getUsageDesc(xExtendedUsage)',',
'Type='getUsageType(xExtendedUsage)')'
if sUsageTypeCode = ''
then do
sMeaning = sMeaning '<-- Error: COLLECTION must be preceded by a known USAGE'
end
if left(xExtendedUsage,2) <> 'FF' & pos(sCollectionType,sUsageTypeCode) = 0
then sMeaning = sMeaning '<-- Warning: USAGE type should be' sCollectionType '('getCollectionDesc(xValue)' Collection)'
end
end
if sCollectionType = 'CA'
then g.0IN_APP_COLLECTION = 1
else do
if \g.0IN_APP_COLLECTION
then sMeaning = sMeaning '<-- Error: No enclosing Application Collection'
end
if sCollectionType = 'NA'
then do
g.0IN_NAMED_ARRAY_COLLECTION = 1
g.0NAMED_ARRAY_USAGE = xExtendedUsage /* remember so a field name can be generated later */
end
if g.0IN_DELIMITER
then sMeaning = sMeaning '<-- Error: DELIMITER set has not been closed'
call emitDecode xItem,xParm,'MAIN','COLLECTION',right(xValue,2),sMeaning
g.0INDENT = g.0INDENT + 2
call clearLocals
end
when sTag = k.0MAIN.END_COLLECTION then do
g.0IN_NAMED_ARRAY_COLLECTION = 0
if length(sValue) <> 0
then sMeaning = '<-- Error: Data ('c2x(sValue)') is not applicable to END_COLLECTION items'
parse var sCollectionStack nCollectionType sCollectionStack /* pop the collection stack */
if nCollectionType = ''
then do
call emitDecode xItem,xParm,'MAIN','END_COLLECTION',,sMeaning '<-- Error: Superfluous END_COLLECTION'
end
else do
/* This is a reasonable place to warn if physical units are still being applied.
If physical units are not reset to 0 after they are needed, then they will be
applied to ALL subsequent LOGICAL_MINIMUM and LOGICAL_MAXIMUM values.
*/
if isSpecified(g.0PHYSICAL_MINIMUM) | isSpecified(g.0PHYSICAL_MAXIMUM) | isSpecified(g.0UNIT) | isSpecified(g.0UNIT_EXPONENT)
then do
sMeaning = sMeaning '<-- Warning: Physical units are still in effect' getFormattedPhysicalUnits()
end
g.0INDENT = g.0INDENT - 2
xCollectionType = d2x(nCollectionType,2)
if g.0IN_DELIMITER
then sMeaning = sMeaning '<-- Error: DELIMITER set has not been closed'
call emitDecode xItem,xParm,'MAIN','END_COLLECTION',,getCollectionDesc(xCollectionType) sMeaning
end
n = words(f.0COLLECTION_NAME)
if n > 0
then do
f.0COLLECTION_NAME = subword(f.0COLLECTION_NAME,1,n-1)
end
if nCollectionType = 1 /* Application Collection */
then do
if o.0DECODE
then do
call emitCloseDecode
end
if o.0STRUCT
then do
call say
call say '// All structure fields should be byte-aligned...'
call say '#pragma pack(push,1)'
if featureField.0 > 0 then call emitFeatureFields
if inputField.0 > 0 then call emitInputFields
if outputField.0 > 0 then call emitOutputFields
call say '#pragma pack(pop)'
end
featureField.0 = 0
inputField.0 = 0
outputField.0 = 0
end
call clearLocals
end
otherwise call emitDecode xItem,xParm,'MAIN',,,'<-- Error: Item ('xItem') is not a MAIN item. Expected INPUT(8x) OUTPUT(9x) FEATURE(Bx) COLLECTION(Ax) or END_COLLECTION(Cx) (where x = 0,1,2,3).'
end
return
processGLOBAL:
xValue = c2x(sValue)
nValue = x2d(xValue,2*length(sValue))
select
when sTag = k.0GLOBAL.USAGE_PAGE then do
select
when nValue = 0 then sMeaning = '<-- Error: USAGE_PAGE must not be 0'
when nValue > 65535 then sMeaning = '<-- Error: USAGE_PAGE must be in the range 0x0001 to 0xFFFF'
otherwise do
xPage = right(xValue,4,'0')
call loadPage xPage
xValue = xPage
sMeaning = getPageDesc(xPage) updateHexValue('USAGE_PAGE',xValue)
end
end
end
when sTag = k.0GLOBAL.LOGICAL_MINIMUM then do
sMeaning = '('nValue')' updateValue('LOGICAL_MINIMUM',nValue) recommendedSize()
end
when sTag = k.0GLOBAL.LOGICAL_MAXIMUM then do
sMeaning = '('nValue')' updateValue('LOGICAL_MAXIMUM',nValue) recommendedSize()
end
when sTag = k.0GLOBAL.PHYSICAL_MINIMUM then do
sMeaning = '('nValue')' updateValue('PHYSICAL_MINIMUM',nValue) recommendedSize()
end
when sTag = k.0GLOBAL.PHYSICAL_MAXIMUM then do
sMeaning = '('nValue')' updateValue('PHYSICAL_MAXIMUM',nValue) recommendedSize()
end
when sTag = k.0GLOBAL.UNIT_EXPONENT then do
nUnitExponent = getUnitExponent(nValue)
sMeaning = '(Unit Value x 10'getSuperscript(nUnitExponent)')' updateValue('UNIT_EXPONENT',nUnitExponent) recommendedSize()
end
when sTag = k.0GLOBAL.UNIT then do
xValue8 = right(xValue,8,'0')
nValue = x2d(xValue8)
parse var k.0UNIT.xValue8 sUnitDesc','
sMeaning = sUnitDesc '('getUnit(xValue8)')' updateHexValue('UNIT',xValue8) recommendedSize()
end
when sTag = k.0GLOBAL.REPORT_SIZE then do
nValue = x2d(xValue) /* REPORT_SIZE is an unsigned value */
sMeaning = '('nValue') Number of bits per field' updateValue('REPORT_SIZE',nValue) recommendedSize()
if nValue = 0
then sMeaning = sMeaning '<-- Error: REPORT_SIZE must be > 0'
end
when sTag = k.0GLOBAL.REPORT_ID then do
nValue = x2d(xValue) /* REPORT_ID is an unsigned value */
c = x2c(xValue)
if isAlphanumeric(c)
then sMeaning = '('x2d(xValue)')' "'"c"'" updateHexValue('REPORT_ID',xValue) recommendedSize()
else sMeaning = '('x2d(xValue)')' updateHexValue('REPORT_ID',xValue) recommendedSize()
if nValue = 0 then sMeaning = sMeaning '<-- Error: REPORT_ID 0x00 is reserved'
if nValue > 255 then sMeaning = sMeaning '<-- Error: REPORT_ID must be in the range 0x01 to 0xFF'
end
when sTag = k.0GLOBAL.REPORT_COUNT then do
nValue = x2d(xValue) /* REPORT_COUNT is an unsigned value */
sMeaning = '('nValue') Number of fields' updateValue('REPORT_COUNT',nValue) recommendedSize()
if nValue = 0
then sMeaning = sMeaning '<-- Error: REPORT_COUNT must be > 0'
end
when sTag = k.0GLOBAL.PUSH then do
call pushStack getGlobals()
sMeaning = getFormattedGlobalsLong()
if nSize <> 0
then sMeaning = sMeaning '<-- Error: PUSH data field size must be 0 (0x'xValue 'ignored)'
xValue = ''
end
when sTag = k.0GLOBAL.POP then do
if nSize <> 0
then sMeaning = sMeaning '<-- Error: POP data field size must be 0 (0x'xValue 'ignored)'
if isStackEmpty()
then sMeaning = sMeaning '<-- Error: No preceding PUSH'
else do
call setGlobals popStack()
sMeaning = getFormattedGlobalsLong() sMeaning
end
xValue = ''
end
otherwise sMeaning = '<-- Error: Item ('xItem') is not a GLOBAL item. Expected 0x, 1x, 2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x, Ax or Bx (where x = 4,5,6,7)'
end
call emitDecode xItem,xParm,'GLOBAL',k.0GLOBAL.sTag,xValue,sMeaning
return
addUsage: procedure expose g. k.
parse arg xUsage
g.0USAGES = g.0USAGES xUsage
return
processLOCAL:
xValue = c2x(sValue)
nValue = x2d(xValue,2*length(sValue))
xPage = right(g.0USAGE_PAGE,4,'0')
bIndent = 0
select
when sTag = k.0LOCAL.USAGE then do
if length(sValue) = 4 & left(sValue,2) <> '0000'x
then do /* Both page and usage are specified: ppppuuuu */
xExtendedUsage = xValue
xPage = left(xValue,4)
call loadPage xPage
sUsageMeaning = getPageAndUsageMeaning(xValue)
sMeaning = sUsageMeaning updateHexValue('USAGE',xValue)
end
else do /* Only usage is specified: uuuu */
xUsage = right(xValue,4,'0')
xValue = xPage || xUsage
xExtendedUsage = xValue
sUsageMeaning = getUsageMeaning(xValue)
sMeaning = sUsageMeaning updateHexValue('USAGE',xValue) recommendedUnsignedSize()
if xPage = '0000'
then sMeaning = sMeaning '<-- Error: USAGE_PAGE must not be 0'
end
if sMeaning = ''
then sMeaning = undocumentedUsage(xValue)
if g.0IN_DELIMITER
then do /* only use the first usage in the delimited set */
if g.0FIRST_USAGE
then call addUsage xValue
g.0FIRST_USAGE = 0
end
else call addUsage xValue
sUsageTypeCode = getUsageTypeCode(xExtendedUsage)
if isInSet(sUsageTypeCode,"CP CA CL CACL CACP CLCP CLNAry CLSV CR NAry UM US") /* If this is a USAGE for a COLLECTION */
then do
g.0EXPECTED_COLLECTION_USAGE = '0x'xExtendedUsage sUsageMeaning
g.0EXPECTED_COLLECTION_ITEM = 'A1' getCollectionCode(sUsageTypeCode)
end
else do /* This USAGE is not for a COLLECTION */
if g.0EXPECTED_COLLECTION_USAGE <> ''
then do
parse var g.0EXPECTED_COLLECTION_ITEM . xCollectionType
sCollectionType = getCollectionDesc(xCollectionType)
sMeaning = sMeaning '<-- Error:' sCollectionType 'COLLECTION item ('g.0EXPECTED_COLLECTION_ITEM') expected for USAGE' g.0EXPECTED_COLLECTION_USAGE
end
else do
if g.0IN_NAMED_ARRAY_COLLECTION & \isInSet(sUsageTypeCode,'Sel MULTI SFDFSEL')
then sMeaning = sMeaning '<-- Error: A Named Array Collection must only contain Selector USAGEs'
end
g.0EXPECTED_COLLECTION_USAGE = '' /* stops further nagging */
end
end
when sTag = k.0LOCAL.USAGE_MINIMUM then do
if length(sValue) = 4 & left(sValue,2) <> '0000'x
then do /* Both page and usage are specified: ppppuuuu */
xPage = left(xValue,4)
call loadPage xPage
sMeaning = getPageAndUsageMeaning(xValue) updateHexValue('USAGE_MINIMUM',xValue)
end
else do /* Only usage is specified: uuuu */
xUsage = right(xValue,4,'0')
xValue = xPage || xUsage
sMeaning = getUsageMeaning(xValue) updateHexValue('USAGE_MINIMUM',xValue) recommendedUnsignedSize()
if xPage = '0000'
then sMeaning = sMeaning '<-- Error: USAGE_PAGE must not be 0'
end
if sMeaning = ''
then sMeaning = undocumentedUsage(xValue)
if isSpecified(g.0USAGE_MAXIMUM)
then call appendRangeOfUsages
end
when sTag = k.0LOCAL.USAGE_MAXIMUM then do
if length(sValue) = 4 & left(sValue,2) <> '0000'x
then do /* Both page and usage are specified: ppppuuuu */
xPage = left(xValue,4)
call loadPage xPage
sMeaning = getPageAndUsageMeaning(xValue) updateHexValue('USAGE_MAXIMUM',xValue)
end
else do /* Only usage is specified: uuuu */
xUsage = right(xValue,4,'0')
xValue = xPage || xUsage
sMeaning = getUsageMeaning(xValue) updateHexValue('USAGE_MAXIMUM',xValue) recommendedUnsignedSize()
if xPage = '0000'
then sMeaning = sMeaning '<-- Error: USAGE_PAGE must not be 0'
end
if sMeaning = ''
then sMeaning = undocumentedUsage(xValue)
if isSpecified(g.0USAGE_MINIMUM)
then call appendRangeOfUsages
end
when sTag = k.0LOCAL.DESIGNATOR_INDEX then do
sMeaning = '('nValue')' updateValue('DESIGNATOR_INDEX',nValue) recommendedSize()
end
when sTag = k.0LOCAL.DESIGNATOR_MINIMUM then do
sMeaning = '('nValue')' updateValue('DESIGNATOR_MINIMUM',nValue) recommendedSize()
end
when sTag = k.0LOCAL.DESIGNATOR_MAXIMUM then do
sMeaning = '('nValue')' updateValue('DESIGNATOR_MAXIMUM',nValue) recommendedSize()
end
when sTag = k.0LOCAL.STRING_INDEX then do
sMeaning = '('nValue')' updateValue('STRING_INDEX',nValue) recommendedSize()
end
when sTag = k.0LOCAL.STRING_MINIMUM then do
sMeaning = '('nValue')' updateValue('STRING_MINIMUM',nValue) recommendedSize()
end
when sTag = k.0LOCAL.STRING_MAXIMUM then do
sMeaning = '('nValue')' updateValue('STRING_MAXIMUM',nValue) recommendedSize()
end
when sTag = k.0LOCAL.DELIMITER then do
select
when nValue = 1 then do
sMeaning = '('nValue') Open set' recommendedSize()
if g.0IN_DELIMITER
then sMeaning = sMeaning '<-- Error: Already in a DELIMITER set'
g.0IN_DELIMITER = 1
g.0FIRST_USAGE = 1
bIndent = 1
end
when nValue = 0 then do
sMeaning = '('nValue') Close set' recommendedSize()
if \g.0IN_DELIMITER
then sMeaning = sMeaning '<-- Error: Not already in a DELIMITER set'
g.0IN_DELIMITER = 0
g.0INDENT = g.0INDENT - 2
end
otherwise sMeaning = '('nValue') <-- Error: DELIMITER data field ('nValue') must be 0 (CLOSE) or 1 (OPEN)'
end
end
otherwise sMeaning = '<-- Error: Item ('xItem') is not a LOCAL item. Expected 0x, 1x, 2x, 3x, 4x, 5x, 7x, 8x, 9x or Ax (where x = 8,9,A,B)'
end
call emitDecode xItem,xParm,'LOCAL',k.0LOCAL.sTag,xValue,sMeaning
if bIndent
then do
g.0INDENT = g.0INDENT + 2
bIndent = 0
end
return
undocumentedUsage: procedure
parse arg xPage +4 xUsage +4
return '<-- Warning: Undocumented usage (document it by inserting' xUsage 'into file' xPage'.conf)'
appendRangeOfUsages:
if left(g.0USAGE_MINIMUM,4) <> left(g.0USAGE_MAXIMUM,4)
then sMeaning = sMeaning '<-- Error: USAGE_PAGE for USAGE_MAXIMUM and USAGE_MINIMUM must be the same'
else do
nUsageMin = x2d(g.0USAGE_MINIMUM)
nUsageMax = x2d(g.0USAGE_MAXIMUM)
if nUsageMax < nUsageMin
then do
sMeaning = sMeaning '<-- Error: USAGE_MINIMUM ('g.0USAGE_MINIMUM') must be less than USAGE_MAXIMUM ('g.0USAGE_MAXIMUM')'
temp = g.0USAGE_MAXIMUM /* Compromise: swap USAGE_MINIMUM and USAGE_MAXIMUM */
g.0USAGE_MAXIMUM = g.0USAGE_MINIMUM
g.0USAGE_MINIMUM = temp
nUsageMin = x2d(g.0USAGE_MINIMUM)
nUsageMax = x2d(g.0USAGE_MAXIMUM)
end
if nUsageMax - nUsageMin + 1 < 3 /* 1 or 2 usages can be more efficiently specified as individual usages */
then sMeaning = sMeaning '<-- Info: Consider specifying individual USAGEs instead of USAGE_MINIMUM/USAGE_MAXIMUM'
do nExtendedUsage = nUsageMin to nUsageMax
xExtendedUsage = d2x(nExtendedUsage,8)
call addUsage xExtendedUsage
end
g.0USAGE_MINIMUM = 0
g.0USAGE_MAXIMUM = 0
end
return
loadPage: procedure expose g. k.
parse arg xPage +4
if g.0CACHED.xPage = 1 then return
call loadUsageFile xPage'.conf'
g.0CACHED.xPage = 1
return
getSanity: procedure expose g.
parse arg sFlags
sError = ''
if isUndefined(g.0REPORT_SIZE) then sError = sError '<-- Error: REPORT_SIZE is undefined'
if g.0REPORT_SIZE = 0 then sError = sError '<-- Error: REPORT_SIZE must not be 0'
if isUndefined(g.0REPORT_COUNT) then sError = sError '<-- Error: REPORT_COUNT is undefined'
if g.0REPORT_COUNT = 0 then sError = sError '<-- Error: REPORT_COUNT must not be 0'
if \isConstant(sFlags)
then do
if isUndefined(g.0LOGICAL_MINIMUM) then sError = sError '<-- Error: LOGICAL_MINIMUM is undefined'
if isUndefined(g.0LOGICAL_MAXIMUM) then sError = sError '<-- Error: LOGICAL_MAXIMUM is undefined'
if isDefined(g.0LOGICAL_MINIMUM) & isDefined(g.0LOGICAL_MAXIMUM) & isDefined(g.0REPORT_SIZE) & isDefined(g.0REPORT_COUNT)
then do
nBitsForLogicalMinimum = getMinBits(g.0LOGICAL_MINIMUM)
if g.0REPORT_SIZE < nBitsForLogicalMinimum
then sError = sError '<-- Error: REPORT_SIZE ('g.0REPORT_SIZE') is too small for LOGICAL_MINIMUM ('g.0LOGICAL_MINIMUM') which needs' nBitsForLogicalMinimum 'bits.'
nBitsForLogicalMaximum = getMinBits(g.0LOGICAL_MAXIMUM)
if g.0REPORT_SIZE < nBitsForLogicalMaximum
then sError = sError '<-- Error: REPORT_SIZE ('g.0REPORT_SIZE') is too small for LOGICAL_MAXIMUM ('g.0LOGICAL_MAXIMUM') which needs' nBitsForLogicalMaximum 'bits.'
if g.0LOGICAL_MAXIMUM < g.0LOGICAL_MINIMUM
then sError = sError '<-- Error: LOGICAL_MAXIMUM ('g.0LOGICAL_MAXIMUM') is less than LOGICAL_MINIMUM ('g.0LOGICAL_MINIMUM')'
end
if isDefined(g.0PHYSICAL_MINIMUM) & isUndefined(g.0PHYSICAL_MAXIMUM)
then sError = sError '<-- Error: PHYSICAL_MAXIMUM is undefined'
if isUndefined(g.0PHYSICAL_MINIMUM) & isDefined(g.0PHYSICAL_MAXIMUM)
then sError = sError '<-- Error: PHYSICAL_MINIMUM is undefined'
if isDefined(g.0PHYSICAL_MINIMUM) & isDefined(g.0PHYSICAL_MAXIMUM)
then do
if g.0PHYSICAL_MAXIMUM < g.0PHYSICAL_MINIMUM
then sError = sError '<-- Error: PHYSICAL_MAXIMUM ('g.0PHYSICAL_MAXIMUM') is less than PHYSICAL_MINIMUM ('g.0PHYSICAL_MINIMUM')'
end
end
if g.0IN_DELIMITER
then sMeaning = sMeaning '<-- Error: DELIMITER set has not been closed'
return sError
isUndefined: procedure
arg nValue
return nValue = ''
isDefined: procedure
arg nValue
return nValue <> ''
isInSet: procedure
arg sKey,sSet
return wordpos(sKey,sSet) > 0
getMinBits: procedure
parse arg n
if n < 0
then nMinBits = length(strip(x2b(d2x(n,16)),'LEADING','1')) + 1
else nMinBits = length(strip(x2b(d2x(n,16)),'LEADING','0'))
return nMinBits
recommendedSize: procedure expose g. xItem xParm nValue nSize
sItem0 = bitand(x2c(xItem),'11111100'b)
xItem0 = c2x(sItem0)
xItem1 = c2x(bitor(sItem0,'00000001'b))
xItem2 = c2x(bitor(sItem0,'00000010'b))
xItem4 = c2x(bitor(sItem0,'00000011'b))
select
when nSize = 0 then do
end
when nSize = 1 then do
select
when nValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
otherwise nop
end
end
when nSize = 2 then do
select
when nValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
when inRange(nValue,-128,127) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem1 left(xParm,2)
otherwise nop
end
end
when nSize = 4 then do
select
when nValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
when inRange(nValue,-128,127) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem1 left(xParm,2)
when inrange(nValue,-32768,32767) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem2 left(xParm,4)
otherwise nop
end
end
otherwise nop
end
return ''
recommendedUnsignedSize: procedure expose g. xItem xParm sValue nSize
uValue = c2d(sValue) /* unsigned interpretation of sValue */
sItem0 = bitand(x2c(xItem),'11111100'b)
xItem0 = c2x(sItem0)
xItem1 = c2x(bitor(sItem0,'00000001'b))
xItem2 = c2x(bitor(sItem0,'00000010'b))
xItem4 = c2x(bitor(sItem0,'00000011'b))
select
when nSize = 0 then do
end
when nSize = 1 then do
select
when uValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
otherwise nop
end
end
when nSize = 2 then do
select
when uValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
when inRange(uValue,0,255) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem1 left(xParm,2)
otherwise nop
end
end
when nSize = 4 then do
select
when nValue = 0 then return '<-- Info: Consider replacing' xItem xParm 'with' xItem0
when inRange(uValue,0,255) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem1 left(xParm,2)
when inrange(uValue,0,65535) then return '<-- Info: Consider replacing' xItem xParm 'with' xItem2 left(xParm,4)
otherwise nop
end
end
otherwise nop
end
return ''
inRange: procedure
parse arg n,min,max
return n >= min & n <= max
updateValue: procedure expose g.
parse arg sName,nValue
sKey = '0'sName
if g.sKey = nValue & sName <> 'USAGE'
then do
g.0REDUNDANT = 1
sWarning = '<-- Redundant:' sName 'is already' nValue
end
else do
g.sKey = nValue
sWarning = ''
end
return sWarning
updateHexValue: procedure expose g.
parse arg sName,xValue
sKey = '0'sName
if x2d(g.sKey) = x2d(xValue) & sName <> 'USAGE'
then do
g.0REDUNDANT = 1
sWarning = '<-- Redundant:' sName 'is already 0x'xValue
end
else do
g.sKey = xValue
sWarning = ''
end
return sWarning
getDimension: procedure
parse arg nCount,nBits
return '('getQuantity(nCount,'field','fields') 'x' getQuantity(nBits, 'bit', 'bits')')'
dumpHex: procedure expose g. o.
parse upper arg xData
do while xData <> ''
parse var xData x1 +8 x2 +8 x3 +8 x4 +8 x5 +8 x6 +8 x7 +8 x8 +8 xData
call say '//' x1 x2 x3 x4 x5 x6 x7 x8
end
return
getStatement: procedure
parse arg sType sName,sComment
sLabel = left(sType,8) sName
return left(sLabel, max(length(sLabel),50)) '//' sComment
emitInputFields: procedure expose inputField. k. o. f.
/* Cycle through all the input fields accumulated and when the report_id
changes, then emit a new structure */
g.0BIT_OFFSET = 0
xLastReportId = 'unknown'
do i = 1 to inputField.0
parse var inputField.i xFlags sGlobals','sLocals','
call setGlobals sGlobals
call setLocals sLocals
if xLastReportId <> g.0REPORT_ID
then do /* The report id has changed */
if i > 1
then call emitEndStructure 'inputReport',xLastReportId
call emitBeginStructure 'inputReport',g.0REPORT_ID,'Device --> Host'
xLastReportId = g.0REPORT_ID
end
call emitField i,inputField.i
end
call emitEndStructure 'inputReport',xLastReportId
return
emitOutputFields: procedure expose outputField. k. o. f.
/* Cycle through all the output fields accumulated and when the report_id
changes, then emit a new structure */
g.0BIT_OFFSET = 0
xLastReportId = 'unknown'
do i = 1 to outputField.0
parse var outputField.i xFlags sGlobals','sLocals','
call setGlobals sGlobals
call setLocals sLocals
if xLastReportId <> g.0REPORT_ID
then do /* The report id has changed */
if i > 1
then call emitEndStructure 'outputReport',xLastReportId
call emitBeginStructure 'outputReport',g.0REPORT_ID,'Device <-- Host'
xLastReportId = g.0REPORT_ID
end
call emitField i,outputField.i
end
call emitEndStructure 'outputReport',xLastReportId
return
emitFeatureFields: procedure expose featureField. k. o. f.
/* Cycle through all the feature fields accumulated and when the report_id
changes, then emit a new structure */
g.0BIT_OFFSET = 0
xLastReportId = 'unknown'
do i = 1 to featureField.0
parse var featureField.i xFlags sGlobals','sLocals','
call setGlobals sGlobals
call setLocals sLocals
if xLastReportId <> g.0REPORT_ID
then do /* The report id has changed */
if i > 1
then call emitEndStructure 'featureReport',xLastReportId
call emitBeginStructure 'featureReport',g.0REPORT_ID,'Device <-> Host'
xLastReportId = g.0REPORT_ID
end
call emitField i,featureField.i
end
call emitEndStructure 'featureReport',xLastReportId
return
emitBeginStructure: procedure expose g. k. f. o.
parse arg sStructureName,xReportId,sDirection
f.0LASTCOLLECTION = ''
if isSpecified(xReportId)
then do
f.0TYPEDEFNAME = getUniqueName(sStructureName || xReportId)'_t'
call emitHeading getPageDesc(g.0USAGE_PAGE) sStructureName xReportId '('sDirection')'
call say 'typedef struct'
call say '{'
c = x2c(xReportId)
if isAlphanumeric(c)
then sDesc = '('x2d(xReportId)')' "'"c"'"
else sDesc = '('x2d(xReportId)')'
call say ' 'getStatement(k.0U8 'reportId;','Report ID = 0x'xReportId sDesc)
end
else do
f.0TYPEDEFNAME = getUniqueName(sStructureName)'_t'
call emitHeading getPageDesc(g.0USAGE_PAGE) sStructureName '('sDirection')'
call say 'typedef struct'
call say '{'
call say ' 'getStatement(,'No REPORT ID byte')
end
return
emitEndStructure: procedure expose g. f. o.
parse arg sStructureName,xReportId
call say '}' f.0TYPEDEFNAME';'
call say
return
emitHeading: procedure expose o.
parse arg sHeading
call say
call say '//--------------------------------------------------------------------------------'
call say '//' sHeading
call say '//--------------------------------------------------------------------------------'
call say
return
emitUsages: procedure expose o.
arg xUsages
nUsagesPerLine = 8
nUsages = words(xUsages)
xFirstUsages = subword(xUsages,1,nUsagesPerLine)
call say ' // Usages: ' xFirstUsages
if nUsages > nUsagesPerLine
then do i = nUsagesPerLine+1 to nUsages by nUsagesPerLine
call say ' // ' subword(xUsages,i,nUsagesPerLine)
end
return
emitField: procedure expose k. o. f. g.0BIT_OFFSET
parse arg nField,xFlags sGlobals','sLocals','xUsages','sFlags','sCollectionNames
call setGlobals sGlobals
call setLocals sLocals
sCollectionName = getCollectionName(sCollectionNames)
g.0FIELD_TYPE = getFieldType()
if o.0VERBOSITY > 0
then do
call say
call say ' // Field: ' nField
call say ' // Offset: ' g.0BIT_OFFSET
call say ' // Width: ' g.0REPORT_SIZE
call say ' // Count: ' g.0REPORT_COUNT
call say ' // Flags: ' xFlags':' sFlags
call say ' // Globals:' getFormattedGlobals()
call say ' // Locals: ' getFormattedLocals()
call emitUsages xUsages
call say ' // Coll: ' sCollectionNames
end
sFlags = x2c(xFlags)
nUsages = words(xUsages)
if o.0VERBOSITY > 0
then do
if isData(sFlags)
then do /* data i.e. can be changed */
call say ' // Access: Read/Write'
end
else do
call say ' // Access: Read/Only'
end
end
xPage = g.0USAGE_PAGE