Оглавление


§ Чтение и запись

Главные процедуры для чтения и записи на диск.
Требуется реализация переменной irq_timer_counter, которая будет инкрементироваться +1 каждые 1/100 сек. Это нужно для включения и выключения мотора.
  • fdc_read и fdc_write запускают чтение или запись, но ответ не ожидается, можно выполнять программу далее. Статус готовности проверяется по наличию не нулевого значения в fdc.ready
  • fdc_read_sync и fdc_write_sync делают тоже самое, но ответ ожидается
Ниже приведен код.
; Чтение сектора (AX) в $1000 -> IRQ #6
fdc_read:

        mov     [fdc.lba], ax           ; Запрошенный LBA
        call    fdc_prepare
        call    fdc_dma_read
        mov     bl, 0
        call    fdc_rw                  ; BL=0 READ
        ret

; Запись сектора (AX) из $1000 -> IRQ #6
fdc_write:

        mov     [fdc.lba], ax
        call    fdc_prepare
        call    fdc_dma_write
        mov     bl, 1
        call    fdc_rw                  ; BL=1 WRITE
        ret

; Чтение AX сектора в $1000, ждать ответа
fdc_read_sync:

        call    fdc_read
@@:     cmp     [fdc.ready], byte 0
        je      @b
        ret

; Запись AX сектора из $1000, ждать ответа
fdc_write_sync:

        call    fdc_write
@@:     cmp     [fdc.ready], byte 0
        je      @b
        ret

§ Инициализация каналов DMA для FDC

Эта процедура программирует контроллер DMA для работы дискеты.
fdc_init:

        ; Установка DMA
        mov     al, $06
        out     $0A, al ; Маскирование DMA channel 2 и 0

        ; Адрес $1000
        mov     al, $FF
        out     $0C, al ; Сброс master flip-flop
        mov     al, $00
        out     $04, al ; [7:0]
        mov     al, $10
        out     $04, al ; [15:8]

        ; Размер $3FFF
        mov     al, $FF
        out     $0C, al ; Сброс master flip-flop
        mov     al, $FF
        out     $05, al ; [7:0]
        mov     al, $3F
        out     $05, al ; [15:8]

        ; Адрес $00
        mov     al, $00
        out     $81, al ; [23:16]
        mov     al, $02
        out     $0A, al ; Размаскировать DMA channel 2

        ; Инициализация
        mov     [fdc.motor], 0
        mov     [fdc.ready], 0
        ret

§ Подготовить DMA на чтение или запись

; Подготовить диск на чтение
fdc_dma_read:

        mov     al, $06 ; mask DMA channel 2 and 0 (assuming 0 is already masked)
        out     $0A, al
        mov     al, $56
        out     $0B, al ; 01010110 single transfer, address increment, autoinit, read, channel2
        mov     al, $02
        out     $0A, al ; unmask DMA channel 2
        ret

; Подготовить диск на запись
fdc_dma_write:

        mov     al, $06
        out     $0A, al
        mov     al, $5A
        out     $0B, al ; 01011010 single transfer, address increment, autoinit, write, channel2
        mov     al, $02
        out     $0A, al
        ret

§ Процедуры

; Ожидать завершения (проверочный параметр AH)
fdc_wait:

        push    eax
        mov     dx, MAIN_STATUS_REGISTER
@@:     in      al, dx
        and     al, ah
        cmp     al, ah
        jne     @b
        pop     eax
        ret

; Запись данных (al) в FIFO
fdc_write_reg:

        mov     ah, $80
        call    fdc_wait            ; Ожидать OK
        mov     dx, FDC_DATA_FIFO
        out     dx, al              ; Записать AL в FIFO
        ret

; Чтение данных (al) из FIFO
fdc_read_reg:

        mov     ah, $c0
        call    fdc_wait            ; Ожидать OK
        mov     dx, FDC_DATA_FIFO
        in      al, dx              ; Прочесть AL из FIFO
        ret

; Проверить IRQ-статус после SEEK, CALIBRATE, etc.
fdc_sensei:

        mov     al, SENSE_INTERRUPT
        call    fdc_write_reg       ; Отправка запроса
        mov     ah, 0xD0
        call    fdc_wait
        mov     dx, FDC_DATA_FIFO   ; Получение результата
        in      al, dx
        mov     [fdc.st0], al
        mov     ah, 0xD0
        call    fdc_wait
        mov     dx, FDC_DATA_FIFO   ; Номер цилиндра
        in      al, dx
        mov     [fdc.cyl], al
        ret

; Конфигурирование
fdc_configure:

        mov     al, SPECIFY
        call    fdc_write_reg
        mov     al, 0
        call    fdc_write_reg       ; steprate_headunload
        call    fdc_write_reg       ; headload_ndma
        ret

; Калибрация драйва
fdc_calibrate:

        call    fdc_motor_on
        mov     [fdc.func],  FDC_STATUS_SENSEI
        mov     [fdc.ready], 0
        mov     al, RECALIBRATE
        call    fdc_write_reg       ; Команда, Drive = A:
        mov     al, 0
        call    fdc_write_reg       ; Команда, Drive = A:
@@:     cmp     [fdc.ready], 0      ; Ожидать ответа IRQ
        je      @b
        ret

; Сбросить контроллер перед работой с диском
fdc_reset:

        mov     [fdc.func],  FDC_STATUS_SENSEI
        mov     [fdc.ready], 0

        ; Отключить и включить контроллер
        mov     dx, DIGITAL_OUTPUT_REGISTER
        mov     al, $00
        out     dx, al
        mov     al, $0C
        out     dx, al
@@:     cmp     [fdc.ready], 0      ; Ожидать ответа IRQ
        je      @b

        ; Конфигурирование
        mov     dx, CONFIGURATION_CONTROL_REGISTER
        mov     al, 0
        out     dx, al
        call    fdc_configure
        call    fdc_calibrate
        ret

; Сбор результирующих данных: если > 0, то ошибка
fdc_get_result:

        call    fdc_read_reg
        mov     [fdc.st0], al
        call    fdc_read_reg
        mov     [fdc.st1], al
        call    fdc_read_reg
        mov     [fdc.st2], al
        call    fdc_read_reg
        mov     [fdc.cyl], al
        call    fdc_read_reg
        mov     [fdc.head_end], al
        call    fdc_read_reg
        mov     [fdc.head_start], al
        call    fdc_read_reg
        and     al, $c0
        ret

; Преобразовать ax=LBA -> CHS
fdc_lba2chs:

        xor     dx, dx
        mov     bx, 18
        div     bx
        inc     dl
        mov     [fdc.r_sec], dl
        mov     dl, al
        and     dl, 1
        mov     [fdc.r_hd], dl
        shr     ax, 1
        mov     [fdc.r_cyl], al
        ret

; Включить мотор
fdc_motor_on:

        cli
        mov     [fdc.motor], 1              ; Включение
        sti
        mov     eax, [irq_timer_counter]
        mov     [fdc.motor_time], eax       ; Запись таймера
        mov     dx, DIGITAL_OUTPUT_REGISTER ; Регистр DOR = $1C (On)
        mov     al, 0x1C
        out     dx, al
        ret

; Выключить мотор
fdc_motor_off:

        cli
        mov     [fdc.motor], 0
        sti
        xor     eax, eax
        mov     dx, DIGITAL_OUTPUT_REGISTER ; Отключить
        out     dx, al
        ret

§ Вспомогательные процедуры чтения и записи в DMA

Процедура записи и чтения на диск.
; bl = 0 READ; 1 WRITE
; ax = lba
; (byte write, byte head, byte cyl, byte sector)

fdc_rw:

        mov     [fdc.ready], 0
        mov     [fdc.func],  FDC_STATUS_RW
        mov     ax, $4546
        and     bl, bl
        je      @f
        mov     al, ah
@@:     call    fdc_write_reg       ; 0 MFM_bit = 0x40 | (W=0x45 | R=0x46)
        mov     al, [fdc.r_hd]
        shl     al, 2
        call    fdc_write_reg       ; 1

        mov     al, [fdc.r_cyl]
        call    fdc_write_reg
        mov     al, [fdc.r_hd]
        call    fdc_write_reg
        mov     al, [fdc.r_sec]
        call    fdc_write_reg
        mov     al, 2
        call    fdc_write_reg       ; 5 Размер сектора (2 ~> 512 bytes)
        mov     al, 18
        call    fdc_write_reg       ; 6 Последний сектор в цилиндре
        mov     al, $1B
        call    fdc_write_reg       ; 7 Длина GAP3
        mov     al, $FF
        call    fdc_write_reg       ; 8 Длина данных, игнорируется
        ret

; Поиск дорожки => IRQ #6
fdc_seek:

        mov     [fdc.ready], 0
        mov     [fdc.func],  FDC_STATUS_SEEK
        mov     al, SEEK
        call    fdc_write_reg           ; Команда
        mov     al, [fdc.r_hd]
        shl     al, 2
        call    fdc_write_reg           ; head<<2
        mov     al, [fdc.r_cyl]
        call    fdc_write_reg           ; Цилиндр
        ret

; Подготовить диск для чтения/записи (AX = LBA)
fdc_prepare:

        call    fdc_lba2chs             ; Вычислить LBA
        mov     [fdc.error], 0          ; Отметить, что ошибок пока нет
        mov     eax, [irq_timer_counter]
        mov     [fdc.motor_time], eax
        cmp     [fdc.motor], 0          ; Включить мотор, если нужно
        jne     @f
        call    fdc_reset
@@:     call    fdc_seek
@@:     cmp     [fdc.ready], 0      ; Ожидать ответа IRQ
        je      @b
        ret

§ Обработчик прерываний (IRQ #6)

fdc_irq:

        pushad
        cmp     [fdc.func], byte FDC_STATUS_RW
        je      .rw
        call    fdc_sensei              ; Выполнить считывание рез-та
        jmp     .exit
.rw:    call    fdc_get_result          ; Забрать результат при R/W
        and     al, al
        jne     .exit
        mov     [fdc.error], byte 1     ; Ошибка чтения при al > 0
.exit:  mov     [fdc.ready], byte 1     ; Завершено
        mov     al, $20                 ; EOI
        out     $20, al
        popad
        iretd

; Вычисление таймаута и выключение FDC из прерывания IRQ #0
; ----------------------------------------------------------------------

fdc_timeout:

        cmp     [fdc.motor], 0           ; Мотор включен?
        je      @f
        mov     eax, [irq_timer_counter] ; Если > 5с крутится, выключить
        sub     eax, [fdc.motor_time]
        cmp     eax, 500
        jb      @f
        call    fdc_motor_off
@@:     ret

§ Переменные для FDC

Контроллер флоппи-диска
fdc:                            ;

    ; результат
    .st0            db 0        ; Статусный регистр 0
    .st1            db 0
    .st2            db 0
    .cyl            db 0
    .head_end       db 0
    .head_start     db 0

    ; функционирование
    .motor          db 0        ; Включен ли мотор
    .motor_time     dd 0        ; Время включения мотора
    .func           db 0        ; Функция запроса на IRQ
    .ready          db 0        ; IRQ обработан
    .error          db 0        ; Ошибка исполнения

    ; запрос
    .lba            dw 0
    .r_hd           db 0        ; * головка
    .r_cyl          db 0        ; * цилиндр
    .r_sec          db 0        ; * сектор

§ Константы


; Floppy Disk Controller
; ----------------------------------------------------------------------

; Порты
STATUS_REGISTER_A               equ 0x3F0  ; read-only
STATUS_REGISTER_B               equ 0x3F1  ; read-only
DIGITAL_OUTPUT_REGISTER         equ 0x3F2
TAPE_DRIVE_REGISTER             equ 0x3F3
MAIN_STATUS_REGISTER            equ 0x3F4  ; read-only
DATARATE_SELECT_REGISTER        equ 0x3F4  ; write-only
FDC_DATA_FIFO                   equ 0x3F5
DIGITAL_INPUT_REGISTER          equ 0x3F7  ; read-only
CONFIGURATION_CONTROL_REGISTER  equ 0x3F7  ; write-only

; Команды
READ_TRACK                      equ 2  ; generates IRQ6
SPECIFY                         equ 3  ; * set drive parameters
SENSE_DRIVE_STATUS              equ 4
WRITE_DATA                      equ 5  ; * write to the disk
READ_DATA                       equ 6  ; * read from the disk
RECALIBRATE                     equ 7  ; * seek to cylinder 0
SENSE_INTERRUPT                 equ 8  ; * ack IRQ6, get status of last command
WRITE_DELETED_DATA              equ 9
READ_ID                         equ 10 ; generates IRQ6
READ_DELETED_DATA               equ 12
FORMAT_TRACK                    equ 13
DUMPREG                         equ 14
SEEK                            equ 15 ; * seek both heads to cylinder X
VERSION                         equ 16 ; * used during initialization, once
SCAN_EQUAL                      equ 17
PERPENDICULAR_MODE              equ 18 ; * used during initialization, once, maybe
CONFIGURE                       equ 19 ; * set controller parameters
LOCK                            equ 20 ; * protect controller params from a reset
VERIFY                          equ 22
SCAN_LOW_OR_EQUAL               equ 25
SCAN_HIGH_OR_EQUAL              equ 29

; Статусы
FDC_STATUS_NONE                 equ 0x0
FDC_STATUS_SEEK                 equ 0x1
FDC_STATUS_RW                   equ 0x2
FDC_STATUS_SENSEI               equ 0x3