-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathLPTCAP.ASM
1915 lines (1664 loc) · 62.3 KB
/
LPTCAP.ASM
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
; LPTCAP.ASM -- DOS parallel printer data capture - [email protected]
;
; (c) Copyright 1996-2000 K. Heidenstrom
;
; May be freely used provided that copyright information is retained
;
; KH.19960731.001 Started
; KH.19960801.002 More work
; KH.19960803.003 More work
; KH.19960804.004 More work on lptcap_port()
; KH.19960805.005 Started work on lptcap_test()
; KH.19960806.006 More work on lptcap_test()
; KH.19960825.007 Updated for hardware with HC132
; KH.19961006.008 Added better diagnostics for lptcap_test()
; Added lptcap_test_fail_data()
; KH.19961009.009 Added check for zero PortNum in lptcap_test()
; Added 1-to-2-tick delay in first lptcap_test() call per port
; KH.19971004.010 Working lptcap_autodetect_irq()
; KH.19971010.011 Moved function documentation out to LPTCAP.TXT
; KH.19971012.012 Work on interrupt-driven receive
; KH.19971013.013 More work
; KH.19971015.014 More work
; KH.19971016.015 Found 'long strobe' problem causing bogus data captures
; KH.19971017.016 Started structural design changes
; KH.19971018.017 Working system - version 1.0.0
; KH.19971220.018 Release version - release 1.
; KH.20000402.019 Email address [email protected] -> [email protected]
FileRevision EQU 019
VersionNum EQU 1
SubVersion EQU 0
ModVersion EQU 0
VerDate EQU 20000402
VerDateStr EQU "20000402"
; LEGAL
; -----
;
; This code and the LPTCAP hardware design are Copyright (c) 1996-2000 by Kris
; Heidenstrom, [email protected]. See the accompanying files LPTCAP.TXT
; or LPTCAP.HTM for further information.
;
; INTRODUCTION
; ------------
;
; This file is the LPTCAP interface module. It provides a set of services
; which interface with an LPTCAP capture adapter, a circuit which allows a PC
; to capture data from another PC or any device that prints to a Centronics
; parallel printer.
;
; This file can be assembled with Borland's TASM (version 3.1 tested) or
; Microsoft's MASM (version 5.00 tested). It uses 8086/8088 instructions only,
; and will run on any member of the PC family. It supports all common memory
; models. It assembles into an object module which can be linked into an
; MS-DOS real-mode program written in C or assembly language.
;
; Sample assembler command line: tasm /ml /w2 /dMODEL_SMALL lptcap;
;
; These functions should not be called from interrupt handlers, as some of
; them make MS-DOS function requests. This modules provides functions to
; implement interrupt-driven (background) print capturing, if required.
;
; Refer to the accompanying files LPTCAP.TXT or LPTCAP.HTM for general
; information about the LPTCAP system.
;
; FUNCTIONS IN THIS MODULE
; ------------------------
;
; The following functions are provided in this module:
;
; lptcap_version()
; lptcap_port()
; lptcap_test()
; lptcap_test_fail_data()
; lptcap_adjust()
; lptcap_get_cont()
; lptcap_set_stat()
; lptcap_send_ack()
; lptcap_poll_new()
; lptcap_next_char()
; lptcap_wait_char()
; lptcap_autodetect_irq()
; lptcap_intmode_install()
; lptcap_intmode_uninstall()
;
; IMPORTANT NOTE: lptcap_port() and lptcap_test() set up global variables
; which are used by other functions, and must be called and
; return a successful result before other functions are used.
;
; Refer to the accompanying files LPTCAP.TXT or LPTCAP.HTM for detailed
; descriptions of these functions.
;
; SLOW AND VERY-SLOW OPTIONS
; --------------------------
;
; Two assembly-time options are available to force the hardware accessing code
; to run slowly - the SLOW and VERYSLOW options.
;
; The SLOW option may be needed if the machine running the LPTCAP software is
; very fast and/or is running with very few I/O wait states, if the parallel
; port outputs do not pull up strongly, or if the cable from the Capture PC
; to the LPTCAP adapter is long or has too much capacitance. It will reduce
; data throughput.
;
; The VERYSLOW option may be used to slow down hardware accesses during
; development but should not be used with production code. If the LPTCAP
; adapter works with VERYSLOW but not with SLOW, there is probably a hardware
; problem which should be investigated.
;
; These two options are specified on the command line to the assembler in the
; following forms:
;
; /dSLOW (slight slow-down),
; /dVERYSLOW (considerable slow-down).
;
; MEMORY MODEL SUPPORT
; --------------------
;
; Memory models are supported via an equate which can be defined on the command
; line when this module is assembled, using the /Dsymbol option (for example,
; /dCOMPACT). Valid model names are TINY, SMALL, COMPACT, MEDIUM, LARGE and
; HUGE. If none of the above equates are specified, SMALL model is used.
;
; The memory model affects whether calls are long or short, and whether data is
; near or far. All data local to this module is placed in the code segment,
; for simplicity. Memory models determine code and data size as follows:
;
; Model Code Data Notes
; ----- ---- ---- -----
;
; TINY Near Near Code and data are in same segment
; SMALL Near Near
; COMPACT Near Far
; MEDIUM Far Near
; LARGE Far Far
; HUGE Far Far Data pointers are huge (normalised) pointers
;
; In this module, whether data is near or far is irrelevant except within the
; functions that use the interrupt-driven receive buffer, where this determines
; whether the pointer to the buffer descriptor structure, and the pointer
; within that structure to the start of the buffer area, are near or far.
; Whether code is near or far is relevant to the function definitions (the
; functions must end with a near or far return as appropriate) and this also
; has an effect on parameter indexing for functions which accept parameters,
; because the return address (which is 16 bits for near, 32 bits for far) is
; on the stack between the base pointer and the parameters.
;
; ==============================================================================
; Memory model setup
IFDEF MODEL_TINY
%OUT -------- Using TINY memory model (single-segment)
CALLSIZE EQU 2
CODEFAR EQU 0
DATAFAR EQU 0
ModelKnown EQU 1
ENDIF
IFDEF MODEL_SMALL
%OUT -------- Using SMALL memory model (near code, near data)
CALLSIZE EQU 2
CODEFAR EQU 0
DATAFAR EQU 0
ModelKnown EQU 1
ENDIF
IFDEF MODEL_MEDIUM
%OUT -------- Using MEDIUM memory model (far code, near data)
CALLSIZE EQU 4
CODEFAR EQU 1
DATAFAR EQU 0
ModelKnown EQU 1
ENDIF
IFDEF MODEL_COMPACT
%OUT -------- Using COMPACT memory model (near code, far data)
CALLSIZE EQU 2
CODEFAR EQU 0
DATAFAR EQU 1
ModelKnown EQU 1
ENDIF
IFDEF MODEL_LARGE
%OUT -------- Using LARGE memory model (far code, far data)
CALLSIZE EQU 4
CODEFAR EQU 1
DATAFAR EQU 1
ModelKnown EQU 1
ENDIF
IFDEF MODEL_HUGE
%OUT -------- Using HUGE memory model (far code, far data)
CALLSIZE EQU 4
CODEFAR EQU 1
DATAFAR EQU 1
ModelKnown EQU 1
ENDIF
IFNDEF ModelKnown
%OUT -------- No memory model specified - assuming SMALL (near code, near data)
CALLSIZE EQU 2
CODEFAR EQU 0
DATAFAR EQU 0
ENDIF
IF CODEFAR
LPTCAP_TEXT SEGMENT WORD PUBLIC 'CODE'
ASSUME cs:LPTCAP_TEXT,ds:nothing,es:nothing,ss:nothing
ELSE
_TEXT SEGMENT WORD PUBLIC 'CODE'
ASSUME cs:_TEXT,ds:nothing,es:nothing,ss:nothing
ENDIF
; Delay type (normal, SLOW, VERYSLOW)
IFDEF VERYSLOW
%OUT -------- Generating VERYSLOW delays
DelayType EQU 2
ELSE
IFDEF SLOW
%OUT -------- Generating SLOW delays
DelayType EQU 1
ELSE
DelayType EQU 0
ENDIF
ENDIF
; @Delay macro
;
; In many I/O access sequences, short delays may be used to give attached
; hardware time to respond. In normal speed mode, no explicit delays are
; generated, and the software relies on the delays inherent in the I/O
; accesses themselves to slow the software execution. If the assembly-time
; parameter SLOW was defined, then delays are implemented with an OUT to
; I/O address 80h, the temporary BIOS diagnostic tracepoint register (which
; should be safe to write arbitrary data to on all machines).
; If the assembly-time parameter VERYSLOW was defined, the @Delay macro
; expands into a CALL to the InsertDelay function, which implements a longer
; delay using several I/O locations.
IF DelayType EQ 0
@Delay MACRO ; Normal - no delay
ENDM
ENDIF
IF DelayType EQ 1
@Delay MACRO
out 80h,al ; SLOW - output to POST trace register
ENDM
ENDIF
IF DelayType EQ 2
@Delay MACRO
call InsertDelay ; VERYSLOW - call InsertDelay function
ENDM
ENDIF
; Equates
Data_DDATA EQU 10000000b ; DDATA: Should always be set to 1
Data_RESETB EQU 01000000b ; -RESETB: 0 to hold BUSY latch clear
Data_NOPAPER EQU 00100000b ; -NOPAPER: 0 signals PAPEROUT to sender
Data_SELCTD EQU 00010000b ; SELCTD: 1 signals SELECTED to sender
Data_ERROR EQU 00001000b ; -ERROR: 0 signals ERROR to sender
Data_DACK EQU 00000100b ; -DACK: 1-0 edge triggered
Data_SCL EQU 00000010b ; -SCL: 1-0 edge triggered
Data_SDO EQU 00000001b ; SDO: data into shift reg and loopback
Data_Idle EQU 11000111b ; Value to OR with to set controls idle
Data_Dflt EQU 11111111b ; Default (initial) value for Data reg.
Stat_ACKP EQU 10000000b ; !-ACKP: goes to 1 during ack pulse
Stat_DRDY EQU 01000000b ; +-DRDY: data ready (jumper sets sense)
Stat_SDI EQU 00100000b ; SDI: data out of shift register
Stat_NOTSDI EQU 00010000b ; -SDI: inverse of Stat_SDI
Stat_LOOPIN EQU 00001000b ; LOOPIN: looped from Data_SDO
Cont_IRQEN EQU 00010000b ; IRQEN: 1 enables port interrupt
Cont_SELECT EQU 00001000b ; !-SELECT: 1= Sender's -SELECT active
Cont_INIT EQU 00000100b ; -INITIALIZE: 0= Sender's -INIT active
Cont_AUTOFD EQU 00000010b ; !-AUTOFEED: 1= Sender's -AUTOFD active
Cont_STROBE EQU 00000001b ; !-STROBE: 1= Sender's -STROBE active
Cont_DFLT EQU 00000100b ; Default (initial) value for Cont. reg.
LA_PRESENT EQU 0 ; Values returned by lptcap_test()
LA_NO_PORT EQU 1
LA_MISSING EQU 2
LA_INTMODE EQU 3
LA_NO_DRDY EQU 4
LA_ACKSTUK EQU 5
LA_NO_DACK EQU 6
LA_ACKLONG EQU 7
LA_BSYSTUK EQU 8
LA_STRSTUK EQU 9
LA_SDISERR EQU 10
LA_DATAERR EQU 11
BD_TAIL EQU 0 ; Offsets into bufdescriptor
BD_HEAD EQU 2
BD_SIZE EQU 4
BD_PTR EQU 6 ; Either near or far pointer
IDETLOOPS EQU 8 ; Number of loops for IRQ detection
; Data (in code segment; makes things easier)
Signature DB 13,10,13,10,"LPTCAP interface module",13,10,13,10
DB "(c) Copyright 1996-2000 K. Heidenstrom "
DB "([email protected])",13,10,13,10
DB "May be freely used provided that this "
DB "copyright notice is retained"
DB 13,10,13,10,"Version ",VersionNum+"0","."
DB SubVersion+"0",".",ModVersion+"0",", ",VerDateStr,13,10,13,10,26
PortNumber DW 0 ; LPT port number (1-4 or 0 for none)
PortBase DW 0 ; LPT port base I/O address
WasInited DB 0,0,0,0,0 ; Flags for hardware init, per LPT port
DataValue DB 11111111b ; Value for data register: 11xxx111b
ContValue DB 00000100b ; Value for control register: 000i0100b
DRDYPolarity DB 0 ; XOR mask; depends on IRQ polarity sw.
TestFailData DW 0 ; Values of failed data loopthrough test
PIC0IMRSave DB 0 ; Saved IMR value for primary PIC
PIC1IMRSave DB 0 ; Saved IMR value for secondary PIC
IRQMask DW 0 ; Mask and working flags for IRQ detect
InvIRQMask DW 0 ; Mask and working flags for inv. IRQs
LPTIntOff DW 0 ; Old IRQ handler offset
LPTIntSeg DW 0 ; Old IRQ handler segment
BufDescOff DW 0 ; Offset of 'bufdescriptor'
BufDescSeg DW 0 ; Segment of 'bufdescriptor'
IRQNum DB 0,0 ; IRQ number for interrupt receive or 0
IntHndFlag DB 0 ; Flag set by interrupt handler
LongStrobe DB 0 ; Flag for -STROBE held low by Sender
OldPIC0IMR DB 0 ; Primary PIC IMR before intmode install
OldPIC1IMR DB 0 ; Secondary PIC IMR before same
VersionData DW VersionNum
DW SubVersion
DW ModVersion
DW FileRevision
DD VerDate
DW (DelayType SHL 2) + (DATAFAR SHL 1) + CODEFAR
VersDataEnd LABEL BYTE
PortFunction DW PortFunc0
DW PortFunc1
DW PortFunc2
PortFuncEnd LABEL BYTE
;=============================== START OF CODE =================================
; GetTickCount - Returns loword of tick count in AX
; Destroys AX
GetTickCount PROC near
push ds ; Preserve DS
xor ax,ax
mov ds,ax ; Address BIOS data with DS
mov ax,ds:[46Ch] ; Get loword of BIOS tick count
pop ds
ret ; Return tick count in AX
GetTickCount ENDP
;-------------------------------------------------------------------------------
; ReadT2 - this function reads the Timer 2 output readback signal on port C
; (I/O address 62h) of a PC or PC/XT machine, or port B (I/O address 61h) of
; an AT or later machine. The I/O address at the 'T2Port' label is modified
; by the DetectT2Port subroutine, which checks whether T2Port has been set up
; and if not, determines the machine type and sets T2Port. This function is
; used by lptcap_test() to implement a short timeout for testing the -ACK
; pulse generator and by lptcap_adjust() for regulating the speed of the
; calibration signal.
ReadT2 PROC near
DB 0E4h ; IN AL,xx
T2Port DB 0 ; Timer 2 readback register
test al,00100000b ; Isolate timer 2 readback bit
ret ; Return not zero if time expired
ReadT2 ENDP
;-------------------------------------------------------------------------------
; DetectT2Port - this function determines whether the machine is a PC/XT or an
; AT or later, and sets the value at T2Port accordingly. For a PC/XT, timer 2
; readback is on I/O address 62h, for an AT it is on I/O address 61h. In both
; cases it is on bit 5. If T2Port is already non-zero, the port has already
; been detected, so this function does nothing.
; Destroys AX, CX.
DetectT2Port PROC near ; Detect timer 2 readback port (61h/62h)
cmp T2Port,0 ; Have we already detected it?
jnz AlreadyT2 ; If so, don't do it again
pushf ; Keep interrupt flag
mov cx,400h ; Six attempts (top bits of CH)
cli ; Lock out interrupts during this stuff
in al,61h ; Get Port B contents
mov ah,al ; Original value to AH
in al,21h ; Short delay
Flip61Loop: xor ah,10000000b ; Flip top bit
mov al,ah ; Get value to AL
out 61h,al ; Write value to port
in al,21h ; Short delay
in al,61h ; Read it back
xor al,ah ; Set bit 7 if value didn't stay
shl al,1 ; Shift bit into carry
rcl cx,1 ; Shift bit into bottom of CX
jnc Flip61Loop ; Loop if more flips (six in total).
popf ; Restore interrupt flag
cmp cl,1 ; Was port read/write? Set carry if so
adc T2Port,61h ; If R/W - carry set - PC/XT - addr 62h
AlreadyT2: ret ; If R/O - carry clear - AT - addr 61h
DetectT2Port ENDP
;-------------------------------------------------------------------------------
; InsertDelay - insert a delay. This function is used if the assembly-time
; conditional parameter VERYSLOW was defined. See notes on DelayType.
IF DelayType EQ 2
InsertDelay PROC near ; No registers modified
push ax ; Preserve AX register
in al,21h ; Read primary PIC IMR
jmp SHORT $+2 ; Flush prefetch queue
in al,0Dh ; Read primary DMAC temporary register
jmp SHORT $+2 ; Flush prefetch queue
in al,21h ; Read primary PIC IMR
jmp SHORT $+2 ; Flush prefetch queue
in al,0Dh ; Read primary DMAC temporary register
pop ax
ret
InsertDelay ENDP
ENDIF
;-------------------------------------------------------------------------------
; EnableLPTInt - enable parallel port interrupt in the PIC(s).
; Assumes IRQNum = interrupt request line number (0-15 on ATs, 0-7 on PCs)
; Destroys AX, CX.
EnableLPTInt PROC near
mov cx,WORD PTR IRQNum ; Get IRQ number or zero
jcxz EnableIntRet ; If zero, no IRQ number specified
mov ax,101h ; Set bit 0 in each byte
shl ax,cl ; Shift bit according to IRQ
not ah ; Convert to AND-mask
cmp cl,8 ; Low or high IRQ?
pushf ; Preserve interrupt flag
cli ; Disable interrupts
jb LowIRQ2 ; If IRQ0-7
in al,0A1h ; Get secondary PIC IMR
and al,ah ; Enable appropriate IRQ
out 0A1h,al ; Store it back
mov ah,11111011b ; Also enable IRQ2 in primary PIC
LowIRQ2: in al,21h ; Get current interrupt mask reg value
and al,ah ; Zero mask bit for desired IRQ
out 21h,al ; Desired IRQ is now enabled in the PICs
popf
EnableIntRet: ret
EnableLPTInt ENDP
;-------------------------------------------------------------------------------
; GetPortAddr - returns parallel port I/O base address in DX, also sets zero
; flag according to whether any port is present. Zero means no port.
GetPortAddr PROC near ; Returns port I/O address in DX
mov dx,PortBase
cmp PortNumber,0 ; Set zero flag if non-existent port
ret
GetPortAddr ENDP
;-------------------------------------------------------------------------------
; DetectLoopback - detects the loopback from Data_SDO to Stat_LOOPIN.
; Assumes DX = port base address on entry.
; Returns zero if loopback was detected, not-zero if not.
; Leaves data register in its original state.
; Destroys AX, CX.
DetectLoopback PROC near ; On entry DX = port base I/O address
mov cx,8 ; Number of loops (must be even!)
pushf ; Preserve interrupt flag
cli ; Lock out interrupts
in al,dx ; Get data register
Loopback_Loop: xor al,Data_SDO ; Toggle loopback output
out dx,al ; Set opposite value
mov ah,al ; Keep value in AH
@Delay ; Wait a moment
inc dx ; To status register
in al,dx ; Read it
dec dx ; Back to data register
push ax ; Preserve AX
and ax,Stat_LOOPIN + 256 * Data_SDO ; Isolate those bits
xor al,ah ; Get difference in the same byte
pop ax ; Restore data value in AH
jpe LoopMatched ; If matched
inc ch ; Increment error flag
LoopMatched: mov al,ah ; Get data register value back
dec cl ; Count down
jnz Loopback_Loop ; Loop and try other state
popf ; Restore interrupt flag
test ch,ch ; Did LOOPIN match LOOPOUT?
ret ; Return ZF=1 (Zero) if loop found
DetectLoopback ENDP
;-------------------------------------------------------------------------------
; TestShift - used by lptcap_test() for data loopthrough test. Accepts a byte
; value in AL. Sends the byte to the adapter and reads it back. The adapter
; must be in shifting mode (-STROBE should have been driven active by the
; software, to set the busy latch). Stores values in TestFailData in case a
; data mismatch occurs. See notes on lptcap_test() for the algorithm.
;
; Assumes interrupts locked out on entry.
; Assumes DX = parallel port base I/O address.
; Returns zero-flag set ('zero') if no error.
; If an error occurred, AX will contain LA_SDISERR or LA_DATAERR.
; If no error occurred, AL contains its value on entry.
; Destroys AX (see above), BX.
TestShift PROC near
mov TestFailData,ax ; Keep test value here
mov bx,8 ; Set up bit count
mov ah,al ; Get to-be-sent value to AH
mov al,DataValue ; Get data register value
SendBitLoop: or al,Data_SCL ; Set -SCL high
shr al,1 ; Shift bits down
shr ah,1 ; Shift data bit into carry
IF Data_SDO NE 1
.ERR
%OUT Code here assumes Data_SDO is bit 0
ENDIF
rcl al,1 ; Set bit 0 (SDO) to data bit value
out dx,al ; Assert data value, -SCL high
@Delay
and al,NOT Data_SCL ; Set -SCL low - clocks data into reg.
out dx,al
@Delay
dec bx ; Count down bit counter
jnz SendBitLoop ; Loop for eight bits
or al,Data_SDO OR Data_SCL ; Raise SDO and -SCL
out dx,al
@Delay
mov ah,80h ; Set up bit flag for bit counting
LoadBitLoop: inc dx ; To status register
in al,dx ; Read it
dec dx ; Back to data register
and al,Stat_SDI OR Stat_NOTSDI ; Get SDI bits
jpo SDIsValid ; If SDI and -SDI are opposite
mov bx,LA_SDISERR ; If same, flag error
SDIsValid: and al,Stat_NOTSDI ; Get negative data
cmp al,1 ; Set carry if negative data is 0
rcr ah,1 ; Shift bit into result value
jc LoadBitsDone ; If we have shifted the last bit in
mov al,DataValue ; Get data register value
and al,NOT Data_SCL ; Set -SCL low - clock data through SR
out dx,al ; Write it
@Delay
or al,Data_SCL ; Raise -SCL to idle state
out dx,al ; Write it
@Delay
jmp SHORT LoadBitLoop ; Loop for all bits
LoadBitsDone: mov BYTE PTR TestFailData+1,ah ; Store the value we read
xchg ax,bx ; Get error indication to AX
test ax,ax ; Did we have SDI/-SDI error?
jnz ShiftDone ; If so
mov al,LA_DATAERR ; Prepare for data error
cmp bh,BYTE PTR TestFailData ; Check for data match/mismatch
jne ShiftDone ; If error
mov al,bh ; Restore up-to value in AL
ShiftDone: ret
TestShift ENDP
;-------------------------------------------------------------------------------
; SendAckPulse - generate an Ack pulse and wait for it to disappear. If
; -STROBE is not active, the BUSY latch will be cleared part-way through
; the ACK pulse. ASSUMES INTERRUPTS LOCKED OUT. ASSUMES DX = PORTBASE.
; Destroys AL and CX.
SendAckPulse PROC near
mov al,DataValue ; Get DataValue
and al,NOT Data_DACK ; Drop -DACK - acknowledge data byte
out dx,al
@Delay
or al,Data_DACK
out dx,al ; Return -DACK high
inc dx
mov cx,100
WaitDAck: @Delay
in al,dx
test al,Stat_ACKP
loopnz WaitDAck ; Wait until timeout or -ACKP completes
dec dx ; Back to data register.
ret
SendAckPulse ENDP
;-------------------------------------------------------------------------------
; SendResetB - generate a reset signal via -RESETB.
; ASSUMES INTERRUPTS LOCKED OUT. ASSUMES DX = PORTBASE.
; Destroys AL.
SendResetB PROC near
mov al,DataValue ; Get DataValue
and al,NOT Data_RESETB ; Drop -RESETB
out dx,al
@Delay
or al,Data_RESETB
out dx,al ; Return -RESETB high
ret
SendResetB ENDP
;-------------------------------------------------------------------------------
; CaptureByte - receive a byte from the hardware and send an acknowledge
; signal to the sender. THIS FUNCTION ASSUMES THAT INTERRUPTS ARE LOCKED OUT.
; Returns AL = captured byte; AH = 0. Destroys AX, BX, DX.
CaptureByte PROC near
mov dx,PortBase ; Get port I/O base address
mov bx,80h ; Set bit 7 of BL; zero BH
mov al,DataValue ; Get data register value
mov ah,al ; Keep it in AH
out dx,al
CaptReadLoop: @Delay
inc dx
in al,dx ; Read status register
and al,Stat_SDI OR Stat_NOTSDI
jpo GotBit ; If SDI and -SDI are different
@Delay ; If same, wait a moment and try again
in al,dx ; Read status register again
GotBit: dec dx ; Back to data register
and al,Stat_NOTSDI ; Get -SDI
cmp al,1 ; Set carry if data active
rcr bl,1 ; Rotate new bit in, shift 1-bit down
jc GotBits ; If the 1-bit popped out, we're done
mov al,ah
and al,NOT Data_SCL ; Clock a bit through
out dx,al
@Delay
mov al,ah ; Raise -CLK back again
out dx,al
jmp SHORT CaptReadLoop ; Loop for all bits
GotBits: push cx ; Don't destroy CX
inc dx
inc dx
in al,dx
dec dx
dec dx
test al,Cont_STROBE ; Is sender still holding -STROBE low?
jz StrobeGone ; If not, continue
mov LongStrobe,1 ; Flag strobe held low by sender
jmp SHORT CaptureExit ; Don't send ack yet - leave busy
StrobeGone: call SendAckPulse ; Send acknowledgement, clear BUSY
CaptureExit: pop cx ; Restore CX
xchg ax,bx ; Return byte value in AX
ret
CaptureByte ENDP
;-------------------------------------------------------------------------------
; TestLatched - tests whether new data has been captured and latched by the
; LPTCAP adapter - returns zero if not, non-zero if so. If LongStrobe flag
; is set, will always return 'no data available'. This condition must be
; unlatched by calling Background repeatedly until it clears.
; Must not be called if there is no LPTCAP port (i.e. it assumes PortBase is
; valid).
; Destroys AL, DX.
TestLatched PROC near
cmp LongStrobe,1 ; Test for -STROBE held low by Sender
jz TestLatchRet ; If so, say 'no data ready'.
mov dx,PortBase
inc dx
in al,dx ; Read status register
xor al,DRDYPolarity
test al,Stat_DRDY ; Is there data ready?
TestLatchRet: ret
TestLatched ENDP
;-------------------------------------------------------------------------------
; StuffBuf - check whether there is space in the interrupt-driven receive
; buffer, and if so, call CaptureByte to capture the byte, and add the byte
; to the interrupt-driven receive buffer.
; Assumes DX = PortBase+1.
; ASSUMES INTERRUPTS ARE LOCKED OUT.
; ASSUMES THAT THERE IS A BYTE READY TO BE CAPTURED.
; Returns carry set if buffer was full, otherwise returns carry clear.
; Destroys AX, BX, DX.
StuffBuf PROC near
push es
les bx,DWORD PTR BufDescOff ; Point to bufdescriptor
mov ax,es:[bx+BD_TAIL]
push ax ; Keep tail pointer
inc ax ; Bump tail pointer
cmp ax,es:[bx+BD_SIZE] ; Time to wrap?
jb NoWrap3 ; If not
xor ax,ax
NoWrap3: cmp ax,es:[bx+BD_HEAD] ; Is the buffer already full?
stc ; Prepare for error
je BufferFull ; If so
mov es:[bx+BD_TAIL],ax ; Store new tail pointer
pop ax ; Get previous tail pointer back
add ax,es:[bx+BD_PTR+0] ; Add offset to index
IF DATAFAR
mov es,es:[bx+BD_PTR+2] ; Get segment
ENDIF
push ax ; Keep combined offset
call CaptureByte ; Get the byte
pop bx ; Restore combined offset
mov es:[bx],al ; Store character in buffer
DB 34h ; XOR AL,xx - clears carry - successful
BufferFull: pop ax ; Fix up stack
pop es
ret
StuffBuf ENDP
;-------------------------------------------------------------------------------
; Background - Housekeeping function.
;
; This function checks for and handles certain conditions which can occur as
; a result of improper behaviour of the Sender or resident software in the
; Capture PC. These conditions are the LongStrobe condition and the lost
; IRQ condition.
;
; The LongStrobe condition occurs when the Sender drives -STROBE low for a much
; longer time than the Centronics specification calls for, usually due to an
; interrupt, DMA burst or other gap in CPU activity occurring at the Sender
; between the time it drives -STROBE low and the time it drives -STROBE high.
; This prevents the LPTCAP adapter from acknowledging the byte and returning
; to its normal state. This software handles this condition by leaving the
; adapter in the latched (BUSY) state and setting the LongStrobe variable.
; While LongStrobe is true, the TestLatched function will always return 'zero'
; (i.e. no data ready) because there is no new data. This function unlatches
; the condition, by checking for LongStrobe set and if so, checking whether
; -STROBE has gone inactive. If so, this function calls SendAckPulse to send
; an acknowledgement pulse and reset the adapter to its normal condition, and
; clears LongStrobe.
;
; The lost IRQ condition occurs when badly behaved TSRs or other resident
; software disables the LPTCAP IRQ (usually IRQ7) or send a bogus EOI command.
; In this state, the LPTCAP hardware says it has a new character latched but
; the interrupt controller(s) report that no IRQ is pending. This condition
; is applicable only in interrupt-driven receive mode.
;
; Destroys AX, BX, CX.
Background PROC near
push dx ; Preserve DX
mov dx,PortBase
cmp LongStrobe,0 ; Is hardware in the LongStrobe state?
jz NoLongStrobe ; If not
; Handle unlatching of LongStrobe state if possible
inc dx
inc dx ; If so, see whether it has cleared
pushf
cli
mov al,ContValue
out dx,al ; Make sure we aren't driving it active!
@Delay
in al,dx
popf
dec dx
dec dx ; Back to data register
test al,Cont_STROBE ; Strobe still active?
jnz NoLongStrobe ; If so, we can't do anything yet
; Strobe was previously stuck and has now gone away - send an acknowledgement
; pulse (which will also clear the BUSY latch) and clear the LongStrobe flag.
pushf
cli
call SendAckPulse
mov LongStrobe,0 ; Clear LongStrobe
popf ; Restore interrupt flag
; If operating in interrupt-driven mode, and not in LongStrobe state, check for
; lost interrupt.
NoLongStrobe: cmp IRQNum,0 ; Are we operating in interrupt mode?
jz NoLostIRQ ; If not
cmp LongStrobe,0 ; Stuck waiting for Strobe?
jnz NoLostIRQ ; If so
pushf
sti
mov IntHndFlag,0 ; Reset interrupt handler flag
call TestLatched ; New data ready?
jz NoDRDY ; If not
cmp IntHndFlag,0 ; Had an interrupt?
jnz NoDRDY ; If so, alright
call EnableLPTInt ; Enable interrupt in PIC(s)
mov IntHndFlag,0 ; Reset interrupt handler flag again
call TestLatched ; Data still waiting?
jz NoDRDY ; If not
cmp IntHndFlag,0 ; Had an interrupt?
jnz NoDRDY ; If so, alright
cli ; Lock out interrupts for StuffBuf
call StuffBuf ; If stuck, get the character now
NoDRDY: popf
NoLostIRQ: pop dx ; Restore DX
ret
Background ENDP
;-------------------------------------------------------------------------------
; LPTCAP_IRQHnd - interrupt handler for LPTCAP parallel port interrupts
LPTCAP_IRQHnd PROC far
push dx
push bx
push ax
pushf
cli ; In case we are chained by naughty code
mov IntHndFlag,1 ; Flag an interrupt occurred
call TestLatched
jz FalseAlarm ; If not
call StuffBuf ; Get character and stuff it in buffer
mov al,20h
cmp IRQNum,8 ; IRQ 8-15?
jb SendEOI ; If not
out 0A0h,al ; Send EOI to secondary PIC
SendEOI: out 20h,al ; Send EOI to primary PIC
popf
pop ax
pop bx
pop dx
iret
FalseAlarm: popf
pop ax
pop bx
pop dx
jmp DWORD PTR LPTIntOff ; Jump to old handler
LPTCAP_IRQHnd ENDP
;-------------------------------------------------------------------------------
; unsigned int lptcap_version(int request)
PUBLIC _lptcap_version
IF CODEFAR
_lptcap_version PROC far
ELSE
_lptcap_version PROC near
ENDIF
push bp
mov bp,sp
mov bx,[bp+2+CALLSIZE] ; Get 'request' parameter
cmp bx,(VersDataEnd - VersionData) / 2 ; Validate
mov ax,-1 ; Prepare for out of range
jae VersionRange ; If so
shl bx,1 ; Double for word-sized values
mov ax,VersionData[bx] ; Get value
VersionRange: pop bp
ret ; Return value in AX
_lptcap_version ENDP
;-------------------------------------------------------------------------------
; int lptcap_port(int findmode)
PUBLIC _lptcap_port
IF CODEFAR
_lptcap_port PROC far
ELSE
_lptcap_port PROC near
ENDIF
push bp
mov bp,sp
mov bx,[bp+2+CALLSIZE] ; Get 'findmode' parameter
cmp bx,(PortFuncEnd - PortFunction) / 2 ; Validate
mov ax,-1 ; Prepare for error
jae PortRange ; If invalid
shl bx,1 ; Double for word-sized values
jmp PortFunction[bx] ; Go to appropriate handler
; Try first port
PortFunc1: mov PortNumber,0 ; Start from before first possible port
; Try next port
PortFunc2: inc PortNumber ; Go to next port
mov ax,PortNumber ; Get port number
cmp ax,5 ; Check for gone too far
jb TryPort ; If not
mov PortNumber,0 ; If so, report nothing found
mov PortBase,0 ; Zero base as well
jmp SHORT PortFunc0 ; Return it
; Get port address
TryPort: mov bx,PortNumber ; Get current port number to BX
shl bx,1 ; Double for word-sized I/O addresses
push ds
xor dx,dx
mov ds,dx ; Address BIOS data with DS
mov dx,ds:[406h+bx] ; Get base address of port to DX
pop ds
mov ax,dx ; To AX
dec ax
cmp ax,3FDh ; Validate (must have been 1-3FDh)
jae PortFunc2 ; If not, try next port
; Test whether port has LOOPOUT looped back to LOOPIN
call DetectLoopback ; Check for loopback
jnz PortFunc2 ; If failed, try another port
; Set port I/O address variables and initialise port
pushf
cli
mov PortBase,dx ; Store as base and data register addr
mov al,Data_Dflt ; Get default (initial) value
mov DataValue,al ; Store
out dx,al ; Set it
inc dx ; To status register
inc dx ; To control register
mov al,Cont_DFLT ; Get default (initial) value
mov ContValue,al ; Store
out dx,al ; Set it
popf
; Return port number or 0 if none
PortFunc0: mov ax,PortNumber ; Get port number
PortRange: pop bp
ret ; Return value in AX
_lptcap_port ENDP
;-------------------------------------------------------------------------------
; int lptcap_test(unsigned int ntests)
PUBLIC _lptcap_test
IF CODEFAR
_lptcap_test PROC far
ELSE
_lptcap_test PROC near
ENDIF
push bp
mov bp,sp
call GetPortAddr ; Get parallel port address
jnz Alright1 ; If there is an LPTCAP port, continue
mov ax,LA_NO_PORT ; If not, return error indication
jmp TestExit ; Return error code
; Check for single line loopback, return error LA_MISSING if not present
Alright1: call DetectLoopback ; First, check for loop
pushf
cli ; Lock interrupts out
mov al,DataValue ; Get data register value
out dx,al ; Set the register
popf ; Restore interrupt flag
jz Alright2 ; If loopback test was successful
mov ax,LA_MISSING ; If error, adapter is not attached
GoTestExit: jmp TestExit ; Return error code
; Make sure it is not running in interrupt mode
Alright2: cmp IRQNum,0
mov ax,LA_INTMODE
jnz GoTestExit ; If interrupt mode, return error
; Force a delay of 1 to 2 ticks if this port hasn't been used before - this
; gives the VCC reservoir capacitor time to charge up.
mov bx,PortNumber
mov al,1
xchg al,WasInited[bx] ; Set flag, get previous state
test al,al ; Has it been initialised?
jnz HasBeenInited ; If so
pushf ; Preserve interrupt flag
sti ; Make sure interrupts are enabled
call GetTickCount ; Get tick count now
xchg ax,bx ; To BX
WaitTick1: call GetTickCount ; Get tick count
cmp ax,bx ; Changed?
je WaitTick1 ; If not, loop
xchg ax,bx ; New tick count to BX
WaitTick2: call GetTickCount ; Get tick count
cmp ax,bx ; Changed?
je WaitTick2 ; If not, loop