Metropoli BBS
VIEWER: 2msys.asm MODE: TEXT (CP437)
;┌───────────────────────────────────────────────────────────────────┐
;│                                                                   │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄  ▒▒▒▒▄      ▒▒▒▒▄                                │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█  ▒▒▒▒▒▄    ▒▒▒▒▒█                                │
;│  ▀▀▀▀▀▀▀▀▀▀▒▒▒▒█  ▒▒▒▒▒▒▄  ▒▒▒▒▒▒█                                │
;│            ▒▒▒▒█  ▒▒▒▒▒▒▒▄▒▒▒▒▒▒▒█                                │
;│            ▒▒▒▒█  ▒▒▒▒█▒▒▒▒▒█▒▒▒▒█                                │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█  ▒▒▒▒█ ▒▒▒█▀▒▒▒▒█    ▒▒▒▒▒▒▒▒▒▒▄     ▒▒▒▒▒▒▒▒▒▒▄ │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█  ▒▒▒▒█  ▒█▀ ▒▒▒▒█     ▀▀▀▀▀▀▀▒▒█     ▒▒█▀▀▀▀▀▒▒█ │
;│ ▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀  ▒▒▒▒█   ▀  ▒▒▒▒█            ▒▒█     ▒▒█     ▒▒█ │
;│ ▒▒▒▒█             ▒▒▒▒█      ▒▒▒▒█    ▒▒▒▒▒▒▒▒▒▒█     ▒▒█ ▒▒▄ ▒▒█ │
;│ ▒▒▒▒█             ▒▒▒▒█      ▒▒▒▒█    ▒▒█▀▀▀▀▀▀▀▀     ▒▒█  ▀▀ ▒▒█ │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄  ▒▒▒▒█      ▒▒▒▒█    ▒▒█             ▒▒█     ▒▒█ │
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█  ▒▒▒▒█      ▒▒▒▒█    ▒▒▒▒▒▒▒▒▒▒▄ ▒▒▄ ▒▒▒▒▒▒▒▒▒▒█ │
;│  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀   ▀▀▀▀       ▀▀▀▀     ▀▀▀▀▀▀▀▀▀▀  ▀▀  ▀▀▀▀▀▀▀▀▀▀ │
;│                                                                   │
;│         2M 2.0  -  (C) Abril 1994 Ciriaco García de Celis.        │
;│                                                                   │
;│      SOPORTE PARA DISQUETES CON MAYOR CAPACIDAD DE LA NORMAL      │
;│                    CREADOS POR LA UTILIDAD 2MF                    │
;│                                                                   │
;│       * * *   Versión instalable desde el CONFIG.SYS  * * *       │
;│                                                                   │
;│ - Sólo para AT y superiores con controladora y unidades de alta.  │
;│ - Programación directa del controlador de disquetes y del DMA.    │
;│ - Soporte para sectores de más de 512 bytes (economizar GAPs).    │
;│ - Mezcla de sectores de distinto tamaño en la misma pista.        │
;│ - Ruptura del límite habitual de 80 pistas (incluido 360K).       │
;│ - Emulación de sectores de 512 bytes en INT 13h.                  │
;│ - Función 5 (INT 13h) reforzada para formatear los nuevos discos. │
;│ - El soporte residente opera eficazmente bajo DOS y WINDOWS 3.X   │
;│                                                                   │
;│   Emplear TASM /m5, TLINK y EXE2BIN para obtener un fichero SYS   │
;│                                                                   │
;└───────────────────────────────────────────────────────────────────┘

               .286                    ; versión para AT o superior

; ------------ 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

DELAY          MACRO                   ; estados de espera
                 JMP SHORT $+2         ; para AT obsoleto
                 JMP SHORT $+2
               ENDM

PMICRO         MACRO                   ; retardo de aprox. 15,09 µs
               LOCAL pmicro_iter       ; (exactamente 18/1193180 sg.)
pmicro_iter:   DELAY
               IN    AL,61h            ; Esta macro puede ejecutarse
               AND   AL,10h            ; repetitivamente (se apoya en
               CMP   AL,AH             ; AX) para hacer retardos a
               JE    pmicro_iter       ; través de la temporización
               MOV   AH,AL             ; del refresco de la memoria
               ENDM                    ; dinámica de los AT.

; ------------ Estructura de datos con información para cada unidad.

info_drv       STRUC
maxs           EQU   13           ; máximo 13 sectores físicos/pista
tipo_drv       DB    ?            ; tipo de la disquetera (0 = no hay)
control2m_flag DB    OFF          ; a ON si 2M controla la unidad
cambio         DB    ON           ; a ON indica cambio de soporte
version_fmt    DB    ?            ; versión del formato de disco 2M
multi_io       DB    ?            ; a 0 si posible acceso multi-sector
chk            DB    ?            ; a 0 si checksum del sector 0 Ok
vunidad        EQU   THIS WORD
vunidad0       DB    ?            ; velocidad pista 0
vunidadx       DB    ?            ; velocidad demás pistas
gap            DB    ?            ; GAP entre sectores (leer/escribir)
sectpista      DB    ?            ; sectores lógicos por pista
tabla_tsect    DB    maxs DUP (?) ; tamaños de sectores 1, 2, ..., N
tam_fat        DB    ?            ; sectores/FAT en la unidad
               ENDS

; ------------ Programa.

_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$     "   ; 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    02h ; 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:2.0",0

               DB    2  ; número de vectores de interrupción usados
tabla_vectores EQU   $
vieja_i13      DB    13h           ; INT 13h
ant_int13      LABEL DWORD         ; dirección original
ant_int13_off  DW    0
ant_int13_seg  DW    0
               DB    2Fh           ; INT 2Fh
ant_int2F      LABEL DWORD         ; dirección original
ant_int2F_off  DW    0
ant_int2F_seg  DW    0

; ------------ Variables del programa.

info_ptr       DW    info_A       ; punteros a datos de las unidades
               DW    info_B
id_sistema     DB    "2M-STV"     ; identificación de disco 2M
unidad         DB    ?            ; unidad física de disco en curso
numsect        DW    ?            ; sectores a transferir
sectini        DW    ?            ; primer sector DOS a transferir
cilindro       DB    ?            ; cilindro del disco a acceder
cabezal        DB    ?            ; cabezal a emplear
sector         DB    ?            ; número de sector físico
sector_ini     DB    ?            ; número de sector físico inicial
sector_fin     DB    ?            ; número de sector físico final
seccion        DB    ?            ; parte del sector físico en curso
secciones      DB    ?            ; sectores lógicos a transferir
tsector        DB    ?            ; LOG2 (tamaño de sector) - 7
buffer         DW    buffer_io    ; puntero al buffer intermedio
buf_unidad     DB    ?            ; unidad del sector en el buffer
buf_cilcab     DW    ?            ; cilindro/cabezal de sector buffer
buf_sector     DB    ?            ; número de sector en el buffer
status         DB    ?            ; resultado de los accesos a disco
fdc_result     DB    7 DUP (?)    ; bytes de resultados del FDC
orden          DB    ?            ; operación F_READ/F_WRITE/F_VERIFY
tab_ordenes    DB    F_READ
               DB    F_WRITE
               DB    F_VERIFY     ; órdenes 2, 3 y 4

info_A         info_drv <>        ; datos de A:
info_B         info_drv <>        ; datos de B:

; ***************************************
; *                                     *
; *   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

; ------------ Nueva rutina de gestión de INT 13h. Llama a la INT 13h
;              original o a una nueva rutina de control para la
;              lectura (AH=2), escritura (AH=3) y verificación (AH=4)
;              según el tipo de disco introducido. Ante una función de
;              formateo (AH=5) se entrega el control a la INT 13h
;              original. Se detecta un posible cambio de disco y se
;              retorna en ese caso con el correspondiente error.

ges_int13      PROC  FAR
               STI
               CLD
               PUSHF
               CMP   DL,2
               JAE   ges13bios         ; no es disquetera A: ó B:
               PUSH  SI
               CALL  set_SI_drv
               CMP   CS:[SI].tipo_drv,2  ; ¿unidad 1.2M?
               JE    ges_2m
               CMP   CS:[SI].tipo_drv,4  ; ¿unidad 1.44/2.88M?
ges_2m:        POP   SI
               JC    ges13bios         ; no es unidad de alta densidad
               CMP   AH,2
               JB    ges13bios         ; no Read/Write/Verify/Format
               CMP   AH,5
               JA    ges13bios         ; no Read/Write/Verify/Format
               CALL  detecta_cambio    ; ¿cambio de disco?
               JNC   sin_cambio
               POPF
               STC                     ; hubo cambio:
               MOV   AX,600h
               RET   2                 ; retornar con error
sin_cambio:    CMP   AH,5
               JNE   dilucida          ; no es orden de formateo
               CALL  leer_lin_camb
               JNZ   format_bios       ; no hay disquete en la unidad
               CMP   AL,7Fh
               JNE   format_bios       ; no es orden formateo de 2M
               CMP   SI,"2M"
               JE    format_2m         ; es orden de formateo de 2M
format_bios:   CLC
               CALL  set_flag_STV      ; CF = 0 -> indicar no 2M
dilucida:      PUSH  SI
               CALL  set_SI_drv        ; SI -> variables de la unidad
               CMP   CS:[SI].control2m_flag,OFF
               POP   SI
               JE    ges13bios         ; la unidad la controla la BIOS
               POPF
               CALL  control2m         ; la controla 2M
               RET   2
ges13bios:     POPF
               JMP   CS:ant_int13      ; saltar al gestor de INT 13h

               ; --- Función de formateo implementada por 2M. En los
               ;     disquetes creados con /M todas las pistas salvo
               ;     la 0 deberían ser formateadas invocando INT 13h
               ;     de manera directa (con CALL) para evitar que se
               ;     ejecute cierto código de WINDOWS en el modo
               ;     protegido que provoca errores al formatear. Antes
               ;     de formatear la primera pista física del disco se
               ;     invoca la función de formateo de la INT 13h
               ;     original (con un error para provocar un rápido
               ;     retorno) con objeto de informar al DOS y a todos
               ;     los TSR previos del cambio de soporte.

format_2m:     POPF
               PUSH  DS                ; *
               PUSHA                   ; **
               PUSH  CS
               POP   DS
               MOV   unidad,DL
               CALL  set_SI_drv
               MOV   cilindro,CH
               MOV   cabezal,DH
               OR    CH,DH
               JNZ   format_trx        ; no es cilindro 0 y cabezal 0
               PUSHA
               MOV   AL,1              ; formatear un sector (AH=5)
               MOV   CH,0
               MOV   DH,2              ; en cabezal 2 (incorrecto)
               PUSHF
               CALL  ant_int13         ; avisar al DOS del nuevo disco
               CLD                     ; mantener DF=0
               STC
               CALL  reset_drv         ; asegurar aceleración motor
               POPA
               CALL  set_info          ; características nuevo soporte
format_trx:    CALL  genera_info       ; tabla de información formateo
               CALL  motor_ok          ; asegurar que está en marcha
               CALL  seek_drv
               CALL  formatea_pista
               PUSHF
               CLC
               CALL  motor_off_cnt     ; cuenta normal detención motor
               POPF
               CALL  set_err
               CALL  set_bios_err      ; no altera flags
               POPA                    ; **
               MOV   AH,status
               POP   DS                ; *
               RET   2
ges_int13      ENDP

; ------------ A la entrada en DL se indica la unidad y a la salida se
;              devuelve SI apuntando sus variables sin alterar flags.

set_SI_drv     PROC
               PUSHF
               PUSH  BX
               MOV   BL,DL
               MOV   BH,0
               SHL   BX,1
               MOV   SI,CS:[BX+OFFSET info_ptr]
               POP   BX
               POPF
               RET
set_SI_drv     ENDP

; ------------ Si CF=1, indicar disquete 2M presente. A la
;              entrada, DL indica la unidad de disco.

set_flag_STV   PROC
               PUSHA
               CALL  set_SI_drv
               MOV   AL,ON                       ; indicar 2M
               JC    tipo_stv_ok
               MOV   AL,OFF                      ; indicar no 2M
tipo_stv_ok:   MOV   CS:[SI].control2m_flag,AL
               POPA
               RET
set_flag_STV   ENDP

; ------------ Devolver ZF=1 si cilindro y cabezal 0.

pista0?        PROC
               PUSH  AX
               MOV   AL,cabezal
               OR    AL,cilindro
               POP   AX
               RET
pista0?        ENDP

; ------------ Devolver ZF=1 si la línea de cambio de disco está
;              inactiva. A la entrada, DL contiene la unidad. El
;              motor es puesto en marcha y, si no lo estaba ya, la
;              variable que indica lo que resta para detenerlo
;              es llevada a su valor normal, por lo que el disco no
;              tardará mucho en detenerse (incluso sin quizá haber
;              acelerado aún). En la práctica, invocando esta rutina
;              desde INT 13h nunca será necesario arrancar el motor
;              ya que el DOS ejecuta antes la función equivalente,
;              la 16h, que lo pone en marcha. Es simplemente una
;              medida de seguridad contra las BIOS «de marca».

leer_lin_camb  PROC
               PUSHA                   ; *
               PUSH  DS
               PUSH  40h
               POP   DS
               MOV   AL,1
               MOV   CL,DL
               SHL   AL,CL             ; bit de motor en 0..3
               TEST  DS:[3Fh],AL
               JNZ   rodando           ; el motor ya está girando
               CLC
               CALL  motor_off_cnt     ; cuenta normal detención motor
rodando:       MOV   AH,DL
               SHL   AH,4
               OR    AH,AL             ; AH = byte BIOS
               SHL   AL,4
               OR    AL,00001100b      ; modo DMA, no hacer reset
               OR    AL,DL             ; AL para reg. salida digital
               MOV   DX,3F2h
               CLI
               MOV   DS:[3Fh],AH       ; actualizar variable BIOS
               OUT   DX,AL             ; arrancado motor en la unidad
               ADD   DX,5
               DELAY
               IN    AL,DX             ; leer línea de cambio de disco
               STI
               TEST  AL,80h            ; ZF=0 -> cambio de disco
               POP   DS
               POPA                    ; *
               RET
leer_lin_camb  ENDP

; ------------ Determinar si ha habido cambio de disco y, en ese caso,
;              si el nuevo disquete es de tipo 2M o no. El cambio de
;              disco se detecta leyendo la línea de cambio de disco o
;              chequeando la variable que indica si ha habido cambio
;              o no (esta variable está a ON tras instalar 2M para
;              forzar la detección del tipo de disco introducido; se
;              pone en ON también si no se logra bajar la línea de
;              cambio de disco por si fuera un soporte raro y la BIOS
;              sí lo lograra -forzando así una detección posterior-).

detecta_cambio PROC
               PUSHA                   ; *
               CALL  set_SI_drv        ; SI -> variables de la unidad
               CMP   CS:[SI].cambio,ON ; ¿cambio de soporte?
               MOV   CS:[SI].cambio,OFF
               JE    hubo_cambio
               CALL  leer_lin_camb     ; leer línea de cambio de disco
               JNZ   hubo_cambio
               POPA
               CLC                     ; no hay cambio de disco
               RET
hubo_cambio:   CLC
               CALL  set_flag_STV      ; CF = 0 -> supuesto no 2M
               XPUSH <DS, ES>          ; **
               MOV   BX,90h
               ADD   BL,DL
               PUSH  40h
               POP   DS
               AND   BYTE PTR [BX],255-16 ; densidad no determinada
               XPUSH <CS, CS>
               XPOP  <DS, ES>
               MOV   unidad,DL
               STC                     ; asegurar motor en marcha
               CALL  reset_drv
               MOV   cilindro,1
               MOV   cabezal,0
               CALL  seek_drv          ; bajar línea cambio de disco
               DEC   cilindro
               CALL  seek_drv
               CLC
               CALL  motor_off_cnt     ; cuenta normal detención motor
               CALL  leer_lin_camb     ; ¿bajada línea cambio disco?
               JZ    disco_dentro      ; se pudo: hay disco dentro
               MOV   [SI].cambio,ON    ; futura detección tipo disco
               CLC                     ; NO indicar cambio de disco...
               JMP   fin_detecta       ; ...para pasar control a BIOS
disco_dentro:  PUSH  DS
               PUSH  40h
               POP   DS
               MOV   BYTE PTR DS:[41h],6  ; error 'media changed'
               POP   DS
               CMP   AH,5              ; ¿función de formateo?
               JE    fin_detecta_c     ; no perder el tiempo
               MOV   buf_unidad,-1     ; invalidar buffer
               MOV   [SI].gap,20       ; GAP provisional
               MOV   CX,3              ; 3 intentos
intenta_io0:   PUSH  CX
               CMP   CX,2              ; CF=1 la 3ª vez (a 0 si CX<>1)
               CALL  reset_drv
               MOV   [SI].vunidad0,0   ; empezar con 500 Kbit/seg.
intenta_io:    MOV   cilindro,0
               MOV   cabezal,0
               MOV   sector,1          ; sector de arranque
               MOV   seccion,0
               MOV   secciones,1
               MOV   orden,F_READ
               MOV   DI,buffer
               CALL  direct_acceso
               JNE   otra_densidad     ; es otra densidad de disco
               POP   CX
               MOV   BX,buffer
               CALL  set_info          ; características nuevo soporte
               CLC
               JMP   fin_detecta_c     ; indicar cambio de disco
otra_densidad: MOV   AL,[SI].vunidad0
               INC   AX                ; próxima velocidad
               CMP   AL,3
               JA    otro_intento
               MOV   [SI].vunidad0,AL
               JMP   intenta_io        ; probar otra velocidad
otro_intento:  MOV   [SI].vunidad0,0
               POP   CX
               LOOP  intenta_io0       ; reintento
fin_detecta_c: STC                     ; indicar cambio de disco
fin_detecta:   XPOP  <ES, DS>          ; **
               POPA                    ; *
               RET
detecta_cambio ENDP

; ------------ Anotar la información del disquete si es de tipo 2M.
;              A la entrada, DS:SI apunta a las variables de la unidad
;              y ES:BX al sector de arranque del disco. Se actualiza
;              también la variable BIOS de tipo de densidad (la BIOS
;              no se da cuenta del cambio de disco y conviene ayudar).

set_info       PROC
               PUSHA
               CALL  calc_chk
               JC    set_info_exit     ; no es disco 2M
               MOV   [SI].chk,AL         ; anotar checksum
               MOV   [SI].version_fmt,CL ; y versión del formato
               MOV   DL,unidad
               STC
               CALL  set_flag_STV      ; CF = 1 -> indicar disco 2M
               MOV   AL,ES:[BX+22]     ; tamaño de FAT
               MOV   [SI].tam_fat,AL
               MOV   CL,ES:[BX+65]     ; CL a 0 si acceso multi-sector
               MOV   [SI].multi_io,CL
               MOV   AX,ES:[BX+66]
               MOV   [SI].vunidad,AX   ; velocidad pista 0 / demás
               MOV   AL,ES:[BX+24]
               MOV   [SI].sectpista,AL ; sectores/pista
               MOV   DI,ES:[BX+72]
               MOV   AL,ES:[BX+DI+1]   ; GAP de formateo
               MOV   AH,AL
               AND   CL,CL             ; CL a 0 si acceso multi-sector
               JZ    gap_rw_ok         ; GAP R/W para /F
               ADD   AH,190
               MOV   AL,11
               MUL   AH                ; AX = (190+GAP)*11
               SUB   AX,2048+62
gap_rw_ok:     SHR   AL,1              ; GAP R/W para /M
               MOV   [SI].gap,AL
               MOV   CX,maxs
               MOV   DI,ES:[BX+74]
               ADD   DI,BX
               LEA   BX,[SI].tabla_tsect
genera_ts:     MOV   AL,ES:[DI]
               MOV   [BX],AL
               INC   BX
               INC   DI
               LOOP  genera_ts         ; información estructura pistas
set_info_exit: MOV   AL,[SI].vunidad0
               SHL   AL,6              ; velocidad en bits 7:6
               OR    AL,00010111b      ; establecido otro medio físico
               CMP   [SI].tipo_drv,2
               JA    modo_ok           ; es unidad de 3½
               AND   AL,11111000b
               OR    AL,00000101b      ; 1.2 en 1.2
               TEST  AL,01000000b
               JZ    modo_ok
               XOR   AL,00100001b      ; 360 en 1.2 y seek * 2
modo_ok:       PUSH  DS
               MOV   BX,90h
               ADD   BL,unidad
               PUSH  40h
               POP   DS
               AND   BYTE PTR DS:[BX],8  ; respetar bit de 2.88M
               OR    DS:[BX],AL        ; actualizar variable BIOS
               POP   DS                ; con el tipo de densidad
               POPA
               RET
set_info       ENDP

; ------------ Calcular el checksum de la zona vital del sector de
;              arranque. A la entrada, ES:BX -> sector de arranque.
;              A la salida, CF=1 si el disco no es 2M; de otro modo
;              checksum en AL y versión del formato de disco en CL.

calc_chk       PROC
               XPUSH <SI, DI>
               LEA   DI,[BX+3]         ; DI=BX+3
               LEA   SI,id_sistema
               MOV   CX,6
               REP   CMPSB             ; comparar identificación
               STC
               JNE   chk_ret           ; el disco no es 2M
               XOR   AX,AX
               MOV   CL,ES:[BX+64]     ; versión del formateador
               CMP   CL,6
               JB    chk_ok            ; no usaba este checksum
               MOV   DI,ES:[BX+68]
chk_sum:       DEC   DI
               ADD   AL,ES:[BX+DI]
               CMP   DI,63
               JA    chk_sum
chk_ok:        CLC
chk_ret:       XPOP  <DI, SI>
               RET
calc_chk       ENDP

; ------------ Determinar el tipo de error producido en el acceso.

set_err        PROC
               PUSH  AX
               JNC   err_ret           ; no hay error
               CMP   status,0          ; ¿'status' ya asignado?
               JNE   err_retc          ; no cambiarlo si es así
               MOV   AL,BYTE PTR fdc_result+1
               SHL   AL,1
               MOV   AH,4              ; 'sector not found'
               JC    err_acc_ok
               SHL   AL,2
               MOV   AH,10h            ; 'bad CRC'
               JC    err_acc_ok
               SHL   AL,1
               MOV   AH,8              ; 'DMA overrun'
               JC    err_acc_ok
               SHL   AL,2
               MOV   AH,4              ; 'sector not found'
               JC    err_acc_ok
               SHL   AL,1
               MOV   AH,3              ; 'write-protect error'
               JC    err_acc_ok
               SHL   AL,1
               MOV   AH,2              ; 'address mark not found'
               JC    err_acc_ok
               MOV   AH,20h            ; 'bad NEC'
err_acc_ok:    OR    status,AH
err_retc:      STC                     ; condición de error
err_ret:       POP   AX
               RET
set_err        ENDP

; ------------ Actualizar variables de error de la BIOS.

set_bios_err   PROC
               PUSHF                   ; *
               PUSHA                   ; **
               PUSH  ES                ; ***
               PUSH  40h
               POP   ES
               MOV   DI,41h            ; bytes de resultados del 765
               LEA   SI,status         ; variable BIOS de status y 7
               MOV   CX,4              ; bytes: 4 palabras
               REP   MOVSW
               POP   ES                ; ***
               POPA                    ; **
               POPF                    ; *
               RET
set_bios_err   ENDP

; ------------ Realizar lecturas, escrituras y verificaciones: rutina
;              que sustituye el código de la BIOS para poder soportar
;              los formatos 2M. La operación puede quedar dividida en
;              tres fases: el fragmento anterior a la FAT2, la zona
;              correspondiente a la FAT2 (se ignora la escritura y se
;              simula su lectura leyendo la FAT1) y un último bloque
;              ubicado tras la FAT2. El sector de arranque es emulado
;              empleando el primer sector físico de la FAT2 (aunque en
;              los discos de versión de formato anterior a la 7 se usa
;              el sector de arranque verdadero -en lectura, sin dejar
;              escribir sobre él-). En cualquier caso, si el número de
;              cabezal tiene el bit 7 activo, se sobreentiende que el
;              programa que llama soporta disquetes 2M y no se emula
;              la FAT2 ni el sector de arranque, para permitirle
;              acceder al código SuperBOOT. Las coordenadas de la BIOS
;              se traducen a las unidades del DOS por mayor comodidad.

control2m      PROC
               PUSH  DS                ; *
               PUSHA                   ; **
               PUSH  CS
               POP   DS
               MOV   unidad,DL
               CALL  set_SI_drv        ; SI -> variables de la unidad
               CMP   [SI].chk,0
               JE    chk_valido        ; checksum correcto en sector 0
               MOV   status,40h        ; devolver 'Seek Error' al DOS
               JMP   exit_2m_ctrl
chk_valido:    PUSH  AX                ; ***
               MOV   AH,0
               MOV   numsect,AX        ; nº sectores
               MOV   AL,CH             ; cilindro
               SHL   AL,1
               MOV   DL,DH
               AND   DH,01111111b
               ADD   AL,DH             ; cabezal físico
               MUL   [SI].sectpista
               ADD   AL,CL             ; sector
               ADC   AH,0
               DEC   AX                ; AX = nº sector DOS
               MOV   sectini,AX
               MOV   DI,BX             ; ES:DI -> dirección
               POP   BX                ; ***
               MOV   BL,BH
               MOV   BH,0
               MOV   CL,[BX+OFFSET tab_ordenes-2]
               MOV   orden,CL
               SHL   DL,1
               JC    acceso_final      ; cabezal >= 128: no emular
               AND   AX,AX             ; ¿comienza en sector 0?
               JNZ   io_emula          ; no
               CMP   [SI].version_fmt,7
               JB    boot_real         ; no soportado BOOT virtual
               MOV   AL,[SI].tam_fat   ; AH = 0
               INC   AX
               MOV   CX,1              ; sector BOOT emulado en
               CALL  ejecuta_io        ; el primer sector FAT2
               JNE   fin_ctrl
boot_fin_op:   DEC   numsect
               INC   sectini
               MOV   AX,sectini
               JMP   io_emula
boot_real:     CMP   orden,F_WRITE
               JNE   io_emula
               ADD   DI,512
               JMP   boot_fin_op       ; impedir estropicio de BOOT
io_emula:      MOV   CL,[SI].tam_fat
               MOV   CH,0              ; CX = primer sector FAT2 - 1
               CMP   AX,CX
               JA    en_fat2?          ; ¿la operación afecta a FAT2?
               CALL  calc_iop          ; calcular sectores antes FAT2
               CALL  ejecuta_io        ; CX sectores desde AX
               JNE   fin_ctrl          ; error
               CMP   numsect,0
               JE    fin_ctrl          ; fin de la transferencia
en_fat2?:      MOV   AX,sectini
               MOV   CL,[SI].tam_fat
               MOV   CH,0
               SHL   CX,1              ; CX = último sector FAT2
               CMP   AX,CX
               JA    acceso_final      ; la operación es tras la FAT2
               CALL  calc_iop          ; sectores hasta fin de FAT2
               CMP   orden,F_WRITE
               JNE   emula_fat1
               SHL   CX,9              ; CX = CX * 512
               ADD   DI,CX             ; ES:DI actualizado
               JMP   acceso_final
emula_fat1:    MOV   DL,[SI].tam_fat
               MOV   DH,0
               SUB   AX,DX             ; leer de FAT1 y no de la FAT2
               CALL  ejecuta_io        ; CX sectores desde AX
               JNE   fin_ctrl          ; error
acceso_final:  CMP   numsect,0
               JE    fin_ctrl          ; fin de la transferencia
               MOV   AX,sectini
               MOV   CX,numsect
               CALL  ejecuta_io
fin_ctrl:      CLC
               CALL  motor_off_cnt     ; cuenta normal detención motor
               CALL  set_bios_err      ; actualizar variables BIOS
exit_2m_ctrl:  POPA                    ; **
               MOV   AH,status
               POP   DS                ; *
               AND   AH,AH
               JZ    st_ok             ; resultado correcto (CF=0)
               STC                     ; error
               MOV   AL,0              ; 0 sectores movidos
st_ok:         RET
calc_iop:      SUB   CX,AX
               INC   CX                ; CX sectores
               CMP   CX,numsect
               JBE   nsect_ok
               MOV   CX,numsect        ; sólo quedan CX
nsect_ok:      SUB   numsect,CX
               ADD   sectini,CX
               RET
control2m      ENDP

; ------------ A la entrada, AX indica el sector inicial (coordenadas
;              del DOS) y CX el número de sectores a procesar.
;              * Definiciones: «Sector físico» es un sector del disco
;              de 512, 1024 ó 2048 bytes (números de sector del 1 al N
;              en la pista). Este sector físico está dividido en
;              «secciones» de 512 bytes, constando por tanto de 1, 2 ó
;              4 secciones. «Sector virtual» es el número de sector
;              del programa que llama a INT 13h, comprendido entre 1 y
;              M. Esta estructura de N sectores por pista de distintos
;              tamaños, se verifica en todo el disco con excepción del
;              cabezal y cilindro 0 (con un formato más convencional
;              de sectores de 512 bytes numerados de 1 a J, aunque no
;              existen algunos de los intermedios que corresponden a
;              la segunda copia de la FAT).
;              * Primero se convierte el sector virtual (1..M) en su
;              correspondiente físico (1..J en la pista 0 y 1..N en
;              las demás), deduciendo qué porción de 512 bytes (o
;              sección) es afectada. Un sector virtual (512 bytes)
;              simulado suele ser parte de un sector físico de 2048
;              bytes en muchos casos. Si dicho sector físico ya había
;              sido leído al buffer en anteriores accesos, se extrae
;              la sección necesaria. Si no, se carga del disco y se
;              extrae dicho fragmento. El número de sectores virtuales
;              que se solicitan (=secciones) permite realizar un bucle
;              hasta completar la transferencia; el interleave 1:2 de
;              los sectores físicos en /M permite acceder sector a
;              sector sin pérdida de rendimiento. En el caso de la
;              escritura, se estudia primero si hay varios sectores
;              virtuales consecutivos que escribir, completando entre
;              todos un sector físico: en ese caso, se prepara el
;              mismo y se escribe sin más. En caso de que haya que
;              modificar sólo una única sección de un sector físico,
;              salvo si éste es de 512 bytes, no hay más remedio que
;              cargarlo al buffer (realizar una prelectura),
;              actualizar la sección correspondiente y volverlo a
;              escribir.
;              * En el formato /F se realiza una operación multisector
;              si es posible y sin emplear el buffer intermedio (si
;              bien podría ser preciso emplearlo con la primera y
;              última sección); en los dos formatos de disco se hace
;              la operación multisector en la primera pista. Las
;              operaciones multisector puede que sea preciso
;              dividirlas en tres fases: los sectores antes de una
;              frontera de DMA, el que la cruza (que es transferido
;              a través del buffer intermedio) y los que están detrás.

ejecuta_io     PROC
               MOV   BX,AX             ; AX = sector DOS inicial
               MOV   secciones,CL      ; CX sectores (CL realmente)
               DIV   [SI].sectpista
               INC   AH                ; numerado desde 1...
               MOV   sector,AH         ; ...el resto es el sector
               SHR   AL,1
               MOV   cilindro,AL       ; cilindro
               RCL   AL,1
               AND   AL,1
               MOV   cabezal,AL        ; cabezal
               MOV   AL,sector
               ADD   AL,secciones
               JC    no_cabe           ; sector+secciones > 255
               DEC   AX                ; DEC AX = DEC AL
               CMP   AL,[SI].sectpista
               JBE   si_cabe
no_cabe:       MOV   status,4          ; 'sector no encontrado'
               JMP   fin_io
si_cabe:       MOV   AL,AH             ; sector en AL
               CBW                     ; sección 0 (AH = 0)
               CALL  pista0?
               JZ    s_xx              ; sector físico en pista/cara 0
               LEA   BX,[SI].tabla_tsect-1
               DEC   AX                ; AH = 0
resta_secc:    INC   BX
               INC   AH
               MOV   CL,[BX]
               SUB   CL,2
               MOV   CH,1
               SHL   CH,CL
               SUB   AL,CH
               JNC   resta_secc        ; en las demás pistas
               ADD   AL,CH
               XCHG  AH,AL
s_xx:          MOV   sector,AL         ; sector lógico convertido a
               MOV   seccion,AH        ; sector y sección físicas
direct_acceso: CALL  motor_ok          ; asegurar que está en marcha
               MOV   AH,0
               MOV   sector_fin,AH     ; no acceder a más de 1 sector
               CALL  pista0?           ; (al menos de momento)
               JNZ   decide_multi      ; no es pista 0
               MOV   AL,secciones
               MOV   secciones,AH      ; las que restan (AH = 0)
               JMP   multi_proc
decide_multi:  CMP   [SI].multi_io,AH  ; AH = 0
               JNE   io_pasos          ; acceso sector a sector
               CMP   seccion,AH
               JE    multi_acc
               CALL  acceso_secc       ; no acceso a inicio sector
               JC    fin_io
multi_acc:     CMP   secciones,AH      ; AH = 0
               JE    fin_io
               CALL  num_secciones
               MOV   CL,AL
               MOV   AL,secciones      ; AH = 0
               DIV   CL
               AND   AL,AL
               JZ    io_pasos          ; no quedan sectores enteros
               MOV   secciones,AH      ; las que restan
multi_proc:    CALL  acceso_multi      ; de AL sectores
               JC    fin_io
io_pasos:      CMP   secciones,0
               JE    fin_io            ; no restan secciones finales
               CALL  acceso_secc
               JNC   io_pasos
fin_io:        CMP   status,0          ; ZF = 1 -> operación correcta
               RET

acceso_secc:   PUSH  AX
               CMP   orden,F_WRITE     ; acabar transferencia sector
               JE    escritura
               CMP   orden,F_VERIFY
               JE    verificacion
               CALL  leido?            ; realizar lectura...
               JNC   ya_leido          ; sector ya en el buffer
hay_que_leer:  CALL  acceso_sector     ; efectuar E/S
               JC    acc_ret           ; ha habido fallo
ya_leido:      CALL  trans_secc        ; buffer -> memoria
               JMP   acc_ret
escritura:     CMP   seccion,0
               JNE   prelectura        ; sólo parte del sector cambia
               CALL  num_secciones
               CMP   secciones,AL
               JAE   escribir          ; Todo el sector físico cambia
prelectura:    CALL  leido?            ; Leer el sector físico para
               JNC   escribir          ; cambiar sólo una parte de él
               MOV   orden,F_READ      ; de momento leer...
               CALL  acceso_sector     ; efectuar E/S
               MOV   orden,F_WRITE     ; ... restaurar orden original
               JC    acc_ret           ; ha habido fallo
escribir:      CALL  trans_secc        ; memoria -> buffer
               CALL  acceso_sector     ; volcar buffer al disco
               JMP   acc_ret
verificacion:  PUSH  BX
               MOV   BL,seccion
               CALL  num_secciones
dec_sec_veri:  DEC   secciones
               JZ    verifica
               INC   BX
               CMP   BL,AL
               JB    dec_sec_veri
verifica:      POP   BX
               CALL  acceso_sector     ; leer para forzar verificación
acc_ret:       PUSHF
               INC   sector            ; preparado para otro sector
               MOV   seccion,0         ; desde su primera sección
               POPF
               POP   AX
               RET

acceso_multi:  PUSH  AX                ; AL = sectores a transferir
               MOV   BX,ES             ;      desde 'sector' teniendo
               SHL   BX,4              ;      cuidado con el DMA
               ADD   BX,DI
               NEG   BX                ; BX = bytes hasta frontera DMA
               CALL  num_secciones
               MOV   CH,AL             ; AL secciones de 512 bytes
               MOV   CL,0
               SHL   CX,1              ; CX = bytes por sector
               XCHG  AX,BX             ; BL = secciones por sector
               XOR   DX,DX
               DIV   CX
               MOV   CL,AL             ; CL = sectores que caben
               POP   AX                ; AL = sectores a transferir
               CMP   AL,CL
               JA    acc_mult2         ; no hay problemas con el DMA
acc_mult1:     MOV   CL,AL
acc_mult2:     AND   CL,CL
               JZ    acc_mult3         ; primer sector problemático
               MOV   AH,sector
               MOV   sector_ini,AH
               ADD   AH,CL
               DEC   AH
               MOV   sector_fin,AH
               INC   AH
               SUB   AL,CL
               CALL  acceso_sector     ; sectores no problemáticos
               MOV   sector,AH
               JC    acc_mult_fin
acc_mult3:     AND   AL,AL             ; ahora el sector problemático
               JZ    acc_mult_fin
               ADD   secciones,BL      ; compensar futuro decremento
               CALL  acceso_secc       ; a través del buffer auxiliar
               JC    acc_mult_fin
               DEC   AL
               JMP   acc_mult1         ; sectores que restan
acc_mult_fin:  RET
ejecuta_io     ENDP

; ------------ Mover secciones desde el buffer hacia la memoria (con
;              orden F_READ) después de la lectura o de la memoria al
;              buffer (orden F_WRITE) antes de la escritura. En la
;              verificación (orden F_VERIFY) no se mueve nada porque
;              esta subrutina no es invocada.

trans_secc     PROC
               XPUSH <AX, BX, CX, SI>  ; *
               MOV   BL,seccion        ; desde esta sección
               CALL  num_secciones     ; nº secciones del sector
otra_secci:    PUSH  BX
               SHL   BX,9              ; sección * 512
               ADD   BX,buffer         ; dirección
               MOV   SI,BX
               MOV   CX,256            ; tamaño sección (palabras)
               CALL  swap_reg          ; ¿intercambiar origen-destino?
               REP   MOVSW             ; copiar 512 bytes
               CALL  swap_reg          ; ¿intercambiar origen-destino?
               POP   BX
               DEC   secciones         ; una menos
               JZ    fin_secc
               INC   BX                ; otra sección del sector
               CMP   BL,AL             ; ¿sector agotado?
               JB    otra_secci        ; aún no
fin_secc:      XPOP  <SI, CX, BX, AX>  ; *
               RET
swap_reg:      CMP   CS:orden,F_WRITE
               JE    interc
               CLC
               RET
interc:        XCHG  SI,DI             ; en escritura, invertir el
               XPUSH <ES, DS>          ; sentido de la operación
               XPOP  <ES, DS>
               RET
trans_secc     ENDP

; ------------ Comprobar si el sector ya está en el buffer.

leido?         PROC
               PUSH  AX
               MOV   AL,buf_unidad
               CMP   AL,unidad
               JNE   no_leido          ; es en otra unidad
               MOV   AL,cilindro
               MOV   AH,cabezal
               CMP   AX,buf_cilcab
               JNE   no_leido          ; es en otro cilindro/cabezal
               MOV   AL,buf_sector
               CMP   AL,sector
               JNE   no_leido          ; es otro sector
               POP   AX
               RET                     ; está en el buffer
no_leido:      STC
               POP   AX
               RET                     ; sector no leído
leido?         ENDP

; ------------ Leer o escribir sector(es). Se selecciona el tamaño de
;              sector correcto antes de llamar a sector_io.  En esta
;              rutina se actualiza la variable «status» en función de
;              los posibles errores de acceso.  Si sector_fin es
;              distinto de 0 se accede a los sectores indicados, si es
;              0 se accede sólo al sector «sector» a través del buffer
;              intermedio y al final se anota el sector cargado ó
;              escrito para evitar futuras lecturas innecesarias, a
;              modo de mini-caché que dispara la velocidad de acceso a
;              sectores lógicos consecutivos.

acceso_sector  PROC
               XPUSH <AX, BX>
               CALL  seek_drv          ; posicionar el cabezal
               JNC   en_pista
               CMP   status,0          ; ¿error ya determinado?
               JNE   acc_fin_err
               OR    status,40h        ; no: pues 'seek error'
acc_fin_err:   STC
               JMP   acceso_fin
en_pista:      CALL  pista0?
               MOV   AL,2
               JZ    tam_acc_ok        ; sectores 512 en cil./cab. 0
               LEA   BX,[SI].tabla_tsect
               ADD   BL,sector
               ADC   BH,0
               MOV   AL,[BX-1]
tam_acc_ok:    MOV   tsector,AL
               CMP   sector_fin,0      ; ¿usar buffer intermedio?
               JE    acceso_buffer
               CALL  sector_io
               MOV   sector_fin,0      ; no acceder a más de 1 sector
               PUSHF                   ; **1
               JMP   acceso_rep        ; en el futuro (por defecto)
acceso_buffer: XPUSH <ES, DI>
               PUSH  CS
               POP   ES
               MOV   DI,buffer         ; acceso con buffer auxiliar
               MOV   AL,sector         ; mismo sector inicial/final
               MOV   sector_ini,AL
               MOV   sector_fin,AL
               CALL  sector_io
               MOV   sector_fin,0
               XPOP  <DI, ES>
               PUSHF                   ; **2
               MOV   AL,-1             ; invalidar contenido buffer
               JC    acceso_anota      ; si hay error
               CMP   orden,F_VERIFY
               JE    acceso_rep        ; nada leído físicamente
               MOV   AL,unidad
acceso_anota:  MOV   buf_unidad,AL
               MOV   AL,cilindro
               MOV   AH,cabezal
               MOV   buf_cilcab,AX
               MOV   AL,sector
               MOV   buf_sector,AL     ; anotado el sector en buffer
acceso_rep:    POPF                    ; ** mucho cuidado con la pila
               CALL  set_err           ; ajustar variable «status»
acceso_fin:    XPOP  <BX, AX>
               RET
acceso_sector  ENDP

; ------------ Devolver el número de secciones del sector en curso.

num_secciones  PROC
               CALL  pista0?
               MOV   AL,1
               JZ    num_secc_ok       ; sectores 512 en cil./cab. 0
               XPUSH <BX, CX>
               LEA   BX,[SI].tabla_tsect
               ADD   BL,sector
               ADC   BH,0
               MOV   CL,[BX-1]
               SUB   CL,2
               MOV   AL,1
               SHL   AL,CL
               XPOP  <CX, BX>
num_secc_ok:   RET                     ; resultado en AL
num_secciones  ENDP

; ------------ Asegurar que el motor está en marcha.

motor_ok       PROC
               PUSHA                   ; *
               PUSH  DS                ; **
               MOV   BX,40h
               PUSH  BX
               POP   DS
               MOV   CH,255-18         ; CH = 255 - 1 segundo
               CLI
               MOV   CL,CS:unidad
               MOV   AL,1
               SHL   AL,CL
               TEST  [BX-1],AL         ; ¿motor en marcha?
               JZ    arrancarlo        ; arrancarlo
               CMP   [BX],CH           ; Si encendido y acelerado...
               JBE   ok_motor          ; ...seguir
arrancarlo:    OR    [BX-1],AL         ; arrancar motor
               AND   BYTE PTR [BX-1],0CFh  ; borrar número unidad
               MOV   AL,CL
               SHL   AL,4              ; unidad << 4
               OR    [BX-1],AL         ; nuevo número de unidad
               MOV   BYTE PTR [BX],255 ; asegurar que no se pare
               STI
               MOV   DX,3F2h           ; registro de salida digital
               ADD   CL,4
               MOV   AL,1
               SHL   AL,CL             ; colocar bit del motor
               OR    AL,CS:unidad      ; seleccionar unidad
               OR    AL,00001100b      ; modo DMA, no hacer reset
               OUT   DX,AL             ; poner en marcha el motor
               MOV   AX,90FDh
               CLC
               INT   15h               ; permitir multitarea
               JC    ok_motor          ; timeout
               MOV   AX,1000           ; 1 segundo aceleración
               CALL  retardo           ; esperar aceleración disco
ok_motor:      MOV   [BX],CH           ; cuenta máxima detención motor
               STI                     ; sin forzar futura aceleración
               POP   DS                ; **
               POPA                    ; *
               RET
motor_ok       ENDP

; ------------ Establecer modalidad de operación del controlador
;              y poner el motor en marcha. Si CF=1 se le da tiempo
;              además a la unidad para que acelere.

reset_drv      PROC
               PUSHA
               CALL  motor_off_cnt     ; cuenta detención motor
               MOV   CL,unidad
               MOV   AL,CL
               SHL   AL,2              ; unidad seleccionada
               OR    AL,1              ; bit de motor
               SHL   AL,CL             ; colocar dicho bit
               PUSH  DS                ; *
               PUSH  40h
               POP   DS
               CLI
               MOV   DS:[3Fh],AL
               AND   BYTE PTR DS:[3Eh],70h ; bit IRQ=0 y recalibrar
               POP   DS                ; *
               SHL   AL,4              ; bits motor en nibble alto
               OR    AL,CL             ; seleccionar unidad
               OR    AL,00001000b      ; interrupciones+DMA y reset
               MOV   DX,3F2h           ; registro de salida digital
               OUT   DX,AL             ; señal de reset
               CALL  fdc_respiro       ; tiempo reconocer reset en 486
               OR    AL,00000100b
               OUT   DX,AL             ; fin de señal de reset
               CALL  espera_int        ; rehabilitará interrupciones
               MOV   AL,8
               CALL  fdc_write         ; comando 'leer estado int...'
               CALL  fdc_read
               CALL  fdc_read
               CALL  envia_specify     ; comando 'specify' adecuado
               POPA
               RET
reset_drv      ENDP

; ------------ Enviar comando specify a la controladora. El step-rate
;              se selecciona según la densidad, para evitar un sonido
;              extraño al posicionar o recalibrar el cabezal.

envia_specify  PROC
               PUSH  AX
               PUSH  DS
               PUSH  40h
               POP   DS
               MOV   AH,DS:[8Bh]
               POP   DS
               MOV   AL,3              ; comando 'specify'
               CALL  fdc_write
               MOV   AL,0BFh           ; step rate para 500 kbps
               AND   AH,11000000b
               JZ    spec1_ok
               MOV   AL,0AFh           ; step rate para 1 Mbps
               CMP   AH,11000000b
               JE    spec1_ok
               MOV   AL,0DFh           ; step rate para 250/300 Kbps
spec1_ok:      CALL  fdc_write
               MOV   AL,2
               CALL  fdc_write         ; head load y modo DMA
               POP   AX
               RET
envia_specify  ENDP

; ------------ Recargar cuenta para la detención del motor. Si CF=1 al
;              entrar, se establece la mayor cuenta posible; en caso
;              contrario, se pone el valor normal de la tabla base.

motor_off_cnt  PROC
               PUSHA
               PUSH  DS
               MOV   AL,0FFh           ; valor máximo
               JC    motor_off_ok
               PUSH  0
               POP   DS
               LDS   BX,DWORD PTR DS:[1Eh*4] ; DS:BX -> INT 1Eh
               MOV   AL,[BX+2]               ; byte 2 tabla base disco
motor_off_ok:  PUSH  40h
               POP   DS
               MOV   BYTE PTR DS:[40h],AL  ; cuenta parada motor
               POP   DS
               POPA
               RET
motor_off_cnt  ENDP

; ------------ Llevar el cabezal a la pista indicada, recalibrando si
;              hubo un reset (se invocó la función 0 de la INT 13h o
;              se ejecutó reset_drv) antes de esta operación. Primero
;              se selecciona la velocidad de transferencia y se borra
;              el resultado de cualquier operación anterior, para que
;              todo quede listo para el próximo acceso a disco.

seek_drv       PROC
               PUSHA
               CALL  set_rate          ; velocidad / borrar resultados
               CALL  envia_specify     ; comando 'specify' adecuado
               MOV   AH,1
               MOV   CL,unidad
               SHL   AH,CL             ; AH = 1 (A:) ó 2 (B:)
               PUSH  DS
               PUSH  40h
               POP   DS
               TEST  AH,DS:[3Eh]
               POP   DS
               JNZ   do_seek           ; la unidad ya fue recalibrada
               CALL  recalibrar
               JC    fallo_seek        ; fallo al recalibrar
do_seek:       MOV   BX,94h
               ADD   BL,unidad
               MOV   AL,cilindro
               PUSH  DS                ; *
               PUSH  40h
               POP   DS
               OR    DS:[3Eh],AH       ; unidad ya recalibrada
               MOV   AH,DS:[41h]       ; código de error previo
               CMP   AL,[BX]
               MOV   [BX],AL
               POP   DS                ; *
               JNE   hacer_seek        ; seek necesario
               CMP   AH,40h            ; ¿error de seek previo?
               JNE   seek_ok           ; no, evitar seek innecesario
hacer_seek:    MOV   AL,0Fh
               CALL  fdc_write         ; comando 'seek'
               JC    fallo_seek
               MOV   AL,cabezal
               SHL   AL,2
               OR    AL,unidad
               CALL  fdc_write         ; enviar HD, US1, US0
               MOV   AL,cilindro
               CALL  fdc_write         ; enviar cilindro
               CALL  espera_int        ; esperar interrupción
               JC    fallo_seek
               MOV   AL,8
               CALL  fdc_write         ; comando 'leer estado int...'
               JC    fallo_seek
               CALL  fdc_read          ; leer registro de estado 0
               JC    fallo_seek
               MOV   AH,AL
               CALL  fdc_read          ; leer cilindro actual
               TEST  AH,11000000b      ; comprobar ST0
               JNZ   fallo_seek
               MOV   AL,15             ; estabilización para escritura
               CMP   orden,F_WRITE
               JE    rseek_ok
               MOV   AL,1              ; estabilización para lectura
rseek_ok:      CBW                     ; AH = 0
               CALL  retardo           ; esperar asentamiento cabezal
seek_ok:       POPA
               CLC                     ; retornar con éxito
               RET
fallo_seek:    POPA
               STC                     ; retornar indicando fallo
               RET
seek_drv       ENDP

; ------------ Establecer velocidad de transferencia correcta si aún
;              no ha sido seleccionada y borrar el resultado de otra
;              operación previa.

set_rate       PROC
               PUSHA
               CALL  pista0?
               MOV   AX,[SI].vunidad   ; velocidad pista 0 / demás
               JZ    vel_ok
               MOV   AL,AH
vel_ok:        PUSH  DS                ; *
               PUSH  40h
               POP   DS
               MOV   AH,DS:[8Bh]
               SHR   AH,6              ; aislar bits de velocidad
               CMP   AL,AH
               JE    vel_set           ; velocidad ya seleccionada
               MOV   DX,3F7h
               OUT   DX,AL             ; seleccionarla
               SHL   AL,6
               AND   BYTE PTR DS:[8Bh],00111111b
               OR    DS:[8Bh],AL
vel_set:       POP   DS                ; *
               LEA   DI,status
               MOV   CX,8
borra_status:  MOV   [DI],CH           ; borrar información de estado
               INC   DI
               LOOP  borra_status
               POPA
               RET
set_rate       ENDP

; ------------ Recalibrar la unidad (si hay error se intenta otra vez
;              para el caso de que deba moverse más de 77 pistas).

recalibrar     PROC
               PUSHA
               MOV   BX,94h
               ADD   BL,unidad
               PUSH  DS                ; *
               PUSH  40h
               POP   DS
               MOV   [BX],BH           ; pista actual = 0
               POP   DS                ; *
               MOV   CX,2              ; dos veces como mucho
recalibra:     MOV   AL,7
               CALL  fdc_write         ; comando de 'recalibrado'
               JC    fallo_recal
               MOV   AL,cabezal
               SHL   AL,2
               OR    AL,unidad
               CALL  fdc_write         ; enviar HD, US1, US0
               JC    fallo_recal
               CALL  espera_int        ; esperar interrupción
               JC    fallo_recal
               MOV   AL,8
               CALL  fdc_write         ; comando 'leer estado int...'
               JC    fallo_recal
               CALL  fdc_read          ; leer registro de estado 0
               JC    fallo_recal
               MOV   AH,AL
               CALL  fdc_read          ; leer cilindro actual
               XOR   AH,00100000b      ; bajar bit de 'seek end'
               TEST  AH,11110000b      ; comprobar resultado y ST0
               JNZ   fallo_recal       ; sin 'seek end' o TRK0
               MOV   AX,1              ; pausa de 1 ms
               CALL  retardo
               JMP   recal_ret
fallo_recal:   LOOP  recalibra         ; reintentar comando
               STC                     ; condición de fallo
recal_ret:     POPA
               RET
recalibrar     ENDP

; ------------ Cargar o escribir sector(es) del disco en ES:DI,
;              actualizando la dirección en ES:DI pero sin alterar
;              ningún otro registro. Si hay error se devuelve CF=1 y
;              no se modifica ES:DI. A partir de fdc_result se dejan
;              los 7 bytes que devuelve el FDC al final del acceso.
;              En caso de verificación (F_VERIFY) se programa el DMA
;              para que no realice transferencia física (convenio de
;              las BIOS con fecha 15/11/85 y posterior).

sector_io      PROC
               XPUSH <AX, BX, CX, DX>
               MOV   CL,tsector
               MOV   CH,0
               STC
               RCL   CH,CL
               MOV   CL,0              ; nº de bytes por sector
               MOV   AL,sector_fin
               SUB   AL,sector_ini
               INC   AX
               CBW                     ; AX sectores (AH = 0)
               MUL   CX
               MOV   DX,AX             ; bytes totales
               MOV   CX,AX
               DEC   CX                ; bytes totales - 1
               MOV   AX,ES
               CALL  calc_dir_DMA      ; AX:DI -> base BX y página AH
               MOV   AL,orden          ; modo DMA necesario
               CALL  prepara_DMA
               CMP   AL,F_WRITE
               MOV   AL,11000101b      ; comando de escritura del FDC
               JE    orden_io_ok
               MOV   AL,11100110b      ; comando leer (verif.) del FDC
orden_io_ok:   CALL  fdc_write         ; comando leer/escribir del FDC
               JC    sector_io_ko
               MOV   AL,cabezal
               SHL   AL,2
               OR    AL,unidad
               CALL  fdc_write         ; byte 1 de la orden
               MOV   AL,cilindro
               CALL  fdc_write         ; enviar cilindro
               MOV   AL,cabezal
               CALL  fdc_write         ; enviar cabezal
               MOV   AL,sector_ini
               CALL  fdc_write         ; enviar nº sector
               MOV   AL,tsector
               CALL  fdc_write         ; longitud sector
               MOV   AL,sector_fin
               CALL  fdc_write         ; último sector
               MOV   AL,[SI].gap
               CALL  fdc_write         ; GAP de lectura/escritura
               MOV   AL,128
               CALL  fdc_write         ; tamaño sector si longitud=0
               CALL  espera_int
               PUSHF                   ; *
               LEA   BX,fdc_result
               MOV   CX,7
sect_io_res:   CALL  fdc_read          ; leyendo resultados
               MOV   [BX],AL
               INC   BX
               LOOP  sect_io_res
               POPF                    ; *
               JC    sector_io_ko
               TEST  fdc_result,11000000b
               JNZ   sector_io_ko
               ADD   DI,DX             ; actualizar dirección
               CLC                     ; Ok
               JMP   sector_io_fin
sector_io_ko:  STC                     ; indicar fallo
sector_io_fin: XPOP  <DX, CX, BX, AX>
               RET
sector_io      ENDP

; ------------ Devolver en AH la página de DMA y en BX la base. A la
;              entrada, AX:DI -> dirección de memoria y CX = bytes-1.
;              Se supone que el buffer no cruza una frontera de DMA.

calc_dir_DMA   PROC
               PUSH  DX
               MOV   BX,16
               MUL   BX
               ADD   AX,DI
               ADC   DX,0              ; DX:AX = dirección 20 bits
               MOV   BX,AX             ; base en BX
               MOV   AH,DL             ; página
               POP   DX
               RET
calc_dir_DMA   ENDP

; ------------ Crear tabla con información para formatear. En ES:BX
;              está el futuro sector de arranque del disquete.

genera_info    PROC
               PUSHA
               MOV   buf_unidad,-1     ; invalidar contenido buffer
               MOV   SI,buffer
               MOV   DI,BX
               CALL  pista0?
               JNZ   no_cilcab0             ; no es cilindro/cabezal 0
               ADD   DI,ES:[BX+70]          ; DI -> datos pista 0
               MOV   CL,ES:[DI]
               MOV   CH,0                   ; CX sectores en pista 0
               INC   DI
               MOV   AL,ES:[DI]             ; GAP para pista 0
               MOV   AH,0                   ; byte de relleno
               INC   DI
               MOV   BYTE PTR [SI],2        ; tamaño de sector
               MOV   BYTE PTR [SI+1],CL     ; número de sectores
               MOV   [SI+2],AX              ; GAP / byte de relleno
genera_0:      ADD   SI,4
               MOV   AL,cilindro
               MOV   AH,cabezal
               MOV   [SI],AX                ; datos para cada sector
               MOV   AL,ES:[DI]
               MOV   AH,2                   ; LOG2 (tamaño)-7
               INC   DI
               MOV   [SI+2],AX              ; nº de sector / tamaño
               LOOP  genera_0
               POPA
               RET
no_cilcab0:    ADD   DI,ES:[BX+72]
               CMP   BYTE PTR ES:[BX+65],1
               JE    info_stv
               MOV   DL,ES:[DI+2]           ; tamaño /F
               MOV   DH,ES:[DI]             ; nº sectores
               MOV   [SI],DX
               XCHG  DH,DL                  ; tamaño en DH
               MOV   CL,DL
               MOV   CH,0                   ; CX sectores
               MOV   AL,ES:[DI+1]           ; GAP para formatear
               MOV   AH,0                   ; byte relleno /F
               MOV   [SI+2],AX              ; GAP / byte de relleno
               PUSH  DX
               MOV   AX,ES:[DI+3]
               PUSH  AX
               ADD   AL,AH
               MUL   cilindro
               MOV   DX,AX
               POP   AX
               MUL   cabezal
               ADD   AX,DX
               XOR   DX,DX
               MOV   BL,ES:[DI]
               MOV   BH,0
               DIV   BX                ; DL = módulo
               SUB   DL,ES:[DI]
               NEG   DL
               MOV   AL,DL
               POP   DX                ; restaurar tamaño en DH
               MOV   DL,AL             ; primer sector de la pista - 1
               MOV   BL,ES:[DI]        ; nº sectores en la pista
genera_pn:     ADD   SI,4
               INC   DX
               CMP   DL,BL
               JBE   ns_ok
               MOV   DL,1              ; empezar desde el 1
ns_ok:         MOV   AL,cilindro
               MOV   AH,cabezal
               MOV   [SI],AX           ; datos para cada sector
               MOV   [SI+2],DX         ; nº sector / LOG2 (tamaño)-7
               LOOP  genera_pn
               POPA
               RET
info_stv:      MOV   CH,ES:[DI]             ; nº sectores
               MOV   CL,0                   ; CL:CH sectores
               MOV   [SI],CX                ; tamaño (CL=0) y número
               XCHG  CH,CL                  ; CX sectores
               MOV   AL,ES:[DI+1]           ; GAP para formatear
               MOV   AH,4Eh                 ; byte de relleno /M
               MOV   [SI+2],AX              ; GAP / byte de relleno
               MOV   DL,128
genera_otro:   ADD   SI,4
               INC   DX
               MOV   AL,cilindro
               MOV   AH,cabezal
               MOV   [SI],AX           ; datos para cada sector
               XPUSH <CX, DI>          ; *
               MOV   CL,ES:[DI+2]      ; CH está a 0
busca_num:     ADD   DI,3
               CMP   DL,ES:[DI]
               MOV   AX,ES:[DI+1]      ; número de sector / tamaño
               JE    hallado           ; es sector a cambiar número
               LOOP  busca_num
               MOV   AL,DL             ; no cambiar número
               MOV   AH,0              ; e indicar tamaño 128
hallado:       XPOP  <DI, CX>          ; *
               MOV   [SI+2],AX         ; nº sector / LOG2 (tamaño)-7
               LOOP  genera_otro
               POPA
               RET
genera_info    ENDP

; ------------ Formatear una pista.

formatea_pista PROC
               PUSHA
               MOV   BX,buffer
               MOV   DI,BX
               MOV   CL,[BX+1]
               MOV   CH,0              ; CX sectores
               SHL   CX,2
               DEC   CX                ; nº de bytes - 1
               MOV   AX,DS
               CALL  calc_dir_DMA      ; AX:DI -> base BX y página AH
               MOV   AL,4Ah            ; modo DMA para escribir
               ADD   BX,4              ; saltar primeros 4 bytes
               CALL  prepara_DMA
               MOV   BX,buffer
               MOV   AL,F_FORMAT
               CALL  fdc_write
               JC    fallo_fmt
               MOV   AL,cabezal
               SHL   AL,2
               OR    AL,unidad
               CALL  fdc_write         ; byte 1 de la orden
               JC    fallo_fmt
               MOV   CX,4
format_cmd:    MOV   AL,[BX]
               CALL  fdc_write
               INC   BX
               LOOP  format_cmd
               CALL  espera_int
fallo_fmt:     PUSHF
               LEA   BX,fdc_result
               MOV   CX,7
format_res:    CALL  fdc_read          ; leyendo resultados
               MOV   [BX],AL
               INC   BX
               LOOP  format_res
               POPF
               JC    fallo_format
               TEST  fdc_result,11000000b
               JZ    format_ret
fallo_format:  STC                     ; fallo
format_ret:    POPA
               RET
formatea_pista ENDP

; ------------ Esperar interrupción de disquete durante casi 2
;              segundos antes de considerar que ha sido un fracaso.

espera_int     PROC
               STI
               PUSHA
               XPUSH <DS, 40h>
               POP   DS
               MOV   AX,9001h
               CLC
               INT   15h               ; permitir multitarea
               MOV   DX,0280h
               MOV   BX,3Eh
               JC    timeout_int
esp_int_1s:    XOR   CX,CX
esp_int:       TEST  [BX],DL           ; ¿llegó la interrupción?
               JNZ   fin_espera
               PMICRO
               LOOP  esp_int           ; esperar durante casi 1 seg.
               DEC   DH
               JNZ   esp_int_1s
timeout_int:   OR    CS:status,DL      ; timeout
               STC
fin_espera:    PUSHF
               AND   BYTE PTR [BX],7Fh ; para la próxima vez
               POPF
               POP   DS
               POPA
               RET
espera_int     ENDP

; ------------ Preparar DMA para E/S. A la entrada, BX = dirección de
;              base, AH = registro de página y CX = nº bytes - 1.

prepara_DMA    PROC
               PUSH  AX
               CLI
               OUT   0Bh,AL            ; registro de modo del DMA
               MOV   AL,0
               DELAY
               OUT   0Ch,AL            ; clear first/last flip-flop
               MOV   AL,BL
               DELAY
               OUT   4,AL
               MOV   AL,BH
               DELAY
               OUT   4,AL              ; enviada dirección base
               DELAY
               MOV   AL,AH
               OUT   81h,AL            ; registro de página del DMA
               MOV   AL,CL
               DELAY
               OUT   5,AL
               MOV   AL,CH
               DELAY
               OUT   5,AL              ; enviada cuenta de bytes
               STI
               MOV   AL,2
               DELAY
               OUT   0Ah,AL            ; habilitar canal 2 de DMA
               POP   AX
               RET
prepara_DMA    ENDP

; ------------ Recibir byte del FDC en AL. A la vuelta, CF=1 si
;              la operación fracasó (el FDC no estaba listo) y
;              se indica la condición de timeout en «status».

fdc_read       PROC
               XPUSH <CX, DX, AX>
               CALL  fdc_respiro       ; no abrasar el FDC
               MOV   DX,3F4h           ; registro de estado del FDC
               MOV   CX,133            ; constante para 0,002 segundos
espera_rd:     DELAY
               IN    AL,DX
               AND   AL,11000000b
               CMP   AL,11000000b      ; ¿dato listo?
               JE    fdc_rd_ok
               DELAY
               IN    AL,61h
               AND   AL,10h
               CMP   AL,AH
               JE    espera_rd         ; reintentarlo durante 15,09 µs
               MOV   AH,AL
               LOOP  espera_rd
               XPOP  <AX, DX, CX>
               OR    status,80h        ; timeout
               MOV   AL,0
               STC                     ; fallo
               RET
fdc_rd_ok:     POP   AX
               INC   DX                ; apuntar al registro de datos
               DELAY
               IN    AL,DX             ; leer byte del FDC
               XPOP  <DX, CX>
               CLC                     ; Ok
               RET
fdc_read       ENDP

; ------------ Enviar byte AL al FDC. A la vuelta, CF=1 si
;              la operación fracasó (el FDC no estaba listo) y
;              se indica la condición de timeout en «status».

fdc_write      PROC
               XPUSH <CX, DX, AX>
               CALL  fdc_respiro       ; no abrasar el FDC
               MOV   DX,3F4h           ; registro de estado del FDC
               MOV   CX,133            ; constante para 0,002 segundos
espera_wr:     DELAY
               IN    AL,DX
               TEST  AL,80h            ; ¿listo para E/S?
               JNZ   fdc_wr_ok
               DELAY
               IN    AL,61h
               AND   AL,10h
               CMP   AL,AH
               JE    espera_wr         ; reintentarlo durante 15,09 µs
               MOV   AH,AL
               LOOP  espera_wr
               XPOP  <AX, DX, CX>
               OR    status,80h        ; timeout
               STC                     ; fallo
               RET
fdc_wr_ok:     INC   DX                ; apuntar al registro de datos
               POP   AX
               DELAY
               OUT   DX,AL             ; enviar byte al FDC
               XPOP  <DX, CX>
               CLC                     ; Ok
               RET
fdc_write      ENDP

; ------------ Retardo de 60 µs para dar tiempo al FDC en 486 rápidos.

fdc_respiro    PROC
               XPUSH <AX, CX>
               MOV   CX,4
fdc_ret:       PMICRO
               LOOP  fdc_ret
               XPOP  <CX, AX>
               RET
fdc_respiro    ENDP

; ------------ Esperar exactamente AX milisegundos.

retardo        PROC
               PUSHF
               PUSHA
               MOV   DX,16970          ; 16970 = 1193180/18*256/1000
               MUL   DX
               MOV   CL,AH             ; dividir DX:AX entre 256 y
               MOV   CH,DL             ; dejar el resultado en DX:CX
               MOV   DL,DH
               MOV   DH,0              ; DX:CX 15,09 µs-avos
retardando:    PMICRO
               LOOP  retardando
               AND   DX,DX
               JZ    retardado
               DEC   DX
               JMP   retardando
retardado:     POPA
               POPF
               RET
retardo        ENDP

               ; --- Ubicación del sector de hasta 2048 bytes.

               EVEN
buffer_io      EQU   $
tbuffer        EQU   2048

fin_residente  EQU   $  ; fin del área residente sin contar el buffer

bytes_resid    EQU   fin_residente-ini_residente


; *****************************
; *                           *
; *   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  pc_ok?
               TEST  error,0FFFFh
               JNZ   exit_ins
               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
               MOV   AX,CS
               LEA   BX,buffer_io
               CALL  testDMA
               JNC   dma_ml_ok         ; no hay problemas con el DMA
               OR    accion,BUFFERPLUS ; sí: aviso al usuario
               MOV   CX,dma_fix
               ADD   buffer,CX         ; nuevo buffer
               MOV   AX,longitud_total
               ADD   AX,dma_fixp
               MOV   longitud_total,AX ; nuevo consumo de memoria
dma_ml_ok:     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


;*********************************************************
;*                                                       *
;*  SUBRUTINAS DE PROPOSITO GENERAL PARA LA INSTALACION  *
;*                                                       *
;*********************************************************

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

inic_general   PROC
               MOV   AX,(bytes_resid+tbuffer+15)/16
               MOV   longitud_total,AX ; memoria necesaria
               MOV   segmento_real,CS  ; anotar segmento del bloque
               MOV   offset_real,0     ; ídem con el offset
               MOV   DL,0
               CALL  tipo_disco
               JC    err_drv
               AND   DL,DL
               JZ    err_drv             ; no hay disqueteras
               MOV   info_A.tipo_drv,AL  ; guardar tipo unidad A:
               CMP   DL,1
               JE    fin_inic            ; no existe unidad B:
               MOV   DL,1
               CALL  tipo_disco
               JC    fin_inic
               MOV   info_B.tipo_drv,AL  ; guardar tipo unidad B:
               RET
err_drv:       MOV   AX,MALBIOS
               OR    error,AX
fin_inic:      RET

tipo_disco:    PUSH  ES
               MOV   AH,8
               MOV   BL,0
               INT   13h
               JC    tipo_dsk_err
               MOV   AL,BL
               AND   AL,AL
               JZ    tipo_dsk_err
               CMP   AL,4
               JB    tipo_dsk_ok
               MOV   AL,4                ; 1.44/2.88 indistinto
tipo_dsk_ok:   CLC
               POP   ES
               RET
tipo_dsk_err:  STC
               POP   ES
               RET
inic_general   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

; ------------ 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:      XPOP  <BP, DX, CX, BX, AX>
               RET
habla_hispana: MOV   idioma_sp,ON      ; país de habla hispana
               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

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

info           PROC
               LEA   DX,instalado_txt
               TEST  error,0FFFFh
               JZ    info_ins_ok
               LEA   DX,noinstall_txt
info_ins_ok:   CALL  print
               LEA   DX,err_malpc
               TEST  error,MALPC
               JNZ   fin_info
               LEA   DX,err_maldos
               TEST  error,MALDOS
               JNZ   fin_info
               LEA   DX,err_malbios
               TEST  error,MALBIOS
               JNZ   fin_info
               LEA   DX,err_maldrv
               TEST  error,MALDRV
               JNZ   fin_info
               LEA   DX,err_mx64full
               TEST  error,MX64FULL
               JZ    info_war
fin_info:      CALL  print
info_war:      TEST  accion,BUFFERPLUS
               JZ    no_warn
               LEA   DX,dma_cross_txt
               CALL  print
no_warn:       RET
info           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.

pc_ok?         PROC
               CALL  test_i40
               TEST  accion,I40
               JZ    test_dos          ; no soportada la INT 40h
               MOV   nueva_i13,40h
               MOV   vieja_i13,40h     ; usar INT 40 en vez de INT 13
test_dos:      MOV   AH,30h
               INT   21h
               XCHG  AH,AL
               CMP   AX,31Eh           ; ¿DOS 3.30 o superior?
               MOV   AX,MALDOS
               JB    pc_nok
               CALL  testAT
               MOV   AX,MALPC
               JC    pc_nok
               TEST  error,MALBIOS
               JNZ   pc_ok             ; con ese error vale
               MOV   AX,MALDRV
               CMP   info_A.tipo_drv,2 ; ¿unidad A: de 1.2?
               JE    pc_ok
               CMP   info_A.tipo_drv,4 ; ¿unidad A: de 1.44 ó 2.88?
               JAE   pc_ok
               CMP   info_B.tipo_drv,2 ; ¿unidad B: de 1.2?
               JE    pc_ok
               CMP   info_B.tipo_drv,4 ; ¿unidad B: de 1.44 ó 2.88?
               JAE   pc_ok
pc_nok:        OR    error,AX
pc_ok:         RET
pc_ok?         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
               PUSH  AX
               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
               POP   AX
               RET
testAT         ENDP

; ------------ Comprobar que el buffer para el DMA en la copia
;              residente no cruza una frontera. En ese caso se emplea
;              otro buffer ubicado tras el habitual, lo que aumenta el
;              consumo de memoria de este área. A la entrada AX apunta
;              al segmento que contendrá el buffer y BX el offset. Si
;              se produce el cruce, dma_fix devuelve la cuantía en que
;              hay que incrementar (en bytes) la dirección base para
;              evitarlo y dma_fixp también (pero en párrafos).

testDMA        PROC
               XPUSH <AX, CX, DX>
               MOV   CX,16
               MUL   CX
               ADD   AX,BX
               ADC   DX,0              ; DX:AX = dirección 20 bits
               MOV   CX,DX
               PUSH  AX
               ADD   AX,tbuffer-1      ; buffer para el mayor sector
               ADC   DX,0
               POP   AX
               CMP   DX,CX
               JE    dmatested
               NEG   AX
               MOV   dma_fix,AX
               ADD   AX,15
               MOV   CL,4
               SHR   AX,CL
               MOV   dma_fixp,AX
               STC                     ; CF=1 -> cruza frontera
dmatested:     XPOP  <DX, CX, AX>
               RET
testDMA        ENDP

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

ON             EQU   1         ; constantes booleanas
OFF            EQU   0

; ------------ Gestión de memoria y control de instalación.

dma_fix        DW    ?         ; bytes para alargar buffer (por DMA)
dma_fixp       DW    ?         ; párrafos

offsets_ints   DW    2         ; número de vectores interceptados
nueva_i13      DB    13h       ; tabla de offsets de los vectores
               DW    ges_int13 ; de interrupción interceptados
               DB    2Fh
               DW    ges_int2F

MALPC          EQU   1         ; Códigos de error
MALDOS         EQU   2
MALBIOS        EQU   4
MALDRV         EQU   8
MX64FULL       EQU 128

BUFFERPLUS     EQU   8         ; códigos de acción e información
I40            EQU  16

error          DW    0         ; variable para acumular errores
accion         DB    0         ; variable que indica lo sucedido

; ------------ Códigos de modos y órdenes del DMA y del FDC.

F_READ         EQU   46h               ; modo DMA para lectura
F_WRITE        EQU   4Ah               ; modo DMA para escritura
F_VERIFY       EQU   42h               ; modo DMA para verificación
F_FORMAT       EQU   01001101b         ; orden de formateo del FDC

; ------------ Otras variables.

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 2.0 instalado.",13,10,255
               DB    13,10,"2M 2.0 installed.",13,10,0

noinstall_txt  DB    13,10,"2M 2.0 no instalado.",13,10,255
               DB    13,10,"2M 2.0 not installed.",13,10,0

err_malpc      DB    "  - Error: Necesario ordenador AT. Utilice 2MX en esta máquina.",13,10,7,255
               DB    "  - Error: Needed AT system. Use 2MX on this computer.",13,10,7,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

err_maldos     DB    "  - Error: necesaria versión DOS 3.30 ó posterior.",13,10,7,255
               DB    "  - Error: needed at least DOS 3.30 or above.",13,10,7,0

err_malbios    DB    "  - Error: No puedo detectar el tipo de las unidades. Instale 2M-ABIOS antes.",13,10,255
               DB    "  - Error: Impossible to detect drive types. Please install 2M-ABIOS before.",13,10,0

err_maldrv     DB    "  - Error: Necesaria(s) unidad(es) de alta densidad.",13,10,255
               DB    "  - Error: Needed high-density floppy drive(s).",13,10,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 de 2M para ahorrar unos bytes.",13,10,255
               DB    "  - Advice: I/O buffer has been extended because 2M cross a DMA frontier.",13,10
               DB    "            Modify the memory location of 2M to save a little of memory.",13,10,0

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

_PRINCIPAL     ENDS
               END
[ RETURN TO DIRECTORY ]