Vous êtes sur la page 1sur 14

TITLE IDESLEEP.EXE (C) 1995 Pierre R.

Desloover
page 58,132

; -----------------------------------------------------------------------------
; NAME: IDESLEEP.ASM - IDE DRIVE AUTO-SLEEP TSR UTILITY
; VER: Version 1.0E (.EXE image instead of .COM)
; DATE: 08/11/95
;
; SYNOPSIS: Automatically issues IDE controller 'Sleep' mode command
; when no hdisk activity for user specified time interval.
; Hooks int 1ch for timer logic and issuing controller cmd.
; Hooks int 15h to monitor device (hdisk) busy/done states.
;
; INVOKE: IDESLEEP 0 | 1-59 <CR>
; (Where: 1-59 is delay interval in minutes, or 0 to deactivate)
; -----------------------------------------------------------------------------

DOSSEG
.MODEL SMALL

;equates
FALSE = 0
TRUE = 0ffh
_CR = 0dh
_SPACE = 20h
_TAB = 09h

.DATA

; NOTE: NO DATA VARS

.CODE

START: jmp INSTALL_TSR

;code segment prog title & version id header


cs_idstamp db 'IDESLEEP.EXE v1.0E (C)1995 P. DESLOOVER '
SIZE_CS_ID = $ - cs_idstamp

oldint15h_vec dd ? ;original int 15h vector before TSR installed


oldint1ch_vec dd ? ;original int 1ch vector before TSR installed
oldintfeh_vec dd ? ; " " feh ", etc.
int15h_busy db FALSE ;Busy flag set when hdisk dev is busy, else clear
int1ch_busy db FALSE ;Busy flag to prevent re-entrant int 1ch calls
tick_count dw 0 ;tick count maintained in int 1ch isr
max_tick_count dw ? ;interval max tick cnt based on user minutes
n_hdisks db ? ;# hdisks for card zero detected via BIOS
;sleepactive db TRUE ;init auto-sleep active (sys-req key toggle)

;NOTE: EQUATES SIZE_CS_HEADER and SIZE_T_CNT must be defined just prior


;TO INTFEH ISR BELOW SINCE USED TO CALC OFFSET TO RESIDENT CS:VARS.
SIZE_CS_HEADER = $ - cs_idstamp
SIZE_T_CNT = $ - max_tick_count

Comment | Establish a vector not likely to be revectored by any other app.


AFTER this TSR installed since reinstall code relies on cs:sign-
ature being found @current vector adr-some offset in vector map.
(There's always a chance that int 1ch or 15h will be revectored.)
This INT vect is known to be "not assigned" or "not used" normally.
This unique vector used in conjunction with reinstallation code. |

INTFEH_ISR PROC FAR


;Chain to other possible isr vector.
;OK to do this since DOS always inits upper vector map INTs F1H-FFH to
;point to a common IRET op address and/or want to preserve vect chain
;in case someone else may have revectored it (not likely, but possible).

JMP DWORD PTR CS:[OLDINTFEH_VEC]

INTFEH_ISR ENDP

INT1CH_ISR PROC FAR

pushf
sti

;Check this routine's busy flag to prevent re-entrant calls to this ISR
cmp cs:[int1ch_busy],FALSE
jz int1ch_not_busy

CHAIN_TO_ORG_ISR:
;busy, use original int1ch (chain to it)
popf ;restore flags
jmp dword ptr cs:[oldint1ch_vec]

int1ch_not_busy:
;go busy
mov cs:[int1ch_busy],TRUE

comment |
;check auto sleep active flag
cmp cs:[sleepactive],TRUE
je chk_hdsk_busy ;active
mov cs:[int1ch_busy],FALSE ;not, exit auto-sleep logic
jmp short chain_to_org_isr
chk_hdsk_busy:
|

;Reset tick counter if hdisk device is busy.


cmp cs:[int15h_busy],FALSE
jz int15h_not_busy ;hdisk dev not busy
mov cs:[tick_count],0 ;reset
mov cs:[int1ch_busy],FALSE
jmp short chain_to_org_isr

int15h_not_busy:
;increment time interval and check for expiration
inc cs:[tick_count]
push ax
mov ax,cs:[max_tick_count] ;get user's tick interval value
cmp cs:[tick_count],ax ;expired?
pop ax
jae issue_sleep_cmd ;interval expired, go to sleep
mov cs:[int1ch_busy],FALSE
jmp short chain_to_org_isr

issue_sleep_cmd:
push ax
push dx

;ide sleep mode 2 command bytes 99, E6 to port 1F7 (1st hdisk)
mov dx,1f7h ;primary hdisk
mov al,99h
out dx,al
mov al,0e6h
out dx,al
;;; check cmd operation status?: in al,dx

;check for possible 2nd hard drive on same controller card


cmp cs:[n_hdisks],1
jz _continue_isr ;only 1 hdisk detected by BIOS

;ide sleep mode 2 command bytes 99, E6 to port 177 (possible 2nd hdisk)
mov dx,177h ;secondary hdisk
mov al,99h
out dx,al
mov al,0e6h
out dx,al
;;; check cmd operation status?: in al,dx

_continue_isr:
pop dx
pop ax
mov cs:[tick_count],0 ;reset
mov cs:[int1ch_busy],FALSE
jmp short chain_to_org_isr

INT1CH_ISR ENDP

INT15H_ISR PROC FAR

pushf
sti

comment |
;check sys req key first and toggle state to prevent/allow ide auto-sleep
;logic/countdown in int 1ch isr
cmp ah,85h
jne chk_dev
;notes: al only set after this int15h int! so make/break ck here won't work
; cmp al,01h ;break of key (released)?
; jne chain2_org15h
;
not cs:[sleepactive] ;toggle flag
mov cs:[tick_count],0 ;reset tick
jmp short chain2_org15h
chk_dev:
|

cmp ah,90h ;device busy or done?


jb chain2_org15h ;not interested in lower fcn #'s
cmp ah,91h
ja chain2_org15h ;not interested in higher fcn #'s

cmp al,1h ;fcn for disk/diskette?


ja chain2_org15h ;no, it's for network, ignore it

;set hdisk busy state flag


mov cs:[int15h_busy],TRUE ;assume busy
cmp ah,90h ;device busy?
je chain2_org15h ;busy
mov cs:[int15h_busy],FALSE ;dev not busy

CHAIN2_ORG15H:
popf ;restore flags
jmp dword ptr cs:[oldint15h_vec]

INT15H_ISR ENDP

comment | THROW AWAY CODE FOLLOWS |


INSTALL_TSR:

;.EXE entry: need to set SP


cli
mov sp,0fffeh ;ss=cs already
sti

;****************************************************************************
; PARSE CMD LINE FOR REQUIRED ARG: EXPIRATION INTERVAL IN MINUTES
;****************************************************************************

;.EXE entry: ds=es=PSP. Leave DS alone for cmd line parsing only
; and set ES=CS.
; Use CS: overrides where necessary for data offsets in parsing only.
push cs
pop es

;have any cmd line args?


mov si,80h
cld
lodsb ;al<-ds:[si]
or al,al
jnz have_arg

;No args on cmd line or invalid cmd line


bad_cmd_line:
mov dx,offset cs:prog_usage
jmp error_exit

HAVE_ARG:
;Skip possible white spaces until first non white found.
lodsb
cmp al,_CR
je bad_cmd_line
cmp al,_SPACE
je have_arg
cmp al,_TAB
je have_arg

;First non-white char found: get minute(s) count NN string into buffer
;until first white char found. Enforce 2 chars max.
mov di,offset cs:temp_buff
dec si ;backup to first char

get_minutes_arg:
cmp di,offset cs:temp_buff+2
ja bad_cmd_line

lodsb
cmp al,_CR ;look-out for REAL EOL char!
je convert_minutes_arg ;to done args

cmp al,_SPACE
je convert_minutes_arg ;to done args
cmp al,_TAB
je convert_minutes_arg ;to done args

;store char
cmp al,41h ;skip case convert if below range
jb store_it3
and al,0dfh ;convert to upper case char (mask off bit 5)
store_it3:
stosb
jmp short get_minutes_arg

convert_minutes_arg:

;set desired mem seg values for rest of program (note no .DATA vars)
;ds=es=cs
mov cs:[psp],ds ;save psp
push cs
pop ds
push cs
pop es
ASSUME DS:_TEXT, ES:_TEXT
sub di,offset temp_buff ;length of entered string
mov si,offset temp_buff
call ascii_to_val
jc bad_cmd_line ;conversion error
;check for special timeout value of ZERO (deactivate auto-sleep mode)
or al,al
jnz have_arg_minutes

;set flag to block (deactivate auto-sleep mode) any further tick processing
mov [mode_state],FALSE ;set OFF
mov [mins],al ;save for display purposes
mov al,1 ;set atleast 1 min dummy value here
jmp short use_minutes ;and continue install

have_arg_minutes:
;do range ck on mins value: 1 <= minutes <= 59
cmp al,1
jb bad_cmd_line ;range error
cmp al,59
ja bad_cmd_line ;range error

;enable (activate auto-sleep mode) tick processing


mov [mode_state],TRUE ;set ON
mov [mins],al ;save for display purposes

use_minutes:
;use minutes as lookup index into tick table to set [max_tick_count]
dec al ;logical based
xor ah,ah
shl ax,1
add ax,offset ticks_tbl
mov bx,ax
mov ax,[bx]
mov [max_tick_count],ax

;REINSTALL CODE
; 1 - get vector map EFh vector and scan at that address
; for idesleep TSR installed ID string
push es
xor ax,ax
mov es,ax

cli
mov di,es:[4*0feh] ;int 0feh vector offset
mov es,es:[4*0feh][2] ;int 0feh vector segment
sti
mov word ptr [oldintfeh_vec],di ;temp copy
mov word ptr [oldintfeh_vec][2],es ;temp copy
sub di,SIZE_CS_HEADER ;possible TSR copy
mov si,offset cs_idstamp ;current copy
mov cx,SIZE_CS_ID

cld
repe cmpsb
pop es
jnz continue_1st_install ;signature not found=TSR not previously installed

;2 - change [max_tick_count] & [tick_count] in resident code


push es
mov ax,[max_tick_count]
mov di,word ptr [oldintfeh_vec] ;get temp copy of TSR offset
mov es,word ptr [oldintfeh_vec][2] ;get temp copy of TSR seg

sub di,SIZE_T_CNT ;calc offset before ISR loc


cld
stosw ;store it in TSR resident code

mov es:[tick_count],0 ;always reset!

;3 - set resident code int 1ch busy flag per local copy mode state flag
mov es:[int1ch_busy],TRUE ;assume busy
cmp [mode_state],FALSE
jz _continue ;block auto-sleep
mov es:[int1ch_busy],FALSE ;allow auto-sleep

_continue:
pop es

;4 - inform user TSR timeout value reset and do regular DOS terminate
call show_installed_msg
call show_mode_state

;5 - then do normal DOS terminate


mov ax,4c00h
int 21h
;END REINSTALL CODE

continue_1st_install:

;find #hard drives 1st card only (0-2 returned by AT BIOS, others return more)
mov dl,80h
mov ah,08h
int 13h
or dl,dl
jnz _cont_w_install
mov dx,offset no_hdisk_msg
jmp short error_exit

_cont_w_install:
mov [n_hdisks],dl

comment | RELEASE DOS ENVIRONMENT BLOCK TO MAKE RESIDENT SIZE SMALLER


|
push es
mov es,[psp]
mov es,es:[2ch]
mov ah,49h
int 21h
pop es

comment | INSTALL NEW ISR'S |


;Revector INT 1cH to our replacement ISR
push es

mov ax,351ch
int 21h
mov word ptr cs:[oldint1ch_vec],bx
mov word ptr cs:[oldint1ch_vec][2],es
mov ax,251ch
mov dx,offset int1ch_isr
int 21h

;Revector INT 15H to our replacement ISR


mov ax,3515h
int 21h
mov word ptr cs:[oldint15h_vec],bx
mov word ptr cs:[oldint15h_vec][2],es
mov ax,2515h
mov dx,offset int15h_isr
int 21h

;Revector INT 0FEH to our replacement ISR


mov ax,35feh
int 21h
mov word ptr cs:[oldintfeh_vec],bx
mov word ptr cs:[oldintfeh_vec][2],es
mov ax,25feh
mov dx,offset intfeh_isr
int 21h

pop es

;display installed OK msg and mode msg


call show_installed_msg
call show_mode_state

;calc amount of code to remain


mov dx,offset cs:install_tsr
add dx,0fh
mov cl,4
shr dx,cl
MOV AX,CS
ADD DX,AX
SUB DX,[PSP]
;tsr
mov ax,3100h
int 21h

error_exit:
;on exit, dx has ptr to msg to display
mov ah,09h
int 21h
mov ax,4cffh
int 21h

; SUBROUTINES

ASCII_TO_VAL:
;Converts Decimal ascii string (up to 5 chars) to value
;On entry: SI has base address of string to convert
; DI has length of string to convert
;On exit: CY set if word overflow or invalid ascii char, else CY clr and
; AX has converted value
;
mov bp,10 ;set base 10
xor ax,ax ;init results
cld

convert_loop:
push ax
lodsb ;al <- ds:[si]
or al,20h ;to lower case
;check if legal decimal ascii char
call is_dec_ascii
jnc continue_convert
;error
pop ax
ret

continue_convert:
xor ah,ah
sub al,'0' ;remove bias

radix_mult:
mov cx,ax
pop ax
mul bp ;ax * bp -> dx:ax
or dx,dx ;word overflow ?
jz no_overflow
overflow:
stc ;overflow error
ret

no_overflow:
add ax,cx
jc overflow ;overflow error

;loop control
dec di
jnz convert_loop
clc ;conversion success
ret

IS_DEC_ASCII:
cmp al,'0'
jc bad1
cmp al,'9'
ja bad
clc
ret
bad: stc
bad1: ret

;TEMP DATA FOLLOWS


INSTALL_MSG db 0dh, 0ah
db 'IDESLEEP.EXE v1.0E (C)1995 P. Desloover - TSR installed OK'
db 0dh, 0ah, 0ah
delay_msg db 'Delay interval set: $'
minutes_msg db ' Minutes', 0dh, 0ah,'$'

mode_state_msg db 'Auto-sleep mode is: $'


active_msg db 'Active',0dh, 0ah, 0ah,'$'
inactive_msg db 'Inactive',0dh, 0ah, 0ah,'$'

no_hdisk_msg db 0dh, 0ah


db 'No Hard Disk(s) found in system - TSR not installed'
db 0dh, 0ah, 0ah,'$'

prog_usage db 0dh, 0ah


db 'Usage: IDESLEEP DELAY_INTERVAL <CR>', 0dh, 0ah, 0ah
db 'Where required command line argument DELAY_INTERVAL is either:', 0dh, 0ah
db 'the number of minutes (1-59) of disk inactivity before the IDE', 0dh, 0ah
db 'sleep mode command is automatically issued by this TSR, or zero', 0dh, 0ah
db 'minutes (0) to deactivate the TSR''s IDE auto-sleep mode.', 0dh, 0ah, 0ah
db 'Examples: idesleep 12 <CR> or: idesleep 0 <CR>', 0dh, 0ah, 0ah,'$'

;int 1ch isr 18.3/sec rate * 60 secs/min = total ticks per min(s)
;DW values imported from a special created .lst file (see: ticks.c).
;NOTE: #'s 1-59 table entries max
TICKS_TBL dw 1098 ;#secs ( 60) * 18.3 (1 minute)
dw 2196 ;#secs ( 120) * 18.3 (2 minutes), etc.
dw 3294
dw 4392
dw 5490
dw 6588
dw 7686
dw 8784
dw 9882
dw 10980
dw 12078
dw 13176
dw 14274
dw 15372
dw 16470
dw 17568
dw 18666
dw 19764
dw 20862
dw 21960
dw 23058
dw 24156
dw 25254
dw 26352
dw 27450
dw 28548
dw 29646
dw 30744
dw 31842
dw 32940
dw 34038
dw 35136
dw 36234
dw 37332
dw 38430
dw 39528
dw 40626
dw 41724
dw 42822
dw 43920
dw 45018
dw 46116
dw 47214
dw 48312
dw 49410
dw 50508
dw 51606
dw 52704
dw 53802
dw 54900
dw 55998
dw 57096
dw 58194
dw 59292
dw 60390
dw 61488
dw 62586
dw 63684
dw 64782 ;mins = 59 MAX
;REF only dw 65880 ;mins = 60 (REF only: over word limit)

;scratch for cmd line arg


temp_buff db 8 dup (20h)

;auto-sleep active/inactive state


mode_state db FALSE
;saved user mins spec'ed
mins db 0
;saved psp seg value
psp dw ?

SHOW_INSTALLED_MSG:
mov si,offset temp_buff
mov di,offset delay_msg+20
cld
lodsw
stosw
mov dx,offset INSTALL_MSG
mov ah,09h
int 21h

mov dx,offset minutes_msg


mov bx,dx ;make copy
cmp [mins],10
jae _disp_delay ;2 digits (plural)
inc dx ;skip 1 space
cmp [mins],2
jae _disp_delay ;1 digit (plural)
cmp [mins],0
je _disp_delay ;1 digit (plural)
mov byte ptr [bx][7],_SPACE ;=1 (make singular case)
_disp_delay:
mov ah,09h
int 21h
ret

SHOW_MODE_STATE:
mov dx,offset mode_state_msg
mov ah,09h
int 21h
mov dx,offset active_msg ;assume active
cmp [mode_state],TRUE
je show_mode
mov dx,offset inactive_msg
show_mode:
mov ah,09h
int 21h
ret

END START