Metropoli BBS
VIEWER: 2mfboot.asm MODE: TEXT (CP437)
;┌───────────────────────────────────────────────────────────────────┐
;│                                                                   │
;│             █████ █   █ █▀▀▀▀ █▀▀▄  ▄▀▀▀▄ ▄▀▀▀▄ ▀▀█▀▀             │
;│                 █ ██ ██ █     █   █ █   █ █   █   █               │
;│             █████ █ █ █ █▀▀   █▀▀█  █   █ █   █   █               │
;│             █     █   █ █     █   █ █   █ █   █   █               │
;│             █████ █   █ █     █▄▄▀  ▀▄▄▄▀ ▀▄▄▄▀   █               │
;│                                                                   │
;│    2MFBOOT 2.0  -  (C) Abril 1994 Ciriaco García de Celis.        │
;│                                                                   │
;│            CODIGO 2M PARA ARRANQUE FRIO DESDE DISQUETE.           │
;│                                                                   │
;│  Proceso:                                                         │
;│                                                                   │
;│    TASM    2MFBOOT /m5                                            │
;│    TLINK   2MFBOOT                                                │
;│    EXE2BIN 2MFBOOT.EXE 2MFBOOT.BIN                                │
;│                                                                   │
;│    El fichero .BIN hay que convertirlo a .DB con 2MFBMAKE.BAS     │
;│    Es necesario que este fichero ocupe exactamente 2560 bytes     │
;│                                                                   │
;└───────────────────────────────────────────────────────────────────┘

               .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
                 CALL pmicro_iter      ; realmente es una subrutina
               ENDM

; ------------ 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                 ; código binario puro

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

; ------------ Variables del programa (justo al principio).

info_ptr       DW    info_A       ; punteros a datos de las unidades
               DW    info_B

               DB    "20"         ; Versión 2MFBOOT 2.0

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

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

ges_int13      PROC  FAR
               STI
               CLD
               PUSHF
               PUSH  SI
               CMP   DL,2
               JAE   ges13bios         ; no es disquetera A: ó B:
               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:        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
               JNE   no_format
               CALL  set_flag_STV      ; CF = 0 -> "disco no 2M"
               JMP   ges13bios
no_format:     CALL  detecta_cambio    ; ¿cambio de disco?
               JNC   dilucida
               POP   SI
               POPF
               STC                     ; hubo cambio:
               MOV   AX,600h
               RET   2                 ; retornar con error
dilucida:      CMP   CS:[SI].control2m_flag,OFF
               JE    ges13bios         ; la unidad la controla la BIOS
               POP   SI
               POPF
               CALL  control2m         ; la controla 2M
               RET   2
ges13bios:     POP   SI
               POPF
               JMP   CS:ant_int13      ; saltar al gestor de INT 13h
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
               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
               AND   AL,AL
               JZ    acc_mult_fin
               MOV   AH,sector
               MOV   sector_ini,AH
               ADD   AL,AH
               DEC   AX
               MOV   sector_fin,AL
               INC   AL
               CALL  acceso_sector     ; sectores no problemáticos
               MOV   sector,AL
acc_mult_fin:  POP   AX
               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
               JC    sector_io_ko
               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.
;              Si se cruza una frontera de DMA se devuelve el error.

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
               MOV   DX,CX
               ADD   DX,BX
               JNC   dir_DMA_ok
               MOV   status,9          ; error de frontera de DMA
dir_DMA_ok:    POP   DX
               RET
calc_dir_DMA   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

; ------------ Esta subrutina sustituye a la macro PMICRO del programa
;              completo por razones de espacio.

pmicro_iter:   DELAY                   ; retardo de aprox. 15,09 µs
               IN    AL,61h            ; (exactamente 18/1193180 sg.)
               AND   AL,10h            ; La rutina se puede ejecutar
               CMP   AL,AH             ; repetitivamente (se apoya en
               JE    pmicro_iter       ; AX) para hacer retardos a
               MOV   AH,AL             ; través de la temporización
               RET                     ; del refresco de la memoria

; ------------ Código invocado durante el SuperBOOT desde 2MFKIT.ASM
;              A la entrada: CS=ES, SS=0 y AX = tipo unidades.

initcode:      PUSH  DS
               PUSH  SS
               POP   DS
               MOV   ES:[info_A.tipo_drv],AL  ; anotar tipo de A:
               MOV   ES:[info_B.tipo_drv],AH  ; anotar tipo de B:
               LEA   DI,ant_int13
               MOV   SI,13h*4              ; vector de INT 13h
               CLD
               CLI
               MOVSW
               MOVSW                       ; anotada dirección INT 13h
               MOV   WORD PTR [SI-4],OFFSET ges_int13
               MOV   [SI-2],ES
               STI                         ; desviada INT 13h
               POP   DS
               RETF                        ; volver a 2MFKIT

               DB    2 DUP (0)    ; para completar 2560 bytes exactos

ant_int13      LABEL DWORD        ; vector de la INT 13h previa
ant_int13_off  DW    initcode
ant_int13_seg  DW    0AA55h       ; significa "2MFBOOT correcto"

               ; --- Ubicación del sector de hasta 2048 bytes.
               ;     Por su peculiar ubicación en la memoria (al final
               ;     de los 640K, 512K, etc, pero sin llegar) nunca
               ;     cruzará una frontera de DMA...

buffer_io      EQU   $          ; obviamente ¡no se almacena!

ON             EQU   1          ; constantes booleanas
OFF            EQU   0
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

_PRINCIPAL     ENDS
               END
[ RETURN TO DIRECTORY ]