📄 ide_drive.cxx
字号:
/* * Copyright (C) 1998, 1999, Jonathan S. Shapiro. * * This file is part of the EROS Operating System. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *//* TODO: This interface won't work with older ST506 or ESDI drives. * At the moment I have no plans to support such drives. Ever. I'm * probably the only person on the face of the planet who still owns * any that work :-). Even the LINUX crew are now compiling those * drives out by default. *//* #define VERBOSE_BUG */#include <kerninc/kernel.hxx>#include <kerninc/MsgLog.hxx>#include <kerninc/AutoConf.hxx>#include <kerninc/IoRegion.hxx>#include <kerninc/IRQ.hxx>#include <kerninc/Machine.hxx>#include <kerninc/SysTimer.hxx>#include <kerninc/BlockDev.hxx>#include <kerninc/IoRequest.hxx>#ifdef VERBOSE_BUG#include <arch/i486/kernel/lostart.hxx>#endif#include <arch/i486/kernel/SysConfig.hxx>#include <arch/i486/kernel/CMOS.hxx>#include "io.h"#include "ide_ide.hxx"#include "ide_drive.hxx"#include "ide_hwif.hxx"#include "ide_group.hxx" #define dbg_probe 0x1 /* steps in taking snapshot *//* Following should be an OR of some of the above */#define dbg_flags ( 0u )#define DBCOND(x) (dbg_##x & dbg_flags)#define DEBUG(x) if DBCOND(x)#define DEBUG2(x,y) if ((dbg_##x|dbg_##y) & dbg_flags)/* #define IDE_DEBUG */#define INITIAL_MULT_COUNT 8 /* 4K per xfer max */#define IS_PROMISE_DRIVE 0 /* for now */#define FANCY_STATUS_DUMPS/* * Probably not wise to fiddle with these */#define ERROR_MAX 8 /* Max read/write errors per sector */#define ERROR_RESET 3 /* Reset controller every 4th retry */#define ERROR_RECAL 1 /* Recalibrate every 2nd retry */#define DRIVE_SELECT_DELAY 10 /* in MILLISECONDS */#define DRIVE_DETECT_DELAY 50ide_drive::ide_drive(){ present = false; mounted = false; needsMount = false; removable = false; media = IDE::med_disk; ctl = 0x08;#if 0 p_chs.heads = 0; p_chs.secs = 0; p_chs.cyls = 0;#endif l_chs.hd = 0; l_chs.sec = 0; l_chs.cyl = 0; b_chs.hd = 0; b_chs.sec = 0; b_chs.cyl = 0; select.all = 0xa0; flags.all = 0; flags.b.recal = 1; /* seek to track 0 */ flags.b.setGeom = 1; /* tell it its geometry */ id = 0; name[0] = 'h'; name[1] = 'd'; name[2] = '?'; name[3] = '?'; name[4] = 0;}/* * lba_capacity_is_ok() performs a sanity check on the claimed "lba_capacity" * value for this drive (from its reported identification information). * * Returns: true if lba_capacity looks sensible * false otherwise */bool ide_driveid::CheckLbaCapacity(){ uint32_t lba_sects = lba_capacity; uint32_t chs_sects = cyls * heads * sectors; uint32_t _10_percent = chs_sects / 10; /* perform a rough sanity check on lba_sects: within 10% is "okay" */ if ((lba_sects - chs_sects) < _10_percent) return true; /* lba_capacity is good */ /* some drives have the word order reversed */ lba_sects = (lba_sects << 16) | (lba_sects >> 16); if ((lba_sects - chs_sects) < _10_percent) { lba_capacity = lba_sects; /* fix it */ return true; /* lba_capacity is (now) good */ } return false; /* lba_capacity value is bad */}/* * current_capacity() returns the capacity (in sectors) of a drive * according to its current geometry/LBA settings. */uint32_tide_drive::Capacity(){ struct ide_driveid *drvid = (ide_driveid*) id;#if 0 MsgLog::printf("l_chs: %d/%d/%d\n", l_chs.cyl, l_chs.hd, l_chs.sec);#endif uint32_t capacity = l_chs.cyl * l_chs.hd * l_chs.sec; if (!present) return 0; if (media != IDE::med_disk) return UINT32_MAX; /* cdrom or tape */ if (!IS_PROMISE_DRIVE) { select.b.lba = 0; /* Determine capacity, and use LBA if the drive properly supports it */ if (drvid != NULL && (drvid->capability & 2) && drvid->CheckLbaCapacity()) { if (drvid->lba_capacity >= capacity) { capacity = drvid->lba_capacity; select.b.lba = 1; } } } return capacity;#if 0 return (capacity - drive->sect0);#endif}/* * This is used for most PIO data transfers *from* the IDE interface */voidide_drive::InputData(void *buffer, unsigned int wcount){ uint16_t io_base = hwif->ioBase; uint16_t data_reg = io_base+IDE_DATA;#if 0 uint8_t io_32bit = io_32bit; if (io_32bit) {#if SUPPORT_VLB_SYNC if (io_32bit & 2) { cli(); do_vlb_sync(io_base+IDE_NSECS); insw(data_reg, buffer, wcount); if (drive->unmask) sti(); } else#endif /* SUPPORT_VLB_SYNC */ insw(data_reg, buffer, wcount); } else#endif ins16(data_reg, buffer, wcount<<1);}/* * ide_multwrite() transfer sectors to the drive, being careful not to * overrun the drive's multCount limit. */voidide_drive::MultWrite (Request *req){ uint32_t nsec = req->req_nsec; /* Can't do more than the block size limit: */ if (nsec > multCount) nsec = multCount; OutputData((void *) req->req_ioaddr, nsec * EROS_SECTOR_SIZE >> 2); req->nsec = nsec;}/* * This is used for most PIO data transfers *from* the IDE interface */voidide_drive::OutputData(void *buffer, unsigned int wcount){ uint16_t io_base = hwif->ioBase; uint16_t data_reg = io_base+IDE_DATA;#if 0 uint8_t io_32bit = io_32bit; if (io_32bit) {#if SUPPORT_VLB_SYNC if (io_32bit & 2) { cli(); do_vlb_sync(io_base+IDE_NSECS); insw(data_reg, buffer, wcount); if (drive->unmask) sti(); } else#endif /* SUPPORT_VLB_SYNC */ insw(data_reg, buffer, wcount); } else#endif outs16(data_reg, buffer, wcount<<1);}voidide_fixstring (uint8_t *s, const int bytecount, const int byteswap){ uint8_t *p = s, *end = &s[bytecount & ~1]; /* bytecount must be even */ if (byteswap) { /* convert from big-endian to host byte order */ for (p = end ; p != s;) { unsigned short *pp = (unsigned short *) (p -= 2); *pp = Machine::ntohhw(*pp); } } /* strip leading blanks */ while (s != end && *s == ' ') ++s; /* compress internal blanks and strip trailing blanks */ while (s != end && *s) { if (*s++ != ' ' || (s != end && *s && *s != ' ')) *p++ = *(s-1); } /* wipe out trailing garbage */ while (p != end) *p++ = '\0';}voidide_drive::AtapiIdentify(uint8_t cmd){ int bswap; /* uint32_t capacity, check; */ id = (ide_driveid *) new (0) char[EROS_SECTOR_SIZE]; InputData(id, EROS_SECTOR_SIZE >> 2); /* read 512 bytes of id info */ MsgLog::printf("Got atapi data.\n"); /* * EATA OPTION_SCSI controllers do a hardware ATA emulation: ignore them */ if ((id->model[0] == 'P' && id->model[1] == 'M') || (id->model[0] == 'S' && id->model[1] == 'K')) { MsgLog::printf("%s: EATA OPTION_SCSI HBA %40s\n", name, id->model); present = 0; return; } /* * WIN_IDENTIFY returns little-endian info, * WIN_PIDENTIFY *usually* returns little-endian info. */ bswap = 1; if (cmd == WIN_PIDENTIFY) { if ((id->model[0] == 'N' && id->model[1] == 'E') /* NEC */ || (id->model[0] == 'F' && id->model[1] == 'X') /* Mitsumi */ || (id->model[0] == 'P' && id->model[1] == 'i'))/* Pioneer */ bswap = 0; /* Vertos drives may still be weird */ } ide_fixstring (id->model, sizeof(id->model), bswap); ide_fixstring (id->fw_rev, sizeof(id->fw_rev), bswap); ide_fixstring (id->serial_no, sizeof(id->serial_no), bswap); /* * Check for an ATAPI device */ if (cmd == WIN_PIDENTIFY) { uint8_t type = (id->config >> 8) & 0x1f; MsgLog::printf("%s: %s, ATAPI ", name, id->model); switch (type) { case 0: /* Early cdrom models used zero */ case 5: MsgLog::printf ("CDROM drive\n"); media = IDE::med_cdrom; present = 0; /* TEMPORARY */ removable = 1; return; case 1:#ifdef IDE_TAPE MsgLog::printf ("TAPE drive"); if (idetape_identify_device (drive,id)) { media = ide_tape; present = 1; removeable = 1; if (hwif->dmaproc != NULL && !hwif->dmaproc(ide_dma_check, drive)) MsgLog::printf(", DMA"); MsgLog::printf("\n"); } else { present = 0; MsgLog::printf ("\nide-tape: the tape is not supported by this version of the driver\n"); } return;#else MsgLog::printf ("TAPE "); break;#endif /* CONFIG_BLK_DEV_IDETAPE */ default: present = 0; MsgLog::printf("Type %d - Unknown device\n", type); return; } present = 0; MsgLog::printf("- not supported by this kernel\n"); return; } /* check for removeable disks (eg. SYQUEST), ignore 'WD' drives */ if (id->config & (1<<7)) { /* removeable disk ? */ if (id->model[0] != 'W' || id->model[1] != 'D') removable = 1; } /* SunDisk drives: treat as non-removeable, force one unit */ if (id->model[0] == 'S' && id->model[1] == 'u') { removable = 0; if (select.b.unit) { present = 0; return; } } media = IDE::med_disk; /* Extract geometry if we did not already have one for the drive */ if (!present) { present = 1; l_chs.cyl = b_chs.cyl = id->cyls; l_chs.hd = b_chs.hd = id->heads; l_chs.sec = b_chs.sec = id->sectors; } /* Handle logical geometry translation by the drive */ if ((id->field_valid & 1) && id->cur_cyls && id->cur_heads && (id->cur_heads <= 16) && id->cur_sectors) { /* * Extract the physical drive geometry for our use. * Note that we purposely do *not* update the bios info. * This way, programs that use it (like fdisk) will * still have the same logical view as the BIOS does, * which keeps the partition table from being screwed. * * An exception to this is the cylinder count, * which we reexamine later on to correct for 1024 limitations. */ l_chs.cyl = id->cur_cyls; l_chs.hd = id->cur_heads; l_chs.sec = id->cur_sectors; /* check for word-swapped "capacity" field in id information */ uint32_t capacity = l_chs.cyl * l_chs.hd * l_chs.sec; uint32_t check = (id->cur_capacity0 << 16) | id->cur_capacity1; if (check == capacity) { /* was it swapped? */ /* yes, bring it into little-endian order: */ id->cur_capacity0 = (capacity >> 0) & 0xffff; id->cur_capacity1 = (capacity >> 16) & 0xffff; } } /* Use physical geometry if what we have still makes no sense */ if ((!l_chs.hd || l_chs.hd > 16) && id->heads && id->heads <= 16) { l_chs.cyl = id->cyls; l_chs.hd = id->heads; l_chs.sec = id->sectors; } /* Correct the number of cyls if the bios value is too small */ if (l_chs.sec == b_chs.sec && l_chs.hd == b_chs.hd) { if (l_chs.cyl > b_chs.cyl) b_chs.cyl = l_chs.cyl; } (void) Capacity(); MsgLog::printf ("%s: %40s, %dMB w/%dkB Cache, %sCHS=%d/%d/%d", name, id->model, Capacity()/2048L, id->buf_size/2, select.b.lba ? "LBA, " : "", b_chs.cyl, b_chs.hd, b_chs.sec); multCount = 0; if (id->max_multsect) { multReq = INITIAL_MULT_COUNT; if (multReq > id->max_multsect) multReq = id->max_multsect; if (multReq || ((id->multsect_valid & 1) && id->multsect)) { flags.b.setMultMode = 1; } } if (hwif->dma_handler != 0) { /* hwif supports DMA? */ if (!(hwif->dma_handler(ide_hwif::dma_check, this))) MsgLog::printf(", DMA"); } MsgLog::printf("\n");}uint8_tide_drive::Identify(uint8_t cmd){ uint32_t irqs = 0; uint16_t hd_status = IDE_CTL_ALTSTATUS; /* non-intrusive preferred */ uint8_t rc = 1; if (hwif->irq == 0) { IRQ::EndProbe(IRQ::BeginProbe()); /* clear pending interrupts */ irqs = IRQ::BeginProbe(); /* wait for interrupt */ hwif->Put8(IDE_CTL_ALTSTATUS, ctl); /* enable device IRQ */ } Machine::SpinWaitMs(DRIVE_DETECT_DELAY); if ((hwif->Get8(IDE_CTL_ALTSTATUS) ^ hwif->Get8(IDE_STATUS)) & ~INDEX_STAT) { MsgLog::printf("%s: probing with STATUS instead of ALTSTATUS\n", name); hd_status = IDE_STATUS; /* use intrusive polling */ } hwif->Put8(IDE_CMD, cmd); uint64_t waitTill = ((cmd == WIN_IDENTIFY) ? WAIT_WORSTCASE : WAIT_PIDENTIFY); waitTill = Machine::MillisecondsToTicks(waitTill); waitTill += SysTimer::Now(); do { if (SysTimer::Now() > waitTill) { if (hwif->irq == 0) (void) IRQ::EndProbe(irqs); MsgLog::printf("Drive timed out.\n"); return 1; /* drive timed-out */ } Machine::SpinWaitMs(DRIVE_DETECT_DELAY); } while (hwif->Get8(hd_status) & BUSY_STAT); Machine::SpinWaitMs(DRIVE_DETECT_DELAY); /* wait for IRQ and DRQ_STAT */ uint8_t status = hwif->Get8(IDE_STATUS); if (OK_STAT(status,DRQ_STAT,BAD_R_STAT)) { MsgLog::printf("Drive returned ID. Fetching ATAPI info...\n"); /* Drive returned an ID. Do an ATAPI identify: */ AtapiIdentify(cmd);#if 0 if (present && media != IDE::med_tape) { /* tune the PIO mode... the BIOS ought to do this these days. */ }#endif rc = 0; /* drive responded with ID */ } else { MsgLog::printf("Drive refused ID.\n"); rc = 2; /* drive refused ID */ } if (hwif->irq == 0) { irqs = IRQ::EndProbe(irqs); /* get irq number */ if (irqs > 0) hwif->irq = irqs; else /* Mmmm.. multiple IRQs */ MsgLog::printf("%s: IRQ probe failed (%d)\n", name, irqs); } DEBUG(probe) MsgLog::dprintf(true, "Pausing after drive probe...\n"); return rc;}voidide_hwif::Put8(uint16_t reg, uint8_t value){#if 0 MsgLog::printf("Writing byte to hwifno %d reg 0x%04x, %d\n", ndx, reg + ioBase, value);#endif old_outb(reg + ioBase, value);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -