;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision$


;**********************************************************
;  Непосредственная работа с контроллером гибкого диска
;**********************************************************
; Автор исходного текста  Кулаков Владимир Геннадьевич.
; Адаптация и доработка Mario79

;give_back_application_data:  ; переслать приложению
;     mov edi,[TASK_BASE]
;     mov edi,[edi+TASKDATA.mem_start]
;     add edi,ecx
give_back_application_data_1:
        mov     esi, FDD_BUFF;FDD_DataBuffer  ;0x40000
        mov     ecx, 128
        cld
        rep movsd
        ret

;take_data_from_application:   ; взять из приложени
;     mov esi,[TASK_BASE]
;     mov esi,[esi+TASKDATA.mem_start]
;     add esi,ecx
take_data_from_application_1:
        mov     edi, FDD_BUFF;FDD_DataBuffer  ;0x40000
        mov     ecx, 128
        cld
        rep movsd
        ret

; Коды завершения операции с контроллером (FDC_Status)
FDC_Normal         equ 0 ;нормальное завершение
FDC_TimeOut        equ 1 ;ошибка тайм-аута
FDC_DiskNotFound   equ 2 ;в дисководе нет диска
FDC_TrackNotFound  equ 3 ;дорожка не найдена
FDC_SectorNotFound equ 4 ;сектор не найден

; Максимальные значения координат сектора (заданные
; значения соответствуют параметрам стандартного
; трехдюймового гибкого диска объемом 1,44 Мб)
MAX_Track   equ 79
MAX_Head    equ  1
MAX_Sector  equ 18

uglobal
; Счетчик тиков таймера
TickCounter dd ?
; Код завершения операции с контроллером НГМД
FDC_Status  DB ?
; Флаг прерывания от НГМД
FDD_IntFlag DB ?
; Момент начала последней операции с НГМД
FDD_Time    DD ?
; Номер дисковода
FDD_Type    db 0
; Координаты сектора
FDD_Track   DB ?
FDD_Head    DB ?
FDD_Sector  DB ?

; Блок результата операции
FDC_ST0 DB ?
FDC_ST1 DB ?
FDC_ST2 DB ?
FDC_C   DB ?
FDC_H   DB ?
FDC_R   DB ?
FDC_N   DB ?
; Счетчик повторения операции чтени
ReadRepCounter  DB ?
; Счетчик повторения операции рекалибровки
RecalRepCounter DB ?
endg
; Область памяти для хранения прочитанного сектора
;FDD_DataBuffer:  times 512 db 0   ;DB 512 DUP (?)
fdd_motor_status db 0
timer_fdd_motor  dd 0

;*************************************
;* ИНИЦИАЛИЗАЦИЯ РЕЖИМА ПДП ДЛЯ НГМД *
;*************************************
Init_FDC_DMA:
        pushad
        mov     al, 0
        out     0x0c, al; reset the flip-flop to a known state.
        mov     al, 6           ; mask channel 2 so we can reprogram it.
        out     0x0a, al
        mov     al, [dmamode]; 0x46 -> Read from floppy - 0x4A Write to floppy
        out     0x0b, al
        mov     al, 0
        out     0x0c, al; reset the flip-flop to a known state.
        mov     eax, 0xD000
        out     0x04, al; set the channel 2 starting address to 0
        shr     eax, 8
        out     0x04, al
        shr     eax, 8
        out     0x81, al
        mov     al, 0
        out     0x0c, al; reset flip-flop
        mov     al, 0xff;set count (actual size -1)
        out     0x5, al
        mov     al, 0x1;[dmasize]       ;(0x1ff = 511 / 0x23ff =9215)
        out     0x5, al
        mov     al, 2
        out     0xa, al
        popad
        ret

;***********************************
;* ЗАПИСАТЬ БАЙТ В ПОРТ ДАННЫХ FDC *
;* Параметры:                      *
;* AL - выводимый байт.            *
;***********************************
FDCDataOutput:
;       DEBUGF 1,'K : FDCDataOutput(%x)',al
;        pusha
        push    eax ecx edx
        mov     AH, AL    ;запомнить байт в AH
; Сбросить переменную состояния контроллера
        mov     [FDC_Status], FDC_Normal
; Проверить готовность контроллера к приему данных
        mov     DX, 3F4h  ;(порт состояния FDC)
        mov     ecx, 0x10000 ;установить счетчик тайм-аута
@@TestRS:
        in      AL, DX    ;прочитать регистр RS
        and     AL, 0C0h  ;выделить разряды 6 и 7
        cmp     AL, 80h   ;проверить разряды 6 и 7
        je      @@OutByteToFDC
        loop    @@TestRS
; Ошибка тайм-аута
;       DEBUGF 1,' timeout\n'
        mov     [FDC_Status], FDC_TimeOut
        jmp     @@End_5
; Вывести байт в порт данных
@@OutByteToFDC:
        inc     DX
        mov     AL, AH
        out     DX, AL
;        DEBUGF 1,' ok\n'
@@End_5:
;        popa
        pop     edx ecx eax
        ret

;******************************************
;*   ПРОЧИТАТЬ БАЙТ ИЗ ПОРТА ДАННЫХ FDC   *
;* Процедура не имеет входных параметров. *
;* Выходные данные:                       *
;* AL - считанный байт.                   *
;******************************************
FDCDataInput:
        push    ECX
        push    DX
; Сбросить переменную состояния контроллера
        mov     [FDC_Status], FDC_Normal
; Проверить готовность контроллера к передаче данных
        mov     DX, 3F4h  ;(порт состояния FDC)
        mov     ecx, 0x10000 ;установить счетчик тайм-аута
@@TestRS_1:
        in      AL, DX    ;прочитать регистр RS
        and     AL, 0C0h  ;выдлить разряды 6 и 7
        cmp     AL, 0C0h  ;проверить разряды 6 и 7
        je      @@GetByteFromFDC
        loop    @@TestRS_1
; Ошибка тайм-аута
;       DEBUGF 1,'K : FDCDataInput: timeout\n'
        mov     [FDC_Status], FDC_TimeOut
        jmp     @@End_6
; Ввести байт из порта данных
@@GetByteFromFDC:
        inc     DX
        in      AL, DX
;       DEBUGF 1,'K : FDCDataInput: %x\n',al
@@End_6:
        pop     DX
        pop     ECX
        ret

;*********************************************
;* ОБРАБОТЧИК ПРЕРЫВАНИЯ ОТ КОНТРОЛЛЕРА НГМД *
;*********************************************
FDCInterrupt:
;       dbgstr 'FDCInterrupt'
; Установить флаг прерывания
        mov     [FDD_IntFlag], 1
        mov     al, 1
        ret

;*******************************************
;* ОЖИДАНИЕ ПРЕРЫВАНИЯ ОТ КОНТРОЛЛЕРА НГМД *
;*******************************************
WaitFDCInterrupt:
        pusha
; Сбросить байт состояния операции
        mov     [FDC_Status], FDC_Normal
; Обнулить счетчик тиков
        mov     eax, [timer_ticks]
        mov     [TickCounter], eax
; Ожидать установки флага прерывания НГМД
@@TestRS_2:
        call    change_task
        cmp     [FDD_IntFlag], 0
        jnz     @@End_7           ;прерывание произошло
        mov     eax, [timer_ticks]
        sub     eax, [TickCounter]
        cmp     eax, 200;50 ;25   ;5 ;ожидать 5 тиков
        jb      @@TestRS_2
;        jl      @@TestRS_2
; Ошибка тайм-аута
;       dbgstr 'WaitFDCInterrupt: timeout'
        mov     [FDC_Status], FDC_TimeOut
@@End_7:
        popa
        ret

;*********************************
;* ВКЛЮЧИТЬ МОТОР ДИСКОВОДА "A:" *
;*********************************
FDDMotorON:
;       dbgstr 'FDDMotorON'
        pusha
;        cmp     [fdd_motor_status],1
;        je      fdd_motor_on
        mov     al, [flp_number]
        cmp     [fdd_motor_status], al
        je      fdd_motor_on
; Произвести сброс контроллера НГМД
        mov     DX, 3F2h;порт управления двигателями
        mov     AL, 0
        out     DX, AL
; Выбрать и включить мотор дисковода
        cmp     [flp_number], 1
        jne     FDDMotorON_B
;        call    FDDMotorOFF_B
        mov     AL, 1Ch   ; Floppy A
        jmp     FDDMotorON_1
FDDMotorON_B:
;        call    FDDMotorOFF_A
        mov     AL, 2Dh   ; Floppy B
FDDMotorON_1:
        out     DX, AL
; Обнулить счетчик тиков
        mov     eax, [timer_ticks]
        mov     [TickCounter], eax
; Ожидать 0,5 с
@@dT:
        call    change_task
        mov     eax, [timer_ticks]
        sub     eax, [TickCounter]
        cmp     eax, 50 ;10
        jb      @@dT
; Read results of RESET command
        push    4
;       DEBUGF 1,'K : floppy reset results:'
@@:
        mov     al, 8
        call    FDCDataOutput
        call    FDCDataInput
;       DEBUGF 1,' %x',al
        call    FDCDataInput
;       DEBUGF 1,' %x',al
        dec     dword [esp]
        jnz     @b
;       DEBUGF 1,'\n'
        pop     eax
        cmp     [flp_number], 1
        jne     fdd_motor_on_B
        mov     [fdd_motor_status], 1
        jmp     fdd_motor_on
fdd_motor_on_B:
        mov     [fdd_motor_status], 2
fdd_motor_on:
        call    save_timer_fdd_motor
        popa
        ret

;*****************************************
;*  СОХРАНЕНИЕ УКАЗАТЕЛЯ ВРЕМЕНИ         *
;*****************************************
save_timer_fdd_motor:
        mov     eax, [timer_ticks]
        mov     [timer_fdd_motor], eax
        ret

;*****************************************
;*  ПРОВЕРКА ЗАДЕРЖКИ ВЫКЛЮЧЕНИЯ МОТОРА  *
;*****************************************
proc check_fdd_motor_status_has_work?
        cmp     [fdd_motor_status], 0
        jz      .no
        mov     eax, [timer_ticks]
        sub     eax, [timer_fdd_motor]
        cmp     eax, 500
        jb      .no
.yes:
        xor     eax, eax
        inc     eax
        ret
.no:
        xor     eax, eax
        ret
endp

align 4
check_fdd_motor_status:
        cmp     [fdd_motor_status], 0
        je      end_check_fdd_motor_status_1
        mov     eax, [timer_ticks]
        sub     eax, [timer_fdd_motor]
        cmp     eax, 500
        jb      end_check_fdd_motor_status
        call    FDDMotorOFF
        mov     [fdd_motor_status], 0
end_check_fdd_motor_status_1:
end_check_fdd_motor_status:
        ret

;**********************************
;* ВЫКЛЮЧИТЬ МОТОР ДИСКОВОДА      *
;**********************************
FDDMotorOFF:
;       dbgstr 'FDDMotorOFF'
        push    AX
        push    DX
        cmp     [flp_number], 1
        jne     FDDMotorOFF_1
        call    FDDMotorOFF_A
        jmp     FDDMotorOFF_2
FDDMotorOFF_1:
        call    FDDMotorOFF_B
FDDMotorOFF_2:
        pop     DX
        pop     AX
        ; сброс флагов кеширования в связи с устареванием информации
        or      [floppy_media_flags+0], FLOPPY_MEDIA_NEED_RESCAN
        or      [floppy_media_flags+1], FLOPPY_MEDIA_NEED_RESCAN
        ret

FDDMotorOFF_A:
        mov     DX, 3F2h;порт управления двигателями
        mov     AL, 0Ch ; Floppy A
        out     DX, AL
        ret

FDDMotorOFF_B:
        mov     DX, 3F2h;порт управления двигателями
        mov     AL, 5h ; Floppy B
        out     DX, AL
        ret

;*******************************
;* РЕКАЛИБРОВКА ДИСКОВОДА "A:" *
;*******************************
RecalibrateFDD:
;       dbgstr 'RecalibrateFDD'
        pusha
        call    save_timer_fdd_motor
; Сбросить флаг прерывания
        mov     [FDD_IntFlag], 0
; Подать команду "Рекалибровка"
        mov     AL, 07h
        call    FDCDataOutput
        mov     AL, 00h
        call    FDCDataOutput
; Ожидать завершения операции
        call    WaitFDCInterrupt
        cmp     [FDC_Status], 0
        jne     .fail
; Read results of RECALIBRATE command
;       DEBUGF 1,'K : floppy recalibrate results:'
        mov     al, 8
        call    FDCDataOutput
        call    FDCDataInput
;       DEBUGF 1,' %x',al
        call    FDCDataInput
;       DEBUGF 1,' %x',al
;       DEBUGF 1,'\n'
.fail:
        call    save_timer_fdd_motor
        popa
        ret

;*****************************************************
;*                    ПОИСК ДОРОЖКИ                  *
;* Параметры передаются через глобальные переменные: *
;* FDD_Track - номер дорожки (0-79);                 *
;* FDD_Head - номер головки (0-1).                   *
;* Результат операции заносится в FDC_Status.        *
;*****************************************************
SeekTrack:
;       dbgstr 'SeekTrack'
        pusha
        call    save_timer_fdd_motor
; Сбросить флаг прерывания
        mov     [FDD_IntFlag], 0
; Подать команду "Поиск"
        mov     AL, 0Fh
        call    FDCDataOutput
        ; Передать байт номера головки/накопител
        mov     AL, [FDD_Head]
        shl     AL, 2
        call    FDCDataOutput
        ; Передать байт номера дорожки
        mov     AL, [FDD_Track]
        call    FDCDataOutput
; Ожидать завершения операции
        call    WaitFDCInterrupt
        cmp     [FDC_Status], FDC_Normal
        jne     @@Exit
; Сохранить результат поиска
        mov     AL, 08h
        call    FDCDataOutput
        call    FDCDataInput
        mov     [FDC_ST0], AL
        call    FDCDataInput
        mov     [FDC_C], AL
; Проверить результат поиска
        ; Поиск завершен?
        test    [FDC_ST0], 100000b
        je      @@Err
        ; Заданный трек найден?
        mov     AL, [FDC_C]
        cmp     AL, [FDD_Track]
        jne     @@Err
        ; Номер головки совпадает с заданным?
; The H bit (Head Address) in ST0 will always return a "0" (c) 82077AA datasheet,
; description of SEEK command. So we can not verify the proper head.
;        mov     AL, [FDC_ST0]
;        and     AL, 100b
;        shr     AL, 2
;        cmp     AL, [FDD_Head]
;        jne     @@Err
        ; Операция завершена успешно
;        dbgstr 'SeekTrack: FDC_Normal'
        mov     [FDC_Status], FDC_Normal
        jmp     @@Exit
@@Err:  ; Трек не найден
;       dbgstr 'SeekTrack: FDC_TrackNotFound'
        mov     [FDC_Status], FDC_TrackNotFound
@@Exit:
        call    save_timer_fdd_motor
        popa
        ret

;*******************************************************
;*               ЧТЕНИЕ СЕКТОРА ДАННЫХ                 *
;* Параметры передаются через глобальные переменные:   *
;* FDD_Track - номер дорожки (0-79);                   *
;* FDD_Head - номер головки (0-1);                     *
;* FDD_Sector - номер сектора (1-18).                  *
;* Результат операции заносится в FDC_Status.          *
;* В случае успешного выполнения операции чтения       *
;* содержимое сектора будет занесено в FDD_DataBuffer. *
;*******************************************************
ReadSector:
;       dbgstr 'ReadSector'
        pushad
        call    save_timer_fdd_motor
; Сбросить флаг прерывания
        mov     [FDD_IntFlag], 0
; Установить скорость передачи 500 Кбайт/с
        mov     AX, 0
        mov     DX, 03F7h
        out     DX, AL
; Инициализировать канал прямого доступа к памяти
        mov     [dmamode], 0x46
        call    Init_FDC_DMA
; Подать команду "Чтение данных"
        mov     AL, 0E6h ;чтение в мультитрековом режиме
        call    FDCDataOutput
        mov     AL, [FDD_Head]
        shl     AL, 2
        call    FDCDataOutput
        mov     AL, [FDD_Track]
        call    FDCDataOutput
        mov     AL, [FDD_Head]
        call    FDCDataOutput
        mov     AL, [FDD_Sector]
        call    FDCDataOutput
        mov     AL, 2   ;код размера сектора (512 байт)
        call    FDCDataOutput
        mov     AL, 18 ;+1; 3Fh  ;число секторов на дорожке
        call    FDCDataOutput
        mov     AL, 1Bh ;значение GPL
        call    FDCDataOutput
        mov     AL, 0FFh;значение DTL
        call    FDCDataOutput
; Ожидаем прерывание по завершении операции
        call    WaitFDCInterrupt
        cmp     [FDC_Status], FDC_Normal
        jne     @@Exit_1
; Считываем статус завершения операции
        call    GetStatusInfo
        test    [FDC_ST0], 11011000b
        jnz     @@Err_1
;        dbgstr 'ReadSector: FDC_Normal'
        mov     [FDC_Status], FDC_Normal
        jmp     @@Exit_1
@@Err_1:
;       dbgstr 'ReadSector: FDC_SectorNotFound'
        mov     [FDC_Status], FDC_SectorNotFound
@@Exit_1:
        call    save_timer_fdd_motor
        popad
        ret

;*******************************************************
;*   ЧТЕНИЕ СЕКТОРА (С ПОВТОРЕНИЕМ ОПЕРАЦИИ ПРИ СБОЕ)  *
;* Параметры передаются через глобальные переменные:   *
;* FDD_Track - номер дорожки (0-79);                   *
;* FDD_Head - номер головки (0-1);                     *
;* FDD_Sector - номер сектора (1-18).                  *
;* Результат операции заносится в FDC_Status.          *
;* В случае успешного выполнения операции чтения       *
;* содержимое сектора будет занесено в FDD_DataBuffer. *
;*******************************************************
ReadSectWithRetr:
        pusha
; Обнулить счетчик повторения операции рекалибровки
        mov     [RecalRepCounter], 0
@@TryAgain:
; Обнулить счетчик повторения операции чтени
        mov     [ReadRepCounter], 0
@@ReadSector_1:
        call    ReadSector
        cmp     [FDC_Status], 0
        je      @@Exit_2
        cmp     [FDC_Status], 1
        je      @@Err_3
        ; Троекратное повторение чтени
        inc     [ReadRepCounter]
        cmp     [ReadRepCounter], 3
        jb      @@ReadSector_1
        ; Троекратное повторение рекалибровки
        call    RecalibrateFDD
        call    SeekTrack
        inc     [RecalRepCounter]
        cmp     [RecalRepCounter], 3
        jb      @@TryAgain
@@Exit_2:
        popa
        ret
@@Err_3:
        popa
        ret

;*******************************************************
;*               ЗАПИСЬ СЕКТОРА ДАННЫХ                 *
;* Параметры передаются через глобальные переменные:   *
;* FDD_Track - номер дорожки (0-79);                   *
;* FDD_Head - номер головки (0-1);                     *
;* FDD_Sector - номер сектора (1-18).                  *
;* Результат операции заносится в FDC_Status.          *
;* В случае успешного выполнения операции записи       *
;* содержимое FDD_DataBuffer будет занесено в сектор.  *
;*******************************************************
WriteSector:
;       dbgstr 'WriteSector'
        pushad
        call    save_timer_fdd_motor
; Сбросить флаг прерывания
        mov     [FDD_IntFlag], 0
; Установить скорость передачи 500 Кбайт/с
        mov     AX, 0
        mov     DX, 03F7h
        out     DX, AL
; Инициализировать канал прямого доступа к памяти
        mov     [dmamode], 0x4A
        call    Init_FDC_DMA
; Подать команду "Запись данных"
        mov     AL, 0xC5 ;0x45  ;запись в мультитрековом режиме
        call    FDCDataOutput
        mov     AL, [FDD_Head]
        shl     AL, 2
        call    FDCDataOutput
        mov     AL, [FDD_Track]
        call    FDCDataOutput
        mov     AL, [FDD_Head]
        call    FDCDataOutput
        mov     AL, [FDD_Sector]
        call    FDCDataOutput
        mov     AL, 2   ;код размера сектора (512 байт)
        call    FDCDataOutput
        mov     AL, 18; 3Fh  ;число секторов на дорожке
        call    FDCDataOutput
        mov     AL, 1Bh ;значение GPL
        call    FDCDataOutput
        mov     AL, 0FFh;значение DTL
        call    FDCDataOutput
; Ожидаем прерывание по завершении операции
        call    WaitFDCInterrupt
        cmp     [FDC_Status], FDC_Normal
        jne     @@Exit_3
; Считываем статус завершения операции
        call    GetStatusInfo
        test    [FDC_ST0], 11000000b ;11011000b
        jnz     @@Err_2
        mov     [FDC_Status], FDC_Normal
        jmp     @@Exit_3
@@Err_2:
        mov     [FDC_Status], FDC_SectorNotFound
@@Exit_3:
        call    save_timer_fdd_motor
        popad
        ret

;*******************************************************
;*   ЗАПИСЬ СЕКТОРА (С ПОВТОРЕНИЕМ ОПЕРАЦИИ ПРИ СБОЕ)  *
;* Параметры передаются через глобальные переменные:   *
;* FDD_Track - номер дорожки (0-79);                   *
;* FDD_Head - номер головки (0-1);                     *
;* FDD_Sector - номер сектора (1-18).                  *
;* Результат операции заносится в FDC_Status.          *
;* В случае успешного выполнения операции записи       *
;* содержимое FDD_DataBuffer будет занесено в сектор.  *
;*******************************************************
WriteSectWithRetr:
        pusha
; Обнулить счетчик повторения операции рекалибровки
        mov     [RecalRepCounter], 0
@@TryAgain_1:
; Обнулить счетчик повторения операции чтени
        mov     [ReadRepCounter], 0
@@WriteSector_1:
        call    WriteSector
        cmp     [FDC_Status], 0
        je      @@Exit_4
        cmp     [FDC_Status], 1
        je      @@Err_4
        ; Троекратное повторение чтени
        inc     [ReadRepCounter]
        cmp     [ReadRepCounter], 3
        jb      @@WriteSector_1
        ; Троекратное повторение рекалибровки
        call    RecalibrateFDD
        call    SeekTrack
        inc     [RecalRepCounter]
        cmp     [RecalRepCounter], 3
        jb      @@TryAgain_1
@@Exit_4:
        popa
        ret
@@Err_4:
        popa
        ret

;*********************************************
;* ПОЛУЧИТЬ ИНФОРМАЦИЮ О РЕЗУЛЬТАТЕ ОПЕРАЦИИ *
;*********************************************
GetStatusInfo:
        push    AX
        call    FDCDataInput
        mov     [FDC_ST0], AL
        call    FDCDataInput
        mov     [FDC_ST1], AL
        call    FDCDataInput
        mov     [FDC_ST2], AL
        call    FDCDataInput
        mov     [FDC_C], AL
        call    FDCDataInput
        mov     [FDC_H], AL
        call    FDCDataInput
        mov     [FDC_R], AL
        call    FDCDataInput
        mov     [FDC_N], AL
        pop     AX
        ret

; Interface for disk subsystem.
; Assume fixed capacity for 1.44M.
FLOPPY_CAPACITY = 2880  ; in sectors

iglobal
align 4
floppy_functions:
        dd      .size
        dd      0       ; no close() function
        dd      0       ; no closemedia() function
        dd      floppy_querymedia
        dd      floppy_read
        dd      floppy_write
        dd      0       ; no flush() function
        dd      0       ; no adjust_cache_size() function
.size = $ - floppy_functions
endg

uglobal
floppy_media_flags      rb      2
n_sector    dd 0  ; temporary save for sector value
flp_number  db 0  ; 1- Floppy A, 2-Floppy B
old_track   db 0  ; old value track
flp_label   rb 15*2 ; Label and ID of inserted floppy disk
align 4
; Hardware does not allow to work with two floppies in parallel,
; so there is one mutex guarding access to any floppy.
floppy_mutex    MUTEX
endg
; Meaning of bits in floppy_media_flags
FLOPPY_MEDIA_PRESENT = 1        ; media was present when last asked
FLOPPY_MEDIA_NEED_RESCAN = 2    ; media was possibly changed, need to rescan
FLOPPY_MEDIA_LABEL_CHANGED = 4  ; temporary state

iglobal
floppy1_name    db      'fd',0
floppy2_name    db      'fd2',0
endg

; This function is called in boot process.
; It creates filesystems /fd and/or /fd2, if the system has one/two floppy drives.
proc floppy_init
        mov     ecx, floppy_mutex
        call    mutex_init
; First floppy is present if [DRIVE_DATA] and 0xF0 is nonzero.
        test    byte [DRIVE_DATA], 0xF0
        jz      .no1
        stdcall disk_add, floppy_functions, floppy1_name, 1, DISK_NO_INSERT_NOTIFICATION
.no1:
; Second floppy is present if [DRIVE_DATA] and 0x0F is nonzero.
        test    byte [DRIVE_DATA], 0x0F
        jz      .no2
        stdcall disk_add, floppy_functions, floppy2_name, 2, DISK_NO_INSERT_NOTIFICATION
.no2:
        ret
endp

; Returns information about disk media.
; Floppy drives do not support insert notifications,
; DISK_NO_INSERT_NOTIFICATION is set,
; the disk subsystem calls this function before each filesystem operation.
; If the media has changed, return error for the first call as signal
; to finalize work with old media and the true geometry for the second call.
; Assume that media is (possibly) changed anytime when motor is off.
proc floppy_querymedia
  virtual at esp+4
    .userdata dd ?
    .info dd ?
  end virtual
; 1. Acquire the global lock.
        mov     ecx, floppy_mutex
        call    mutex_lock
        mov     edx, [.userdata]        ; 1 for /fd, 2 for /fd2
; 2. If the media was reported and has been changed, forget it and report an error.
        mov     al, [floppy_media_flags+edx-1]
        and     al, FLOPPY_MEDIA_PRESENT + FLOPPY_MEDIA_NEED_RESCAN
        cmp     al, FLOPPY_MEDIA_PRESENT + FLOPPY_MEDIA_NEED_RESCAN
        jnz     .not_reported
.no_media:
        mov     [floppy_media_flags+edx-1], 0
.return_no_media:
        mov     ecx, floppy_mutex
        call    mutex_unlock
        mov     eax, DISK_STATUS_NO_MEDIA
        retn    8
.not_reported:
; 3. If we are in the temporary state LABEL_CHANGED, this is the second call
; after intermediate DISK_STATUS_NO_MEDIA due to media change;
; clear the flag and return the current geometry without rereading the bootsector.
        cmp     [floppy_media_flags+edx-1], FLOPPY_MEDIA_LABEL_CHANGED
        jz      .report_geometry
; 4. Try to read the bootsector.
        mov     [flp_number], dl
        mov     [FDC_Status], 0
        call    floppy_read_bootsector
; 5. If reading bootsector failed, assume that media is not present.
        mov     edx, [.userdata]
        cmp     [FDC_Status], 0
        jnz     .no_media
; 6. Check whether the previous status is "present". If not, go to 10.
        push    esi edi
        imul    edi, edx, 15
        add     edi, flp_label-15
        mov     esi, FDD_BUFF+39
        mov     ecx, 15
        test    [floppy_media_flags+edx-1], FLOPPY_MEDIA_PRESENT
        jz      .set_label
; 7. Compare the old label with the current one.
        rep cmpsb
; 8. If the label has not changed, go to 11.
        jz      .ok
; 9. If the label has changed, store it, enter temporary state LABEL_CHANGED
; and report DISK_STATUS_NO_MEDIA.
;       dbgstr 'floppy label changed'
        add     esi, ecx
        add     edi, ecx
        mov     ecx, 15
        sub     esi, ecx
        sub     edi, ecx
        rep movsb
        mov     [floppy_media_flags+edx-1], FLOPPY_MEDIA_LABEL_CHANGED
        pop     edi esi
        jmp     .return_no_media
.set_label:
; 10. The previous state was "not present". Copy the label.
        rep movsb
.ok:
        pop     edi esi
.report_geometry:
; 11. Fill DISKMEDIAINFO structure.
        mov     ecx, [.info]
        and     [ecx+DISKMEDIAINFO.Flags], 0
        mov     [ecx+DISKMEDIAINFO.SectorSize], 512
        mov     dword [ecx+DISKMEDIAINFO.Capacity], FLOPPY_CAPACITY
        and     dword [ecx+DISKMEDIAINFO.Capacity+4], 0
; 12. Update state: media is present, data are actual.
        mov     [floppy_media_flags+edx-1], FLOPPY_MEDIA_PRESENT
; 13. Release the global lock and return successful status.
        mov     ecx, floppy_mutex
        call    mutex_unlock
        xor     eax, eax
        retn    8
endp

proc floppy_read_bootsector
        pushad
        mov     [FDD_Track], 0; Цилиндр
        mov     [FDD_Head], 0; Сторона
        mov     [FDD_Sector], 1; Сектор
        call    FDDMotorON
        call    RecalibrateFDD
        cmp     [FDC_Status], 0
        jne     .nothing
        call    SeekTrack
        cmp     [FDC_Status], 0
        jne     .nothing
        call    ReadSectWithRetr
.nothing:
        popad
        ret
endp

read_chs_sector:
        call    calculate_chs
        call    ReadSectWithRetr
        ret

save_chs_sector:
        call    calculate_chs
        call    WriteSectWithRetr
        ret

calculate_chs:
        mov     bl, [FDD_Track]
        mov     [old_track], bl
        mov     ebx, 18
        xor     edx, edx
        div     ebx
        inc     edx
        mov     [FDD_Sector], dl
        mov     edx, eax
        shr     eax, 1
        and     edx, 1
        mov     [FDD_Track], al
        mov     [FDD_Head], dl
        mov     dl, [old_track]
        cmp     dl, [FDD_Track]
        je      no_seek_track_1
        call    SeekTrack
no_seek_track_1:
        ret

; Writes one or more sectors to the device.
proc floppy_write
        mov     dl, 1
        jmp     floppy_read_write
endp

; Reads one or more sectors from the device.
proc floppy_read
        mov     dl, 0
endp

; Common part of floppy_read and floppy_write.
proc floppy_read_write userdata:dword, buffer:dword, start_sector:qword, numsectors_ptr:dword
virtual at ebp-8
.sectors_todo   dd      ?
.operation      db      ?
end virtual
        push    edx             ; save operation code to [.operation]
; 1. Get number of sectors to read/write
; and zero number of sectors that were actually read/written.
        mov     eax, [numsectors_ptr]
        push    dword [eax]     ; initialize [.sectors_todo]
        and     dword [eax], 0
        push    ebx esi edi     ; save used registers to be stdcall
; 2. Acquire the global lock.
        mov     ecx, floppy_mutex
        call    mutex_lock
; 3. Set floppy number for this operation.
        mov     edx, [userdata]
        mov     [flp_number], dl
; 4. Read/write sector-by-sector.
.operation_loop:
; 4a. Check that the sector is inside the media.
        cmp     dword [start_sector+4], 0
        jnz     .end_of_media
        mov     eax, dword [start_sector]
        cmp     eax, FLOPPY_CAPACITY
        jae     .end_of_media
; 4b. For read operation, call read_chs_sector and then move data from FDD_BUFF to [buffer].
; For write operation, move data from [buffer] to FDD_BUFF and then call save_chs_sector.
        cmp     [.operation], 0
        jz      .read
        mov     esi, [buffer]
        mov     edi, FDD_BUFF
        mov     ecx, 512/4
        rep movsd
        mov     [buffer], esi
        call    save_chs_sector
        jmp     @f
.read:
        call    read_chs_sector
        mov     esi, FDD_BUFF
        mov     edi, [buffer]
        mov     ecx, 512/4
        rep movsd
        mov     [buffer], edi
@@:
; 4c. If there was an error, propagate it to the caller.
        cmp     [FDC_Status], 0
        jnz     .fail
; 4d. Otherwise, increment number of sectors processed and continue the loop.
        mov     eax, [numsectors_ptr]
        inc     dword [eax]
        inc     dword [start_sector]
        dec     [.sectors_todo]
        jnz     .operation_loop
; 5. Release the global lock and return with the correct status.
        push    0
.return:
        mov     ecx, floppy_mutex
        call    mutex_unlock
        pop     eax
        pop     edi esi ebx     ; restore used registers to be stdcall
        ret     ; this translates to leave/retn N and purges local variables
.fail:
        push    -1
        jmp     .return
.end_of_media:
        push    DISK_STATUS_END_OF_MEDIA
        jmp     .return
endp