-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSOFTHDDI.ASM
1210 lines (1066 loc) · 45.4 KB
/
SOFTHDDI.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
COMMENT *
SOFTHDDI.ASM
Software Hard Disk Drive Indicators -- Jim Leonard, 2022
Provides simulated hard disk activity "LED"s and seeking sounds.
Intended for DOS systems with solid-state hard drives that lack both,
so that disk activity can again be seen/heard.
Three hard disk "indicators" are available, and can be enabled in any
combination:
- An on-screen "LED" that appears in the upper left corner
- An audible "click" that simulates HDD head seek noise
- On AT+ systems, the keyboard CAPSLOCK/NUMLOCK/SCROLLLOCK indicator LEDs
Run "SOFTHDDI" with no arguments to see command-line usage.
IMPACT:
The TSR uses roughly 1KB of RAM when resident, and automatically loads
itself into upper memory blocks if present.
The on-screen "LED" and speaker click indicators are effectively free, but
the keyboard LEDs indicator can result in a slowdown (see LIMITATIONS below).
IMPLEMENTATION NOTES
Painting the "virtual LED" on-screen is handled differently based on
what video mode the system is currently in. The goal is to display a
flashing square in the upper left corner whenever there is disk
activity. The basic ideas:
- Color and B&W text modes: Colors are changed to become lightred-on-red.
- Mono text mode: Colors are changed to become darkgreen-on-green.
On some monitors this attribute can result in a "halo", enhancing the effect.
- Graphics modes (fixed palettes): An 8x8-pixel "LED" graphic is painted.
- Graphics modes (redefinable palettes): An 8x8 square area is created
by flipping all bits in that area. This method was chosen to have the
least impact to the system while still having a high likelyhood of
visibility no matter what the color palette is set to.
LIMITATIONS
On-screen "LED":
- Video modes above 13h (ie. SVGA/VESA) are ignored. Supporting those
would take up too much resident RAM and CPU time. Besides, if you can run
those modes, you have a fast system, and would likely never see the "LED"
in practice anyway. Other indicators (sound, kbd LEDs) work fine in SVGA.
- Not all video modes below 13h are supported; more support will be added
as time permits.
- Unchained VGA modes are not yet detected and handled correctly, which
results in some (harmless) graphical corruption in the upper right corner.
Keyboard LEDs:
- Enabling the keyboard LEDs will result in a loss of I/O performance.
This is due to the amount of hardware reads and writes necessary to interact
with the keyboard controller safely.
- If the keyboard controller is busy when this program needs to flash the
LEDs, the LEDs may temporarily stop illuminating. If this happens, hit
numlock, capslock, or scrollock to return LEDs to normal.
Speaker clicks:
- The volume of the speaker click is fixed and cannot be made louder/softer.
Volume level was made as loud as possible with minimal impact to the system.
- If playing digitized audio through the PC speaker, such as in RealSound
games, the speaker click may stop working. Make any normal sound through
the speaker to return functionality. (One quick way is to type CTRL-G at
the DOS prompt.)
FUTURE IMPROVEMENTS
- Add support for missing modes < 13h (Hercules graphics, Tandy, etc.)
- Explore potential size/speed improvements, such as only installing and
calling the exact code paths needed
ACKNOWLEDGEMENTS
This TSR relies on the Alternate Multiplex Interrupt Specification authored
by Ralf Brown for its terminate-and-stay-resident functionality.
AMIS-compliant TSRs have several advantages over typical TSRs, including:
- Only resident code goes resident, resulting in smaller RAM usage
- Can automatically load themselves to upper ram if UMBs are available
- Can load and unload TSRs in any order
The AMIS specification, and code portions used here, are (C) Ralf Brown.
For a description of AMIS and an example AMIS library, consult AMISL092.ZIP.
Information on programming the keyboard LEDs courtesy of Frank van Gilluwe.
Information on determining PC vs. AT 8255 from Kris Heidenstrom (RIP).
Methodology for detecting video hardware adapted from Richard Wilton.
Greetz to VileR, who drew a CGA mode4+mode6 "LED" bitmap on short notice :-)
*
.8086
LOCALS ;Enable local symbols within each PROC
INCLUDE AMIS.MAC
@Startup 2,00 ;need DOS 2.00
;this macro also takes care of declaring
;segments in the required order
VERSION_NUM equ 0011h ;v1.1
VERSION_STR equ "1.1"
; Program behaviors that can be tuned by changing vars and re-assembling:
; (It isn't necessary to change these in most cases)
REMOVESNOW EQU 1 ;Avoid CGA "snow" on original CGA cards.
;0 = No snow avoidance
;1 = Avoids snow at a slight speed penalty
;Set to 0 if you never use a CGA card; saves ~60 bytes
SEEKSOUNDS EQU 1 ;Tries to make sounds only when there is head movement.
;0 = Clicks on all calls; can generate random noise
;1 = Only clicks when head moves (realistic)
;Leave at 1 for more realistic-sounding behavior.
OMITFLOPPY EQU 1 ;Don't show activity for floppy drives.
;0 = Show INT13 activity for floppy+hard drives
;1 = Limit activity to hard disk drives
;Leave at 1 unless your floppy/gotek lacks LEDs.
NOBIOSMODE EQU 0 ;Show LED in lower-right corner in non-BIOS CGA modes.
;0 = Non-BIOS modes will not show an onscreen LED
;1 = All modes will show LED, but with much CGA "snow"
;Leave at 0 unless you *must* have an LED in games
;that bypass the BIOS and also perform HDD activity.
;(There are hardly any games that meet both criteria)
; Resident code goes into its own segment so that all the offsets are
; proper for the new location after copying it into a new location
TSRcode@
start_TSRcode label byte
; Declare the interrupt vector hooked by the program, then set up the
; Alternate Multiplex Interrupt Spec handler
;
HOOKED_INTS 13h
ALTMPX 'JLeonard','SOFTHDDI',VERSION_NUM,'Virtual HDD LED/sound indicators'
; Resident portion of interrupt handler follows:
;feature flags for visual indicators we can install:
fLED equ (1 SHL 0) ;virtual on-screen LED
fSND equ (1 SHL 1) ;speaker click
fKBD equ (1 SHL 2) ;keyboard LED
fHED equ (1 SHL 3) ;head moved, time to make noise
; 8253 commands:
iMC_BinaryMode EQU 0
iMC_OpMode2 EQU 4 ;Rate generator
iMC_LatchCounter EQU 0
iMC_Chan0 EQU 0
; Draw assumptions
LEDattrC EQU 4Ch ;lightred on red
LEDattrM EQU 78h ;dark green on green
; Video card hardware class -- used for catching modes not set by the BIOS
h_MDA EQU 1
h_HGC EQU h_MDA OR 80h
h_CGA EQU 2
h_PCJR EQU h_CGA OR 80h
h_EGA EQU 3
h_MCGA EQU 4
h_VGA EQU 5
curvidm db 0 ;video mode active at time handler was invoked
vidcard db 0 ;video card detected at startup, assume nothing
oldVRAM db (8*2) DUP (?) ;Save VRAM we change (enough for CGA graphics)
oldVTXT db 2 DUP (?) ;Same, but for text if we need to do T+G
oldSpkr db 0 ;for restoring speaker state when click ends
spkrTic dw 1193 ;# of ticks to fire speaker for 1ms
starTic dw 0 ;starting tick counter value
i_flags db 0 ;user-desired indicator flags
lastTrk dw 0 ;last track we were on
spkrbit EQU 00000010b ;bit that enables/disables current to speaker
;Video segment table
VIDSEGS dw 0b800h,\ ;00 40x25 B/W text
0b800h,\ ;01 40x25 color text
0b800h,\ ;02 80x25 shades of gray text
0b800h,\ ;03 80x25 color text
0b800h,\ ;04 320x200x4
0b800h,\ ;05 320x200x4
0b800h,\ ;06 640x200x2
0b000h,\ ;07 80x25 Monochrome text
0b800h,\ ;08 160x200x16 (Tandy/PCjr)
0b800h,\ ;09 320x200x16 (Tandy/PCjr)
0b800h,\ ;0A 640x200x4 (Tandy/PCjr)
0b800h,\ ;0B 640x200x16 (Tandy SL/TL)
0a000h,\ ;0C Reserved (EGA BIOS internal?)
0a000h,\ ;0D 320x200x16
0a000h,\ ;0E 640x200x16
0a000h,\ ;0F 640x350 Monochrome graphics
0a000h,\ ;10 640x350x16
0a000h,\ ;11 640x480x2
0a000h,\ ;12 640x480x16
0a000h ;13 320x200x256
;Draw routine jumptable
drawtbl dw OFFSET draw_40col,\ ;00 40x25 B/W text
OFFSET draw_40col,\ ;01 40x25 color text
OFFSET draw_80col,\ ;02 80x25 shades of gray text
OFFSET draw_80col,\ ;03 80x25 color text
OFFSET draw_mode4,\ ;04 320x200x4
OFFSET draw_mode4,\ ;05 320x200x4
OFFSET draw_mode4,\ ;06 640x200x2
OFFSET draw_mono, \ ;07 80x25 Monochrome text
OFFSET draw_null, \ ;08 160x200x16 (Tandy/PCjr)
OFFSET draw_null, \ ;09 320x200x16 (Tandy/PCjr)
OFFSET draw_null, \ ;0A 640x200x4 (Tandy/PCjr)
OFFSET draw_null, \ ;0B 640x200x16 (Tandy SL/TL)
OFFSET draw_null, \ ;0C Reserved (EGA BIOS internal?)
OFFSET draw_null, \ ;0D 320x200x16
OFFSET draw_null, \ ;0E 640x200x16
OFFSET draw_null, \ ;0F 640x350 Monochrome graphics
OFFSET draw_null, \ ;10 640x350x16
OFFSET draw_null, \ ;11 640x480x2
OFFSET draw_null, \ ;12 640x480x16
OFFSET draw_mod13 ;13 320x200x256
;Erase routine jumptable
erastbl dw OFFSET eras_40col,\ ;00 40x25 B/W text
OFFSET eras_40col,\ ;01 40x25 color text
OFFSET eras_80col,\ ;02 80x25 shades of gray text
OFFSET eras_80col,\ ;03 80x25 color text
OFFSET eras_mode4,\ ;04 320x200x4
OFFSET eras_mode4,\ ;05 320x200x4
OFFSET eras_mode4,\ ;06 640x200x2
OFFSET eras_mono, \ ;07 80x25 Monochrome text
OFFSET eras_null, \ ;08 160x200x16 (Tandy/PCjr)
OFFSET eras_null, \ ;09 320x200x16 (Tandy/PCjr)
OFFSET eras_null, \ ;0A 640x200x4 (Tandy/PCjr)
OFFSET eras_null, \ ;0B 640x200x16 (Tandy SL/TL)
OFFSET eras_null, \ ;0C Reserved (EGA BIOS internal?)
OFFSET eras_null, \ ;0D 320x200x16
OFFSET eras_null, \ ;0E 640x200x16
OFFSET eras_null, \ ;0F 640x350 Monochrome graphics
OFFSET eras_null, \ ;10 640x350x16
OFFSET eras_null, \ ;11 640x480x2
OFFSET eras_null, \ ;12 640x480x16
OFFSET eras_mod13 ;13 320x200x256
;INT 13 services jumptable:
;Decision table; only perform indicators for calls that generate activity
dsvctbl db 0, \ ;0 Reset disk system
0, \ ;1 Get disk status
1, \ ;2 Read disk sectors
1, \ ;3 Write disk sectors
1, \ ;4 Verify disk sectors
1, \ ;5 Format disk track
0, \ ;6 Format track and set bad sector flag
0, \ ;7 Format the drive starting at track
0, \ ;8 Get current drive parameters
0, \ ;9 Initialize 2 fixed disk base tables
1, \ ;A Read long sector
1, \ ;B Write long sector
1, \ ;C Seek to cylinder
0, \ ;D Alternate disk reset
0, \ ;E Read sector buffer
0 ;F Write sector buffer
IODELAY macro
jmp short $+2 ;force bus cycle by clearing prefetch queue
jmp short $+2 ;again, in case the L1 cache invalidated the first one
endm
GET_TIMER_CHAN0 macro
mov al,iMC_Chan0 + iMC_LatchCounter + iMC_OpMode2 + iMC_BinaryMode
out 43h,al ;latch counter value
in al,40h ;get LSB of timer counter
mov ah,al
in al,40h ;get MSB of timer counter
xchg al,ah ;ax = starting value
endm
WAITHRETRACE macro ;dx must be 03dah before calling
LOCAL wait1,wait2,in_vert
wait1:
in al,dx ;grab status bits
test al,8 ;are we in vertical retrace?
jnz in_vert ;if so, immediately exit
shr al,1 ;are we already in random h. retrace?
jc wait1 ;if so, keep waiting
wait2:
in al,dx ;grab status bits
shr al,1 ;are we in horizontal retrace?
jnc wait2 ;if not, keep waiting
in_vert:
endm
;----------------------------------------------------------------------------
; Indicator (speaker, on-screen "LED", keyboard LED) start procedures.
; These fire before the real INT 13h call executes.
diskIntEnter PROC
push ds
push ax
push cs
pop ds ;to make var/table addressing easier
;The speaker click needs time to be audible, so we start it first.
;This frees up the CPU to go run other code while the speaker cone
;travels toward the maximum +5v position. We note the current 8253
;timer value, so that we can later measure if enough time has elapsed
;to produce a lound enough "click".
startSpeaker:
test [i_flags],fSND ;did user want sound?
jz startLED ;skip to virtual LED if not
;If we sound off for every call, even without head movement, speaker
;gets noisy! We want to make noise only if the "heads" actually move.
;On entry, CX has track+sector values, so we'll use that.
IF SEEKSOUNDS
and [i_flags],NOT fHED ;clear "head moved" flag
mov ax,cx ;copy track+sector argument to ax
and al,11000000b ;isolate track # in ax
cmp [lastTrk],ax ;did track change? (sets flags)
mov [lastTrk],ax ;(store track for next int13 call)
je startLED ;if it didnt change, leave spkr alone
ENDIF
or [i_flags],fHED ;set "head moved" flag
in al,61h ;get existing 8255 spkr port settings
mov [oldSpkr],al ;save them for restoring later
or al,spkrbit ;set bit to force speaker on
out 61h,al ;send current to speaker coil
;note current timer count for comparing later
GET_TIMER_CHAN0
mov [starTic],ax ;save for later comparisons
startLED:
test [i_flags],fLED ;did user want virtual LED?
jz startKBD ;skip if not
push es
mov ax,40h
mov es,ax ;es->BIOS Data Area
mov al,[es:49h] ;40:49 -> what BIOS thinks vidmode is
mov [curvidm],al ;store for when we exit the handler
cmp al,13h ;is our video mode unsupported?
ja invalidvideomode ;bail if so, otherwise fall through
push bx
xor bx,bx
mov bl,al ;bx = video mode
shl bl,1 ;bx = index into vseg and jump tables
push di
xor di,di ;draw LED at offset 0 (upper left)
mov es,[bx+VIDSEGS] ;es = current active vidram segment
call [bx+drawtbl] ;draw the virtual LED on-screen
pop di
pop bx
pop es
startKBD:
test [i_flags],fKBD ;did user want keyboard LEDs?
jz stopSpeaker ;skip if not
push dx
mov dl,7 ;set all three LEDs to ON
call setKBDLEDs
pop dx
stopSpeaker:
;We're done with other indicators, so we check if enough time has
;elapsed for the click to be audible. If not, we wait until it has.
test [i_flags],fHED ;did we make sound?
jz doneEnterHandler ;skip if not
push bx
clickwait:
GET_TIMER_CHAN0
mov bx,[starTic] ;copy original value to scratch
sub bx,ax ;subtract new value from old value
cmp bx,[spkrTic] ;compare si to maximum time allowed
jb clickwait ;keep waiting if min ticks not elapsed
in al,61h ;get existing 8255 spkr port settings
and al,NOT spkrbit ;turn off current to speaker coil
out 61h,al
pop bx
doneEnterHandler:
pop ax
pop ds
ret
invalidvideomode:
pop es ;pop ES from beginning of startLED
jmp startKBD ;continue with the handler
diskIntEnter ENDP
;----------------------------------------------------------------------------
;Set keyboard physical LEDs
;Input: dl = LED bits to enable
;Trashes: ax
setKBDLEDs PROC
push es
mov ax,40h
mov es,ax ;es->BIOS Data Area
push bx
mov bx,97h ;es:bx = 40h:97h = BDA keyboard flags
pushf
cli ;need no kbd ints while we're here
test byte ptr es:[bx],40h ;already updating keyboard?
jnz setLED_return3 ;bail; we'll catch it on next run
or byte ptr es:[bx],40h ;set update in-progress
mov al,0EDh ;EDh = Update LED command
call keyboard_write ;send keyboard command
test byte ptr es:[bx],80h ;error writing to keyboard?
jnz setLED_return1 ;exit and recover if so
mov al,dl
and al,7 ;sending anything other than LEDs=hang
call keyboard_write ;send keyboard data
test byte ptr es:[bx],80h ;error writing to keyboard?
jz setLED_return2 ;jump if not
setLED_return1:
mov al,0F4h ;error occured; reset+enable keyboard
call keyboard_write ;sent keyboard command
setLED_return2:
and byte ptr es:[bx],3Fh ;Record no error in BDA flags
setLED_return3:
popf ;done with keyboard hardware
pop bx
pop es
ret
setKBDLEDs ENDP
;----------------------------------------------------------------------------
;keyboard_write proc for safely sending data to keyboard controller
;
;Send byte AL to the keyboard controller (port 60h).
;Assumes no BIOS interrupt 9 handler active.
;If the routine times out due to the buffer remaining full, ah is non-zero.
;
;Called with: al = byte to send
; ds = cs
;Returns: ah = 0 if successful, ah = 1 if failed
keyboard_write PROC
push cx
push dx
mov dl,al ;save data for keyboard
;wait until "keyboard receive" timeout is clear (usually is)
xor cx,cx ;counter for timeout
kbd_wrt_loop1:
in al,64h ;get keyboard status
IODELAY
test al,20h ;receive timeout occurred?
jz kbd_wrt_ok1 ;jump if not
loop kbd_wrt_loop1 ;try again
mov ah,1 ;return as failed
jmp kbd_wrt_exit
kbd_wrt_ok1:
in al,60h ;dispose of anything in buffer
;wait for input buffer to clear (usually is)
xor cx,cx ;counter for timeout
kbd_wrt_loop:
in al,64h ;get keyboard status
IODELAY
test al,2 ;check if buffer in use
jz kbd_wrt_ok ;jump if not in use
loop kbd_wrt_loop ;try again
mov ah, 1 ;still busy; return as failed
jmp kbd_wrt_exit
kbd_wrt_ok:
mov al,dl
out 60h,al ;send data to controller/keyboard
IODELAY
;wait until input buffer clear (usually is)
xor cx,cx ;counter for timeout
kbd_wrt_loop3:
in al,64h ;get keyboard status
IODELAY
test al,2 ;check if kbd buffer in use
jz kbd_wrt_ok3 ;jump if not in use
loop kbd_wrt_loop3
mov ah,1 ;still in use; return as failed
jmp kbd_wrt_exit
; wait until output buffer clear
kbd_wrt_ok3:
mov ah,8 ;larger delay loop (8 * 64K)
kbd_wrt_loop4:
xor cx,cx ;counter for timeout
kbd_wrt_loop5:
in al,64h ;get keyboard status
IODELAY
test al,1 ;check if buffer in use
jnz kbd_wrt_ok4 ;jump if not in use
loop kbd_wrt_loop5
dec ah
jnz kbd_wrt_loop4
kbd_wrt_ok4:
xor ah,ah ;return status ok
kbd_wrt_exit:
pop dx
pop cx
ret
keyboard_write ENDP
;----------------------------------------------------------------------------
;Sub-handlers for supported video modes:
;
;All sub-handlers can assume:
; DS = CS
; ES = video ram segment on entry
; DI = offset of "LED" in VRAM (ok to trash)
; AX = video mode (ok to trash)
; BX = video mode * 2 (ok to trash)
;All draw sub-handlers save what they're touching, then draw virtual LED
;All erase sub-handlers restore what the draw routine touched
draw_80col PROC
IF NOBIOSMODE
push di
mov di,(80*(99-4))+78 ;LED in lower-left corner
call draw_mode4
pop di
ENDIF
inc di ;first char attribute is at offset 1
IF REMOVESNOW
cmp [vidcard],h_CGA ;hold up, is this a real CGA card?
jne @@quickwrite ;if not, just write the attributes
push dx
mov dx,3DAh
;save existing attributes without snow:
push ds
push si ;preserve ds:si for restoring later
push es
pop ds ;ds = es
mov si,di ;ds:si = VRAM
WAITHRETRACE
lodsb ;1st attr in al
mov bl,al
inc si ;point to next attr
WAITHRETRACE
lodsb ;2nd attr in al
mov bh,al ;1st in bl, 2nd in bh
pop si
pop ds ;ds back to data seg
mov [word ptr oldVTXT],bx ;record saved attributes
;write new attributes without snow
mov bl,LEDattrC ;get attribute ready
WAITHRETRACE
xchg bx,ax
stosb
xchg bx,ax ;prime next attribute write
inc di
WAITHRETRACE
xchg bx,ax
stosb
pop dx
ret
@@quickwrite:
mov al,es:[di] ;grab first attr
mov ah,es:[di+2] ;grab second attr
mov [word ptr oldVTXT],ax ;record saved attributes
mov al,LEDattrC ;write out the two attributes
stosb ;that will make up our on-screen "LED"
inc di
stosb
ret
ELSE
mov al,es:[di] ;grab first attr
mov ah,es:[di+2] ;grab second attr
mov [word ptr oldVTXT],ax ;record saved attributes
mov al,LEDattrC ;write out the two attributes
stosb ;that will make up our on-screen "LED"
inc di
stosb
ret
ENDIF
draw_80col ENDP
eras_80col PROC
IF NOBIOSMODE
push di
mov di,(80*(99-4))+78 ;LED in lower-left corner
call eras_mode4
pop di
ENDIF
inc di ;first char attribute is at offset 1
mov ax,[word ptr oldVTXT] ;retrieve what we need to restore
IF REMOVESNOW
cmp [vidcard],h_CGA ;hold up, is this a real CGA card?
jne @@quickwrite ;if not, just write the attributes
push dx
mov bx,ax ;prime values for later writing
mov dx,3DAh
WAITHRETRACE
xchg bx,ax
stosb
inc di
mov al,ah
xchg bx,ax
WAITHRETRACE
xchg bx,ax
stosb
pop dx
ret
@@quickwrite:
stosb ;restore 1st attr
inc di ;di already +1; make +2
mov al,ah ;retrieve second attribute
stosb ;restore 2nd attr
ret
ELSE
stosb ;restore 1st attr
inc di ;di already +1; make +2
mov al,ah ;retrieve second attribute
stosb ;restore 2nd attr
ret
ENDIF
eras_80col ENDP
draw_mono PROC
inc di ;first char attribute is at offset 1
mov al,es:[di] ;grab first attr
mov ah,es:[di+2] ;grab second attr
mov [word ptr oldVTXT],ax ;record saved attributes
mov al,LEDattrM ;write out the two attributes
stosb ;that will make up our on-screen "LED"
inc di
stosb
ret
draw_mono ENDP
eras_mono PROC
inc di ;first char attribute is at offset 1
mov ax,[word ptr oldVTXT] ;retrieve what we need to restore
stosb ;restore 1st attr
mov es:[di+1],ah ;restore 2nd attr (di already +1)
ret
eras_mono ENDP
draw_40col PROC
inc di ;first char attribute is at offset 1
mov al,es:[di] ;grab original attribute
mov [byte ptr oldVTXT],al ;save it
mov byte ptr es:[di],LEDattrC;paint our "LED"
ret
draw_40col ENDP
eras_40col PROC
inc di ;first char attribute is at offset 1
mov ax,[word ptr oldVTXT] ;retrieve what we need to restore
stosb ;restore attr
ret
eras_40col ENDP
draw_mode4 PROC
;Draws LED bitmap to offset in DI
push cx
push si
mov si,di
push si ;save for later drawing
push ds ;swap ds and es:
push es
pop ds ;ds=b800:(loc)
pop es
mov di,offset oldVRAM ;es:di = temp vram storage
mov cx,4
save4loop:
lodsw ;load word bank 0; si now +2
stosw
mov ax,[si+8192-2] ;load word bank 1 (0+8192)
stosw
add si,80-2 ;si next row, +0
loop save4loop
;paint new "LED" bitmap (immed. values adjusted for little-endian)
pop di ;retreive our base for drawing
push es
push ds
pop es ;es:di = b800:(loc)
pop ds
mov si,offset LEDbitmap ;ds:si = bitmap data
mov cx,4
putmode4b:
lodsw ;load from 0
stosw ;store to 0 (bank 0), di now +2
lodsw ;load from 2
mov es:[di+8192-2],ax ;store to 0 (bank 1), minding the +2
add di,80-2 ;di = next line of bank 0
loop putmode4b
pop si
pop cx
ret
LEDbitmap:
dw 00000h ; 0000000000000000
dw 0A00Ah ; 0000101010100000
dw 0E82Bh ; 0010101111101000
dw 0E82Bh ; 0010101111101000
dw 0A82Ah ; 0010101010101000
dw 0A82Ah ; 0010101010101000
dw 0A00Ah ; 0000101010100000
dw 00000h ; 0000000000000000
draw_mode4 ENDP
eras_mode4 PROC
push cx
push si
mov si,offset oldVRAM
mov cx,4
erasmode4b:
lodsw ;load from 0
stosw ;store to 0 (bank 0), di now +2
lodsw ;load from 2
mov es:[di+8192-2],ax ;store to 2 (bank 1)
add di,80-2 ;di = 4 (bank 0)
loop erasmode4b
pop si
pop cx
ret
eras_mode4 ENDP
MODE13SHAPE EQU 0 ;Set to 1 for an "LED" shape, but it doesn't show up well
draw_mod13 PROC
eras_mod13 PROC
IF MODE13SHAPE
;NOTs an area in mode 13h in this pattern:
;00000000
;00111100
;01100110
;01100110
;01111110
;01111110
;00111100
;00000000
not word ptr es:[(1*320)+2]
not word ptr es:[(1*320)+4]
not word ptr es:[(2*320)+1]
not word ptr es:[(2*320)+5]
not word ptr es:[(3*320)+1]
not word ptr es:[(3*320)+5]
not word ptr es:[(4*320)+1]
not word ptr es:[(4*320)+3]
not word ptr es:[(4*320)+5]
not word ptr es:[(5*320)+1]
not word ptr es:[(5*320)+3]
not word ptr es:[(5*320)+5]
not word ptr es:[(6*320)+2]
not word ptr es:[(6*320)+4]
ELSE
push cx
mov cx,8
@row13loop:
not word ptr es:[di]
not word ptr es:[di+2]
not word ptr es:[di+4]
not word ptr es:[di+6]
add di,320
loop @row13loop
pop cx
ENDIF
ret
eras_mod13 ENDP
draw_mod13 ENDP
draw_null:
eras_null:
ret
;----------------------------------------------------------------------------
; Indicator (speaker, on-screen "LED", keyboard LED) finish procedures.
; These fire after the real INT 13h call concludes to restore system state.
diskIntLeave PROC
push ds
push ax
push cs
pop ds ;to make var/table addressing easier
restoreLED:
test [i_flags],fLED ;did we paint a virtual LED?
jz restoreKBD ;skip if not
push bx
xor bx,bx
mov bl,[curvidm] ;retrieve video mode we're still in
shl bl,1 ;bx = index into vseg and jump tables
push es
push di
xor di,di ;erase LED at location 0 (upper left)
mov es,[bx+VIDSEGS] ;es = current active vidram segment
call [bx+erastbl] ;erase the virtual LED on-screen
pop di
pop es
pop bx
restoreKBD:
test [i_flags],fKBD ;did we enable any keyboard LEDs?
jz restoreSpkr ;skip if not
push es
mov ax,40h
mov es,ax ;es->BIOS Data Area
push dx
mov dl,es:[97h] ;get actual LED flags
call setKBDLEDs ;return kbd LEDs to saved state
pop dx
pop es
restoreSpkr:
test [i_flags],fHED ;did we make sound?
jz doneExitHandler ;if not, just exit
mov al,[cs:oldSpkr] ;retrieve original speaker state
out 61h,al ;...and restore it
doneExitHandler:
pop ax
pop ds
ret
diskIntLeave ENDP
;----------------------------------------------------------------------------
;INT 13h ENTRY POINT
;We have hooked INT 13h (BIOS disk services) and have come here instead of
;the original INT 13h call. We will check to see if the requested service
;is something that would normally cause HDD physical activity, and perform
;our software indicators if so, or just JMP to the original int13 if not.
ISP_HEADER 13h
pushf ;preserve flags on entry
push bx ;need an index register
IF OMITFLOPPY
cmp dl,80h ;is the target for this call a HDD?
jb noindicators ;if not, skip indicators
ENDIF
cmp ah,0Fh ;is this outside our understood calls?
ja noindicators ;if so, just do original int13
xor bx,bx
mov bl,ah
cmp byte ptr [bx+dsvctbl],1 ;does this call do physical activity?
jne noindicators ;if not, don't do any indicators
doindicators:
pop bx ;restore changed bx
call diskIntEnter ;start indicators
popf ;restore flags from entrypoint
pushf ;simulate INT call
call ORIG_INT13h ;pass to original disk interrupt
pushf ;save INT 13h result flags
call diskIntLeave ;stop indicators
popf ;restore INT 13h result flags
;To pass the results as if they were ours, patch them into stack frame
push bp
mov bp,sp ;bp+0 = saved bp
;bp+2 = ip
;bp+4 = cs
;bp+6 = flags
push ax
lahf ;ah = flags
mov byte ptr [bp+6],ah ;patch flags into stack frame
pop ax
pop bp
iret ;leave int13
noindicators:
pop bx ;restore potentially-changed bx
popf ;restore flags from entrypoint
jmp ORIG_INT13h
resident_code_size equ offset $
TSRcodeEnd@
;-----------------------------------------------------------------------
;Transient portion of TSR begins
_TEXT SEGMENT 'CODE'
ASSUME cs:_TEXT,ds:NOTHING,es:NOTHING,ss:NOTHING
banner_ db 13,10,'SOFTHDDI v',VERSION_STR,' - Jim Leonard, 2022',13,10,'$'
usage_ db 'Displays "virtual" hard disk LEDs and sounds during disk activity.',13,10
db 'Intended for use with silent storage, ie. solid-state drives (SD/CF cards).',13,10
db 10
db 'Usage:'
db 9,'SOFTHDDI [LSK]',9,'Install with one or more indicators (see below)',13,10
db 9,'SOFTHDDI R',9,'Disable and remove from memory',13,10
db 13,10
db 'Installation options:',13,10
db 9,'L',9,'Enable on-screen LED',13,10
db 9,'S',9,'Enable virtual seek sound',13,10
db 9,'K',9,'Enable keyboard LED',13,10
; db 9,'1..9',9,'Volume of click (optional)',13,10
db 13,10
db 'Example:',13,10,9,'SOFTHDDI L S',9,'Install with on-screen LED and sound',13,10
db '$'
installed_ db 'Installed.',13,10,'$'
enableLED_ db 'Virtual LED requested',13,10,'$'
enableKBD_ db 'Keyboard LED requested',13,10,'$'
enableSND_ db 'Virtual seek sound requested',13,10,'$'
PC_no_KBDLEDs_ db 'This program cannot control keyboard LEDs on PC/XT-class hardware.',13,10,'$'
already_inst_ db 'Already installed.',13,10,'To change options, remove with R, then reinstall.',13,10,'$'
cant_remove_ db "Can't remove from memory.",13,10,'$'
uninstalled_ db 'Removed.',13,10,'$'
indicators db 0
vdetected db 0
@Startup2 Y ;Y = allocate a __psp variable
push ds
pop es
ASSUME ES:_INIT
push cs
pop ds
ASSUME DS:_TEXT
DISPLAY_STRING banner_
mov bx,1000h ;set memory block to 64K
mov ah,4Ah
int 21h
mov si,80h ;si->command-line pascal string
cld
xor cx,cx
seges lodsb
or al,al
jz showusage
mov cl,al ;loop through chars on command-line
cmdline_loop:
seges lodsb ;load char from command-line
call uppercase ;al->AL
cmp al,'L'
je wantLED
cmp al,'S'
je wantSnd
cmp al,'K'
je wantKbd
cmp al,'R'
je removing
cmdcontinue:
loop cmdline_loop
cmp indicators,0
jne installing ;install if so, otherwise print errmsg
showusage:
mov dx,offset _TEXT:usage_
jmp exit_with_error
wantLED:
call videoid ;discover what video card is installed
or indicators,fLED
DISPLAY_STRING enableLED_
jmp cmdcontinue
wantSnd:
or indicators,fSND
DISPLAY_STRING enableSND_
jmp cmdcontinue
wantKbd:
;We need to stop the user if they try to enable AT keyboard LEDs on an XT
;system, because they'll effectively hang the system with the resulting
;timeouts. One way to determine if we're on an AT is to check if the
;8255 is PC-type or AT-type, which can be done by exploiting bit 7 at 61h
;(Port B) which is read-write on PC but read-only on AT. For more
;information, see PCTIM003.TXT by Kris Heidenstrom.
push cx
mov cx,400h ;Six attempts (top bits of CH)
pushf
cli ;timing-sensitive operation
in al,61h ;Get Port B contents
IODELAY
mov ah,al ;Original value to AH
Flip61Loop:
xor ah,10000000b ;Flip top bit
mov al,ah ;Get value to AL
out 61h,al ;Write value to port
in al,61h ;Read it back
IODELAY
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).