Serenity Operating System
at portability 564 lines 17 kB view raw
1/* 2 * Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <Kernel/Devices/FloppyDiskDevice.h> 28#include <Kernel/VM/MemoryManager.h> 29#include <LibBareMetal/IO.h> 30 31namespace Kernel { 32 33// Uncomment me for a LOT of output 34//#define FLOPPY_DEBUG 35 36// THESE ARE OFFSETS! 37#define FLOPPY_STATUS_A 0x00 // ro 38#define FLOPPY_STATUS_B 0x01 // ro 39#define FLOPPY_DOR 0x02 // rw 40#define FLOPPY_TDR 0x03 // rw 41#define FLOPPY_MSR 0x04 // ro 42#define FLOPPY_DSR 0x04 // wo 43#define FLOPPY_FIFO 0x05 44#define FLOPPY_RSVD 0x06 45#define FLOPPY_DIR 0x07 // ro 46#define FLOPPY_CCR 0x07 // wo 47 48#define FLOPPY_STATUS_DIR 0x01 49#define FLOPPY_STATUS_WP 0x02 50#define FLOPPY_STATUS_INDX 0x04 51#define FLOPPY_STATUS_HDSEL 0x08 52#define FLOPPY_STATUS_TRK0 0x10 53#define FLOPPY_STATUS_STEP 0x20 54#define FLOPPY_STATUS_DRV2 0x40 55#define FLOPPY_STATUS_INTW 0x80 // A.K.A INT_PENDING 56 57#define FLOPPY_DOR_DRVSEL0 0x01 58#define FLOPPY_DOR_DRVSEL1 0x02 59#define FLOPPY_DOR_RESET 0x04 60#define FLOPPY_DOR_DMAGATE 0x08 61#define FLOPPY_DOR_MOTEN0 0x10 62#define FLOPPY_DOR_MOTEN1 0x20 63#define FLOPPY_DOR_MOTEN2 0x40 64#define FLOPPY_DOR_MOTEN3 0x80 65// Preset values to activate drive select and motor enable for each drive 66#define FLOPPY_DOR_DRV0 0x1C 67#define FLOPPY_DOR_DRV1 0x2D 68#define FLOPPY_DOR_DRV2 0x4E 69#define FLOPPY_DOR_DRV3 0x8F 70 71#define FLOPPY_MSR_FDD0BSY 0x01 72#define FLOPPY_MSR_FDD1BSY 0x02 73#define FLOPPY_MSR_FDD2BSY 0x04 74#define FLOPPY_MSR_FDD3BSY 0x08 75#define FLOPPY_MSR_FDCBSY 0x10 76#define FLOPPY_MSR_MODE 0x20 // 0 in DMA mode, 1 in PIO mode 77#define FLOPPY_MSR_DIO 0x40 // 0 FDC is expecting data from the CPU, 1 if FDC has data for CPU 78#define FLOPPY_MSR_RQM 0x80 // 0 Data register not ready, 1 data register ready 79 80#define FLOPPY_CCR_DRTESEL0 0x01 81#define FLOPPY_CCR_DRTESEL1 0x02 82 83#define FLOPPY_MT 0x80 // Multi-track selector. The controller treats 2 tracks (on side 0 and side 1) as a single track instead 84#define FLOPPY_MFM 0x40 // 1 Means this disk is double density (double sided??) 85#define FLOPPY_SK 0x20 // Skip flag. Skips sectors containing deleted data automatically for us :) 86 87#define SR0_OKAY (0x00) << 6 88#define SR0_ABORMAL_TERMINATION (0x01) << 6 89#define SR0_INVALID_CMD (0x02) << 6 90#define SR0_ABNORMAL_TERM_POLL (0x03) << 6 91 92#define FLOPPY_DMA_CHANNEL 2 // All FDCs are DMA channel 2 93#define IRQ_FLOPPY_DRIVE 6 94 95NonnullRefPtr<FloppyDiskDevice> FloppyDiskDevice::create(DriveType type) 96{ 97 return adopt(*new FloppyDiskDevice(type)); 98} 99 100const char* FloppyDiskDevice::class_name() const 101{ 102 if (m_controller_version == 0x90) 103 return "Intel 82078 Floppy Disk Controller"; 104 else if (m_controller_version == 0x80) 105 return "NEC uPD765"; 106 107 return "Generic Floppy Disk Controller"; 108} 109 110FloppyDiskDevice::FloppyDiskDevice(FloppyDiskDevice::DriveType type) 111 : IRQHandler(IRQ_FLOPPY_DRIVE) 112 , BlockDevice(89, (type == FloppyDiskDevice::DriveType::Master) ? 0 : 1, BYTES_PER_SECTOR) 113 , m_io_base_addr((type == FloppyDiskDevice::DriveType::Master) ? 0x3F0 : 0x370) 114{ 115 initialize(); 116} 117 118FloppyDiskDevice::~FloppyDiskDevice() 119{ 120} 121 122bool FloppyDiskDevice::read_blocks(unsigned index, u16 count, u8* data) 123{ 124 return read_sectors_with_dma(index, count, data); 125} 126 127bool FloppyDiskDevice::write_blocks(unsigned index, u16 count, const u8* data) 128{ 129 return write_sectors_with_dma(index, count, data); 130 ; 131} 132 133bool FloppyDiskDevice::read_sectors_with_dma(u16 lba, u16 count, u8* outbuf) 134{ 135 LOCKER(m_lock); // Acquire lock 136#ifdef FLOPPY_DEBUG 137 kprintf("fdc: read_sectors_with_dma lba = %d count = %d\n", lba, count); 138#endif 139 140 motor_enable(is_slave()); // Should I bother casting this?! 141 write_ccr(0); 142 recalibrate(); 143 144 if (!seek(lba)) { 145 kprintf("fdc: failed to seek to lba = %d!\n", lba); 146 return false; 147 } 148 149 // We have to wait for about 300ms for the drive to spin up, because of 150 // the inertia of the motor and diskette. This is only 151 // important on real hardware 152 // TODO: Fix this if you want to get it running on real hardware. This code doesn't allow 153 // time for the disk to spin up. 154 155 //u32 start = PIT::seconds_since_boot(); 156 //while(start < PIT::seconds_since_boot() + 1) 157 // ; 158 159 disable_irq(); 160 161 IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1 162 IO::out8(0x0B, 0x56); // Begin DMA, Single Transfer, Increment, Auto, FDC -> RAM, Channel 2 163 IO::out8(0xA, 0x2); // Unmask channel 2. The transfer will now begin 164 165 // Translate the LBA address into something the FDC understands. 166 u16 cylinder = lba2cylinder(lba); 167 u16 head = lba2head(lba); 168 u16 sector = lba2sector(lba); 169 170#ifdef FLOPPY_DEBUG 171 kprintf("fdc: addr = 0x%x c = %d h = %d s = %d\n", lba * BYTES_PER_SECTOR, cylinder, head, sector); 172#endif 173 174 // Intel recommends 3 attempts for a read/write 175 for (int i = 0; i < 3; i++) { 176 // Now actually send the command to the drive. This is a big one! 177 send_byte(FLOPPY_MFM | FLOPPY_MT | FLOPPY_SK | static_cast<u8>(FloppyCommand::ReadData)); 178 send_byte((head << 2) | is_slave()); 179 send_byte(cylinder); 180 send_byte(head); 181 send_byte(sector); 182 send_byte(SECTORS_PER_CYLINDER >> 8); // Yikes! 183 send_byte(((sector + 1) >= SECTORS_PER_CYLINDER) ? SECTORS_PER_CYLINDER : sector + 1); 184 send_byte(0x1b); // GPL3 value. The Datasheet doesn't really specify the values for this properly... 185 send_byte(0xff); 186 187 enable_irq(); 188 189 wait_for_irq(); // TODO: See if there was a lockup here via some "timeout counter" 190 m_interrupted = false; 191 192 // Flush FIFO 193 // Let's check the value of Status Register 1 to ensure that 194 // the command executed correctly 195 u8 cmd_st0 = read_byte(); 196 if ((cmd_st0 & 0xc0) != 0) { 197 kprintf("fdc: read failed with error code (st0) 0x%x\n", cmd_st0 >> 6); 198 return false; 199 } 200 201 u8 cmd_st1 = read_byte(); 202 if (cmd_st1 != 0) { 203 kprintf("fdc: read failed with error code (st1) 0x%x\n", cmd_st1); 204 return false; 205 } 206 207 read_byte(); 208 u8 cyl = read_byte(); 209 read_byte(); 210 read_byte(); 211 read_byte(); 212 213 if (cyl != cylinder) { 214#ifdef FLOPPY_DEBUG 215 kprintf("fdc: cyl != cylinder (cyl = %d cylinder = %d)! Retrying...\n", cyl, cylinder); 216#endif 217 continue; 218 } 219 220 // Let the controller know we handled the interrupt 221 send_byte(FloppyCommand::SenseInterrupt); 222 u8 st0 = read_byte(); 223 u8 pcn = read_byte(); 224 static_cast<void>(st0); 225 static_cast<void>(pcn); 226 227 memcpy(outbuf, m_dma_buffer_page->paddr().as_ptr(), 512 * count); 228 229 return true; 230 } 231 232#ifdef FLOPPY_DEBUG 233 kprintf("fdc: out of read attempts (check your hardware maybe!?)\n"); 234#endif 235 return false; 236} 237 238bool FloppyDiskDevice::write_sectors_with_dma(u16 lba, u16 count, const u8* inbuf) 239{ 240 LOCKER(m_lock); // Acquire lock 241#ifdef FLOPPY_DEBUG 242 kprintf("fdc: write_sectors_with_dma lba = %d count = %d\n", lba, count); 243#endif 244 245 motor_enable(is_slave() ? 1 : 0); // Should I bother casting this?! 246 write_ccr(0); 247 recalibrate(); // Recalibrate the drive 248 249 if (!seek(lba)) { 250 kprintf("fdc: failed to seek to lba = %d!\n", lba); 251 return false; 252 } 253 254 // We have to wait for about 300ms for the drive to spin up, because of 255 // the inertia of the motor and diskette. 256 // TODO: Fix this abomination please! 257 //u32 start = PIT::seconds_since_boot(); 258 //while(start < PIT::seconds_since_boot() + 1) 259 // ; 260 261 disable_irq(); 262 263 IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1 264 IO::out8(0x0B, 0x5A); // Begin DMA, Single Transfer, Increment, Auto, RAM -> FDC, Channel 2 265 IO::out8(0xA, 0x2); // Unmask channel 2. The transfer will now begin 266 267 u16 cylinder = lba2cylinder(lba); 268 u16 head = lba2head(lba); 269 u16 sector = lba2sector(lba); 270 271#ifdef FLOPPY_DEBUG 272 kprintf("fdc: addr = 0x%x c = %d h = %d s = %d\n", lba * BYTES_PER_SECTOR, cylinder, head, sector); 273#endif 274 275 for (int i = 0; i < 3; i++) { 276 // Now actually send the command to the drive. This is a big one! 277 send_byte(FLOPPY_MFM | FLOPPY_MT | static_cast<u8>(FloppyCommand::WriteData)); 278 send_byte(head << 2 | is_slave()); 279 send_byte(cylinder); 280 send_byte(head); 281 send_byte(sector); 282 send_byte(SECTORS_PER_CYLINDER >> 8); // Yikes! 283 send_byte((sector + 1) >= SECTORS_PER_CYLINDER ? SECTORS_PER_CYLINDER : sector + 1); 284 send_byte(0x1b); // GPL3 value. The Datasheet doesn't really specify the values for this properly... 285 send_byte(0xff); 286 287 enable_irq(); 288 289 wait_for_irq(); // TODO: See if there was a lockup here via some "timeout counter" 290 m_interrupted = false; 291 292 // Flush FIFO 293 u8 cmd_st0 = read_byte(); 294 if ((cmd_st0 & 0xc0) != 0) { 295 kprintf("fdc: write failed! Error code 0x%x\n", cmd_st0 >> 6); 296 return false; 297 } 298 299 u8 cmd_st1 = read_byte(); 300 if (cmd_st1 != 0) { 301 kprintf("fdc: write failed with error code (st1) 0x%x\n", cmd_st1); 302 return false; 303 } 304 305 read_byte(); 306 u8 cyl = read_byte(); 307 read_byte(); 308 read_byte(); 309 read_byte(); 310 311 if (cyl != cylinder) { 312#ifdef FLOPPY_DEBUG 313 kprintf("fdc: cyl != cylinder (cyl = %d cylinder = %d)! Retrying...\n", cyl, cylinder); 314#endif 315 continue; 316 } 317 318 // Let the controller know we handled the interrupt 319 send_byte(FloppyCommand::SenseInterrupt); 320 u8 st0 = read_byte(); 321 u8 pcn = read_byte(); 322 static_cast<void>(st0); 323 static_cast<void>(pcn); 324 325 memcpy(m_dma_buffer_page->paddr().as_ptr(), inbuf, 512 * count); 326 327 return true; 328 } 329 330#ifdef FLOPPY_DEBUG 331 kprintf("fdc: out of read attempts (check your hardware maybe!?)\n"); 332#endif 333 return false; 334} 335 336bool FloppyDiskDevice::wait_for_irq() 337{ 338#ifdef FLOPPY_DEBUG 339 kprintf("fdc: Waiting for interrupt...\n"); 340#endif 341 342 while (!m_interrupted) { 343 Scheduler::yield(); 344 } 345 346 memory_barrier(); 347 return true; 348} 349 350void FloppyDiskDevice::handle_irq(RegisterState&) 351{ 352 // The only thing we need to do is acknowledge the IRQ happened 353 m_interrupted = true; 354 355#ifdef FLOPPY_DEBUG 356 kprintf("fdc: Received IRQ!\n"); 357#endif 358} 359 360void FloppyDiskDevice::send_byte(u8 value) const 361{ 362 for (int i = 0; i < 1024; i++) { 363 if (read_msr() & FLOPPY_MSR_RQM) { 364 IO::out8(m_io_base_addr + FLOPPY_FIFO, value); 365 return; 366 } 367 } 368 369#ifdef FLOPPY_DEBUG 370 kprintf("fdc: FIFO write timed out!\n"); 371#endif 372} 373 374void FloppyDiskDevice::send_byte(FloppyCommand value) const 375{ 376 for (int i = 0; i < 1024; i++) { 377 if (read_msr() & FLOPPY_MSR_RQM) { 378 IO::out8(m_io_base_addr + FLOPPY_FIFO, static_cast<u8>(value)); 379 return; 380 } 381 } 382 383#ifdef FLOPPY_DEBUG 384 kprintf("fdc: FIFO write timed out!\n"); 385#endif 386} 387 388u8 FloppyDiskDevice::read_byte() const 389{ 390 for (int i = 0; i < 1024; i++) { 391 if (read_msr() & (FLOPPY_MSR_RQM | FLOPPY_MSR_DIO)) { 392 return IO::in8(m_io_base_addr + FLOPPY_FIFO); 393 } 394 } 395 396#ifdef FLOPPY_DEBUG 397 kprintf("fdc: FIFO read timed out!\n"); 398#endif 399 400 return 0xff; 401} 402 403void FloppyDiskDevice::write_dor(u8 value) const 404{ 405 IO::out8(m_io_base_addr + FLOPPY_DOR, value); 406} 407 408void FloppyDiskDevice::write_ccr(u8 value) const 409{ 410 IO::out8(m_io_base_addr + FLOPPY_CCR, value); 411} 412 413u8 FloppyDiskDevice::read_msr() const 414{ 415 return IO::in8(m_io_base_addr + FLOPPY_MSR); 416} 417 418void FloppyDiskDevice::motor_enable(bool slave) const 419{ 420 u8 val = slave ? 0x2D : 0x1C; 421 write_dor(val); 422} 423 424bool FloppyDiskDevice::is_busy() const 425{ 426 return read_msr() & FLOPPY_MSR; 427} 428 429bool FloppyDiskDevice::recalibrate() 430{ 431#ifdef FLOPPY_DEBUG 432 kprintf("fdc: recalibrating drive...\n"); 433#endif 434 435 u8 slave = is_slave(); 436 motor_enable(slave); 437 438 for (int i = 0; i < 16; i++) { 439 send_byte(FloppyCommand::Recalibrate); 440 send_byte(slave); 441 wait_for_irq(); 442 m_interrupted = false; 443 444 send_byte(FloppyCommand::SenseInterrupt); 445 u8 st0 = read_byte(); 446 u8 pcn = read_byte(); 447 static_cast<void>(st0); 448 449 if (pcn == 0) 450 return true; 451 } 452 453#ifdef FLOPPY_DEBUG 454 kprintf("fdc: failed to calibrate drive (check your hardware!)\n"); 455#endif 456 return false; 457} 458 459bool FloppyDiskDevice::seek(u16 lba) 460{ 461 u8 head = lba2head(lba) & 0x01; 462 u8 cylinder = lba2cylinder(lba) & 0xff; 463 u8 slave = is_slave(); 464 465 // First, we need to enable the correct drive motor 466 motor_enable(slave); 467#ifdef FLOPPY_DEBUG 468 kprintf("fdc: seeking to cylinder %d on side %d on drive %d\n", cylinder, head, slave); 469#endif 470 471 // Try at most 5 times to seek to the desired cylinder 472 for (int attempt = 0; attempt < 5; attempt++) { 473 send_byte(FloppyCommand::Seek); 474 send_byte((head << 2) | slave); 475 send_byte(cylinder); 476 wait_for_irq(); 477 m_interrupted = false; 478 479 send_byte(FloppyCommand::SenseInterrupt); 480 u8 st0 = read_byte(); 481 u8 pcn = read_byte(); 482 483 if ((st0 >> 5) != 1 || pcn != cylinder || (st0 & 0x01)) { 484#ifdef FLOPPY_DEBUG 485 kprintf("fdc: failed to seek to cylinder %d on attempt %d!\n", cylinder, attempt); 486#endif 487 continue; 488 } 489 490 return true; 491 } 492 493 kprintf("fdc: failed to seek after 3 attempts! Aborting...\n"); 494 return false; 495} 496 497// This is following Intel's datasheet for the 82077, page 41 498void FloppyDiskDevice::initialize() 499{ 500#ifdef FLOPPY_DEBUG 501 kprintf("fdc: m_io_base = 0x%x IRQn = %d\n", m_io_base_addr, IRQ_FLOPPY_DRIVE); 502#endif 503 504 enable_irq(); 505 506 // Get the version of the Floppy Disk Controller 507 send_byte(FloppyCommand::Version); 508 m_controller_version = read_byte(); 509 kprintf("fdc: Version = 0x%x\n", m_controller_version); 510 511 // Reset 512 write_dor(0); 513 write_dor(FLOPPY_DOR_RESET | FLOPPY_DOR_DMAGATE); 514 515 write_ccr(0); 516 wait_for_irq(); 517 m_interrupted = false; 518 519 // "If (and only if) drive polling mode is turned on, send 4 Sense Interrupt commands (required). " 520 // Sorry OSDev, but the Intel Manual states otherwise. This ALWAYS needs to be performed. 521 for (int i = 0; i < 4; i++) { 522 send_byte(FloppyCommand::SenseInterrupt); 523 u8 sr0 = read_byte(); 524 u8 trk = read_byte(); 525 526 kprintf("sr0 = 0x%x, cyl = 0x%x\n", sr0, trk); 527 } 528 529 // This is hardcoded for a 3.5" floppy disk drive 530 send_byte(FloppyCommand::Specify); 531 send_byte(0x08); // (SRT << 4) | HUT 532 send_byte(0x0A); // (HLT << 1) | NDMA 533 534 // Allocate a buffer page for us to read into. This only needs to be one sector in size. 535 m_dma_buffer_page = MM.allocate_supervisor_physical_page(); 536#ifdef FLOPPY_DEBUG 537 kprintf("fdc: allocated supervisor page at paddr 0x%x\n", m_dma_buffer_page->paddr()); 538#endif 539 540 // Now, let's initialise channel 2 of the DMA controller! 541 // This only needs to be done here, then we can just change the direction of 542 // the transfer 543 IO::out8(0xA, FLOPPY_DMA_CHANNEL | 0x4); // Channel 2 SEL, MASK_ON = 1 544 545 IO::out8(0xC, 0xFF); // Reset Master Flip Flop 546 547 // Set the buffer page address (the lower 16-bits) 548 IO::out8(0x4, m_dma_buffer_page->paddr().get() & 0xff); 549 IO::out8(0x4, (m_dma_buffer_page->paddr().get() >> 8) & 0xff); 550 551 IO::out8(0xC, 0xFF); // Reset Master Flip Flop again 552 553 IO::out8(0x05, (SECTORS_PER_CYLINDER * BYTES_PER_SECTOR) & 0xff); 554 IO::out8(0x05, (SECTORS_PER_CYLINDER * BYTES_PER_SECTOR) >> 8); 555 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 556 557 IO::out8(0xA, 0x2); // Unmask Channel 2 558 559#ifdef FLOPPY_DEBUG 560 kprintf("fdc: fd%d initialised succesfully!\n", is_slave() ? 1 : 0); 561#endif 562} 563 564}