Metropoli BBS
VIEWER: 2m-abios.asm MODE: TEXT (CP437)
;*********************************************************************
;*                                                                   *
;*     2M-ABIOS 1.1  -  (C) Abril 1994 Ciriaco García de Celis.      *
;*                                                                   *
;*          Código para emular al 100% la BIOS AMI de 1993.          *
;*                                                                   *
;*     Si el ordenador posee disco duro, la INT 40h controla los     *
;*     accesos a disquete. Desviar esta interrupción en lugar de     *
;*     la  INT 13h  permite que este programa tome el control de     *
;*     las disqueteras antes que cualquier otro  (incluso aunque     *
;*     se instale después) y además permite seguir trabajando al     *
;*     código del DOS que, desde INT 13h, soluciona el cruce con     *
;*     las fronteras de DMA antes de invocar a la INT 40h.           *
;*                                                                   *
;*     Si el ordenador no posee disco duro, o tiene controladora     *
;*     de disco duro de  XT,  se desvía INT 13h y se cuelga este     *
;*     código de la misma,  cuidando  evitar un cruce con el DMA     *
;*     a través de un buffer intermedio auxiliar si es preciso.      *
;*                                                                   *
;*     Ensamblar con TASM /m5 y linkar con TLINK para obtener un     *
;*     fichero EXE que se instala con DEVICE desde el CONFIG.SYS     *
;*                                                                   *
;*********************************************************************

               .286

; ------------ Macros de propósito general.

XPUSH          MACRO regmem            ; apilar lista de registros
                 IRP rm, <regmem>
                   PUSH rm
                 ENDM
               ENDM

XPOP           MACRO regmem            ; desapilar lista de registros
                 IRP rm, <regmem>
                   POP rm
                 ENDM
               ENDM

; ************ Inicio del área residente.

_PRINCIPAL     SEGMENT
               ASSUME CS:_PRINCIPAL, DS:_PRINCIPAL

               ORG   0

ini_residente  EQU   $

               DD    -1           ; encadenamiento con otros drivers
tipo_drive     DW    8000h        ; palabra de atributo:
                                  ; bit 15 a 1: dispositivo caracteres
                                  ; bit 14 a 0: sin control IOCTL
               DW    estrategia   ; rutina de estrategia
               DW    interrupcion ; rutina de interrupción
               DB    "2M-BIOS$"   ; nombre del dispositivo

estrategia     PROC  FAR
               MOV   CS:pcab_pet_segm,ES
               MOV   CS:pcab_pet_desp,BX
               RET
estrategia     ENDP

interrupcion   PROC  FAR
               CALL  main  ; tras instalar: XPUSH <DS, BX> y MOV BX,??
pcab_pet_segm  DW    ?
               MOV   DS,BX
               DB    0BBh  ; opcode de MOV BX,??
pcab_pet_desp  DW    ?
               MOV   WORD PTR [BX+3],8103h  ; código de error
               XPOP  <BX, DS>
               RET
interrupcion   ENDP

; ****************************************
; *                                      *
; *   D A T O S    R E S I D E N T E S   *
; *                                      *
; ****************************************

; ------------ Identificación estandarizada del programa.

program_id     LABEL BYTE
segmento_real  DW    0   ; segmento real donde será cargado
offset_real    DW    0   ; offset real     "     "     "
longitud_total DW    0   ; zona de memoria ocupada (párrafos)
info_extra     DB    03h ; bits 0, 1 y 2-> 000: normal, con PSP
                         ;                 001: bloque UMB XMS
                         ;                 010: *.SYS
                         ;                 011: *.SYS formato EXE
                         ; bit 7 a 1: «extension_id» definida
multiplex_id   DB    0   ; número Multiplex de este TSR
vectores_id    DW    tabla_vectores
extension_id   DW    0
               DB    "*##*"
autor_nom_ver  DB    "CiriSOFT:2M-ABIOS:1.1",0

               DB    2  ; número de vectores de interrupción usados
tabla_vectores EQU   $
               DB    2Fh           ; INT 2Fh
ant_int2F      LABEL DWORD         ; dirección original
ant_int2F_off  DW    0
ant_int2F_seg  DW    0
               DB    40h           ; INT 40h
ant_int40      LABEL DWORD         ; dirección original
ant_int40_off  DW    0
ant_int40_seg  DW    0
               DB    13h           ; INT 13h podría llegar a usarse
ant_int13      LABEL DWORD
ant_int13_off  DW    0
ant_int13_seg  DW    0

; ***************************************
; *                                     *
; *   C O D I G O   R E S I D E N T E   *
; *                                     *
; ***************************************

; ------------ Rutina de gestión de INT 2Fh.

ges_int2F      PROC  FAR
               STI
               CMP   AH,CS:multiplex_id
               JE    preguntan
               JMP   CS:ant_int2F      ; saltar al gestor de INT 2Fh
preguntan:     CMP   DI,1992h
               JNE   ret_no_info       ; no llama alguien del convenio
               MOV   AX,ES
               CMP   AX,1492h
               JNE   ret_no_info       ; no llama alguien del convenio
               PUSH  CS
               POP   ES                ; sí llama: darle información
               LEA   DI,autor_nom_ver
ret_no_info:   MOV   AX,0FFFFh         ; "entrada multiplex en uso"
               IRET
ges_int2F      ENDP

; ------------ Rutina de control de INT 40h.

r_flags        EQU   WORD PTR [BP+18h] ; constantes para parámetros
r_flags_l      EQU   BYTE PTR [BP+18h]
r_flags_h      EQU   BYTE PTR [BP+19h]
r_ax           EQU   WORD PTR [BP+12h]
r_al           EQU   BYTE PTR [BP+12h]
r_ah           EQU   BYTE PTR [BP+13h]
r_cx           EQU   WORD PTR [BP+10h]
r_cl           EQU   BYTE PTR [BP+10h]
r_ch           EQU   BYTE PTR [BP+11h]
r_dx           EQU   WORD PTR [BP+0Eh]
r_dl           EQU   BYTE PTR [BP+0Eh]
r_dh           EQU   BYTE PTR [BP+0Fh]
r_bx           EQU   WORD PTR [BP+0Ch]
r_bl           EQU   BYTE PTR [BP+0Ch]
r_bh           EQU   BYTE PTR [BP+0Dh]
r_bp           EQU   WORD PTR [BP+0Ah]
r_si           EQU   WORD PTR [BP+08h]
r_di           EQU   WORD PTR [BP+06h]
r_ds           EQU   WORD PTR [BP+04h]
r_es           EQU   WORD PTR [BP+02h]

ges_int40      PROC
               STI
               CLD
               PUSH  AX
               PUSH  CX
               PUSH  DX
               PUSH  BX
               PUSH  BP
               PUSH  SI
               PUSH  DI
               PUSH  DS
               PUSH  ES
               PUSH  BP
               MOV   BP,SP
               PUSH  40h
               POP   DS
               PUSH  AX
               MOV   AL,AH
               CMP   AL,18h
               JA    mal_funcion
               CMP   AL,5
               JBE   func_oper
               CMP   AL,8
               JNE   func_aux?
               MOV   AL,6
               JMP   func_oper
func_aux?:     CMP   AL,15h
               JB    mal_funcion
               SUB   AL,0Eh
func_oper:     CBW
               MOV   DI,AX
               POP   AX
               SHL   DI,1
               JMP   CS:tab_jmp[DI]    ; ejecutar función
main_exit:     MOV   AL,AH             ; preservar resultado
               LAHF                    ; preservar flags
               PUSH  AX
               PUSH  40h
               POP   DS
               MOV   AL,r_dl           ; DL a la llamada (unidad)
               CMP   AL,1
               JA    u_det             ; unidad incorrecta
               XOR   AH,AH
               MOV   BX,90h
               ADD   BX,AX             ; [BX] -> estado físico unidad
               TEST  BYTE PTR DS:[BX],10h  ; ¿densidad determinada?
               JZ    u_det                 ; no
               MOV   DL,4                  ; sí
               MUL   DL
               MOV   CL,AL                 
               SHL   DL,CL
               OR    DS:[8Fh],DL           ; unidad determinada
u_det:         POP   AX
               SAHF                    ; recuperar flags
               MOV   AH,AL             ; recuperar resultado
exit_i40:      MOV   r_ah,AH           ; AH para la salida del IRET
               MOV   AX,201H           ; STI + STC en flags
               JC    set_err           ; hay error
               AND   r_flags_l,0FEH    ; CLC (si no hay error)
               DEC   AX                ; dejar sólo STI
set_err:       OR    r_flags,AX        ; flags a la salida del IRET
               POP   BP
               POP   ES
               POP   DS
               POP   DI
               POP   SI
               POP   BP
               POP   BX
               POP   DX
               POP   CX
               POP   AX                ; registros con resultado
               IRET
ges_int40      ENDP

mal_funcion:   POP   AX
               MOV   AH,1              ; función/parámetro incorrecto
               MOV   DS:[41h],AH       ; código de error
               STC                     ; condición de error
               JMP   exit_i40

; ------------ Función 0: Resetear el sistema de disco.

reset          PROC
               CALL  full_init         ; inicialización plena
               MOV   DS:[41h],AH       ; código de error
               MOV   AL,AH             ; preservar código
               LAHF                    ; preservar flags
               PUSH  AX
               PUSH  DS
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   CL,[SI+2]
               POP   DS
               MOV   DS:[40h],CL       ; tics para detención motor
               POP   AX
               SAHF
               MOV   AH,AL             ; restaurado código y flags
               JMP   exit_i40
reset          ENDP

; ------------ Inicialización plena.

full_init      PROC
               AND   BYTE PTR DS:[3Eh],0F0h   ; futuro recalibramiento
               JMP   init_fdc                 ; inicializar FDC
fdc_init:      JC    init_end
               JMP   send_specify             ; enviar specify
specify_sent:  JC    init_end
               XOR   AH,AH                    ; no hay error
init_end:      RET
full_init      ENDP

; ------------ Función 1: Obtener resultado de la última operación.

get_status     PROC
               MOV   AH,DS:[41h]
               OR    AH,AH
               JZ    no_err
               STC
no_err:        JMP   exit_i40
get_status     ENDP

; ------------ Función 15h: Obtener el tipo de disco.

get_disk_type  PROC
               CMP   DL,1
               JBE   gdt_ok
               MOV   AH,1              ; función/parámetro incorrecto
               STC
               MOV   DS:[41h],AH       ; código de error
               JMP   gdt_exit
gdt_ok:        MOV   BX,90h
               XOR   DH,DH
               ADD   BX,DX             ; [BX] -> estado físico unidad
               MOV   BL,[BX]           ; estado físico de la unidad
               OR    BL,BL
               JNZ   gdt_posible
               XOR   AH,AH             ; no existe tal unidad
               JMP   gdt_bye
gdt_posible:   AND   BL,7
               JZ    gdt_without       ; 360K
               CMP   BL,3
               JE    gdt_without       ; 360K
               MOV   AH,2
               JMP   gdt_bye           ; con soporte cambio de línea
gdt_without:   MOV   AH,1              ; sin soporte cambio de línea
gdt_bye:       CLC
               MOV   BYTE PTR DS:[41h],0  ; siempre sin error
gdt_exit:      JMP   main_exit
get_disk_type  ENDP

; ------------ Función 17h: Establecer tipo de soporte para formateo.

set_type_fmt   PROC
               XOR   AH,AH
               CMP   DL,1
               JBE   set_tp
set_tp_bad_p:  MOV   AH,1              ; función/parámetro incorrecto
set_tp_err:    MOV   DS:[41h],AH       ; código de error
               STC
               JMP   set_tp_exit
set_tp:        CMP   AL,0              ; validar parámetro
               JE    set_tp_bad_p
               CMP   AL,4
               JA    set_tp_bad_p
               MOV   BX,90h
               XOR   DH,DH
               ADD   BX,DX             ; [BX] -> estado físico unidad
               CMP   AL,1              ; ¿360K en 360K?
               JNE   set_tp_n360
               MOV   BYTE PTR DS:[BX],93h  ; actualizar variable tipo
               MOV   BYTE PTR DS:[41h],0   ; anular errores previos
               JMP   set_tp_t_exit
set_tp_n360:   MOV   CX,AX
               PUSH  BX
               CALL  motor_on              ; arrancar motor
               POP   SI
               PUSH  SI
               CALL  read_disk_chg         ; ¿cambio de disco?
               POP   BX
               CMP   AH,6                  ; (0: no, 6: sí)
               JBE   set_tp_cd             ; haya cambio o no
               CMP   AH,80h
               JNE   set_tp_cd             ; sí existe disco
               CMP   BYTE PTR DS:[BX],97h  ; ¿250 Kbps y no es 5.25?
               JE    set_tp_err            ; error
               MOV   BYTE PTR DS:[BX],61h  ; 300 Kbps, try 360 en 1.2
               JMP   set_tp_err            ; error
set_tp_cd:     CMP   CL,4                  ; ¿720K en 720K?
               JNE   set_tp_n720           ; no
               MOV   BYTE PTR DS:[BX],97h  ; actualizar variable tipo
               JMP   set_tp_t_exit
set_tp_n720:   CMP   CL,2                  ; ¿360K en 1.2M?
               JNE   set_tp_ndd            ; no
               MOV   BYTE PTR DS:[BX],74h  ; actualizar variable tipo
               JMP   set_tp_t_exit
set_tp_ndd:    MOV   BYTE PTR DS:[BX],15h  ; 1.2M en 1.2M
set_tp_t_exit: OR    AH,AH                 ; comprobar posible error
               JNZ   set_tp_err
               MOV   BYTE PTR DS:[41h],0
set_tp_exit:   JMP   main_exit
set_type_fmt   ENDP

; ------------ Función 16h: Detectar cambio de disco.

detect_change  PROC
               CMP   DL,1
               JBE   det_ch
               MOV   AH,1              ; función/parámetro incorrecto
det_ch_r_err:  STC
               JMP   det_ch_end
det_ch:        XOR   DH,DH
               MOV   BX,90h
               ADD   BX,DX               ; [BX] -> estado físico
               CMP   BYTE PTR DS:[BX],0  ; ¿estado indeterminado?...
               MOV   AH,80h              ; "unidad no preparada"
               JE    det_ch_r_err        ; ...en efecto
               MOV   AH,[BX]
               AND   AH,7
               JZ    det_ch_yes          ; 360K en 360K, no es posible
               CMP   AH,3
               JNE   det_ch_calc         ; no es 360K en 360K
det_ch_yes:    MOV   AH,6                ; hay cambio (o desconocido)
               STC
               JMP   det_ch_end
det_ch_calc:   CALL  motor_on          ; arrancar motor
               MOV   DX,3F7h
               IN    AL,DX             ; leer línea de cambio de disco
               SHL   AL,1
               JC    det_ch_yes        ; hay cambio de disco
               XOR   AH,AH             ; no lo hay
det_ch_end:    MOV   DS:[41h],AH       ; actualizar código de error
               PUSH  AX
               PUSH  SI
               PUSH  DS
               MOV   SI,0
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   AL,[SI+2]
               POP   DS
               MOV   DS:[40h],AL       ; tiempo detención motor
               POP   SI
               POP   AX
               JMP   main_exit
detect_change  ENDP

; ------------ Funciones 2, 3 y 4: Leer, escribir y verificar.

read_wr_verify PROC
               CMP   DL,1
               JBE   rwv_posible
               MOV   AH,1              ; función/parámetro incorrecto
rwv_err:       MOV   DS:[41h],AH       ; código de error
               XOR   AL,AL
               STC
               JMP   rwv_exit
rwv_posible:   MOV   SI,90h
               PUSH  DX
               XOR   DH,DH
               ADD   SI,DX               ; [SI] -> estado físico
               CMP   BYTE PTR DS:[SI],0  ; ¿estado indeterminado?...
               POP   DX
               JNE   rwv_state_ok      ; no
               PUSH  AX
               CALL  get_drive_type    ; obtener tipo disquetera en AL
               JZ    rwv_type_ok       ; ha resultado posible
               POP   AX
rwv_not_ready: MOV   AH,80h            ; "unidad no preparada"
               JMP   rwv_err
rwv_type_ok:   OR    AL,AL             ; ¿existe la unidad?
               POP   AX
               JZ    rwv_not_ready     ; no existe esa unidad
               MOV   BYTE PTR DS:[SI],2    ; probando 1.2M en 1.2M
rwv_state_ok:  MOV   DI,3Fh
               AND   BYTE PTR DS:[DI],7Fh  ; operación Read/Verify
               CMP   AH,3                  ; ¿operación de escritura?
               JNE   rwv_nowr
               OR    BYTE PTR DS:[DI],80h  ; operación Write/Format
rwv_nowr:      PUSH  SI
               CALL  get_drive_type    ; obtener tipo disquetera en AL
               JNZ   rwv_media_ok
               CMP   AL,1
               JE    rwv_set360            ; es de 360K
               CMP   AL,3
               JNE   rwv_media_ok          ; no es de 720K
               MOV   BYTE PTR DS:[SI],97h    ; forzar medio de 720K
               JMP   rwv_media_ok
rwv_set360:    MOV   BYTE PTR DS:[SI],93h    ; forzar medio de 360K
rwv_media_ok:  CALL  motor_on          ; arrancar motores
               CALL  read_disk_chg     ; leer línea de cambio de disco
               POP   SI
               JNC   rwv_no_dschg      ; no hay cambio de disco
rwv_end_err:   CALL  end_io_access
               JMP   rwv_err
rwv_no_dschg:  TEST  BYTE PTR DS:[SI],10h  ; ¿densidad determinada?
               JNZ   rwv_set_rate          ; en efecto
               CALL  detect_media          ; pues determinarla
               JC    rwv_end_err           ; problemas
               JMP   rwv_dens_ok
rwv_set_rate:  CALL  select_rate       ; seleccionar la velocidad
rwv_dens_ok:   MOV   SI,90h
               PUSH  DX
               XOR   DH,DH
               ADD   SI,DX             ; [SI] -> estado físico unidad
               POP   DX
               MOV   AX,0AF03H                ; AF byte 0 specify 2.88
               CMP   BYTE PTR DS:[SI],0D7h    ; ¿2.88M?
               JE    rwv_spec_ok              ; así es
               MOV   AX,0DF03h                ; DF para 360/1.2/720
               CMP   BYTE PTR DS:[SI],17h     ; ¿1.44M?
               JNE   rwv_spec_ok              ; no
               MOV   AH,0BFh                  ; sí
rwv_spec_ok:   MOV   SI,AX                    ; SI 0-7: orden specify
               MOV   DI,2                     ; DI 0-7: byte 1 specify
               MOV   CH,3                     ; comando de 3 bytes
               OR    BYTE PTR DS:[3Eh],80h    ; no esperar INT
               CALL  exec_cmd
               JC    rwv_end_err       ; fallo
               MOV   AX,r_ax
               XOR   AH,AH
               PUSH  DS
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   CL,[SI+3]         ; bytes/sector
               SHL   AL,CL             ; multiplicar por nº sectores
               MOV   CL,80h
               MUL   CL                ; y por 128
               POP   DS
               DEC   AX                ; un byte menos...
               MOV   CX,AX             ; ...cuenta para el DMA
               CALL  eval_dir_DMA
               JNC   rwv_dma_ok        ; no hay problemas con el DMA
               MOV   CX,r_cx           ; restaurar CX
               JMP   rwv_end_err       ; problemas con el DMA
rwv_dma_ok:    MOV   AX,r_ax
               CMP   AH,2              ; ¿operación de lectura?
               JNE   rwv_no_read
               MOV   AH,46h            ; byte de modo DMA para lectura
               JMP   rwv_dma_set
rwv_no_read:   CMP   AH,3              ; ¿escritura?
               MOV   AH,4Ah            ; modo DMA para escritura
               JZ    rwv_dma_set
               MOV   AH,42h            ; modo DMA para verificación
rwv_dma_set:   CALL  set_dma           ; preparar DMA
               MOV   AX,r_ax
               MOV   CX,r_cx           ; restaurar parámetros
               JMP   perform_io        ; efectuar E/S
io_performed:  CALL  end_io_access
               OR    AL,AL
               JZ    rwv_end
               SUB   BL,CL             ; próximo sector-sector inicial
               MOV   AL,BL             ; nº sectores transferidos
rwv_end:       MOV   AH,DS:[41h]
               OR    AH,AH             ; ¿error?
               JZ    rwv_exit
               STC                     ; señalizar error
rwv_exit:      MOV   r_al,AL           ; nº sectores transferidos
               JMP   main_exit
read_wr_verify ENDP

; ------------ Función 5: Formatear pista.

format_track   PROC
               CMP   DL,1
               JBE   fmt_do
               MOV   AH,1              ; función/parámetro incorrecto
fmt_exit_err:  MOV   DS:[41h],AH       ; código de error
               STC
               JMP   main_exit
fmt_do:        MOV   SI,90h
               PUSH  DX
               XOR   DH,DH
               ADD   SI,DX             ; [SI] -> estado físico unidad
               POP   DX
               CMP   BYTE PTR DS:[SI],0
               MOV   AH,80h            ; "unidad no preparada"
               JE    fmt_exit_err      ; indeterminado
               MOV   DI,3Fh
               OR    BYTE PTR DS:[DI],80h  ; no esperar INT
               CALL  motor_on          ; arrancar motores
               CALL  read_disk_chg     ; leer línea de cambio de disco
               JNC   fmt_posible       ; no hay cambio de disco
fmt_err:       CALL  end_io_access
               JMP   fmt_exit_err      ; error
fmt_posible:   CALL  select_rate       ; seleccionar la velocidad
               CALL  send_specify      ; enviar comando specify
               PUSH  DS
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   AL,[SI+4]         ; sectores por pista
               POP   DS
               XOR   AH,AH
               MOV   CL,4
               MUL   CL                ; 4 bytes para cada uno
               MOV   CX,AX
               DEC   CX                ; un byte menos...
               CALL  eval_dir_DMA      ; ...cuenta para el DMA
               JC    fmt_err           ; cruza frontera de DMA
               MOV   AH,4Ah            ; modo DMA para escritura
               CALL  set_dma           ; preparar DMA
               MOV   CX,r_cx           ; restaurar CX
               CALL  seek              ; llevar el cabezal a la pista
               JNC   fmt_continue      ; no hay problemas
fmt_err_res:   MOV   BX,42h
               MOV   CX,7
               PUSH  AX
               CALL  get_results       ; leer bytes de resultados
               POP   AX
               JMP   fmt_exit          ; error
fmt_continue:  SHL   DH,2
               OR    DH,DL             ; DH = byte 1 del comando
               MOV   DL,0CDh           ; comando de formateo del FDC
               PUSH  DS
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               PUSH  AX
               MOV   AX,[SI+7]         ; GAP formateo/byte de relleno
               MOV   [BP],AX           ; preservarlo
               POP   AX
               MOV   DI,[SI+3]         ; bytes/sector, sectores/pista
               POP   DS
               MOV   SI,DX             ; primeros bytes del comando
               MOV   CH,6              ; comando de 6 bytes
               AND   BYTE PTR DS:[3Eh],7Fh  ; con espera de IRQ
               CALL  exec_cmd
               JC    fmt_err_res       ; hay error
               MOV   BX,42h
               MOV   CX,7
               CALL  get_results       ; leer bytes de resultados
               JC    fmt_exit
               CALL  get_bios_err      ; obtener código de error
fmt_exit:      MOV   DS:[41h],AH       ; código de error
               CALL  end_io_access
               MOV   AH,DS:[41h]
               OR    AH,AH
               JZ    fmt_end           ; no hay error
               STC
fmt_end:       JMP   main_exit
format_track   ENDP

; ------------ Función 8: Obtener parámetros de disco.

get_drv_param  PROC
               CMP   DL,80h
               JB    gdrv_do
               MOV   AH,1              ; función/parámetro incorrecto
               MOV   DS:[41h],AH       ; código de error
               STC
               JMP   main_exit
gdrv_do:       XOR   DI,DI
               XOR   SI,SI
               XOR   DH,DH
               MOV   AL,DS:[10h]       ; hardware instalado
               AND   AL,0C1h           ; nº disqueteras (7-6) y bit
               MOV   DI,2              ; que indica arrancable (0)
               CMP   AL,41h            ; ¿dos disqueteras? (DI=2)
               JE    gdrv_ndisk_ok     ; en efecto
               DEC   DI
               CMP   AL,1              ; ¿una disquetera? (DI=1)
               JE    gdrv_ndisk_ok     ; así es
               JMP   gdrv_res_null     ; no hay disqueteras
gdrv_ndisk_ok: CMP   DL,1
               JBE   gdrv_a_or_b       ; la disquetera es A: o B:
               JMP   gdrv_half_res
gdrv_a_or_b:   CALL  peek_cmos         ; leer tipo de disqueteras
               OR    DL,DL
               JNZ   gdrv_sel
               MOV   CL,4
               SHR   AL,CL             ; dejar disquetera en bits 0-3
gdrv_sel:      AND   AL,0Fh
               JZ    gdrv_media?       ; no existe esa unidad
               CMP   AL,5
               JA    gdrv_media?       ; es mayor de 2.88M
               XOR   AH,AH
               MOV   SI,AX
               MOV   DH,AL
               MOV   BX,90h
               ADD   BL,DL             ; [BX] -> estado físico unidad
               MOV   AL,[BX]
               TEST  AL,10h            ; ¿densidad determinada?
               JNZ   gdrv_calc_p       ; en efecto
               CMP   SI,1              ; ¿unidad de 360K?
               MOV   AL,93h            ; 360K en 360K, 250 Kbps
               JE    gdrv_media_ok
               CMP   SI,2              ; ¿unidad de 1.2M?
               MOV   AL,2              ; "intentando 1.2M"
               JE    gdrv_media_ok
               CMP   SI,3              ; ¿unidad de 720K?
               MOV   AL,97h            ; 720K en 720K, 250 Kbps
               JE    gdrv_media_ok
               CMP   SI,4              ; ¿unidad de 1.44M?
               MOV   AL,7              ; "intentando 1.44M"
               JE    gdrv_media_ok
               MOV   AL,0C7h           ; 2.88M en 2.88M
gdrv_media_ok: MOV   [BX],AL
               JMP   gdrv_calc_p       ; medio físico asignado
gdrv_media?:   MOV   BX,90h
               ADD   BL,DL             ; [BX] -> estado físico unidad
               MOV   AL,[BX]
               TEST  AL,10h            ; ¿densidad determinada?
               JZ    gdrv_eval         ; no
               MOV   AH,AL
               AND   AL,0C0h           ; aislar bits de velocidad
               CMP   AL,80h            ; ¿250 Kbps?
               MOV   SI,2              ; 1.2M
               JNE   gdrv_m144?
               TEST  AH,4
               MOV   SI,1              ; 360K
               JZ    gdrv_calc_p
               MOV   SI,4              ; 1.44M
gdrv_m144?:    TEST  AH,7
               JZ    gdrv_calc_p
               MOV   SI,4              ; 1.44M
gdrv_calc_p:   MOV   BX,DI
               MOV   DI,SI
               DEC   DI
               ADD   DI,DI
               MOV   AX,CS:tab_disksize[DI] ; AL sect/pista, AH pistas
               MOV   r_dh,1                 ; dos cabezales
               MOV   DI,CS:tab_ptr_1e[DI]   ; DI -> tabla parámetros
               PUSH  CS
               POP   ES                ; ES:DI -> parámetros disco
gdrv_set_res:  MOV   r_dl,BL           ; número de unidades
               MOV   r_ch,AH           ; mayor número de cilindro
               MOV   r_cl,AL           ; mayor número de sector
               MOV   r_bl,DH           ; tipo de la unidad
               MOV   r_bh,0
               MOV   r_es,ES           ; ES:DI para la salida
               MOV   r_di,DI
               XOR   AX,AX
               MOV   DS:[41h],AH       ; resultado correcto
               MOV   r_al,AL
               JMP   main_exit
gdrv_res_null: XOR   DI,DI             ; devolver todo a 0
gdrv_half_res: XOR   DH,DH
               XOR   AX,AX
               MOV   ES,AX
               MOV   r_dh,0
               MOV   BX,DI
               XOR   DI,DI
               JMP   gdrv_set_res      ; resultado trivial
gdrv_eval:     MOV   BX,DI
               OR    SI,SI
               JZ    gdrv_res_null     ; no existe la unidad
               CMP   SI,3
               JBE   gdrv_calc_p       ; es de 5¼
               XOR   SI,SI
               JMP   gdrv_res_null
get_drv_param  ENDP

; ------------ Función 18h: Establecer densidad de formateo.

set_media_fmt  PROC
               CMP   DL,1
               JBE   setm_do
               MOV   AH,1              ; función/parámetro incorrecto
setm_exit_err: STC
               JMP   main_exit
setm_do:       CALL  get_drive_type    ; obtener tipo disquetera en AL
               JZ    setm_drv_ok
setm_drv_unkn: MOV   AH,0Ch            ; tipo de unidad desconocido
               JMP   setm_exit_err
setm_drv_ok:   XOR   AH,AH
               MOV   DI,AX             ; tipo de unidad
               MOV   DL,r_dl
               MOV   BX,90h
               XOR   DH,DH
               ADD   BX,DX             ; [BX] -> estado físico unidad
               CMP   AL,1              ; ¿360K?
               JNE   setm_not360
               MOV   CX,r_cx           ; restaurar CX
               CMP   CX,2709h          ; ¿40 pistas 9 sectores?
               LEA   SI,t360in360
               JNZ   setm_drv_unkn     ; sólo se permite ese formato
               JMP   setm_360in360
setm_not360:   CMP   AL,3              ; ¿720K?
               JNE   setm_not720
               MOV   CX,r_cx           ; restaurar CX
               CMP   CX,4F09h          ; ¿80 pistas 9 sectores?
               JNE   setm_drv_unkn     ; sólo se permite ese formato
               JMP   setm_setm
setm_not720:   CMP   AL,4              ; ¿1.44M?
               JE    setm_1440
               CMP   AL,2              ; ¿1.2M?
               JE    setm_1200
               CMP   AL,5              ; ¿2.88M?
               JNE   setm_drv_unkn
               MOV   CX,r_cx           ; 2.88M: restaurar CX
               CMP   CX,4F24h          ; ¿80 pistas 36 sectores?
               JE    setm_setm         ; correcto
               CMP   CX,4F12h          ; ¿80 pistas 18 sectores?
               JE    setm_setm         ; correcto
               CMP   CX,4F09h          ; ¿80 pistas 9 sectores?
               JE    setm_setm         ; correcto
               JMP   setm_drv_unkn     ; permitir sólo esos formatos
setm_1200:     MOV   CX,r_cx           ; restaurar CX
               CMP   CX,4F0Fh          ; ¿80 pistas 15 sectores?
               JE    setm_setm         ; correcto
               CMP   CX,2709h          ; ¿80 pistas 9 sectores?
               JNE   setm_drv_unkn     ; permitir sólo esos formatos
               JMP   setm_setm         ; correcto
setm_1440:     MOV   CX,r_cx           ; restaurar CX
               CMP   CX,4F12h          ; ¿80 pistas 18 sectores?
               JE    setm_setm         ; correcto
               CMP   CX,4F09h          ; ¿80 pistas 9 sectores?
               JNE   setm_drv_unkn     ; permitir sólo esos formatos
setm_setm:     MOV   CX,r_cx           ; restaurar CX
               CMP   CX,4F12h          ; ¿80 pistas 18 sectores?
               MOV   AL,17h            ; su byte de medio físico
               LEA   SI,t1440          ; es 1.44M
               MOV   DH,0              ; 500 Kbps
               JZ    setm_m_ok
               CMP   CX,4F09h          ; ¿80 pistas 9 sectores?
               MOV   AL,97h            ; su byte de medio físico
               LEA   SI,t720           ; es 720K
               MOV   DH,2              ; 250 Kbps
               JZ    setm_m_ok
               CMP   CX,4F0Fh          ; ¿80 pistas 15 sectores?
               MOV   AL,15h            ; su byte de medio físico
               LEA   SI,t1200          ; es 1.2M
               MOV   DH,0              ; 500 Kbps
               JZ    setm_m_ok
               CMP   CX,4F24h          ; ¿80 pistas 36 sectores?
               MOV   AL,0D7h           ; su byte de medio físico
               LEA   SI,t2880          ; es 2.88M
               MOV   DH,3              ; 1 Mbps
               JZ    setm_m_ok
               MOV   AL,74h            ; byte medio físico 360K en 1.2
               LEA   SI,t360en1200     ; es 360K en 1.2M
               MOV   DH,1              ; 300 Kbps
setm_m_ok:     MOV   [BX],AL           ; establecer medio físico
               PUSH  DX
               CALL  set_rate          ; velocidad de transferencia DH
               POP   DX
               MOV   AL,[BX]
               AND   AL,0C0h
               AND   BYTE PTR DS:[8Bh],3Fh  ; borrar bits de velocidad
               OR    BYTE PTR DS:[8Bh],AL   ; nueva velocidad
               MOV   r_di,SI
               MOV   r_es,CS              ; retornar tabla parámetros
               MOV   BYTE PTR DS:[41h],0  ; no hay error
               XOR   AH,AH
               JMP   main_exit
setm_360in360: MOV   DH,2                 ; 250 Kbps
               MOV   AL,93h               ; 360K en 360K
               JMP   setm_m_ok
set_media_fmt  ENDP

; ------------ Recalibrar.

recalibrate    PROC
               PUSH  SI
               PUSH  CX
               PUSH  DX
               MOV   DH,DL
               MOV   DL,7              ; comando "recalibrate"
               MOV   SI,DX
               MOV   CH,2              ; comando de 2 bytes
               AND   BYTE PTR DS:[3Eh],7Fh  ; con espera de IRQ
               CALL  exec_cmd
               JC    recal_end         ; fallo
               MOV   SI,8              ; "leer estado interrupciones"
               MOV   CH,1              ; comando de 1 byte
               OR    BYTE PTR DS:[3Eh],80h  ; sin espera de IRQ
               CALL  exec_cmd
               JC    recal_end         ; fallo
               MOV   BX,42h
               MOV   CX,2              ; 2 bytes de resultado
               CALL  get_results       ; almacenar resultado
               JC    recal_end         ; fallo
               MOV   BX,42h
               MOV   AH,40h
               MOV   DL,[BX]           ; ST0
               AND   DL,60h
               CMP   DL,60h            ; ¿terminación anormal y
               STC                     ; seek-end?
               JE    recal_end         ; fallo
               POP   DX
               PUSH  DX
               XOR   DH,DH             
               MOV   BX,94H
               ADD   BX,DX
               MOV   BYTE PTR DS:[BX],0  ; cilindro en curso = 0
               MOV   CL,DL
               MOV   DL,1
               SHL   DL,CL
               OR    DS:[3Eh],DL       ; unidad recalibrada
               MOV   CX,43h
               CALL  wait_time         ; retardo de 1 ms
               XOR   AH,AH
recal_end:     MOV   DS:[41h],AH       ; código de error / acierto
               POP   DX
               POP   CX
               POP   SI
               RET
recalibrate    ENDP

; ------------ Llevar el cabezal al cilindro adecuado.

seek           PROC
               PUSH  BX
               PUSH  CX
               MOV   AH,DS:[3Eh]       ; estado de recalibración
               MOV   CL,DL
               INC   CL
               SHR   AH,CL
               JC    seek_only
               CALL  recalibrate       ; hay que recalibrar
               JNC   seek_only
               CALL  recalibrate       ; segundo intento
               JNC   seek_only
               JMP   seek_exit
seek_only:     MOV   BX,94H
               XOR   DH,DH
               ADD   BX,DX             ; [BX] -> cilindro actual
               MOV   SI,90h
               ADD   SI,DX             ; [SI] -> estado físico unidad
               MOV   DL,CH
               TEST  BYTE PTR DS:[SI],20h  ; ¿hacer double stepping?
               JZ    seek_cil_ok1
               ADD   DL,DL             ; sí: cilindro=cilindro*2
seek_cil_ok1:  CMP   [BX],DL           ; ¿ya estamos en ese cilindro?
               MOV   DX,r_dx
               JNE   seek_do           ; aún no
               CMP   BYTE PTR DS:[41h],40h  ; ¿hubo "seek error"?
               JE    seek_do
               XOR   AH,AH             ; no, seek innecesario
               JMP   seek_exit
seek_do:       SHL   DH,2
               OR    DH,DL             ; byte 1 del comando seek
               MOV   DL,0FH            ; orden seek
               MOV   SI,DX
               MOV   CL,CH             ; cilindro
               MOV   DX,r_dx           ; unidad / cabezal
               MOV   BX,90h
               XOR   DH,DH
               ADD   BX,DX                 ; [BX] -> estado físico
               TEST  BYTE PTR DS:[BX],20h  ; ¿hacer double stepping?
               JZ    seek_cil_ok2      ; no
               ADD   CL,CL             ; sí: cilindro=cilindro*2
seek_cil_ok2:  MOV   DI,CX
               MOV   CH,3              ; comando de 3 bytes
               AND   BYTE PTR DS:[3Eh],7Fh  ; hay que esperar IRQ
               CALL  exec_cmd
               JNC   seek_ok           ; seek correcto
               JMP   seek_result
seek_ok:       MOV   SI,8              ; "leer estado interrupciones"
               MOV   CH,1              ; comando de 1 byte
               OR    BYTE PTR DS:[3Eh],80h  ; no hay que esperar IRQ
               CALL  exec_cmd
               JC    seek_result       ; fallo
               MOV   BX,42h
               MOV   CX,2
               CALL  get_results       ; leer bytes de resultados
               JC    seek_result
               MOV   BX,42h
               MOV   AH,40h            ; "seek error"
               MOV   DL,[BX]           ; ST0
               AND   DL,60h
               CMP   DL,60h            ; comprobarlo
               STC
               JE    seek_result       ; terminación brusca
               MOV   DX,r_dx           ; restaurar DX
               POP   CX
               PUSH  CX                ; restaurar CX
               MOV   SI,94H
               XOR   DH,DH
               ADD   SI,DX
               MOV   [SI],CH           ; actualizar cilindro actual
               MOV   BX,90h
               ADD   BX,DX             ; [BX] -> estado físico unidad
               MOV   BL,[BX]
               TEST  BL,20h            ; ¿double stepping?
               JZ    seek_cil_ok3
               ADD   [SI],CH           ; pues cilindro*2
seek_cil_ok3:  PUSH  DS
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   AL,[SI+9]         ; tiempo estabilización cabezal
               POP   DS
               TEST  BYTE PTR DS:[3Fh],80h  ; ¿operación en curso?
               JZ    seek_wait         ; es lectura o verificación
               OR    AL,AL
               JNZ   seek_wait         ; escritura, cte != 0
               CMP   BL,17h
               MOV   AL,0Fh
               JE    seek_wait         ; 15 ms excepto para 360K
               AND   BL,7
               MOV   AL,14h
               JZ    seek_wait         ; 20 ms para unidades de 360K
               CMP   BL,3
               JE    seek_wait         ; 20 ms para unidades de 360K
               MOV   AL,0Fh            ; 15 ms para demás unidades
seek_wait:     OR    AL,AL
               JZ    seek_wait_end
               MOV   CX,43h
               CALL  wait_time         ; esperar 1 ms...
               DEC   AL
               JMP   seek_wait         ; ...durante AL veces
seek_wait_end: XOR   AH,AH
seek_result:   MOV   DS:[41h],AH       ; resultado
seek_exit:     MOV   DX,r_dx           ; restaurar DX
               POP   CX
               POP   BX
               RET
seek           ENDP

; ------------ Ejecutar operación de E/S a través del FDC.

perform_io     PROC
               CALL  seek
               JNC   p_io
p_io_dsk_err:  MOV   AL,0              ; fallo
               PUSH  AX
               MOV   BX,42h
               MOV   CX,7
               CALL  get_results       ; leer bytes de resultados
               POP   AX
               JMP   p_io_exit
p_io:          PUSH  DS                ; preparar bytes comando R/W
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   AX,[SI+2]
               AND   AX,0FF00h         ; AH = bytes por sector
               MOV   AL,CL             ; AL = primer sector
               MOV   [BP],AX
               MOV   BX,[SI+4]         ; BL=sectores/pista, BH=GAP R/W
               MOV   CL,[SI+6]         ; CL=longitud de sector (tam=0)
               POP   DS
               MOV   SI,90h
               XOR   DH,DH
               ADD   SI,DX             ; nº sector/tamaño (bytes 4-5)
               MOV   DL,[SI]           ; estado físico de la unidad
               AND   DL,7
               MOV   DH,1Bh            ; GAP R/W para 1.2M/1.44M/2.88M
               CMP   DL,5
               JE    p_io_gap_ok       ; 1.2M en 1.2M
               CMP   BYTE PTR DS:[SI],17h
               JE    p_io_gap_ok            ; 1.44M
               CMP   BYTE PTR DS:[SI],0D7h
               JE    p_io_gap_ok            ; 2.88M
               MOV   DH,23h            ; GAP R/W para 360K en 1.2M
               CMP   DL,4
               JE    p_io_gap_ok       ; 360K en 1.2M
               MOV   DH,2Ah            ; GAP R/W para demás casos
p_io_gap_ok:   MOV   BH,DH             ; BX=numsect/GAP (bytes 6-7)
               MOV   DX,r_dx           ; restaurar DX
               PUSH  CX
               MOV   CL,CH             ; cilindro
               MOV   CH,DH             ; cabezal
               MOV   DI,CX             ; cabezal/cilindro (bytes 2-3)
               SHL   DH,2
               OR    DH,DL             ; byte 1 de comando FDC
               MOV   DL,0E6h           ; comando leer datos
               MOV   AX,r_ax           ; orden
               CMP   AH,3              ; ¿write?
               JNE   p_io_orden_ok
               MOV   DL,0C5h           ; comando escribir datos
p_io_orden_ok: MOV   SI,DX             ; (bytes 0-1 de la orden)
               POP   CX
               MOV   CH,9
               AND   BYTE PTR DS:[3Eh],7Fh  ; esperar interrupción
               CALL  exec_cmd
               JNC   p_io_dsk_ok
               JMP   p_io_dsk_err      ; fallo
p_io_dsk_ok:   MOV   BX,42h
               MOV   CX,7
               CALL  get_results       ; leer bytes de resultados
               JNC   p_io_res_ok
               MOV   AL,0
               JMP   p_io_exit         ; fallo: respetar código error
p_io_res_ok:   CALL  get_bios_err
p_io_exit:     MOV   DS:[41h],AH
               MOV   DX,r_dx           ; restaurar registros
               MOV   BX,r_bx
               MOV   CX,r_cx
               JMP   io_performed      ; continuar operación E/S
perform_io     ENDP

; ------------ Arrancar motor si no lo está.

motor_on       PROC
               PUSH  DX
               PUSH  CX
               CLI                     ; * evitar reentrada
               MOV   BYTE PTR DS:[40h],0FFh  ; evitar detención motor
               AND   BYTE PTR DS:[3Fh],0CFh  ; a 0 bits de disquetera
               MOV   CH,DL
               SHL   DL,4
               OR    DS:[3Fh],DL       ; nueva disquetera seleccionada
               MOV   CL,CH
               MOV   DL,DS:[3Fh]       ; estado de motores
               INC   CL
               SHR   DL,CL
               JC    motor_is_on       ; motor ya en marcha
               MOV   DL,1
               DEC   CL
               SHL   DL,CL
               OR    DS:[3Fh],DL       ; señalizar que está en marcha
               STI                     ; * fin de la fase crítica
               MOV   AL,DS:[3Fh]       ; estado de motores
               ROR   AL,4
               OR    AL,0CH            ; no resetear, modo DMA
               MOV   DX,3F2h
               OUT   DX,AL             ; registro salida digital
               MOV   AX,90FDh
               INT   15h               ; permitir multitarea
               JC    motor_on_end
               MOV   AH,DS:[3Fh]
               PUSH  DS
               PUSH  SI
               XOR   SI,SI
               MOV   DS,SI
               LDS   SI,DWORD PTR DS:[78h]  ; DS:SI -> INT 1Eh
               MOV   AL,[SI+0Ah]
               POP   SI                ; AL = tiempo aceleración motor
               POP   DS                ;      en octavos de segundo
               SHL   AH,1
               JNC   motor_on_rv       ; operación read/verify
               CMP   AL,8
               JAE   motor_on_wait
               MOV   AL,8
               JMP   motor_on_wait     ; escritura: al menos 1 segundo
motor_on_rv:   CMP   AL,5
               JAE   motor_on_wait
               MOV   AL,5              ; read/verify: al menos 625 ms
motor_on_wait: MOV   CX,208EH
               CALL  wait_time         ; retardo de unos 125 ms
               DEC   AL
               JNZ   motor_on_wait     ; completar retardo
               JMP   motor_on_end
motor_is_on:   STI
               MOV   AL,DS:[3Fh]       ; estado de motores
               ROR   AL,4
               OR    AL,0CH            ; no resetear, modo DMA
               MOV   DX,3F2h
               OUT   DX,AL             ; seleccionar unidad
motor_on_end:  POP   CX
               POP   DX
               RET
motor_on       ENDP

; ------------ Asignar cuenta para detención motor y devolver en BL
;              y AL el próximo número de sector a transferir.

end_io_access  PROC
               PUSH  AX
               PUSH  DS
               XOR   BX,BX
               MOV   DS,BX
               LDS   BX,DWORD PTR DS:[78h]  ; DS:BX -> INT 1Eh
               MOV   AH,[BX+2]         ; tics hasta detención motor
               MOV   AL,[BX+4]
               INC   AL                ; sectores/pista + 1
               POP   DS
               MOV   BX,42h
               CMP   CH,[BX+3]         ; ¿mismo cilindro resultante?
               JNE   end_io_exit
               CMP   DH,[BX+4]         ; ¿mismo cabezal resultante?
               JNE   end_io_exit
               MOV   AL,[BX+5]         ; número de sector resultante
end_io_exit:   MOV   DS:[40h],AH       ; tiempo para detención motor
               MOV   BL,AL             ; último nº sector transferido
               POP   AX
               RET
end_io_access  ENDP

; ------------ Leer la línea de cambio de disco y bajarla si está
;              activa.

read_disk_chg  PROC
               PUSH  CX
               CALL  get_drive_type    ; obtener tipo disquetera en AL
               MOV   AH,0
               JNZ   fallo_cmos
               DEC   AL
               JZ    rdchg_set_cod     ; evitar test en 360K
fallo_cmos:    MOV   AL,[SI]           ; estado físico de la unidad
               AND   AL,7
               JZ    rdchg_set_cod     ; evitar test en 360K
               CMP   AL,3
               JE    rdchg_set_cod     ; evitar test en 360K
               MOV   DX,3F7h           ; registro de entrada digital
               IN    AL,DX             ; leer línea de cambio de disco
               SHL   AL,1
               JNC   rdchg_exit        ; no hay cambio de disco
               AND   BYTE PTR DS:[SI],0EFH  ; medio no determinado
               CALL  full_init         ; inicialización plena
               JC    rdchg_exit        ; fallo
               MOV   DX,r_dx           ; restaurar DX
               MOV   CH,1
               CALL  seek              ; cabezal a cilindro 1
               JC    rdchg_exit
               MOV   CH,0
               CALL  seek              ; cabezal a cilindro 0
               JC    rdchg_exit
               MOV   AH,6              ; error "disk changed"
               MOV   DX,3F7h
               IN    AL,DX             ; leer línea de cambio de disco
               SHL   AL,1
               JNC   rdchg_set_cod     ; se ha podido bajar
               MOV   AH,80h            ; no se pudo: no hay disquete
rdchg_set_cod: OR    AH,AH
               JZ    rdchg_exit
               STC
rdchg_exit:    MOV   DX,r_dx           ; restaurar DX
               POP   CX
               RET
read_disk_chg  ENDP

; ------------ Programar el DMA para efectuar la E/S.

set_dma        PROC
               PUSH  AX
               PUSH  DX
               CLI
               MOV   AL,AH
               OUT   0CH,AL            ; clear first/last flip-flop
               JCXZ  $+2               ; retardo para E/S
               JCXZ  $+2
               OUT   0BH,AL            ; registro de modo del DMA
               JCXZ  $+2
               JCXZ  $+2
               MOV   AL,CL
               OUT   5,AL
               JCXZ  $+2
               JCXZ  $+2
               MOV   AL,CH
               OUT   5,AL              ; enviada cuenta de bytes
               JCXZ  $+2
               JCXZ  $+2
               MOV   AL,BL
               OUT   4,AL
               JCXZ  $+2
               JCXZ  $+2
               MOV   AL,BH
               OUT   4,AL              ; enviada dirección base
               JCXZ  $+2
               JCXZ  $+2
               MOV   AX,ES
               OUT   81H,AL            ; registro de página canal 2
               JCXZ  $+2
               JCXZ  $+2
               MOV   AL,2
               OUT   0AH,AL            ; habilitar canal 2 del DMA
               STI
               POP   DX
               POP   AX
               RET
set_dma        ENDP

; ------------ Calcular parámetros para programar el DMA.

eval_dir_DMA   PROC
               PUSH  CX
               XOR   AX,AX
               MOV   CX,ES             ; segmento ES
               SHL   CX,1              ; desplazar...
               RCL   AL,1
               SHL   CX,1
               RCL   AL,1
               SHL   CX,1
               RCL   AL,1
               SHL   CX,1
               RCL   AL,1              ; ... AL:CX >> 4
               MOV   BX,r_bx           ; offset BX
               ADD   BX,CX
               ADC   AX,0              ; AX:BX dirección física 20 bit
               MOV   ES,AX             ; página de DMA
               POP   CX
               MOV   AX,CX
               ADD   AX,BX
               JNC   eval_dma_ret      ; no cruza frontera de 64k
               MOV   AH,9              ; "DMA across 64k boundary"
eval_dma_ret:  RET
eval_dir_DMA   ENDP

; ------------ Enviar comando completo al FDC de CH bytes, contenido
;              en SI, DI, [BP], BX y CL (parte baja - alta).

send_full_cmd  PROC
               MOV   AX,SI
               MOV   AH,AL
               CALL  fdc_write         ; enviar SI-L
               DEC   CH
               JBE   send_full_ret     ; acabado (ZF=1) ó error (CF=1)
               MOV   AX,SI
               CALL  fdc_write         ; enviar SI-H
               DEC   CH
               JBE   send_full_ret
               MOV   AX,DI
               MOV   AH,AL
               CALL  fdc_write         ; enviar DI-L
               DEC   CH
               JBE   send_full_ret
               MOV   AX,DI
               CALL  fdc_write         ; enviar DI-H
               DEC   CH
               JBE   send_full_ret
               MOV   AX,[BP]
               MOV   AH,AL
               CALL  fdc_write         ; enviar [BP]-L
               DEC   CH
               JBE   send_full_ret
               MOV   AX,[BP]
               CALL  fdc_write         ; enviar [BP]-H
               DEC   CH
               JBE   send_full_ret
               MOV   AH,BL
               CALL  fdc_write         ; enviar BL
               DEC   CH
               JBE   send_full_ret
               MOV   AH,BH
               CALL  fdc_write         ; enviar BH
               DEC   CH
               JBE   send_full_ret
               MOV   AH,CL
               CALL  fdc_write         ; enviar CL
send_full_ret: RET
send_full_cmd  ENDP

; ------------ Enviar comando al FDC con/sin espera de interrupción.

exec_cmd       PROC
               TEST  BYTE PTR DS:[3Eh],80h  ; ¿hay que esperar IRQ?
               JZ    exec_cmd_irq           ; sí
               AND   BYTE PTR DS:[3Eh],7Fh  ; no: devolver a 0 bit IRQ
               CALL  send_full_cmd          ; enviar comando
               RET
exec_cmd_irq:  CALL  send_full_cmd          ; enviar comando
               JC    exec_cmd_ret
               MOV   AX,9001h
               INT   15h                    ; permitir multitarea
               STI
               JC    exec_cmd_err
               CALL  wait_int               ; esperar IRQ
               JNC   exec_cmd_ok
exec_cmd_err:  MOV   AH,80h                 ; "not ready" (AH=80h)
exec_cmd_ret:  RET
exec_cmd_ok:   AND   BYTE PTR DS:[3Eh],7Fh  ; bit IRQ listo para otra
               XOR   AH,AH                  ; éxito
               RET
exec_cmd       ENDP

; ------------ Enviar byte al FDC.

fdc_write      PROC
               PUSH  CX
               PUSH  DX
               MOV   CX,2
               CALL  wait_time         ; esperar 15-30 µs
               MOV   DX,3F4h           ; registro de estado
               PUSH  AX
               MOV   AH,40h
               XOR   CX,CX
               CALL  wait0             ; esperar FDC listo para OUT
               JC    fdc_wr_fail
               MOV   AH,80h
               XOR   CX,CX
               CALL  wait1
               JC    fdc_wr_fail       ; error
               POP   AX
               MOV   DX,3F5h           ; registro de datos
               MOV   AL,AH
               OUT   DX,AL             ; escribir el byte
               JMP   fdc_wr_ret
fdc_wr_fail:   POP   AX
               MOV   AH,80h            ; error "not ready"
fdc_wr_ret:    POP   DX
               POP   CX
               RET
fdc_write      ENDP

; ------------ Leer del FDC CX bytes de resultado en [BX++].

get_results    PROC
               PUSH  DX
get_one_byte:  PUSH  CX
               CALL  fdc_read
               POP   CX
               JC    get_res_ret       ; no hay más bytes que leer
               MOV   [BX],AL
               INC   BX
               LOOP  get_one_byte      ; leer todos los bytes
               MOV   CX,4
               CALL  wait_time         ; esperar 45-60 µs
               MOV   DX,3F4h
               IN    AL,DX             ; leer registro de estado
               TEST  AL,10h
               JZ    get_res_ok        ; el FDC no está ocupado
               MOV   AH,20h
               STC                     ; lo estaba: "bad NEC"
               JMP   get_res_ret
get_res_ok:    XOR   AH,AH             ; operación correcta
get_res_ret:   POP   DX
               RET
get_results    ENDP

; ------------ Leer byte del FDC.

fdc_read       PROC
               PUSH  DX
               MOV   CX,3
               CALL  wait_time         ; esperar 30-45 µs
               MOV   DX,3F4h           ; registro de estado
               MOV   AH,80h
               XOR   CX,CX
               CALL  wait1             ; esperar FDC listo para E/S
               MOV   AH,80h
               JC    fdc_read_end      ; error "not ready" (AH=80h)
               IN    AL,DX
               TEST  AL,40h            ; ¿el FDC quiere dar un byte?
               JNZ   fdc_read_ok
               MOV   AH,20H            ; no: error "bad NEC" (AH=20h)
               STC
               JMP   fdc_read_end
fdc_read_ok:   JCXZ  $+2               ; retardo para E/S
               JCXZ  $+2
               MOV   DX,3F5h           ; registro de datos
               IN    AL,DX             ; leer el byte
fdc_read_end:  POP   DX
               RET
fdc_read       ENDP

; ------------ Obtener código de error de la BIOS.

get_bios_err   PROC
               MOV   BX,42h            ; área de resultados del FDC
               MOV   BX,[BX]
               TEST  BL,0C0h           ; ¿que tal ST0?
               MOV   AH,0
               JZ    bios_err_det      ; ¡perfecto!
               TEST  BL,40h            ; ¿terminación brusca/anormal?
               MOV   AH,20H            ; "bad NEC"
               JZ    bios_err_det
               TEST  BH,1              ; ¿falta marca de direcciones?
               MOV   AH,2              ; "address mark not found"
               JNZ   bios_err_det
               TEST  BH,2              ; ¿protegido contra escritura?
               MOV   AH,3              ; "write-protect error"
               JNZ   bios_err_det
               TEST  BH,4              ; ¿sector no encontrado?
               MOV   AH,4              ; "sector not found"
               JNZ   bios_err_det
               TEST  BH,10H            ; ¿DMA no atendido a tiempo?
               MOV   AH,8              ; "DMA overrun"
               JNZ   bios_err_det
               TEST  BH,20H            ; ¿falla el CRC?
               MOV   AH,10H            ; "CRC error"
               JNZ   bios_err_det
               TEST  BH,80h            ; ¿acceso fuera de la pista?
               MOV   AH,4              ; "sector not found"
               JNZ   bios_err_det
               MOV   AH,20H            ; otro error: "bad NEC"
bios_err_det:  RET
get_bios_err   ENDP

; ------------ Reinicializar la controladora de disquetes.

init_fdc       PROC
               CALL  reset_disk        ; resetear FDC
               JC    init_fin          ; fallo al resetear
               MOV   DX,3F4h           ; registro de estado del FDC
               IN    AL,DX
               TEST  AL,80h
               JZ    init_otravez      ; el FDC no está listo
               TEST  AL,40h
               JZ    init_bien         ; el FDC espera datos de la CPU
init_otravez:  CALL  reset_disk        ; otro intento más
               MOV   DX,3F4h
               IN    AL,DX
               TEST  AL,80h
               JZ    init_mal          ; nada, que no tira
               TEST  AL,40h
               JZ    init_bien         ; bueno, ya despierta
init_mal:      MOV   AH,20h            ; fallo de la controladora
               STC
               JMP   init_fin
init_bien:     MOV   AH,8              ; "leer estado interrupciones"
               CALL  fdc_write         ; enviar comando
               JC    init_fin          ; fallo
               CALL  fdc_read          ; leer ST0
               JC    init_fin          ; fallo
               MOV   DS:[42h],AL       ; guardar ST0
               PUSH  AX
               CALL  fdc_read          ; leer cilindro actual
               MOV   DS:[42h+1],AL     ; guardarlo
               POP   CX
               JC    init_fin          ; fallo
               AND   CL,0C0h
               CMP   CL,0C0h           ; ¿terminación anormal?
               JNE   init_mal          ; no es terminación anormal
               XOR   AH,AH             ; ok: el FDC detecta el fallo
init_fin:      JMP   fdc_init
init_fdc       ENDP

; ------------ Resetear el FDC.

reset_disk     PROC
               CLI
               AND   BYTE PTR DS:[3Fh],7Fh  ; operación R/V
               AND   BYTE PTR DS:[3Eh],7Fh  ; borrar bit IRQ
               MOV   AL,DS:[3Fh]
               ROL   AL,4              ; bits 7-4: motores
               AND   AL,0FBH           ; bits 3-0: unidad
               OR    AL,8              ; interrupciones ON + reset
               MOV   DX,3F2h
               OUT   DX,AL             ; hacer reset
               MOV   CX,3
               CALL  wait_time         ; retardo de 30-45µs
               OR    AL,0CH
               OUT   DX,AL             ; fin del reset
               MOV   AX,9001h
               INT   15h               ; facilitar multitarea
               STI
               JC    fin_wait          ; error
               CALL  wait_int          ; esperar interrupción de disco
fin_wait:      MOV   AH,80h            ; "unidad no preparada"
               JC    exit_reset        ; hay error
               AND   BYTE PTR DS:[3Eh],7Fh  ; borrar bit IRQ
               XOR   AH,AH                  ; no hay error
exit_reset:    RET
reset_disk     ENDP

; ------------ Enviar comando specify obtenido de INT 1Eh al FDC.

send_specify   PROC
               PUSH  DS
               XOR   BX,BX
               MOV   DS,BX
               LDS   BX,DWORD PTR DS:[78h]  ; DS:BX -> INT 1Eh
               MOV   AL,3                   ; comando specify del FDC
               MOV   AH,[BX]           ; byte 0 del comando specify
               MOV   SI,AX
               MOV   AL,[BX+1]         ; byte 1 del comando specify
               MOV   DI,AX
               MOV   CH,3              ; orden de 3 bytes (SI, DI-L)
               POP   DS
               OR    BYTE PTR DS:[3Eh],80h  ; no esperar IRQ
               CALL  exec_cmd           ; mandar specify al FDC
               JMP   specify_sent
send_specify   ENDP

; ------------ Esperar una interrupción de disco durante 2 segundos.

wait_int       PROC
               MOV   BX,3Eh            ; variable con flag de INT
               XOR   CX,CX
               CALL  wait_event        ; esperar IRQ durante 1 segundo
               JNC   wait_int_ret
               XOR   CX,CX
               CALL  wait_event        ; esperar otro segundo más
wait_int_ret:  RET
wait_int       ENDP

; ------------ Devolver en AL el tipo de la unidad DL.

get_drive_type PROC
               CALL  peek_cmos         ; leer tipo de disqueteras
               OR    DL,DL
               JNZ   gdt_unidad_ok
               SHR   AL,4              ; unidad A:
gdt_unidad_ok: AND   AL,0Fh
               CMP   SP,SP             ; ZF=1 -> resultado correcto
               RET
get_drive_type ENDP

; ------------ Determinar la densidad del disquete.

detect_media   PROC
               PUSH  DX
               PUSH  CX
               PUSH  BX
               MOV   BL,r_dl
               XOR   BH,BH
               MOV   DL,BL
               CALL  get_drive_type    ; obtener tipo disquetera en AL
               MOV   AH,0
               JZ    dm_fast           ; ha sido posible obtenerlo
               JMP   dm_slow           ; usar procedimiento lento
dm_fast:       MOV   BYTE PTR DS:[BX+90h],0  ; estado físico unidad
               DEC   AX
               JNZ   dm_es_1200K?
               MOV   AL,93h            ; 360K: 360K en 360K, 250 Kbps
dm_try:        PUSH  AX
               MOV   DH,AL
               AND   DH,0C0h
               SHR   DH,6
               CALL  set_rate          ; velocidad de transferencia DH
               POP   AX
dm_result:     MOV   DS:[BX+90h],AL    ; estado físico...
               TEST  AL,10h            ; ...¿determinado?
               JZ    dm_fails          ; aún no
               XOR   AH,AH
               JMP   dm_exit           ; sí: resultado correcto
               NOP
dm_es_1200K?:  DEC   AX
               JNZ   dm_es_720K?
               MOV   AL,0              
               CALL  read_ids          ; 1.2M: 500 kbps
               MOV   AL,15h            ; indicar 1.2M en 1.2M
               JNC   dm_result         ; sí funciona
               MOV   AL,40h
               MOV   BYTE PTR DS:[BX+90h],2
               CALL  read_ids          ; probar 300 Kbps
               MOV   AL,74h            ; indicar 360K en 1.2M
               JNC   dm_result         ; sí funciona
               MOV   AL,2              ; indicar "¿1.2M en 1.2M?"
               JMP   dm_result
dm_es_720K?:   DEC   AX
               JNZ   dm_es_1440K?
               MOV   AL,97h            ; 720K: 250 Kbps
               JMP   dm_try            ; a probar suerte
dm_es_1440K?:  DEC   AX
               JNZ   dm_es_2880K
               MOV   AL,0
               CALL  read_ids          ; 1.44M: 500 Kbps
               MOV   AL,17h            ; indicar 1.44M
               JNC   dm_result         ; sí funciona
               MOV   AL,80h
               CALL  read_ids          ; probar a 250 Kbps
               MOV   AL,97h            ; indicar 720K
               JNC   dm_result         ; sí funciona
               MOV   AL,7              ; indicar "¿1.44M?"
               JMP   dm_result
dm_es_2880K:   MOV   AL,0C0h
               CALL  read_ids          ; 2.88M: 1 Mbps
               MOV   AL,0D7h           ; indicar 2.88M en 2.88M
               JNC   dm_result         ; sí funciona
               MOV   AL,0
               CALL  read_ids          ; probar 500 Kbps
               MOV   AL,17h            ; indicar 1.44M
               JNC   dm_result         ; sí funciona
               MOV   AL,80h
               CALL  read_ids          ; probar 250 Kbps
               MOV   AL,97h            ; indicar 720K
               JNC   dm_result         ; sí funciona
               MOV   AL,0C7h           ; indicar "¿2.88M en 2.88M?"
               JMP   dm_result
dm_fails:      STC                     ; condición de error
dm_exit:       MOV   DS:[41h],AH       ; código de error
               POP   BX
               POP   CX
               POP   DX
               RET
dm_slow:       MOV   AL,0
               CALL  read_ids          ; probar 500 Kbps
               MOV   AL,15h            ; indicar 1.2M en 1.2M
               JNC   dm_slow_ok        ; sí funciona
               MOV   AL,40h
               MOV   BYTE PTR DS:[BX+90h],2
               CALL  read_ids          ; probar 300 Kbps
               MOV   AL,74h            ; indicar 360K en 1.2M
               JNC   dm_slow_ok        ; sí funciona
               MOV   AL,80h
               CALL  read_ids          ; probar 250 Kbps
               MOV   AL,97h            ; indicar 720K ó 360K en 360K
               JNC   dm_slow_ok        ; sí funciona
               MOV   AL,0C0h
               CALL  read_ids          ; probar 1 Mbps
               MOV   AL,0D7h           ; indicar 2.88M en 2.88M
               JC    dm_fails          ; no funciona
dm_slow_ok:    JMP   dm_result
detect_media   ENDP

; ------------ Efectuar una lectura de ID's a velocidad AL (bits 6-7).

read_ids       PROC
               PUSH  BX
               PUSHF
               CLI
               MOV   BYTE PTR DS:[40h],0FFh  ; evitar detención motor
               POPF
               SHR   AL,6              ; colocar bits de velocidad
               MOV   DH,AL
               CALL  set_rate          ; velocidad de transferencia DH
               MOV   DL,r_dl
               CALL  recalibrate       ; recalibrar
               JNC   read_id_try
               CALL  recalibrate       ; segundo intento
               JC    read_id_err
read_id_try:   MOV   CX,3              ; 3 intentos
read_id_retry: PUSH  CX
               MOV   DH,DL             ; en el cabezal 0 de la unidad
               MOV   DL,4Ah            ; comando de leer ID's
               MOV   SI,DX
               MOV   DL,DH
               MOV   CH,2              ; comando de 2 bytes
               AND   BYTE PTR DS:[3Eh],7Fh  ; esperar interrupción
               CALL  exec_cmd
               JC    read_id_fails     ; fallo
               MOV   BX,42h
               MOV   CX,7
               CALL  get_results       ; leer bytes de resultados
               JC    read_id_fails
               CALL  get_bios_err      ; obtener código de error
               POP   CX
               OR    AH,AH
               JZ    read_id_ret       ; ya no hay fallo
               LOOP  read_id_retry     ; reintentar
               JMP   read_id_err       ; mala suerte
read_id_fails: POP   CX
read_id_err:   STC
read_id_ret:   POP   BX
               RET
read_ids       ENDP

; ------------ Seleccionar la velocidad de transferencia adecuada.

select_rate    PROC
               PUSH  SI
               MOV   SI,90h
               XOR   DH,DH
               ADD   SI,DX             ; [SI] -> estado físico unidad
               MOV   DH,[SI]           ; estado físico nueva unidad
               MOV   DL,DS:[8Bh]       ; control del medio físico
               AND   DX,0C0C0h         ; aislar bits de velocidad
               CMP   DL,DH             ; ¿velocidad ya seleccionada?
               JE    selected_rate
               AND   BYTE PTR DS:[8Bh],3Fh  ; no: borrar la anterior
               OR    DS:[8Bh],DH            ; indicar la nueva
               AND   DH,0C0h
               ROL   DH,2
               CALL  set_rate          ; nueva velocidad transferencia
selected_rate: POP   SI
               MOV   DX,r_dx           ; restaurar DX
               RET
select_rate    ENDP

; ------------ Establecer la velocidad de transferencia DH.

set_rate       PROC
               PUSH  AX
               MOV   AL,DH
               MOV   DX,3F7h      ; registro de control del disquete
               OUT   DX,AL        ; seleccionar velocidad
               POP   AX
               RET
set_rate       ENDP

; ------------ Esperar que alguno de los bits a 1 de AH en el
;              puerto DX se pongan a 0 en no más de CX 15.09 µs.

wait0          PROC
               PUSH  AX
wait0_do:      IN    AL,DX             ; leer del puerto E/S
               TEST  AL,AH
               JZ    wait0_end         ; bit(s) ya a 0
wait0_delay0:  IN    AL,61h
               TEST  AL,10h
               JZ    wait0_delay0      ; esperar 15.09 µs
               DEC   CX
               JZ    wait0_fail        ; timeout
               IN    AL,DX             ; volver a leer del puerto E/S
               TEST  AL,AH
               JZ    wait0_end         ; bit(s) ya a 0
wait0_delay1:  IN    AL,61h
               TEST  AL,10h
               JNZ   wait0_delay1      ; esperar 15.09 µs
               DEC   CX
               JNZ   wait0_do          ; aún no hay timeout
wait0_fail:    STC                     ; error de timeout
wait0_end:     POP   AX
               RET
wait0          ENDP

; ------------ Esperar que alguno de los bits a 1 de AH en el
;              puerto DX se pongan a 1 en no más de CX 15.09 µs.

wait1          PROC
               PUSH  AX
wait1_do:      IN    AL,DX             ; leer del puerto E/S
               TEST  AL,AH
               JNZ   wait1_end         ; bit(s) ya a 1
wait1_delay0:  IN    AL,61h
               TEST  AL,10h
               JZ    wait1_delay0      ; esperar 15.09 µs
               DEC   CX
               JZ    wait1_fail        ; timeout
               IN    AL,DX             ; volver a leer del puerto E/S
               TEST  AL,AH
               JNZ   wait1_end         ; bit(s) ya a 1
wait1_delay1:  IN    AL,61h
               TEST  AL,10h
               JNZ   wait1_delay1      ; esperar 15.09 µs
               DEC   CX
               JNZ   wait1_do          ; aún no hay timeout
wait1_fail:    STC                     ; error de timeout
wait1_end:     POP   AX
               RET
wait1          ENDP

; ------------ Esperar evento durante CX 15.09 µs.

wait_event     PROC
               PUSH  AX
test_int:      TEST  BYTE PTR [BX],80h
               JNZ   fin_w_event       ; llegó la interrupción
w_ref1:        IN    AL,61h
               TEST  AL,10h
               JZ    w_ref1            ; esperar 15 µs
               DEC   CX
               JZ    w_event_none      ; timeout
               TEST  BYTE PTR [BX],80h
               JNZ   fin_w_event       ; llegó la interrupción
w_ref2:        IN    AL,61h
               TEST  AL,10h
               JNZ   w_ref2            ; esperar 15 µs
               DEC   CX
               JNZ   test_int          ; queda tiempo, esperar más
w_event_none:  STC                     ; no llegó la interrupción
fin_w_event:   POP   AX
               RET
wait_event     ENDP

; ------------ Retardo de CX 15.09 µs aproximadamente.

wait_time      PROC
               PUSH  AX
wait_ref_h:    IN    AL,61h
               TEST  AL,10h            ; esperar ciclo de refresco
               JZ    wait_ref_h        ; de memoria (15,09 µs)
               DEC   CX
               JZ    wait_time_fin     ; fin de la espera
wait_ref_l:    IN    AL,61h
               TEST  AL,10h            ; esperar ciclo de refresco
               JNZ   wait_ref_l        ; de memoria (15,09 µs)
               DEC   CX
               JNZ   wait_ref_h        ; completar espera
wait_time_fin: POP   AX
               RET
wait_time      ENDP

; ------------ Simular la lectura del registro 10h de la CMOS, para
;              el tipo de las disqueteras.

peek_cmos      PROC
               MOV   AL,CS:tipo_drvs   ; nuestro tipo simulado
               RET
peek_cmos      ENDP

; ------------ Datos.

tipo_drvs      DB    0    ; tipo de disqueteras (definido al instalar)

               ; --- Tabla de saltos a las funciones

tab_jmp        DW    reset, get_status
               DW    read_wr_verify, read_wr_verify, read_wr_verify
               DW    format_track, get_drv_param, get_disk_type
               DW    detect_change, set_type_fmt, set_media_fmt

               ; --- Tabla de tamaños en sectores y cilindros

tab_disksize   DW     9 + 39 * 256  ; 360K
               DW    15 + 79 * 256  ; 1.2M
               DW     9 + 79 * 256  ; 720K
               DW    18 + 79 * 256  ; 1.44M
               DW    36 + 79 * 256  ; 2.88M

               ; --- Tablas de parámetros de disco (sintaxis INT 1Eh)

tab_ptr_1e     DW    t360in360, t1200, t720, t1440, t2880

               ; Bytes de esta tabla, similar a la de INT 1Eh:
               ;
               ;     0) byte 1 para 'Specify' (step rate-head unload)
               ;     1) byte 2 para 'Specify' (head load-modo DMA)
               ;     2) tics de reloj hasta detención del motor
               ;     3) tamaño de sector (0-128, 1-256, 2-512, ...)
               ;     4) sectores por pista
               ;     5) GAP3 para lectura/escritura
               ;     6) longitud concreta de sector si tamaño=0
               ;     7) GAP3 para formateo
               ;     8) byte de relleno al formatear
               ;     9) tiempo de estabilización del cabezal en ms
               ;    10) tiempo aceleración motor, en 1/8 segundos
               ;    11) número de cilindros menos uno
               ;    12) velocidad de transferencia (en bits 6-7)

t360in360      DB    0DFh, 002h, 025h, 002h, 009h   ; 360K en 360K
               DB    02Ah, 0FFh, 050h, 0F6h, 00Fh
               DB    008h, 027h, 080h
t1200          DB    0DFh, 002h, 025h, 002h, 00Fh   ; 1.2M
               DB    01Bh, 0FFh, 054h, 0F6h, 00Fh
               DB    008h, 04Fh, 000h
t720           DB    0DFh, 002h, 025h, 002h, 009h   ; 720K
               DB    02Ah, 0FFh, 050h, 0F6h, 00Fh
               DB    008h, 04Fh, 080h
t1440          DB    0BFh, 002h, 025h, 002h, 012h   ; 1.44M
               DB    01Bh, 0FFh, 06Ch, 0F6h, 00Fh
               DB    008h, 04Fh, 000h
t360en1200     DB    0DFh, 002h, 025h, 002h, 009h   ; 360K en 1.2M
               DB    02Ah, 0FFh, 050h, 0F6h, 00Fh
               DB    008h, 027h, 040h
t2880          DB    0AFh, 002h, 025h, 002h, 024h   ; 2.88M
               DB    01Bh, 0FFh, 050h, 0F6h, 00Fh
               DB    008h, 04Fh, 0C0h

               ; --- Fin del área residente para INT 40h

fin_residente  EQU   $

bytes_resid    EQU   fin_residente-ini_residente

; ------------ Rutina de gestión de INT 13h (si se necesita). Se llama
;              a la INT 40h de manera que esta última no devuelva
;              nunca errores de frontera de DMA, para lo que emplea un
;              buffer auxiliar de 512 bytes para transferir el sector
;              que cruzaría la frontera. En la función de formateo, al
;              acceder a la primera pista del disco se invoca primero
;              la interrupción original para que el DOS (cargado antes
;              que este programa) se entere del cambio de soporte.

ges_int13      PROC
               STI
               CMP   DL,80h
               JB    floppy
               JMP   CS:ant_int13
floppy:        CMP   AH,2
               JB    floppy_bios
               CMP   AH,5
               JE    test_format
               JB    test_rwv
floppy_bios:   INT   40h               ; función sin problemas de DMA
               RETF  2
ges_int13      ENDP

test_format    PROC
               XPUSH <DS, ES, BX>
               PUSHA
               PUSH  DX
               OR    DH,CH
               POP   DX
               JNZ   skip_aviso        ; no es pista y cabezal 0
               PUSHA
               MOV   AL,1              ; formatear un sector (AH=5)
               MOV   CH,0
               MOV   DH,2              ; en cabezal 2 (incorrecto)
               PUSHF
               CALL  CS:ant_int13      ; avisar al DOS del nuevo disco
               POPA
skip_aviso:    MOV   DI,CS:pbuffer
               MOV   SI,BX
               PUSH  ES
               POP   DS
               PUSH  CS
               POP   ES
               MOV   CX,256
               CLD
               REP   MOVSW             ; datos de formateo al buffer
               POPA                    ; auxiliar
               MOV   BX,CS:pbuffer
               INT   40h               ; formateo
               XPOP  <BX, ES, DS>      ; restaurar registros iniciales
               RETF  2
test_format    ENDP

test_rwv       PROC                    ; para Read/Write/Verify
               MOV   CS:funcion,AH
               MOV   CS:nsects,AL
               XPUSH <SI, DI>
               XPUSH <AX, DX>
               MOV   AX,ES
               MOV   DX,16
               MUL   DX
               ADD   AX,BX
               NEG   AX                ; AX = bytes hasta frontera DMA
               SHR   AX,9
               MOV   SI,AX             ; sectores antes frontera
               XPOP  <DX, AX>
               PUSH  AX
               MOV   AH,0
               MOV   DI,AX
               POP   AX
               CMP   SI,DI
               JBE   trwv_no_sobra
               MOV   SI,DI
trwv_no_sobra: SUB   DI,SI             ; sectores+1 tras frontera
               JZ    trwv_1ok
               PUSH  CX
               MOV   CX,SI
               MOV   AL,CL             ; AL sectores antes de frontera
               POP   CX
trwv_1ok:      AND   AL,AL
               JZ    trwv_1cruza       ; primer sector cruza frontera
               INT   40h
               PUSHF
               JNC   trwv_cont         ; sin fallo E/S
trwv_exit:     JMP   trwv_ret
trwv_cont:     AND   DI,DI
               JZ    trwv_exit         ; se acabó
               POPF
trwv_1cruza:   CMP   CS:funcion,3      ; ¿escritura?
               JNE   trwv_sectdma
               PUSHA
               XPUSH <DS, ES>
               SHL   SI,9              ; sectores transferidos * 512
               ADD   SI,BX
               MOV   DI,CS:pbuffer
               PUSH  ES
               POP   DS
               PUSH  CS
               POP   ES
               MOV   CX,256
               CLD
               REP   MOVSW             ; sector conflictivo a través
               XPOP  <ES, DS>          ; de buffer auxiliar
               POPA
trwv_sectdma:  XPUSH <ES, BX, CX>
               PUSH  CS
               POP   ES
               MOV   BX,CS:pbuffer     ; ES:BX buffer auxiliar
               MOV   AH,CS:funcion
               MOV   AL,1              ; un sector
               ADD   CX,SI             ; nuevo sector inicial
               INT   40h
               XPOP  <CX, BX, ES>
               PUSHF
               JC    trwv_ret          ; fallo E/S
               CMP   CS:funcion,2      ; ¿lectura?
               JNE   trwv_tras?
               PUSHA
               XPUSH <DS, ES>
               SHL   SI,9              ; sectores transferidos * 512
               MOV   DI,SI
               ADD   DI,BX
               MOV   SI,CS:pbuffer
               PUSH  CS
               POP   DS
               MOV   CX,256
               CLD
               REP   MOVSW             ; sector conflictivo a través
               XPOP  <ES, DS>          ; de buffer auxiliar
               POPA
trwv_tras?:    MOV   AL,CS:nsects      ; sectores transferidos
               DEC   DI
               JZ    trwv_ret          ; no queda nada tras frontera
               POPF
               XPUSH <BX, CX>
               INC   SI
               ADD   CX,SI             ; nuevo sector inicial
               SHL   SI,9              ; sectores transferidos * 512
               ADD   BX,SI
               MOV   AX,DI             ; sectores restantes
               MOV   AH,CS:funcion
               INT   40h
               XPOP  <CX, BX>
               MOV   AL,CS:nsects      ; número sectores transferidos
               PUSHF
trwv_ret:      POPF
               XPOP  <DI, SI>
               RETF  2
test_rwv       ENDP

funcion        DB    ?
nsects         DB    ?
pbuffer        DW    buffer_io

               EVEN
buffer_io      EQU   $


; *****************************
; *                           *
; *   I N S T A L A C I O N   *
; *                           *
; *****************************

main           PROC
               ADD   SP,2              ; quitar dirección de retorno
               XPUSH <AX, BX, CX, DX, SI, DI, BP, DS, ES>
               PUSH  CS
               POP   DS
               MOV   WORD PTR interrupcion,531Eh  ; opcode PUSH DS,BX
               MOV   BYTE PTR interrupcion+2,0BBh ; opcode MOV BX,??
               PUSH  CS
               POP   ES
               CALL  inic_general      ; inicializar ciertas variables
               CALL  analiza_equipo
               CALL  lee_params        ; parámetros de tipo unidades
               CALL  set_params        ; actualizar tipo de unidades
               CALL  valida_drives     ; asegurar que hay unidades
               TEST  error,0FFFFh
               JNZ   exit_ins
               CALL  hay2m?
               JC    no_2m
               OR    error,ERR_HAY2M   ; 2M ó 2MX residente
               JMP   exit_ins
no_2m:         TEST  accion,I40        ; ¿soporta INT 40h el sistema?
               JNZ   i40_ok            ; en efecto
               CALL  set_i13           ; añadir soporte vía INT 13h
i40_ok:        CALL  mx_get_handle     ; obtener entrada Multiplex
               JNC   handle_ok
               OR    error,MX64FULL    ; no quedan entradas
               JMP   exit_ins
handle_ok:     MOV   multiplex_id,AH   ; entrada multiplex para 2M
               CALL  preservar_ints    ; tomar nota de vectores
               CALL  activar_ints      ; interceptar vectores
exit_ins:      CALL  info
               MOV   BX,pcab_pet_segm
               MOV   ES,BX
               MOV   BX,pcab_pet_desp
               MOV   WORD PTR ES:[BX+3],100h ; indicar retorno correcto
               MOV   AX,longitud_total
               MOV   CL,4
               SHL   AX,CL
               TEST  error,0FFFFh
               JZ    exit_ok
               MOV   WORD PTR ES:[BX+14],0   ; OFFSET 0: no quedará
               MOV   WORD PTR ES:[BX+16],CS  ; instalado en memoria
               JMP   exit_interr
exit_ok:       MOV   WORD PTR ES:[BX+14],AX  ; OFFSET al último byte residente
               MOV   WORD PTR ES:[BX+16],CS
exit_interr:   XPOP  <ES, DS, BP, DI, SI, DX, CX, BX, AX>
               RETF
main           ENDP

; ------------ Leer los parámetros en la línea del CONFIG (ES:BX).

lee_params     PROC
               PUSH  ES
               MOV   BX,pcab_pet_segm
               MOV   ES,BX
               MOV   BX,pcab_pet_desp
               LES   BX,ES:[BX+12h]    ; apuntar a los parámetros
               CALL  salta_nombre      ; buscar inicio parámetros
mas_param:     CALL  busca_param       ; saltar delimitadores
               JC    fin_params        ; no hay parámetros
otro_drive:    CMP   AX,':a'
               JE    unidad_ok
               CMP   AX,':b'           ; admitidas A: y B:
               JE    unidad_ok
param_error:   OR    error,ERR_SYNTAX  ; error de sintaxis
               POP   ES
               RET
unidad_ok:     MOV   CL,ES:[BX+2]
               SUB   CL,'0'            ; tipo de la unidad
               SUB   AL,'a'
               MOV   AH,0
               LEA   DI,tipos_drv
               ADD   DI,AX
               CMP   CL,5              ; ¿tipo entre 0 y 5?
               JA    param_error
               MOV   [DI],CL           ; definir tipo
               ADD   BX,3
               JMP   mas_param         ; próximo parámetro
fin_params:    POP   ES
               RET
lee_params     ENDP

salta_nombre   PROC                    ; saltar nombre del driver en
               MOV   AL,ES:[BX]        ; línea de órdenes del CONFIG
               INC   BX
               CMP   AL,' '
               JE    fin_nombre
               CMP   AL,9
               JE    fin_nombre
               CMP   AL,0Dh
               JE    fin_nombre
               CMP   AL,0Ah
               JE    fin_nombre
               AND   AL,AL
               JZ    fin_nombre
               JMP   salta_nombre
fin_nombre:    RET
salta_nombre   ENDP

busca_param    PROC                    ; saltar delimitadores
               DEC   BX
p_delimit:     INC   BX
               MOV   AX,ES:[BX]
               CMP   AL,' '
               JE    p_delimit         ; espacio en blanco
               CMP   AL,9
               JE    p_delimit         ; tabulador
               CMP   AL,13
               JE    p_final           ; CR ó LF indican el final
               CMP   AL,10
               JE    p_final
               OR    AX,"  "           ; poner en minúsculas
               CLC
               RET
p_final:       STC                     ; se acabaron los parámetros
               RET
busca_param    ENDP

; ------------ Establecer tipo disqueteras (según BIOS o parámetros).

set_params     PROC
               PUSH  ES
               MOV   AL,tipos_drv
               CMP   AL,-1
               JNE   ta_calc           ; A: definida por el usuario
               MOV   AH,8
               MOV   DL,0
               INT   13h               ; consultar tipo a la BIOS
               MOV   AL,BL
               JNC   ta_val
               JMP   set_type_exit     ; unidad no existente
ta_val:        CMP   DL,1
               JB    set_type_exit
ta_calc:       CMP   AL,4
               JBE   ta_set
               MOV   AL,5              ; 2.88M representado por 5
ta_set:        AND   tipo_drvs,0Fh
               MOV   CL,4
               SHL   AL,CL
               OR    tipo_drvs,AL      ; tipo A: en nibble alto
               MOV   AL,tipos_drv+1
               CMP   AL,-1             ; B: definida por el usuario
               JNE   tb_calc
               MOV   AH,8
               MOV   DL,1
               INT   13h               ; consultar tipo a la BIOS
               MOV   AL,BL
               JC    set_type_exit
               CMP   DL,2
               JB    set_type_exit     ; unidad no existente
tb_calc:       CMP   AL,4
               JBE   tb_set
               MOV   AL,5              ; 2.88M representado por 5
tb_set:        AND   tipo_drvs,0F0h
               OR    tipo_drvs,AL      ; tipo B: en nibble bajo
set_type_exit: POP   ES
               RET
set_params     ENDP

; ------------ Asegurar que se conoce el tipo de las unidades.

valida_drives  PROC
               MOV   AL,tipo_drvs
               AND   AL,0F0h
               JNZ   drvs_ok
               OR    error,ERR_MALDRV
drvs_ok:       RET
valida_drives  ENDP

; ------------ Código ejecutado desde la línea de comandos.

inicio         PROC  FAR
               MOV   AX,_PRINCIPAL
               MOV   DS,AX
               CALL  param_i?
               LEA   DX,info_txt
               CALL  print
               MOV   AX,4C00h
               INT   21h                    ; final normal
inicio         ENDP

               ; ----- Buscar posible parámetro /I

param_i?       PROC
               MOV   DI,80h
               MOV   CL,ES:[DI]
               MOV   CH,0
               INC   CX
busca_mas:     MOV   DL,OFF
               JCXZ  ret_bparam
busca_barra:   INC   DI
               CMP   BYTE PTR ES:[DI],'/'
               LOOPNE busca_barra
               JNE   ret_bparam
               MOV   AL,ES:[DI+1]
               OR    AL,32
               CMP   AL,'i'
               MOV   DL,ON
               JE    ret_bparam
               JMP   busca_mas
ret_bparam:    MOV   param_i,DL
               RET
param_i?       ENDP

; ------------ Inicializar ciertas variables.

inic_general   PROC
               MOV   AX,(bytes_resid+15)/16
               MOV   longitud_total,AX ; memoria necesaria
               MOV   segmento_real,CS  ; anotar segmento del bloque
               MOV   offset_real,0     ; ídem con el offset
               RET
inic_general   ENDP

; ------------ Comprobar que la configuración es la adecuada. Para
;              saber si la INT 13h de este ordenador acaba llamando a
;              la INT 40h, se desvía la INT 40h y se provoca un inocuo
;              reset de disquetes vía INT 13h para comprobar si pasa
;              por la INT 40h.

analiza_equipo PROC
               PUSH  ES
               CALL  testAT
               MOV   AX,ERR_TIPOPC
               JC    cod_err_ok        ; no es AT o superior
               CALL  test_i40
               XOR   AX,AX
cod_err_ok:    OR    error,AX
               POP   ES
               RET
analiza_equipo ENDP

               ; --- Comprobar si la INT 40h está en uso

test_i40:      XPUSH <DS, ES>          ; *
               MOV   AX,3540h
               INT   21h
               XPUSH <ES, BX>          ; vector de INT 40h original
               LEA   DX,i40_aux
               MOV   AX,2540h
               INT   21h               ; establecer nueva INT 40h
               XOR   AX,AX
               MOV   DL,0
               INT   13h               ; reset de disco
               XPOP  <DX, DS>
               MOV   AX,2540h
               INT   21h               ; restaurar INT 40h original
               XPOP  <ES, DS>          ; *
               RET

i40_aux        PROC
               OR    CS:accion,I40     ; sí utilizada INT 40h
               IRET                    ; desde la INT 13h
i40_aux        ENDP

               ; ----- Detectar 286 ó superior.

testAT         PROC
               PUSHF
               POP   AX
               OR    AH,70h        ; intentar activar bit 12, 13 ó 14
               PUSH  AX            ; del registro de estado
               POPF
               PUSHF
               POP   AX
               AND   AH,0F0h
               CMP   AH,0F0h
               JE    testedAT
               STC
testedAT:      CMC                 ; CF = 0 en AT y 1 en PC/XT
               RET
testAT         ENDP

; ------------ Desviar también INT 13h ya que en esta máquina el
;              gestor de INT 13h no invoca la INT 40h.

set_i13        PROC
               INC   offsets_ints      ; usado un vector más
               INC   BYTE PTR tabla_vectores-1
               MOV   AX,CS
               MOV   CX,16
               MUL   CX
               ADD   AX,pbuffer
               ADC   DX,0              ; DX:AX = dirección 20 bits
               MOV   CX,DX
               PUSH  AX
               ADD   AX,511            ; buffer para el mayor sector
               ADC   DX,0
               POP   AX
               CMP   DX,CX
               JE    dma_ok
               NEG   AX
               ADD   pbuffer,AX        ; saltar hasta próxima frontera
               OR    accion,BUFFERPLUS
dma_ok:        MOV   AX,pbuffer
               ADD   AX,512
               SUB   AX,OFFSET ges_int13
               ADD   AX,15
               MOV   CL,4
               SHR   AX,CL
               ADD   longitud_total,AX ; es necesaria más memoria
               RET
set_i13        ENDP

; ------------ Preservar vectores de interrupción previos.

preservar_INTs PROC
               XPUSH <ES, DI>
               LEA   DI,tabla_vectores
               MOV   CL,[DI-1]
               MOV   CH,0              ; CX vectores interceptados
otro_vector:   XPUSH <CX, DI>
               MOV   AH,35h
               MOV   AL,[DI]
               INT   21h               ; obtener vector de INT xx
               XPOP  <DI, CX>
               MOV   [DI+1],BX         ; anotar donde apunta
               MOV   [DI+3],ES
               ADD   DI,5
               LOOP  otro_vector       ; repetir con los restantes
               XPOP  <DI, ES>
               RET
preservar_INTs ENDP

; ------------ desviar vectores de interrupción a las nuevas rutinas.

activar_INTs   PROC
               LEA   SI,offsets_ints
               MOV   CX,CS:[SI]        ; CX vectores a desviar
               ADD   SI,2
desvia_otro:   MOV   AL,CS:[SI]        ; número del vector en curso
               MOV   DX,CS:[SI+1]      ; obtener offset
               MOV   AH,25h
               INT   21h               ; desviar INT xx a DS:DX
               ADD   SI,3
               LOOP  desvia_otro
               RET
activar_INTs   ENDP

; ------------ Buscar entrada no usada en la interrupción Multiplex.
;              A la salida, CF=1 si no hay hueco (ya hay 64 programas
;              residentes instalados con esta técnica). Si CF=0, se
;              devuelve en AH un valor de entrada libre en la INT 2Fh.

mx_get_handle  PROC
               MOV   AH,0C0h
mx_busca_hndl: PUSH  AX
               MOV   AL,0
               INT   2Fh
               CMP   AL,0FFh
               POP   AX
               JNE   mx_si_hueco
               INC   AH
               JNZ   mx_busca_hndl
               STC
               RET
mx_si_hueco:   CLC
               RET
mx_get_handle  ENDP

; ------------ Devolver CF=0 si 2M o 2MX están instalados o se ha
;              cargado el código 2M en modo SuperBOOT.

hay2m?         PROC
               PUSH  ES
               LEA   SI,id_2m          ; identificación del programa
               MOV   CX,id_2m_tam
               MOV   AX,1492h
               MOV   ES,AX
               MOV   DI,1992h          ; ES:DI protocolo de búsqueda
               CALL  mx_find_tsr       ; buscar si está en memoria
               JNC   hay2m?_ret
               LEA   SI,id_2mx         ; identificación del programa
               MOV   CX,id_2mx_tam
               CALL  mx_find_tsr
               JNC   hay2m?_ret
               INT   12h               ; tamaño memoria convencional
               CMP   AX,640
               JBE   base_sc_ok
               MOV   AX,640            ; alguien la ha manipulado
base_sc_ok:    SUB   AX,5
               MOV   BX,64
               MUL   BX                ; AX = segmento de SuperBOOT
               MOV   CX,6
scan_boot:     PUSH  CX
               MOV   ES,AX
               MOV   DI,6
               LEA   SI,id_boot
               MOV   CX,id_boot_tam
               CLD
               REP   CMPSB
               POP   CX
               JE    hay2m?_ret        ; CF = 0 -> 2M SuperBOOT
               SUB   AX,1000h
               LOOP  scan_boot         ; buscar 64K más abajo
               STC
hay2m?_ret:    POP   ES
               RET
hay2m?         ENDP

; ------------ Buscar un TSR por la interrupción Multiplex. A la
;              entrada, DS:SI cadena de identificación del programa
;              (CX bytes) y ES:DI protocolo de búsqueda (normalmente
;              1492h:1992h). A la salida, si el TSR ya está instalado,
;              CF=0 y ES:DI apunta a la cadena de identificación del
;              mismo. Si no, CF=1 y ningún registro alterado.

mx_find_tsr    PROC
               MOV   AH,0C0h
mx_rep_find:   XPUSH <AX, CX, SI, DS, ES, DI>
               MOV   AL,0
               PUSH  CX
               INT   2Fh
               POP   CX
               CMP   AL,0FFh
               JNE   mx_skip_hndl      ; no hay TSR ahí
               CLD
               PUSH  DI
               REP   CMPSB             ; comparar identificación
               POP   DI
               JE    mx_tsr_found      ; programa buscado hallado
mx_skip_hndl:  XPOP  <DI, ES, DS, SI, CX, AX>
               INC   AH
               JNZ   mx_rep_find
               STC
               RET
mx_tsr_found:  ADD   SP,4              ; «sacar» ES y DI de la pila
               XPOP  <DS, SI, CX, AX>
               CLC
               RET
mx_find_tsr    ENDP

; ------------ Informar al usuario.

info           PROC
               TEST  error,0FFFFh
               JZ    info_mas
               LEA   DX,no_inst_txt
               CALL  print
               LEA   DX,mal_cpu_txt
               TEST  error,ERR_TIPOPC
               JNZ   print_err
               LEA   DX,hay2m_txt
               TEST  error,ERR_HAY2M
               JNZ   print_err
               LEA   DX,null_drv_txt
               TEST  error,ERR_MALDRV
               JNZ   print_err
               LEA   DX,err_syntax_txt
               TEST  error,ERR_SYNTAX
               JNZ   print_err
               LEA   DX,err_mx64full
               TEST  error,MX64FULL
               JZ    fin_info
print_err:     CALL  print
               RET
info_mas:      LEA   DX,instalado_txt
               CALL  print
               CALL  info_drives
               TEST  accion,BUFFERPLUS
               JZ    fin_info
               LEA   DX,dma_cross_txt
               CALL  print
fin_info:      RET
info           ENDP

               ; --- Informar de las unidades controladas.

info_drives    PROC
               MOV   AH,8
               MOV   DL,0
               INT   13h
               JC    info_null         ; no hay información sobre A:
               AND   BL,BL
               JZ    info_null
               CMP   DL,1
               JB    info_null
               PUSH  DX
               MOV   BH,0
               DEC   BX
               SHL   BX,1
               LEA   DX,a_txt          ; "A:"
               CALL  print
               MOV   DX,[BX+OFFSET ptr_txt_tipos]  ; su tipo
               CALL  print
               POP   DX
               CMP   DL,2
               JB    info_exit         ; no hay información sobre B:
               MOV   AH,8
               MOV   DL,1
               INT   13h
               JC    info_exit
               AND   BL,BL
               JZ    info_exit
               MOV   BH,0
               DEC   BX
               SHL   BX,1
               LEA   DX,b_txt          ; "B:"
               CALL  print
               MOV   DX,[BX+OFFSET ptr_txt_tipos]  ; su tipo
               CALL  print
info_exit:     LEA   DX,i40_txt
               TEST  accion,I40
               JNZ   imodo_ok
               LEA   DX,i13_txt
imodo_ok:      CALL  print             ; modo de instalación
               RET
info_null:     LEA   DX,null_drv_txt   ; sin indicar tipo de unidades
               CALL  print
               RET
info_drives    ENDP

; ------------ Inicializar variable idioma_sp según idioma del país.

habla_hispana? PROC
               XPUSH <AX, BX, CX, DX, BP>
               MOV   AH,30h
               INT   21h
               XCHG  AH,AL             ; AX = versión del DOS
               MOV   BP,AX
               MOV   idioma_sp,OFF     ; supuesto de habla no hispana
               CMP   BP,200h
               JB    habla_ok
               LEA   DX,buffer_aux
               MOV   AX,3800h
               INT   21h               ; obtener información del pais
               CMP   BP,20Bh
               JE    habla_ax          ; DOS 2.11: AX cód. telefónico
               CMP   BP,300h
               JB    habla_ok          ; 2.x excepto 2.11: mala suerte
               MOV   AX,BX
habla_ax:      LEA   BX,paises_sp-2
               MOV   CX,numpaises_sp
habla_sp?:     ADD   BX,2
               CMP   AX,[BX]
               JE    habla_hispana
               LOOP  habla_sp?
habla_ok:      MOV   AL,param_i
               XOR   idioma_sp,AL      ; considerar parámetro /I
               XPOP  <BP, DX, CX, BX, AX>
               RET
habla_hispana: MOV   idioma_sp,ON      ; país de habla hispana
               MOV   AL,param_i
               XOR   idioma_sp,AL      ; considerar parámetro /I
               XPOP  <BP, DX, CX, BX, AX>
               RET
habla_hispana? ENDP

; ------------ Imprimir cadena en DS:DX delimitada por un 0 ó un 255.
;              Si hay que imprimir en inglés se toma la cadena que va
;              después si ésta acaba en 255 (si acaba en 0, no hay
;              distinción entre mensaje castellano e inglés).

print          PROC
               XPUSH <AX, BX, CX, DX>
pr_decidir:    CMP   idioma_sp,OFF
               JE    usar_uk
               CMP   idioma_sp,ON
               JE    usar_sp
               PUSH  DX
               CALL  habla_hispana?         ; determinar lengua
               POP   DX
               JMP   pr_decidir
usar_uk:       MOV   BX,DX
               DEC   BX
usar_uk?:      INC   BX
               CMP   BYTE PTR [BX],0
               JE    usar_sp                ; acaba en 0: no traducir
               CMP   BYTE PTR [BX],255
               JNE   usar_uk?
               LEA   DX,[BX+1]              ; acaba en 255: traducir
usar_sp:       MOV   BX,DX
               DEC   BX
print_cad:     INC   BX
               CMP   BYTE PTR [BX],0
               JE    prlong_ok
               CMP   BYTE PTR [BX],255
               JNE   print_cad              ; calcular longitud
prlong_ok:     MOV   CX,BX
               SUB   CX,DX
               MOV   AH,40h
               MOV   BX,1
               INT   21h
               XPOP  <DX, CX, BX, AX>
               RET
print          ENDP

; ************ Datos no residentes para la instalación

ON             EQU   1                 ; constantes booleanas
OFF            EQU   0

param_i        DB    OFF               ; a ON si indicado parámetro /I

tipos_drv      DB    -1                ; tipo de A:
               DB    -1                ; y de B:

id_2m          DB    "CiriSOFT:2M:"    ; marcas de presencia de 2M
id_2m_tam      EQU   $-OFFSET id_2m
id_2mx         DB    "CiriSOFT:2MX:"
id_2mx_tam     EQU   $-OFFSET id_2mx
id_boot        DB    "2M-STV"
id_boot_tam    EQU   $-OFFSET id_boot

ERR_TIPOPC     EQU   1                 ; códigos de error
ERR_HAY2M      EQU   2
ERR_MALDRV     EQU   4
ERR_SYNTAX     EQU   8
MX64FULL       EQU  16

I40            EQU   1                 ; códigos de acción
BUFFERPLUS     EQU   2

offsets_ints   DW    2         ; número de vectores interceptados
               DB    2Fh       ; tabla de offsets de los vectores
               DW    ges_int2F ; de interrupción interceptados
               DB    40h
               DW    ges_int40
               DB    13h       ; INT 13h podría usarse
               DW    ges_int13 

accion         DW    0
error          DW    0
idioma_sp      DB    5Ah       ; ni en ON ni en OFF al principio

               ; --- Código telefónico de países de
               ;     habla hispana (mucha o poca).

paises_sp      DW    54                ; Argentina
               DW    591               ; Bolivia
               DW    57                ; Colombia
               DW    506               ; Costa Rica
               DW    56                ; Chile
               DW    593               ; Ecuador
               DW    503               ; El Salvador
               DW    34                ; España
               DW    63                ; Filipinas
               DW    502               ; Guatemala
               DW    504               ; Honduras
               DW    212               ; Marruecos
               DW    52                ; México
               DW    505               ; Nicaragua
               DW    507               ; Panamá
               DW    595               ; Paraguay
               DW    51                ; Perú
               DW    80                ; Puerto Rico
               DW    508               ; República Dominicana
               DW    598               ; Uruguay
               DW    58                ; Venezuela
               DW    3                 ; genérico latinoamérica
numpaises_sp   EQU   ($-OFFSET paises_sp)/2

; ------------ Texto.

instalado_txt  DB    13,10,"2M-ABIOS 1.1 instalado en ",255
               DB    13,10,"2M-ABIOS 1.1 installed on ",0

a_txt          DB    "A:",0
b_txt          DB    " y B:",255," and B:",0
ptr_txt_tipos  DW    d360, d1200, d720, d1440, d2880
d360           DB    "360K",0
d1200          DB    "1.2M",0
d720           DB    "720K",0
d1440          DB    "1.44M",0
d2880          DB    "2.88M",0
i40_txt        DB    "  [INT 40h]",13,10,0
i13_txt        DB    "  [INT 13h]",13,10,0

no_inst_txt    DB    13,10,"2M-ABIOS 1.1 *NO* instalado.",13,10,255
               DB    13,10,"2M-ABIOS 1.1 *NOT* installed.",13,10,0

mal_cpu_txt    DB    "  + Error: necesario equipo AT ó superior. Utilice 2M-XBIOS en este equipo.",13,10,255
               DB    "  + Error: needed AT or upper system. Try 2M-XBIOS on this system.",13,10,0

hay2m_txt      DB    "  + Error: 2M-ABIOS debe instalarse *ANTES* de 2M (y nunca en SuperBOOT).",13,10,255
               DB    "  + Error: 2M-ABIOS must be installed *BEFORE* 2M (and never in SuperBOOT).",13,10,0

null_drv_txt   DB    "  + Utilice los parámetros para indicar expresamente el tipo de las unidades.",13,10,255
               DB    "  + Please use the switches to set the correct diskette drives type.",13,10,0

err_syntax_txt DB    "  + Error de sintaxis: ejecútelo desde el símbolo DOS para obtener ayuda.",13,10,255
               DB    "  + Syntax error: execute from DOS command line to obtain help.",13,10,0

err_mx64full   DB    "  + Error: Ya hay 64 programas residentes con la misma técnica.",13,10,7,255
               DB    "  + Error: There are already 64 TSR with the same technique.",13,10,7,0

dma_cross_txt  DB    "    - Nota: El buffer de E/S cruzaba una frontera de DMA y fue ampliado.",13,10
               DB    "            Cambie la ubicación en memoria si desea ahorrar unos bytes.",13,10,255
               DB    "    - Advice: I/O buffer has been extended because cross a DMA frontier.",13,10
               DB    "              Modify the memory location if want to save a little of memory.",13,10,0

info_txt       LABEL BYTE
               DB    13,10
               DB    "          2M-ABIOS 1.1 - Control BIOS de disco flexible actualizado.",13,10
               DB    "       (C) Abril 1994 Ciriaco García de Celis - Email: ciri@gui.uva.es",13,10,10
               DB    "               Sintaxis:  DEVICE=2M-ABIOS.EXE [A:tipo [B:tipo]]",13,10,10
               DB    "    Algunos ordenadores poseen una BIOS antigua o con un diseño propio poco",13,10
               DB    "  compatible en el control de disco. En estas máquinas 2M y otros programas",13,10
               DB    "  de acceso a bajo nivel pueden fallar.  En dichos casos, conviene instalar",13,10
               DB    "  esta utilidad antes que 2M,  y en general que cualquier otro software que",13,10
               DB    "  acceda al subsistema de disco. Este programa es sólo para máquinas AT.",13,10,10
               DB    "    2M-ABIOS  actualiza el soporte de disco flexible a la última tecnología",13,10
               DB    "  de las BIOS AMI de 1993.  Si con 2M-ABIOS instalado 2M no opera de manera",13,10
               DB    "  totalmente correcta y en su máquina no está instalado algún otro software",13,10
               DB    "  de disco incompatible con 2M, entonces su ordenador no es 100% compatible",13,10
               DB    "  hardware con el estándar;  esto es particularmente cierto si con 2M-ABIOS",13,10
               DB    "  instalado no se reconocen siquiera los discos estándar del DOS.",13,10,10
               DB    "    Este programa ocupa 3.4-4.2 Kb de RAM, y contiene una emulación al 100%",13,10
               DB    "  del eficaz código de control de disco de las BIOS AMI,  relevando así por",13,10
               DB    "  completo de esta tarea a la BIOS del sistema.  Generalmente no hará falta",13,10
               DB    "  indicar el tipo (0:no hay, 1:360K, 2:1.2M, 3:720K, 4:1.44M, 5:2.88M). AMI",13,10
               DB    "  es marca registrada de American Megatrends Inc.",13,10
               DB    255
               DB    13,10,10
               DB    "         2M-ABIOS 1.1 - Floppy disk BIOS management upgrade utility.",13,10
               DB    "       (C) April 1994 Ciriaco García de Celis - Email: ciri@gui.uva.es",13,10,10
               DB    "               Syntax:  DEVICE=2M-ABIOS.EXE [A:type [B:type]]",13,10,10
               DB    "    Some computers have an old BIOS or a BIOS built with a peculiar design,",13,10
               DB    "  few compatible in disk operation. In those systems 2M and other low-level",13,10
               DB    "  software can fails. In this cases you must install 2M-ABIOS before 2M and",13,10
               DB    "  before any other software which takes access to disk subsystem.  Use this",13,10
               DB    "  program only in AT computer systems.",13,10,10
               DB    "    2M-ABIOS upgrades floppy-disk support to last AMI BIOS 1993 technology.",13,10
               DB    "  If 2M-ABIOS is installed,  and 2M continues not working full correctly on",13,10
               DB    "  your computer, and is not installed in the system any other disk software",13,10
               DB    "  incompatible with 2M,  this probably means that your computer is not 100%",13,10
               DB    "  hardware compatible with the standard AT;  this is specially true if only",13,10
               DB    "  2M-ABIOS is installed and DOS standard diskettes don't work.",13,10,10
               DB    "    This program takes 3.4-4.2 Kb of RAM,  and provides a full emulation of",13,10
               DB    "  the effective disk control management of AMI BIOS,  absolutely overriding",13,10
               DB    "  your native BIOS in this job. Usually you do not need to select the drive",13,10
               DB    "  type (0:not present, 1:360K, 2:1.2M, 3:720K, 4:1.44M, 5:2.88M).  AMI is a",13,10
               DB    "  registered trademark of American Megatrends Inc.",13,10
               DB    0

buffer_aux     DB    64 DUP (0)   ; buffer para alguna función del DOS

_PRINCIPAL     ENDS

_PILA          SEGMENT STACK 'STACK'
               DB    1024 DUP (?)      ; 1 Kb de pila es suficiente
_PILA          ENDS

               END   inicio
[ RETURN TO DIRECTORY ]