diff --git a/Makefile b/Makefile index 478a217..fa81ba7 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ panic.o uaccess.o qr.o time.o) KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ - bio.o cdev.o device.o fb.o ide.o input.o irq.o kbd.o mouse.o \ + bio.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o mouse.o \ pci.o text.o ttf.o uart.o vga.o ramdisk.o procfs.o) # Drivers for hardware that has to be *found* rather than assumed. Anything diff --git a/cmd/init/main.c b/cmd/init/main.c index 6f5586b..a5f68e7 100644 --- a/cmd/init/main.c +++ b/cmd/init/main.c @@ -56,6 +56,16 @@ main(void) mknod("/dev/input/event0", 7, 0); // keyboard mknod("/dev/input/event1", 7, 1); // mouse + // Raw disks (kernel/dev/disk.c). The minor is the IDE device number, so + // hda is the disk the kernel booted from and hdb the second channel. + // + // Created unconditionally, the same way every other node here is: opening + // one when no drive is there fails with ENXIO, which is a better answer + // than a missing path, and it keeps this list a description of the device + // model rather than of one machine's hardware. + mknod("/dev/hda", 8, 0); + mknod("/dev/hdb", 8, 1); + // --- /proc ----------------------------------------------------------- // Synthesised status files, major 6 (kernel/dev/procfs.c). These are the // global ones; per-process /proc// directories need synthetic diff --git a/cmd/schinit/main.c b/cmd/schinit/main.c index 1955ffc..ac0ae2b 100644 --- a/cmd/schinit/main.c +++ b/cmd/schinit/main.c @@ -444,6 +444,18 @@ bootstrap(void) mknod("/dev/input/event0", 7, 0); // keyboard mknod("/dev/input/event1", 7, 1); // mouse + // Raw disks (kernel/dev/disk.c). The minor is the IDE device number, so + // hda is device 0 and hdb device 1 -- the same numbering iderw() uses. + // + // Created unconditionally, like everything else here: opening one when no + // drive is present fails with ENXIO, which is a better answer than a + // missing path. It also matters that these exist even when absent -- with + // no node, `echo x > /dev/hdb` silently creates an ordinary file of that + // name and appears to succeed, which is a confusing way to discover the + // node was never made. + mknod("/dev/hda", 8, 0); + mknod("/dev/hdb", 8, 1); + // /proc is a real filesystem (kernel/fs/procfs.c), not a handful of nodes: // it has to be, because one directory per process cannot be created ahead // of time. That is what makes ps and top work. diff --git a/include/defs.h b/include/defs.h index ec70dc2..470bf1d 100644 --- a/include/defs.h +++ b/include/defs.h @@ -404,6 +404,10 @@ void fb_mapped(void); int fbread(struct inode*, uint, char*, int); int fbwrite(struct inode*, uint, char*, int); int fb_ioctl(int request, addr_t arg); +// dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the +// IDE device number. Writes bypass every filesystem above them. +int diskread(struct inode*, uint, char*, int); +int diskwrite(struct inode*, uint, char*, int); // drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these // are the whole interface: queue a frame, take a frame, and the MAC. int e1000_transmit(const void *frame, int len); diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c index 004fe9f..b9c1361 100644 --- a/kernel/dev/cdev.c +++ b/kernel/dev/cdev.c @@ -104,4 +104,8 @@ struct devsw devsw[] = { // /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The // minor is the device index, so event0 is the keyboard and event1 the mouse. [7] = { inputread, inputwrite, inputready, inputclose, inputopen }, + // /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE + // device number. No `ready` hook: a disk is always ready, which is what + // fileread assumes when one is absent. + [8] = { diskread, diskwrite }, }; diff --git a/kernel/dev/disk.c b/kernel/dev/disk.c new file mode 100644 index 0000000..256b6ba --- /dev/null +++ b/kernel/dev/disk.c @@ -0,0 +1,100 @@ +// 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 "types.h" +#include "defs.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" +#include "file.h" +#include "errno.h" + +// A byte offset arrives as a 32-bit `uint` from the devsw signature, so a raw +// device is addressable to 4 GB. Larger disks need the signature widened -- +// worth doing before anyone installs onto a real one, and called out here +// rather than discovered as silent wraparound at the 4 GB mark. +#define DISK_MAX_BYTES 0xFFFFFFFFULL + +int +diskread(struct inode *ip, uint off, char *dst, int n) +{ + int dev = ip->minor; + + if (!ide_disk_present(dev)) + return -ENXIO; + if (n <= 0) + return 0; + + int done = 0; + while (done < n) { + uint64 pos = (uint64)off + (uint64)done; + uint blk = (uint)(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, uint off, char *src, int n) +{ + int dev = ip->minor; + + if (!ide_disk_present(dev)) + return -ENXIO; + if (n <= 0) + return 0; + + int done = 0; + while (done < n) { + uint64 pos = (uint64)off + (uint64)done; + uint blk = (uint)(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; +} diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 840197c..49fc4fe 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -47,6 +47,17 @@ struct linux_stat { #define LINUX_S_IFREG 0100000 #define LINUX_S_IFLNK 0120000 +// Linux's dev_t: 12 bits of major and 8 of minor packed into the low 32, with +// the overflow of each above that. Spelled out in full rather than as +// (major << 8 | minor), because that is how the other side decodes it and a +// shortcut here would only work while both numbers stayed small. +static uint64 +linux_makedev(uint32 major, uint32 minor) +{ + return ((uint64)(major & 0xfffu) << 8) | (uint64)(minor & 0xffu) + | ((uint64)(major & ~0xfffu) << 32) | ((uint64)(minor & ~0xffu) << 12); +} + static void stati_linux(struct inode *ip, struct linux_stat *st) { @@ -74,6 +85,12 @@ stati_linux(struct inode *ip, struct linux_stat *st) } st->st_mode = type_bits | perm_bits; + // Which device this node actually is. Left at zero until now, so every + // entry in /dev printed as "0, 0" under ls -l -- harmless while nothing + // read it, and wrong the moment something has to tell two disks apart. + if (ip->type == T_DEV) + st->st_rdev = linux_makedev(ip->major, ip->minor); + st->st_atime = ip->atime; st->st_mtime = ip->mtime; st->st_ctime = ip->ctime; }