Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
158 lines
5.1 KiB
C
158 lines
5.1 KiB
C
// Raw block devices: /dev/hda, /dev/hdb.
|
|
//
|
|
// Until this existed, nothing outside the kernel could touch a disk. sys_mount
|
|
// does not even look at the device it is given -- it mounts the one SchFS
|
|
// volume the kernel found at boot and ignores `source` -- so a program had no
|
|
// way to read a partition table, format a volume, or write a boot sector. An
|
|
// installer is exactly a program that needs all three.
|
|
//
|
|
// This is a character device in the devsw table rather than a separate block
|
|
// layer, because that is where the offset already is: fileread/filewrite pass
|
|
// f->off and advance it by whatever comes back, which is all a seekable device
|
|
// needs. `dd if=/dev/hdb bs=4096 count=1` works, and so does lseek.
|
|
//
|
|
// The minor number is the IDE device number: 0 is the disk the kernel booted
|
|
// from, 1 is the second channel. Same numbering iderw() uses, deliberately --
|
|
// two numbering schemes for the same disks is a bug waiting to be written.
|
|
//
|
|
// WARNING, and the reason this file is short: writes here bypass the
|
|
// filesystem entirely. Writing to a device with a mounted filesystem on it will
|
|
// corrupt that filesystem, because the buffer cache above still believes its
|
|
// own copy. That is inherent to a raw device and not a defect -- but it is why
|
|
// an installer must target a disk it has not mounted.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/buf.h"
|
|
#include "fs/file.h"
|
|
#include "uapi/errno.h"
|
|
#include "fs/blkdev.h"
|
|
|
|
// The byte offset is 64-bit, all the way from struct file's off through the
|
|
// devsw signature to bread's block number. It used to narrow to a 32-bit uint
|
|
// at the devsw boundary, which capped a raw device at 4 GB -- and the constant
|
|
// that documented the cap was never referenced by anything, so the failure was
|
|
// not a refusal at 4 GB but silent wraparound past it. Nothing here may narrow
|
|
// it again: an NVMe namespace or any modern disk is far beyond that.
|
|
|
|
// A disk or partition that registered with the block layer (minor 4 onward):
|
|
// a SATA or NVMe disk, a USB stick, any partition on them. Moved through
|
|
// blkdev_xfer in whole sectors rather than through the buffer cache, because a
|
|
// partition need not be a whole number of BSIZE blocks and the buffer cache's
|
|
// last block of one would run into the next. A read at the end returns 0, as
|
|
// a file's does, and a write there fails with ENOSPC.
|
|
static int
|
|
blk_rw(uint dev, uint64 off, char *p, int n, int write)
|
|
{
|
|
uint32 ss = 0;
|
|
uint64 nsec = blkdev_sectors(dev, &ss);
|
|
if (nsec == 0)
|
|
return -ENXIO;
|
|
if (n <= 0)
|
|
return 0;
|
|
uint64 size = nsec * ss;
|
|
if (off >= size)
|
|
return write ? -ENOSPC : 0;
|
|
if ((uint64)n > size - off)
|
|
n = (int)(size - off);
|
|
|
|
char *tmp = kalloc();
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
int done = 0;
|
|
while (done < n) {
|
|
uint64 pos = off + (uint64)done;
|
|
uint64 lba = pos / ss;
|
|
uint in = (uint)(pos % ss);
|
|
uint32 cnt = (uint32)(BSIZE / ss);
|
|
if (lba + cnt > nsec)
|
|
cnt = (uint32)(nsec - lba);
|
|
int want = (int)(cnt * ss) - (int)in;
|
|
if (want > n - done)
|
|
want = n - done;
|
|
// Whole sectors are written as they are; a partial one is read first.
|
|
uint32 rcnt = (in + want + ss - 1) / ss;
|
|
if (!write || in || want % ss)
|
|
if (blkdev_xfer(dev, lba, rcnt, tmp, 0) < 0)
|
|
break;
|
|
if (write) {
|
|
memmove(tmp + in, p + done, want);
|
|
if (blkdev_xfer(dev, lba, rcnt, tmp, 1) < 0)
|
|
break;
|
|
} else
|
|
memmove(p + done, tmp + in, want);
|
|
done += want;
|
|
}
|
|
kfree(tmp);
|
|
return done ? done : -EIO;
|
|
}
|
|
|
|
int
|
|
diskread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
int dev = ip->minor;
|
|
|
|
if (dev >= BLKDEV_FIRST)
|
|
return blk_rw((uint)dev, off, dst, n, 0);
|
|
if (!ide_disk_present(dev))
|
|
return -ENXIO;
|
|
if (n <= 0)
|
|
return 0;
|
|
|
|
int done = 0;
|
|
while (done < n) {
|
|
uint64 pos = off + (uint64)done;
|
|
uint64 blk = pos / BSIZE;
|
|
uint inblk = (uint)(pos % BSIZE);
|
|
int want = BSIZE - (int)inblk;
|
|
if (want > n - done)
|
|
want = n - done;
|
|
|
|
struct buf *b = bread((uint)dev, blk);
|
|
memmove(dst + done, b->data + inblk, want);
|
|
brelse(b);
|
|
|
|
done += want;
|
|
}
|
|
return done;
|
|
}
|
|
|
|
int
|
|
diskwrite(struct inode *ip, uint64 off, char *src, int n)
|
|
{
|
|
int dev = ip->minor;
|
|
|
|
if (dev >= BLKDEV_FIRST)
|
|
return blk_rw((uint)dev, off, src, n, 1);
|
|
if (!ide_disk_present(dev))
|
|
return -ENXIO;
|
|
if (n <= 0)
|
|
return 0;
|
|
|
|
int done = 0;
|
|
while (done < n) {
|
|
uint64 pos = off + (uint64)done;
|
|
uint64 blk = pos / BSIZE;
|
|
uint inblk = (uint)(pos % BSIZE);
|
|
int want = BSIZE - (int)inblk;
|
|
if (want > n - done)
|
|
want = n - done;
|
|
|
|
// Read-modify-write for anything that is not a whole block. bread() first
|
|
// even when the whole block is being replaced would be a wasted revolution,
|
|
// but distinguishing the two cases means trusting the caller's alignment;
|
|
// the disk is not the bottleneck in an installer and correctness here is
|
|
// worth more than the read.
|
|
struct buf *b = bread((uint)dev, blk);
|
|
memmove(b->data + inblk, src + done, want);
|
|
bwrite(b);
|
|
brelse(b);
|
|
|
|
done += want;
|
|
}
|
|
return done;
|
|
}
|