diff --git a/include/buf.h b/include/buf.h index 3266495..3e094e6 100644 --- a/include/buf.h +++ b/include/buf.h @@ -1,7 +1,11 @@ struct buf { int flags; uint dev; - uint blockno; + // 64-bit, because the filesystem's block numbers are. This was a uint, which + // capped any device at 2^32 blocks (16 TB at 4 KB) no matter what the format + // above it could address -- SchFS is a 64-bit format and was being served by + // a 32-bit cache. + uint64 blockno; struct sleeplock lock; uint refcnt; struct buf *prev; // LRU cache list diff --git a/include/defs.h b/include/defs.h index af8c317..cff4c08 100644 --- a/include/defs.h +++ b/include/defs.h @@ -20,7 +20,7 @@ void ignore_sysret(void); // bio.c void binit(void); -struct buf* bread(uint, uint); +struct buf* bread(uint, uint64); void brelse(struct buf*); void bwrite(struct buf*); diff --git a/kernel/dev/bio.c b/kernel/dev/bio.c index 7685c87..84b8696 100644 --- a/kernel/dev/bio.c +++ b/kernel/dev/bio.c @@ -77,7 +77,7 @@ bdevrw(struct buf *b) // If not found, allocate a buffer. // In either case, return locked buffer. static struct buf* -bget(uint dev, uint blockno) +bget(uint dev, uint64 blockno) { struct buf *b; @@ -112,7 +112,7 @@ bget(uint dev, uint blockno) // Return a locked buf with the contents of the indicated block. struct buf* -bread(uint dev, uint blockno) +bread(uint dev, uint64 blockno) { struct buf *b; diff --git a/kernel/dev/ide.c b/kernel/dev/ide.c index f9fdf91..5af48c7 100644 --- a/kernel/dev/ide.c +++ b/kernel/dev/ide.c @@ -25,6 +25,11 @@ #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. @@ -154,25 +159,60 @@ idestart(struct buf *b) { if(b == 0) panic("idestart"); - if(b->blockno >= FSSIZE) - panic("incorrect blockno"); - int sector_per_block = BSIZE/SECTOR_SIZE; - int sector = b->blockno * sector_per_block; - int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL; - int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL; + + // 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 - outb(0x1f2, sector_per_block); // number of sectors - 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(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); diff --git a/kernel/fs/schfs/kmount.c b/kernel/fs/schfs/kmount.c index faa4bb0..578a37d 100644 --- a/kernel/fs/schfs/kmount.c +++ b/kernel/fs/schfs/kmount.c @@ -112,10 +112,9 @@ kd_read(void *ctx, uint64_t blk, void *out) if (blk == 0) return -1; - // The buffer cache addresses blocks with a uint, which caps the volume long - // before the on-disk format does. Refusing out here beats truncating and - // reading some unrelated sector, which would surface as corruption. - if (blk > 0xffffffffULL / SCHFS_BPB) + // The buffer cache takes 64-bit block numbers now, so this no longer caps the + // volume; the check remains as an overflow guard on the multiply below. + if (blk > (~0ULL) / SCHFS_BPB) return -1; for (int i = 0; i < SCHFS_BPB; i++) { @@ -133,7 +132,7 @@ kd_write(void *ctx, uint64_t blk, const void *in) if (blk == 0) return -1; - if (blk > 0xffffffffULL / SCHFS_BPB) + if (blk > (~0ULL) / SCHFS_BPB) return -1; // Straight through the buffer cache, with no log. The xv6 log exists to make