The Floppy Drive

The Floppy Drive

Robbert Haarman

2005-05-08


Introduction

Accessing the floppy drive on a PC is one of the most complex tasks. Besides programming the floppy drive controller (FDC), it requires programming the DMA controller, because the FDC uses it to transfer data to and from memory. In this part, we develop all the code necessary to read data from the floppy drive.


DMA

Recall that, in the keyboard handler, we were reading bytes from the keyboard controller with in al, 60h. This instruction uses the CPU to read one byte from the keyboard controller and store it in al. We can then transfer this byte to memory with a mov instruction.

The DMA controller allows us to transfer data between peripherals and memory without using the CPU (Well, almost. We use the CPU to program the DMA controller first.) This has the advantage that the CPU can work on other things while data is being transfered in the background. Originally, the DMA controller could also transfer data much faster than the CPU, but the extreme increase of CPU speeds has reversed that picture.

In order to use the DMA controller to transfer data from the FDC to memory, we must initialize the DMA controller with the right parameters. The controller has four channels, 0 through 3. Channel 2 is connected to the FDC. Before we start fiddling with it, we disable the channel. This can be done by writing to the mask register of the DMA controller, located at port 0x0a. We pass the number of the channel to mask in the lower 2 bits, and set bit 2 (this indicates we want to disable the channel).

; Disable channel 2
mov al, 110b
out 0ah, al

Now we can set the type of transfer. We are going to perform a write transfer (meaning that it writes to memory), in single mode (meaning the DMA controller will stop after the transfer is done). For this, we need to write a byte to port 0x0b, which is the controller's mode register. FIXME: Explain value.

; Set single write mode for channel 2
mov al, 01000110b
out 0bh, al

We also need to set the address to transfer data to. When talking to the DMA controller, we specify the address as a 64KB page and an offset within that page. For channel 2, the page number is written to port 0x81, and the offset to port 4. Both registers are 8-bit. For the page register, this is not a problem, as 4 bits are enough to encode any page accessible to the CPU. However, we need 16 bits to encode the offset. The trick used by the DMA controller is that the offset register is written to twice, once for the low byte and once for the high byte. The controller keeps track of which one it expects by means of a flip-flop, which we will need to initialize first. This is accomplished by writing any byte to port 0x0c. The full sequence is as follows:

; Set start address
out 0ch, al	; Initialize flip-flop
; Write offset
mov al, [low_offset]
out 04h, al
mov al, [high_offset]
out 04h, al
; Write page
mov al, [page]
out 081h, al

Next, we specify how much data we want to transfer by writing to port 5. The value to write is the number of 16-bit words, minus one. Just as with the start offset, we write a low and a high byte.

; Set count
out 0ch, al	; Initialize flip-flop
mov al, [low_count]
out 05h, al
mov al, [high_count]
out 05h, al

Finally, we enable the channel. This works just like disabling it, except that bit 2 is cleared.

; Enable channel 2
mov al, 10b
out 0ah, al

That's it for the DMA controller. Now comes the floppy drive.


The Floppy Drive Controller

The floppy drive controller (FDC) is a complex beast, sporting a number of registers and several commands. This section describes how to access some basic functions of the NEC PD765, which other FDCs are compatible with, at least enough for our purposes.

Magic Numbers

Although the FDC has several ports, for now we only need the digital output register (DOR) at 0x3f1, the main status register (MSR) at 0x3f4 and the data register at 0x3f5. All these registers are 8 bits wide. These registers are for the primary controller; ports are also reserved for a secondary controller at addresses 80h lower than the ones for the primary controller. However, most PCs don't have a secondary FDC, so we'll focus on the primary FDC here. In addition to the aforementioned ports, the FDC uses IRQ 6 and DMA channel 2. Let's define all these magic values as constants:


FDC_BASE EQU 3f0h
FDC_DOR EQU FDC_BASE + 1
FDC_MSR EQU FDC_BASE + 4
FDC_DATA EQU FDC_BASE + 5
FDC_IRQ EQU 6
FDC_DMA EQU 2

IRQ Handler

Now that we know the registers, IRQ, and DMA channel to use, we can go on to program the FDC. First of all, we will set up a handler for the IRQ. IRQ 6 is triggered when the FDC completes a command. Our IRQ handler will increment a byte in memory, to signal that the IRQ has been triggered:


fdcIRQHandler:
inc byte [cs:_fdcDone]
mov al, 60h + FDC_IRQ
out 20h, al
iret

Now we can write a routine that waits for that byte to be incremented:


_waitFDCDone:
test [cs:_fdcDone], byte 0xff
jnz .end
hlt
jmp _waitFDCDone
.end:
mov [cs:_fdcDone], byte 0
ret

The routine simply waits until the byte is nonzero, and once it is, it sets it back to zero and returns. Now, whenever we need to wait for the FDC to complete a command, we can call _waitFDCDone.

Sending and Receiving Data

Next, we will want to be able to send and receive data to and from the FDC. In principle, this is as simple as reading and writing the data regsiter, but we may only do so when the FDC is ready for it. The FDC indicates this by setting the most significant bit in the main status register. In addition, the next most significant bit of that register indicates whether the FDC expects us to read (1) or write (0) the data register. Thus, the procedures for reading and writing the data register can be coded as follows:


_FDCRecvByte:
mov dx, FDC_MSR
.loop0:
in al, dx
test al, 11000000b
jnz .end
hlt
jmp .loop0
.end:
mov dx, FDC_DATA
in al, dx
ret

_FDCSendByte:
push bp
mov bp, sp
mov dx, FDC_MSR
.loop0:
in al, dx
test al, 10000000b
jnz .end
hlt
jmp .loop0
.end:
mov dx, FDC_DATA
mov al, [bp + 4]
out dx, al
pop bp
ret

Spinning Up the Drive

We're almost ready to start sending commands to the FDC, but first there is one very important step: spinning up the disk drive! This is where the digital output register comes in. Its structure is as follows:

bit76543210
meaningmot3mot2mot1mot0dma rstdrive

The motx fields address the motors of the disk drives. The PD765 supports up to four drives, so there are four motx fields. A drive motor is activated by setting the corresponding field to 1, and disabled by setting it to 0. The dma field can be used to enable (1) or disable (0) the use of DMA. The FDCs in most PCs won't function without DMA, so the field should be set to 1. The rst field can be used to reset the controller; this is done by setting it to 0. Since we don't want to reset the FDC now, we will set it to 1. Finally, the last two bits select the disk drive. I don't know what effect that has, but it seems a good idea to select the disk drive whose motor you're activating. Finally, after instructing the FDC to spin up a drive, we need to wait a bit while it gets up to speed. The FDC does not seem to give any sign when this is done, so we'll just have to guess how long it takes. Half a second seems long enough. The code for starting the motor of the first drive, then, looks as follows: (FIXME: timer has not been explained yet)


mov dx, FDC_DOR
mov al, 00011100b	; start motor 0, activate DMA/IRQ, no reset, 
drive 00
out dx, al
push word 9		; Wait for 9 jiffies (half a second)
mov ax, SYSCALL_WAIT_JIFFIES
int SYSCALL_INTERRUPT
pop ax

Files

The file dma.inc contains code for working with the DMA controller. The file floppy.inc contains code to work with the FDC. syscall.inc contains updated system call numbers for use with the DMA controller and the FDC. Finally, the file floppy_kernel.asm contains a kernel which reads a messages from sectors 18 and 36 on the diskette and displays them.

An example message is contained in secretmessage.txt. This message can be written to the diskette with a command like dd if=secretmessage.txt of=/dev/fd0 bs=512 seek=18. Compiling the kernel and writing it to the diskette works as usual.

Comments

Posted by bubach
at 2005-05-23 01:05:40

great work, please continue writing os-dev tutorials, and please finish the fdd article.

Posted by inglorion
at 2005-05-23 03:05:05

Thanks. I will resume work as soon as I have a PC with a working floppy driveagain (which should be Real Soon Now).

After the fdd chapter, there will be a filesystem chapter (code is not finishedyet), a shell, and probably a few little programs. I plan to wrap it all up thissummer.

Posted by hackgod
at 2005-05-25 02:05:43

Great Tutorials! Please continue writing so great Tutorials for os-dev! Could you make a Tutorial about Protected Mode? That would be nice!

Posted by Mike Austin
at 2005-06-04 11:06:19

I always thought I was difficult to build a simple OS, but I now realize it's really not - thanks!. I only wish the article didn't use optimization techniques that make it hard for people who don't know assembly well. For example "xor ax, ax" vs. "mov ax, 0". I would also love to see a protected mode tutorial.

-- Mike

Post a comment