summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse <jesse.buhagiar@student.rmit.edu.au>2019-07-17 23:51:51 +1000
committerAndreas Kling <awesomekling@gmail.com>2019-07-17 15:51:51 +0200
commit10ffaf019f7437f86dca0bbcc5e983c4bbabc824 (patch)
treed73ed21614a8f93f596e02308b5cd5c791d1890a
parent4f94fbc9e1dc9563a03e3464ee3b03ac5d835d92 (diff)
downloadserenity-10ffaf019f7437f86dca0bbcc5e983c4bbabc824.zip
Kernel: Initial FDC Device Driver (#315)
A basic Floppy Disk Controller device driver for any system later than (and including) the IBM AT. The driver is based on the documentation supplied by QEMU, which is the datasheet for the Intel 82078 Floppy Disk controller (found here: https://wiki.qemu.org/images/f/f0/29047403.pdf) Naturally, floppy disks are a _very_ outdated storage medium, however, as Serenity is a throwback to aesthetic 90s computing, it's a definite must have. Not to mention that there are still a lot of floppy disks around, with countless petabytes of software on them, so it would be nice if people could create images of said disks with serenity. The code for this is mostly clean. however there are a LOT of values specified in the datasheet, so some of them might be wrong, not to mention that the actual specification itself is rather dirt and seemingly hacked together. I'm also only supporting 3.5" floppy disks, without PIO polling (DMA only), so if you want anything more/less than 1.44MB HD Floppys, you'll have to do it yourself.
-rw-r--r--Kernel/Devices/FloppyDiskDevice.cpp520
-rw-r--r--Kernel/Devices/FloppyDiskDevice.h190
-rw-r--r--Kernel/Makefile1
-rw-r--r--Kernel/init.cpp20
4 files changed, 731 insertions, 0 deletions
diff --git a/Kernel/Devices/FloppyDiskDevice.cpp b/Kernel/Devices/FloppyDiskDevice.cpp
new file mode 100644
index 0000000000..a895359950
--- /dev/null
+++ b/Kernel/Devices/FloppyDiskDevice.cpp
@@ -0,0 +1,520 @@
+#include <Kernel/Arch/i386/PIT.h>
+#include <Kernel/Devices/FloppyDiskDevice.h>
+#include <Kernel/FileSystem/ProcFS.h>
+#include <Kernel/IO.h>
+#include <Kernel/Process.h>
+#include <Kernel/VM/MemoryManager.h>
+
+// Uncomment me for a LOT of output
+//#define FLOPPY_DEBUG
+
+// THESE ARE OFFSETS!
+#define FLOPPY_STATUS_A 0x00 // ro
+#define FLOPPY_STATUS_B 0x01 // ro
+#define FLOPPY_DOR 0x02 // rw
+#define FLOPPY_TDR 0x03 // rw
+#define FLOPPY_MSR 0x04 // ro
+#define FLOPPY_DSR 0x04 // wo
+#define FLOPPY_FIFO 0x05
+#define FLOPPY_RSVD 0x06
+#define FLOPPY_DIR 0x07 // ro
+#define FLOPPY_CCR 0x07 // wo
+
+#define FLOPPY_STATUS_DIR 0x01
+#define FLOPPY_STATUS_WP 0x02
+#define FLOPPY_STATUS_INDX 0x04
+#define FLOPPY_STATUS_HDSEL 0x08
+#define FLOPPY_STATUS_TRK0 0x10
+#define FLOPPY_STATUS_STEP 0x20
+#define FLOPPY_STATUS_DRV2 0x40
+#define FLOPPY_STATUS_INTW 0x80 // A.K.A INT_PENDING
+
+#define FLOPPY_DOR_DRVSEL0 0x01
+#define FLOPPY_DOR_DRVSEL1 0x02
+#define FLOPPY_DOR_RESET 0x04
+#define FLOPPY_DOR_DMAGATE 0x08
+#define FLOPPY_DOR_MOTEN0 0x10
+#define FLOPPY_DOR_MOTEN1 0x20
+#define FLOPPY_DOR_MOTEN2 0x40
+#define FLOPPY_DOR_MOTEN3 0x80
+// Preset values to activate drive select and motor enable for each drive
+#define FLOPPY_DOR_DRV0 0x1C
+#define FLOPPY_DOR_DRV1 0x2D
+#define FLOPPY_DOR_DRV2 0x4E
+#define FLOPPY_DOR_DRV3 0x8F
+
+#define FLOPPY_MSR_FDD0BSY 0x01
+#define FLOPPY_MSR_FDD1BSY 0x02
+#define FLOPPY_MSR_FDD2BSY 0x04
+#define FLOPPY_MSR_FDD3BSY 0x08
+#define FLOPPY_MSR_FDCBSY 0x10
+#define FLOPPY_MSR_MODE 0x20 // 0 in DMA mode, 1 in PIO mode
+#define FLOPPY_MSR_DIO 0x40 // 0 FDC is expecting data from the CPU, 1 if FDC has data for CPU
+#define FLOPPY_MSR_RQM 0x80 // 0 Data register not ready, 1 data register ready
+
+#define FLOPPY_CCR_DRTESEL0 0x01
+#define FLOPPY_CCR_DRTESEL1 0x02
+
+#define FLOPPY_MT 0x80 // Multi-track selector. The controller treats 2 tracks (on side 0 and side 1) as a single track instead
+#define FLOPPY_MFM 0x40 // 1 Means this disk is double density (double sided??)
+#define FLOPPY_SK 0x20 // Skip flag. Skips sectors containing deleted data automatically for us :)
+
+#define SR0_OKAY (0x00) << 6
+#define SR0_ABORMAL_TERMINATION (0x01) << 6
+#define SR0_INVALID_CMD (0x02) << 6
+#define SR0_ABNORMAL_TERM_POLL (0x03) << 6
+
+#define FLOPPY_DMA_CHANNEL 2 // All FDCs are DMA channel 2
+#define IRQ_FLOPPY_DRIVE 6
+
+NonnullRefPtr<FloppyDiskDevice> FloppyDiskDevice::create(DriveType type)
+{
+ return adopt(*new FloppyDiskDevice(type));
+}
+
+const char* FloppyDiskDevice::class_name() const
+{
+ if (m_controller_version == 0x90)
+ return "Intel 82078 Floppy Disk Controller";
+ else if (m_controller_version == 0x80)
+ return "NEC uPD765";
+
+ return "Generic Floppy Disk Controller";
+}
+
+FloppyDiskDevice::FloppyDiskDevice(FloppyDiskDevice::DriveType type)
+ : IRQHandler(IRQ_FLOPPY_DRIVE)
+ , m_io_base_addr((type == FloppyDiskDevice::DriveType::Master) ? 0x3F0 : 0x370)
+{
+ initialize();
+}
+
+FloppyDiskDevice::~FloppyDiskDevice()
+{
+}
+
+unsigned FloppyDiskDevice::block_size() const
+{
+ return BYTES_PER_SECTOR;
+}
+
+bool FloppyDiskDevice::read_block(unsigned index, u8* data) const
+{
+ return const_cast<FloppyDiskDevice*>(this)->read_blocks(index, 1, data);
+}
+
+bool FloppyDiskDevice::write_block(unsigned index, const u8* data)
+{
+ return write_sectors_with_dma(index, 1, data);
+}
+
+bool FloppyDiskDevice::read_blocks(unsigned index, u16 count, u8* data)
+{
+ return read_sectors_with_dma(index, count, data);
+}
+
+bool FloppyDiskDevice::write_blocks(unsigned index, u16 count, const u8* data)
+{
+ return write_sectors_with_dma(index, count, data);
+ ;
+}
+
+bool FloppyDiskDevice::read_sectors_with_dma(u16 lba, u16 count, u8* outbuf)
+{
+ LOCKER(m_lock); // Acquire lock
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: read_sectors_with_dma lba = %d count = %d\n", lba, count);
+#endif
+
+ motor_enable(is_slave() ? 1 : 0); // Should I bother casting this?!
+ write_ccr(0);
+ recalibrate(); // Recalibrate the drive
+
+ // We have to wait for about 300ms for the drive to spin up, because of
+ // the inertia of the motor and diskette. This is only
+ // important on real hardware
+ // TODO: Fix this if you want to get it running on real hardware. This code doesn't allow
+ // time for the disk to spin up.
+
+ //u32 start = PIT::seconds_since_boot();
+ //while(start < PIT::seconds_since_boot() + 1)
+ // ;
+
+ disable_irq();
+
+ IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1
+ IO::out8(0x0B, 0x56); // Begin DMA, Single Transfer, Increment, Auto, FDC -> RAM, Channel 2
+ IO::out8(0xA, 0x2); // Unmask channel 2. The transfer will now begin
+
+ // Translate the LBA address into something the FDC understands.
+ u16 cylinder = lba2cylinder(lba);
+ u16 head = lba2head(lba);
+ u16 sector = lba2sector(lba);
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: addr = 0x%x c = %d h = %d s = %d\n", lba * BYTES_PER_SECTOR, cylinder, head, sector);
+#endif
+
+ // Intel recommends 3 attempts for a read/write
+ for (int i = 0; i < 3; i++) {
+ // Now actually send the command to the drive. This is a big one!
+ send_byte(FLOPPY_MFM | FLOPPY_MT | FLOPPY_SK | static_cast<u8>(FloppyCommand::ReadData));
+ send_byte(head << 2 | is_slave() ? 1 : 0);
+ send_byte(cylinder);
+ send_byte(head);
+ send_byte(sector);
+ send_byte(SECTORS_PER_CYLINDER >> 8); // Yikes!
+ send_byte((sector + 1) >= SECTORS_PER_CYLINDER ? SECTORS_PER_CYLINDER : sector + 1);
+ send_byte(0x27); // GPL3 value. The Datasheet doesn't really specify the values for this properly...
+ send_byte(0xff);
+
+ enable_irq();
+
+ wait_for_irq(); // TODO: See if there was a lockup here via some "timeout counter"
+ m_interrupted = false;
+
+ // Flush FIFO
+ read_byte();
+ read_byte();
+ read_byte();
+ u8 cyl = read_byte();
+ read_byte();
+ read_byte();
+ read_byte();
+
+ if (cyl != cylinder) {
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: cyl != cylinder (cyl = %d cylinder = %d)! Retrying...\n", cyl, cylinder);
+#endif
+ continue;
+ }
+
+ // Let the controller know we handled the interrupt
+ send_byte(FloppyCommand::SenseInterrupt);
+ u8 st0 = read_byte();
+ u8 pcn = read_byte();
+ static_cast<void>(st0);
+ static_cast<void>(pcn);
+
+ memcpy(outbuf, m_dma_buffer_page->paddr().as_ptr(), 512 * count);
+ //kprintf("fdc: 0x%x\n", *outbuf);
+
+ return true;
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: out of read attempts (check your hardware maybe!?)\n");
+#endif
+ return false;
+}
+
+bool FloppyDiskDevice::write_sectors_with_dma(u16 lba, u16 count, const u8* inbuf)
+{
+ LOCKER(m_lock); // Acquire lock
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: write_sectors_with_dma lba = %d count = %d\n", lba, count);
+#endif
+
+ motor_enable(is_slave() ? 1 : 0); // Should I bother casting this?!
+ write_ccr(0);
+ recalibrate(); // Recalibrate the drive
+
+ // We have to wait for about 300ms for the drive to spin up, because of
+ // the inertia of the motor and diskette.
+ // TODO: Fix this abomination please!
+ //u32 start = PIT::seconds_since_boot();
+ //while(start < PIT::seconds_since_boot() + 1)
+ // ;
+
+ disable_irq();
+
+ IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1
+ IO::out8(0x0B, 0x5A); // Begin DMA, Single Transfer, Increment, Auto, RAM -> FDC, Channel 2
+ IO::out8(0xA, 0x2); // Unmask channel 2. The transfer will now begin
+
+ u16 cylinder = lba2cylinder(lba);
+ u16 head = lba2head(lba);
+ u16 sector = lba2sector(lba);
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: addr = 0x%x c = %d h = %d s = %d\n", lba * BYTES_PER_SECTOR, cylinder, head, sector);
+#endif
+
+ for (int i = 0; i < 3; i++) {
+ // Now actually send the command to the drive. This is a big one!
+ send_byte(FLOPPY_MFM | FLOPPY_MT | static_cast<u8>(FloppyCommand::WriteData));
+ send_byte(head << 2 | is_slave() ? 1 : 0);
+ send_byte(cylinder);
+ send_byte(head);
+ send_byte(sector);
+ send_byte(SECTORS_PER_CYLINDER >> 8); // Yikes!
+ send_byte((sector + 1) >= SECTORS_PER_CYLINDER ? SECTORS_PER_CYLINDER : sector + 1);
+ send_byte(0x27); // GPL3 value. The Datasheet doesn't really specify the values for this properly...
+ send_byte(0xff);
+
+ enable_irq();
+
+ wait_for_irq(); // TODO: See if there was a lockup here via some "timeout counter"
+ m_interrupted = false;
+
+ // Flush FIFO
+ read_byte();
+ read_byte();
+ read_byte();
+ u8 cyl = read_byte();
+ read_byte();
+ read_byte();
+ read_byte();
+
+ if (cyl != cylinder) {
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: cyl != cylinder (cyl = %d cylinder = %d)! Retrying...\n", cyl, cylinder);
+#endif
+ continue;
+ }
+
+ // Let the controller know we handled the interrupt
+ send_byte(FloppyCommand::SenseInterrupt);
+ u8 st0 = read_byte();
+ u8 pcn = read_byte();
+ static_cast<void>(st0);
+ static_cast<void>(pcn);
+
+ memcpy(m_dma_buffer_page->paddr().as_ptr(), inbuf, 512 * count);
+
+ return true;
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: out of read attempts (check your hardware maybe!?)\n");
+#endif
+ return false;
+}
+
+bool FloppyDiskDevice::wait_for_irq()
+{
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: Waiting for interrupt...\n");
+#endif
+
+ while (!m_interrupted) {
+ Scheduler::yield();
+ }
+
+ memory_barrier();
+ return true;
+}
+
+void FloppyDiskDevice::handle_irq()
+{
+ // The only thing we need to do is acknowledge the IRQ happened
+ m_interrupted = true;
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: Received IRQ!\n");
+#endif
+}
+
+void FloppyDiskDevice::send_byte(u8 value) const
+{
+ for (int i = 0; i < 1024; i++) {
+ if (read_msr() & FLOPPY_MSR_RQM) {
+ IO::out8(m_io_base_addr + FLOPPY_FIFO, value);
+ return;
+ }
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: FIFO write timed out!\n");
+#endif
+}
+
+void FloppyDiskDevice::send_byte(FloppyCommand value) const
+{
+ for (int i = 0; i < 1024; i++) {
+ if (read_msr() & FLOPPY_MSR_RQM) {
+ IO::out8(m_io_base_addr + FLOPPY_FIFO, static_cast<u8>(value));
+ return;
+ }
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: FIFO write timed out!\n");
+#endif
+}
+
+u8 FloppyDiskDevice::read_byte() const
+{
+ for (int i = 0; i < 1024; i++) {
+ if (read_msr() & (FLOPPY_MSR_RQM | FLOPPY_MSR_DIO)) {
+ return IO::in8(m_io_base_addr + FLOPPY_FIFO);
+ }
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: FIFO read timed out!\n");
+#endif
+
+ return 0xff;
+}
+
+void FloppyDiskDevice::write_dor(u8 value) const
+{
+ IO::out8(m_io_base_addr + FLOPPY_DOR, value);
+}
+
+void FloppyDiskDevice::write_ccr(u8 value) const
+{
+ IO::out8(m_io_base_addr + FLOPPY_CCR, value);
+}
+
+u8 FloppyDiskDevice::read_msr() const
+{
+ return IO::in8(m_io_base_addr + FLOPPY_MSR);
+}
+
+void FloppyDiskDevice::motor_enable(bool slave) const
+{
+ u8 val = slave ? 0x1C : 0x2D;
+ write_dor(val);
+}
+
+bool FloppyDiskDevice::is_busy() const
+{
+ return read_msr() & FLOPPY_MSR;
+}
+
+bool FloppyDiskDevice::recalibrate()
+{
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: recalibrating drive...\n");
+#endif
+
+ u8 slave = is_slave() ? 1 : 0;
+ motor_enable(slave);
+
+ for (int i = 0; i < 16; i++) {
+ send_byte(FloppyCommand::Recalibrate);
+ send_byte(slave);
+ wait_for_irq();
+ m_interrupted = false;
+
+ send_byte(FloppyCommand::Recalibrate);
+ u8 st0 = read_byte();
+ u8 pcn = read_byte();
+ static_cast<void>(st0);
+
+ if (pcn == 0)
+ return true;
+ }
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: failed to calibrate drive (check your hardware!)\n");
+#endif
+ return false;
+}
+
+bool FloppyDiskDevice::seek(u16 lba)
+{
+ u8 head = lba2head(lba) & 0x01;
+ u8 cylinder = lba2cylinder(lba) & 0xff;
+ u8 slave = is_slave() ? 1 : 0;
+
+ // First, we need to enable the correct drive motor
+ motor_enable(slave);
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: seeking to cylinder %d on side %d on drive %d\n", cylinder, head, slave);
+#endif
+
+ // Try at most 5 times to seek to the desired cylinder
+ for (int attempt = 0; attempt < 5; attempt++) {
+ send_byte(FloppyCommand::Seek);
+ send_byte((head << 2) | slave);
+ send_byte(cylinder);
+ wait_for_irq();
+ m_interrupted = false;
+
+ send_byte(FloppyCommand::SenseInterrupt);
+ u8 st0 = read_byte();
+ u8 pcn = read_byte();
+
+ if ((st0 >> 5) != 1 || pcn != cylinder || (st0 & 0x01)) {
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: failed to seek to cylinder %d on attempt %d!\n", cylinder, attempt);
+#endif
+ continue;
+ }
+
+ return true;
+ }
+
+ kprintf("fdc: failed to seek after 3 attempts! Aborting...\n");
+ return false;
+}
+
+// This is following Intel's datasheet for the 82077, page 41
+void FloppyDiskDevice::initialize()
+{
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: m_io_base = 0x%x IRQn = %d\n", m_io_base_addr, IRQ_FLOPPY_DRIVE);
+#endif
+
+ enable_irq();
+
+ // Get the version of the Floppy Disk Controller
+ send_byte(FloppyCommand::Version);
+ m_controller_version = read_byte();
+ kprintf("fdc: Version = 0x%x\n", m_controller_version);
+
+ // Reset
+ write_dor(0);
+ write_dor(FLOPPY_DOR_RESET | FLOPPY_DOR_DMAGATE);
+
+ write_ccr(0);
+ wait_for_irq();
+ m_interrupted = false;
+
+ // "If (and only if) drive polling mode is turned on, send 4 Sense Interrupt commands (required). "
+ // Sorry OSDev, but the Intel Manual states otherwise. This ALWAYS needs to be performed.
+ for (int i = 0; i < 4; i++) {
+ send_byte(FloppyCommand::SenseInterrupt);
+ u8 sr0 = read_byte();
+ u8 trk = read_byte();
+
+ kprintf("sr0 = 0x%x, cyl = 0x%x\n", sr0, trk);
+ }
+
+ // This is hardcoded for a 3.5" floppy disk drive
+ send_byte(FloppyCommand::Specify);
+ send_byte(0x08); // (SRT << 4) | HUT
+ send_byte(0x0A); // (HLT << 1) | NDMA
+
+ // Allocate a buffer page for us to read into. This only needs to be one sector in size.
+ m_dma_buffer_page = MM.allocate_supervisor_physical_page();
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: allocated supervisor page at paddr 0x%x\n", m_dma_buffer_page->paddr());
+#endif
+
+ // Now, let's initialise channel 2 of the DMA controller!
+ // This only needs to be done here, then we can just change the direction of
+ // the transfer
+ IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1
+
+ IO::out8(0xC, 0xFF); // Reset Master Flip Flop
+
+ // Set the buffer page address (the lower 16-bits)
+ IO::out8(0x4, m_dma_buffer_page->paddr().get() & 0xff);
+ IO::out8(0x4, (m_dma_buffer_page->paddr().get() >> 8) & 0xff);
+
+ IO::out8(0xC, 0xFF); // Reset Master Flip Flop again
+
+ IO::out8(0x05, (SECTORS_PER_CYLINDER * BYTES_PER_SECTOR) & 0xff);
+ IO::out8(0x05, (SECTORS_PER_CYLINDER * BYTES_PER_SECTOR) >> 8);
+ IO::out8(0x81, (m_dma_buffer_page->paddr().get() >> 16) & 0xff); // Supervisor page could be a 24-bit address, so set the External Page R/W register
+
+ IO::out8(0xA, 0x2); // Unmask Channel 2
+
+#ifdef FLOPPY_DEBUG
+ kprintf("fdc: fd%d initialised succesfully!\n", is_slave() ? 1 : 0);
+#endif
+}
diff --git a/Kernel/Devices/FloppyDiskDevice.h b/Kernel/Devices/FloppyDiskDevice.h
new file mode 100644
index 0000000000..8120415d68
--- /dev/null
+++ b/Kernel/Devices/FloppyDiskDevice.h
@@ -0,0 +1,190 @@
+//
+// Intel 82078 Floppy Disk controller driver
+// Author: Jesse Buhagiar [quaker762]
+// Datasheet: https://wiki.qemu.org/images/f/f0/29047403.pdf
+// Email me at jooster669@gmail.com if you have any questions/suggestions :)
+//
+// The Intel 82078 is a 44-pin package, CHMOS Single Chip Floppy Disk Controller found commonly
+// on later PCs in the mid to late 90s. It supports a multitude of floppy drives found in computers
+// at the time, up to and including 2.8MB ED Floppy Disks and is software compatible with previous FDCs.
+// Drive in this case refers to the actual drive where the media is inserted and a disk is the actual
+// magnetic floppy disk media. This controller is emulated by QEMU.
+//
+// Certain terminology exists in the code of this driver that may be confusing, being that there
+// is a lot of code and documentation online that is seemingly conflicting. I've used terms found
+// directly in the datasheet however for the sake of completeness I'll explain them here:
+//
+// - Cylinder: One full circular 'slice' of the floppy disk. It contains 18 sectors
+// on a 3.5" floppy disk. It is also known as a 'track'. There are
+// 80 tracks on a single side of a floppy disk.
+// - Sector: One 512 byte chunk of a track.
+// - Head: The read write arm found inside the drive itself. On a double sided
+// floppy disk drive, there are two, one for the top tracks of the disk
+// and the other for the bottom tracks.
+// - CHS: Cylinder, Head, Sector. The addressing type this floppy controller
+// uses to address the disk geometry.
+//
+// A normal PC System usually contains one or two floppy drives. This controller contains the
+// ability to control up to four drives with the one controller, however it is very rare for
+// most systems to contain this amount of drives.
+//
+// The basic operation of the drive involves reseting the drive in hardware, then sending command
+// bytes to the FIFO, allowing the command to execute, then flushing the FIFO by reading `n` bytes
+// from it. Most commands are multi-parameter and multi-result, so it's best to consult the datasheet
+// from page 23. It is recommended that a SENSE command is performed to retrieve valubable interrupt
+// information about the performed action.
+//
+// Reseting the controller involves the following:
+// - Acquire the version ID of the controller.
+// - Reset the DOR register
+// - Deassert software reset bit in the DOR register and assert the DMAGATE pin to initialize DMA mode
+// - Program the Configuration Control Register (CCR) for 3.5" 1.44MB diskettes
+// - Send a SPECIFY command to specify more drive information. Refer to the datasheet
+//
+// The drive (being mapped to the controller) will then be in a state that will accept the correct media.
+// The DMA controller is also set up here, which is on channel 2. This only needs to be done once, the
+// read and write commands can toggle the appropriate bits themselves to allow a specific transfer direction.
+//
+// Recalibrating the drive refers to the act of resetting the head of the drive back to track/cylinder 0. It
+// is essentially the same as a seek, however returning the drive to a known position. For the sake of brevity,
+// only the recalibrate sequence will be described.
+//
+// - Enable the drive and it's motor (all drive motors are manually enabled by us!).
+// - Issue a recalibrate or a seek command
+// - Wait for interrupt
+// - Issue a SENSE command, letting the drive know we handled the interrupt
+// - Flush the FIFO and check the cylinder value to ensure we are at the correct spot.
+//
+// Once this has been completed, the drive will either be at the desired position or back at cylinder 0.
+//
+// To perform a READ or a WRITE of the diskette inserted, the following actions must be taken:
+//
+// -The drive and it's motor must be enabled
+// -The data rate must be set via CCR
+// -The drive must be then recalibrated to ensure the head has not drifted.
+// -A wait of 500ms or greater must occur to allow the drive to spin up from inertia.
+// -The DMA direction of the transfer is then configured.
+// -The READ or WRITE command is issued to the controller.
+// -A timeout counter is started. This is only for real hardware and is currently not implemented.
+// -Read the result bytes.
+// -Attempt to READ or WRITE to the disk. Intel recommends doing this a max of 3 times before failing.
+//
+//
+//
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <Kernel/Devices/DiskDevice.h>
+#include <Kernel/IRQHandler.h>
+#include <Kernel/Lock.h>
+#include <Kernel/VM/PhysicalAddress.h>
+#include <Kernel/VM/PhysicalPage.h>
+
+struct FloppyControllerCommand {
+ u8 cmd; // Command to send to the controller
+ u8 numParams; // Number of parameters to send to the drive
+ u8 numReturned; // Number of values we expect to be returned by the command
+ u8* params;
+ u8* result;
+};
+
+//
+// NOTE: This class only supports 3.5" 1.44MB floppy disks!
+// Any other type of drive will be ignored
+//
+// Also not that the floppy disk controller is set up to be in PS/2 mode, which
+// uses the Intel 82077A controller. More about this controller can
+// be found here: http://www.buchty.net/casio/files/82077.pdf
+//
+class FloppyDiskDevice final : public IRQHandler
+ , public DiskDevice {
+ AK_MAKE_ETERNAL
+
+ static constexpr u8 SECTORS_PER_CYLINDER = 18;
+ static constexpr u8 CCYLINDERS_PER_HEAD = 80;
+ static constexpr u16 BYTES_PER_SECTOR = 512;
+
+public:
+ //
+ // Is this floppy drive the master or the slave on the controller??
+ //
+ enum class DriveType : u8 {
+ Master,
+ Slave
+ };
+
+private:
+ // Floppy commands
+ enum class FloppyCommand : u8 {
+ ReadTrack = 0x02,
+ Specify = 0x03,
+ CheckStatus = 0x04,
+ WriteData = 0x05,
+ ReadData = 0x06,
+ Recalibrate = 0x07,
+ SenseInterrupt = 0x08,
+ WriteDeletedData = 0x09,
+ ReadDeletedData = 0x0C,
+ FormatTrack = 0x0D,
+ Seek = 0x0F,
+ Version = 0x10,
+ Verify = 0x16,
+ };
+
+public:
+ static NonnullRefPtr<FloppyDiskDevice> create(DriveType type);
+ virtual ~FloppyDiskDevice() override;
+
+ // ^DiskDevice
+ virtual unsigned block_size() const override;
+ virtual bool read_block(unsigned index, u8*) const override;
+ virtual bool write_block(unsigned index, const u8*) override;
+ virtual bool read_blocks(unsigned index, u16 count, u8*) override;
+ virtual bool write_blocks(unsigned index, u16 count, const u8*) override;
+
+protected:
+ explicit FloppyDiskDevice(DriveType);
+
+private:
+ // ^IRQHandler
+ void handle_irq();
+
+ // ^DiskDevice
+ virtual const char* class_name() const override;
+
+ // Helper functions
+ inline u16 lba2cylinder(u16 lba) const { return lba / (2 * SECTORS_PER_CYLINDER); } // Convert an LBA into a cylinder value
+ inline u16 lba2head(u16 lba) const { return ((lba / SECTORS_PER_CYLINDER) % 2); } // Convert an LBA into a head value
+ inline u16 lba2sector(u16 lba) const { return ((lba % SECTORS_PER_CYLINDER) + 1); } // Convert an LBA into a sector value
+
+ void initialize();
+ bool read_sectors_with_dma(u16, u16, u8*);
+ bool write_sectors_with_dma(u16, u16, const u8*);
+ bool wait_for_irq();
+
+ bool is_busy() const;
+ bool seek(u16);
+ bool recalibrate();
+
+ void send_byte(u8) const;
+ void send_byte(FloppyCommand) const;
+
+ void write_dor(u8) const;
+ void write_ccr(u8) const;
+ void motor_enable(bool) const;
+ void configure_drive(u8, u8, u8) const;
+
+ u8 read_byte() const;
+ u8 read_msr() const;
+
+ bool is_slave() const { return m_drive_type == DriveType::Slave; }
+
+ Lock m_lock { "FloppyDiskDevice" };
+ u16 m_io_base_addr { 0 };
+ volatile bool m_interrupted { false };
+
+ DriveType m_drive_type { DriveType::Master };
+ RefPtr<PhysicalPage> m_dma_buffer_page;
+
+ u8 m_controller_version { 0 };
+};
diff --git a/Kernel/Makefile b/Kernel/Makefile
index 272334ba08..a508a8cc24 100644
--- a/Kernel/Makefile
+++ b/Kernel/Makefile
@@ -14,6 +14,7 @@ KERNEL_OBJS = \
Arch/i386/PIC.o \
Syscall.o \
Devices/IDEDiskDevice.o \
+ Devices/FloppyDiskDevice.o \
VM/MemoryManager.o \
VM/Region.o \
VM/VMObject.o \
diff --git a/Kernel/init.cpp b/Kernel/init.cpp
index b580fcfac3..c2bba580c4 100644
--- a/Kernel/init.cpp
+++ b/Kernel/init.cpp
@@ -7,9 +7,11 @@
#include <Kernel/Arch/i386/CPU.h>
#include <Kernel/Arch/i386/PIC.h>
#include <Kernel/Arch/i386/PIT.h>
+#include <Kernel/CMOS.h>
#include <Kernel/Devices/BXVGADevice.h>
#include <Kernel/Devices/DebugLogDevice.h>
#include <Kernel/Devices/DiskPartition.h>
+#include <Kernel/Devices/FloppyDiskDevice.h>
#include <Kernel/Devices/FullDevice.h>
#include <Kernel/Devices/IDEDiskDevice.h>
#include <Kernel/Devices/KeyboardDevice.h>
@@ -136,6 +138,24 @@ VFS* vfs;
vfs->mount(ProcFS::the(), "/proc");
vfs->mount(DevPtsFS::the(), "/dev/pts");
+ // Now, detect whether or not there are actually any floppy disks attached to the system
+ u8 detect = CMOS::read(0x10);
+ RefPtr<FloppyDiskDevice> fd0;
+ RefPtr<FloppyDiskDevice> fd1;
+ if ((detect >> 4) & 0x4) {
+ fd0 = FloppyDiskDevice::create(FloppyDiskDevice::DriveType::Master);
+ kprintf("fd0 is 1.44MB floppy drive\n");
+ } else {
+ kprintf("fd0 type unsupported! Type == 0x%x\n", detect >> 4);
+ }
+
+ if (detect & 0x0f) {
+ fd0 = FloppyDiskDevice::create(FloppyDiskDevice::DriveType::Slave);
+ kprintf("fd1 is 1.44MB floppy drive");
+ } else {
+ kprintf("fd1 type unsupported! Type == 0x%x\n", detect & 0x0f);
+ }
+
int error;
auto* system_server_process = Process::create_user_process("/bin/SystemServer", (uid_t)100, (gid_t)100, (pid_t)0, error, {}, {}, tty0);