SchFS is a 64-bit format addressing 64 ZB. The stack under it was not, and the lowest limit was three orders of magnitude below the one anybody would have guessed: ide.c panicked on any block >= FSSIZE -- 150000 blocks, 614 MB ide.c computed the sector in an int, and addressed it with LBA28 bio.c took a 32-bit block number, capping any device at 16 TB schfs refused blocks past 0xffffffff, mirroring the cache The FSSIZE check is the one that mattered. FSSIZE describes the size of a v1 filesystem *image*; it had no business bounding a physical device, and it meant a disk larger than half a gigabyte panicked the kernel rather than being read. struct buf's blockno is uint64 now, and so is bread's. idestart computes the sector in 64 bits and picks LBA48 when it does not fit in 28 -- per request, so everything that fits stays on the LBA28 path every existing image has been read through. The 48-bit registers are written high half first, because each is a two-deep FIFO and the other order leaves the drive addressing whatever the low bytes alone describe. Verified with a 2 GB disk carrying a marker at 1 GiB -- block 262144, well past the old panic at 150000. The guest reads it back: "MARKER-PAST-THE-OLD-LIMIT". schfs-check passes including the round-trip; bootcheck 108 [ok]. Two limits deliberately left, both noted rather than guessed at: raw /dev/hdN access still goes through devsw's 32-bit offset, so a raw device is addressable to 4 GB; and usb_storage computes a 32-bit LBA, which is what SCSI READ(10) carries -- READ(16) is a different command, not a wider variable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
291 lines
8.7 KiB
C
291 lines
8.7 KiB
C
// Simple PIO-based (non-DMA) IDE driver code.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "irq.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "x86.h"
|
|
#include "traps.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "buf.h"
|
|
|
|
#define SECTOR_SIZE 512
|
|
#define IDE_BSY 0x80
|
|
#define IDE_DRDY 0x40
|
|
#define IDE_DF 0x20
|
|
#define IDE_ERR 0x01
|
|
|
|
#define IDE_CMD_READ 0x20
|
|
#define IDE_CMD_WRITE 0x30
|
|
#define IDE_CMD_RDMUL 0xc4
|
|
#define IDE_CMD_SETMULT 0xc6
|
|
#define IDE_CMD_WRMUL 0xc5
|
|
// The 48-bit forms. Same operations, an address that is not capped at 128 GB.
|
|
#define IDE_CMD_READ_EXT 0x24
|
|
#define IDE_CMD_WRITE_EXT 0x34
|
|
#define IDE_CMD_RDMUL_EXT 0x29
|
|
#define IDE_CMD_WRMUL_EXT 0x39
|
|
|
|
// idequeue points to the buf now being read/written to the disk.
|
|
// idequeue->qnext points to the next buf to be processed.
|
|
// You must hold idelock while manipulating queue.
|
|
|
|
static struct spinlock idelock;
|
|
static struct buf *idequeue;
|
|
|
|
static int havedisk0;
|
|
static int havedisk1;
|
|
static void idestart(struct buf*);
|
|
|
|
// Wait for the drive to become ready, or give up.
|
|
//
|
|
// This used to be an unbounded `while`. On a machine with nothing attached to
|
|
// the primary channel the status register reads 0xFF forever, so the very
|
|
// first call from ideinit() hung the kernel -- no panic, no message, just a
|
|
// dead machine right after the VGA banner. That is not an exotic
|
|
// configuration: it is every CD boot, and most hardware newer than IDE.
|
|
//
|
|
// The bound is deliberately enormous. A real drive answers in microseconds;
|
|
// this only expires when there is nothing there at all.
|
|
static int
|
|
idewait(int checkerr)
|
|
{
|
|
int r = 0;
|
|
|
|
for(int i = 0; i < 20000000; i++){
|
|
r = inb(0x1f7);
|
|
if((r & (IDE_BSY|IDE_DRDY)) == IDE_DRDY){
|
|
if(checkerr && (r & (IDE_DF|IDE_ERR)) != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
ide_irq(void *arg)
|
|
{
|
|
(void)arg;
|
|
ideintr();
|
|
}
|
|
|
|
// SET MULTIPLE MODE: how many sectors the drive moves per interrupt during
|
|
// READ/WRITE MULTIPLE. Must equal BSIZE/SECTOR_SIZE or idestart()'s single
|
|
// insl/outsl of a whole block does not line up with the drive's bursts.
|
|
//
|
|
// A drive may refuse a count it cannot do (IDENTIFY word 47 carries its
|
|
// maximum; QEMU allows 16). If it does, say so rather than continue: the
|
|
// transfers would silently desynchronise, which reads as filesystem
|
|
// corruption rather than as a driver problem.
|
|
static void
|
|
ide_set_multiple(int dev)
|
|
{
|
|
int spb = BSIZE / SECTOR_SIZE;
|
|
|
|
outb(0x1f6, 0xe0 | ((dev & 1) << 4));
|
|
if(idewait(0) < 0)
|
|
return;
|
|
outb(0x1f2, spb);
|
|
outb(0x1f7, IDE_CMD_SETMULT);
|
|
if(idewait(1) < 0)
|
|
cprintf("ide%d: SET MULTIPLE MODE %d rejected; expect trouble\n", dev, spb);
|
|
}
|
|
|
|
void
|
|
ideinit(void)
|
|
{
|
|
initlock(&idelock, "ide");
|
|
// ISA signalling: edge-triggered, active high. Everything at a fixed
|
|
// legacy port is wired this way; PCI devices are not.
|
|
irq_register(IRQ_IDE, ide_irq, 0, "ide", 0);
|
|
|
|
// A timeout here means there is no drive on this channel. Say so and leave
|
|
// havedisk0/1 clear: with a ramdisk root there is nothing else the system
|
|
// needs from IDE, and booting on is strictly better than stopping.
|
|
if(idewait(0) < 0){
|
|
cprintf("ide: no drive on the primary channel\n");
|
|
return;
|
|
}
|
|
havedisk0 = 1;
|
|
|
|
// Check if disk 1 is present
|
|
outb(0x1f6, 0xe0 | (1<<4));
|
|
for(int i=0; i<1000; i++){
|
|
if(inb(0x1f7) != 0){
|
|
havedisk1 = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Tell each drive how many sectors to move per interrupt.
|
|
//
|
|
// READ/WRITE MULTIPLE transfer the drive's *multiple count* of sectors
|
|
// between interrupts, and that count is whatever SET MULTIPLE MODE last
|
|
// established -- it is not taken from the sector-count register. Nothing
|
|
// ever issued it here, so the count was undefined, which is the real reason
|
|
// idestart() carried a `sector_per_block > 7` panic: BSIZE was 1024, two
|
|
// sectors, and nobody had tested further.
|
|
//
|
|
// Establishing it explicitly is what lets BSIZE be a whole SchFS block. At
|
|
// 1024 every 4096-byte SchFS block cost four IDE requests, each with its own
|
|
// interrupt and sleep/wakeup, and exec() of the 800 KB toybox binary meant
|
|
// ~800 round trips -- about two seconds, on every command.
|
|
ide_set_multiple(0);
|
|
if(havedisk1)
|
|
ide_set_multiple(1);
|
|
|
|
// Switch back to disk 0.
|
|
outb(0x1f6, 0xe0 | (0<<4));
|
|
}
|
|
|
|
// Is there actually a drive there? Callers that probe -- rather than callers
|
|
// that know what they are looking for -- have to ask, because reading an
|
|
// absent drive is a bug and iderw() treats it as one.
|
|
int
|
|
ide_disk_present(int dev)
|
|
{
|
|
return dev == 0 ? havedisk0 : havedisk1;
|
|
}
|
|
|
|
// Start the request for b. Caller must hold idelock.
|
|
static void
|
|
idestart(struct buf *b)
|
|
{
|
|
if(b == 0)
|
|
panic("idestart");
|
|
|
|
// No FSSIZE check. It used to panic on any block past 150000 -- 614 MB at
|
|
// BSIZE -- which is a constant describing the *v1 filesystem image*, not the
|
|
// disk. It had no business bounding a device, and it was the lowest ceiling
|
|
// in the stack by three orders of magnitude: the format addresses 64 ZB and
|
|
// the drive was refused past half a gigabyte.
|
|
int sector_per_block = BSIZE/SECTOR_SIZE;
|
|
uint64 sector = b->blockno * (uint64)sector_per_block;
|
|
|
|
// No `sector_per_block > 7` panic here any more. That limit existed because
|
|
// nothing had set the drive's multiple count, so anything past the tested
|
|
// two sectors was a guess; ideinit() issues SET MULTIPLE MODE now, and the
|
|
// count is whatever BSIZE says.
|
|
|
|
// LBA28 addresses 2^28 sectors, which is 128 GB and no more. Past that the
|
|
// 48-bit commands are the only way to name the sector at all, so the mode is
|
|
// chosen per request: LBA28 stays the path for everything that fits, because
|
|
// it is the one every existing image has been read through.
|
|
int lba48 = sector >= (1ULL << 28);
|
|
int read_cmd, write_cmd;
|
|
|
|
if(lba48){
|
|
read_cmd = (sector_per_block == 1) ? IDE_CMD_READ_EXT : IDE_CMD_RDMUL_EXT;
|
|
write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE_EXT : IDE_CMD_WRMUL_EXT;
|
|
} else {
|
|
read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL;
|
|
write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL;
|
|
}
|
|
|
|
idewait(0);
|
|
outb(0x3f6, 0); // generate interrupt
|
|
|
|
if(lba48){
|
|
// The high half goes first, then the low half into the same registers:
|
|
// each is a two-deep FIFO, and writing them in the other order leaves the
|
|
// drive addressing whichever sector the low bytes alone describe.
|
|
outb(0x1f2, 0); // sector count, high byte
|
|
outb(0x1f3, (sector >> 24) & 0xff);
|
|
outb(0x1f4, (sector >> 32) & 0xff);
|
|
outb(0x1f5, (sector >> 40) & 0xff);
|
|
outb(0x1f2, sector_per_block); // sector count, low byte
|
|
outb(0x1f3, sector & 0xff);
|
|
outb(0x1f4, (sector >> 8) & 0xff);
|
|
outb(0x1f5, (sector >> 16) & 0xff);
|
|
// LBA48 carries no address bits in the drive/head register.
|
|
outb(0x1f6, 0x40 | ((b->dev&1)<<4));
|
|
} else {
|
|
outb(0x1f2, sector_per_block);
|
|
outb(0x1f3, sector & 0xff);
|
|
outb(0x1f4, (sector >> 8) & 0xff);
|
|
outb(0x1f5, (sector >> 16) & 0xff);
|
|
outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f));
|
|
}
|
|
|
|
if(b->flags & B_DIRTY){
|
|
outb(0x1f7, write_cmd);
|
|
outsl(0x1f0, b->data, BSIZE/4);
|
|
} else {
|
|
outb(0x1f7, read_cmd);
|
|
}
|
|
}
|
|
|
|
// Interrupt handler.
|
|
void
|
|
ideintr(void)
|
|
{
|
|
struct buf *b;
|
|
|
|
// First queued buffer is the active request.
|
|
acquire(&idelock);
|
|
if((b = idequeue) == 0){
|
|
release(&idelock);
|
|
// cprintf("spurious IDE interrupt\n");
|
|
return;
|
|
}
|
|
idequeue = b->qnext;
|
|
|
|
// Read data if needed.
|
|
if(!(b->flags & B_DIRTY) && idewait(1) >= 0)
|
|
insl(0x1f0, b->data, BSIZE/4);
|
|
|
|
// Wake process waiting for this buf.
|
|
b->flags |= B_VALID;
|
|
b->flags &= ~B_DIRTY;
|
|
wakeup(b);
|
|
|
|
// Start disk on next buf in queue.
|
|
if(idequeue != 0)
|
|
idestart(idequeue);
|
|
|
|
release(&idelock);
|
|
}
|
|
|
|
//PAGEBREAK!
|
|
// Sync buf with disk.
|
|
// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID.
|
|
// Else if B_VALID is not set, read buf from disk, set B_VALID.
|
|
void
|
|
iderw(struct buf *b)
|
|
{
|
|
struct buf **pp;
|
|
|
|
if(!holdingsleep(&b->lock))
|
|
panic("iderw: buf not locked");
|
|
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
|
|
panic("iderw: nothing to do");
|
|
if(b->dev != 0 && !havedisk1)
|
|
panic("iderw: ide disk 1 not present");
|
|
if(b->dev == 0 && !havedisk0)
|
|
panic("iderw: ide disk 0 not present");
|
|
|
|
acquire(&idelock); //DOC:acquire-lock
|
|
|
|
// Append b to idequeue.
|
|
b->qnext = 0;
|
|
for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue
|
|
;
|
|
*pp = b;
|
|
|
|
// Start disk if necessary.
|
|
if(idequeue == b)
|
|
idestart(b);
|
|
|
|
// Wait for request to finish.
|
|
while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
|
|
sleep(b, &idelock);
|
|
}
|
|
|
|
release(&idelock);
|
|
}
|