// 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" #include "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; }