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
407 lines
11 KiB
C
407 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// The block device registry, and the partition tables on what is in it.
|
|
// See include/fs/blkdev.h.
|
|
|
|
#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/blkdev.h"
|
|
|
|
struct blkdev {
|
|
int used;
|
|
char name[16];
|
|
const struct blkdev_ops *ops;
|
|
void *priv;
|
|
uint64 start; // first sector, in the disk's own sectors
|
|
uint64 nsectors;
|
|
uint32 secsize;
|
|
int parent; // the disk's slot for a partition, -1 for a disk
|
|
int schfs; // 1: typed SchFS; -1: typed FAT; 0: anything else
|
|
};
|
|
|
|
static struct blkdev devs[NBLKDEV];
|
|
static struct spinlock blklock;
|
|
static int blkinit;
|
|
|
|
static struct blkdev *
|
|
lookup(uint dev)
|
|
{
|
|
if (dev < BLKDEV_FIRST || dev >= BLKDEV_FIRST + NBLKDEV)
|
|
return 0;
|
|
struct blkdev *d = &devs[dev - BLKDEV_FIRST];
|
|
return d->used ? d : 0;
|
|
}
|
|
|
|
static int
|
|
add(const char *name, const struct blkdev_ops *ops, void *priv,
|
|
uint64 start, uint64 nsectors, uint32 secsize, int parent, int schfs)
|
|
{
|
|
if (!blkinit) {
|
|
initlock(&blklock, "blkdev");
|
|
blkinit = 1;
|
|
}
|
|
acquire(&blklock);
|
|
for (int i = 0; i < NBLKDEV; i++) {
|
|
if (devs[i].used)
|
|
continue;
|
|
struct blkdev *d = &devs[i];
|
|
safestrcpy(d->name, name, sizeof(d->name));
|
|
d->ops = ops;
|
|
d->priv = priv;
|
|
d->start = start;
|
|
d->nsectors = nsectors;
|
|
d->secsize = secsize;
|
|
d->parent = parent;
|
|
d->schfs = schfs;
|
|
__sync_synchronize();
|
|
d->used = 1;
|
|
release(&blklock);
|
|
return BLKDEV_FIRST + i;
|
|
}
|
|
release(&blklock);
|
|
cprintf("blkdev: no slot for %s; it will not be usable\n", name);
|
|
return -1;
|
|
}
|
|
|
|
// --- partition tables -------------------------------------------------------
|
|
|
|
static uint32 le32(const uchar *p) { return p[0] | p[1] << 8 | p[2] << 16 | (uint32)p[3] << 24; }
|
|
static uint64 le64(const uchar *p) { return le32(p) | (uint64)le32(p + 4) << 32; }
|
|
|
|
// A GUID as GPT stores it: the first three fields little-endian, the last two
|
|
// as bytes. Compared against the printed form, so the constant in blkdev.h is
|
|
// the one a person types into sgdisk.
|
|
static int
|
|
guid_is(const uchar *g, const char *text)
|
|
{
|
|
static const int order[16] = { 3,2,1,0, 5,4, 7,6, 8,9, 10,11,12,13,14,15 };
|
|
uchar want[16];
|
|
int n = 0;
|
|
|
|
for (const char *p = text; *p && n < 32; p++) {
|
|
int v;
|
|
if (*p == '-')
|
|
continue;
|
|
if (*p >= '0' && *p <= '9') v = *p - '0';
|
|
else if (*p >= 'a' && *p <= 'f') v = *p - 'a' + 10;
|
|
else if (*p >= 'A' && *p <= 'F') v = *p - 'A' + 10;
|
|
else return 0;
|
|
if (n & 1) want[n / 2] |= v; else want[n / 2] = v << 4;
|
|
n++;
|
|
}
|
|
for (int i = 0; i < 16; i++)
|
|
if (g[order[i]] != want[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
guid_zero(const uchar *g)
|
|
{
|
|
for (int i = 0; i < 16; i++)
|
|
if (g[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
partname(char *out, const char *disk, int n)
|
|
{
|
|
int i = 0;
|
|
for (; disk[i] && i < 12; i++)
|
|
out[i] = disk[i];
|
|
out[i++] = 'p';
|
|
if (n >= 10)
|
|
out[i++] = '0' + n / 10;
|
|
out[i++] = '0' + n % 10;
|
|
out[i] = 0;
|
|
}
|
|
|
|
static int
|
|
readsec(struct blkdev *d, uint64 lba, uchar *buf)
|
|
{
|
|
return d->ops->rw(d->priv, lba, 1, buf, 0);
|
|
}
|
|
|
|
#define ESP_TYPE "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
|
|
|
// The GUID partition table: the header in sector 1, and its entries wherever
|
|
// it says. Only the primary copy is read -- a disk whose primary is damaged
|
|
// gets reported as having no partitions rather than half of some.
|
|
//
|
|
// The EFI system partition is registered like any other, as FAT: the root
|
|
// probe passes over it (see blkdev_is_schfs), and kernel/fs/fat mounts it.
|
|
static int
|
|
scan_gpt(int disk, uchar *sec)
|
|
{
|
|
struct blkdev *d = &devs[disk];
|
|
int found = 0;
|
|
|
|
if (readsec(d, 1, sec) < 0 || memcmp(sec, "EFI PART", 8) != 0)
|
|
return -1;
|
|
|
|
uint64 elba = le64(sec + 72);
|
|
uint32 count = le32(sec + 80);
|
|
uint32 esize = le32(sec + 84);
|
|
if (esize < 128 || esize > d->secsize || (esize & (esize - 1)) ||
|
|
count > 256 || elba < 2 || elba >= d->nsectors) {
|
|
cprintf("blkdev: %s: GPT header makes no sense; ignoring it\n", d->name);
|
|
return -1;
|
|
}
|
|
|
|
uint32 per = d->secsize / esize;
|
|
for (uint32 i = 0; i < count; i++) {
|
|
if (i % per == 0 && readsec(d, elba + i / per, sec) < 0)
|
|
break;
|
|
uchar *e = sec + (i % per) * esize;
|
|
if (guid_zero(e))
|
|
continue;
|
|
uint64 first = le64(e + 32), last = le64(e + 40);
|
|
if (first == 0 || last < first || last >= d->nsectors)
|
|
continue;
|
|
char name[16];
|
|
partname(name, d->name, i + 1);
|
|
int schfs = guid_is(e, SCHFS_GPT_TYPE) ? 1 : guid_is(e, ESP_TYPE) ? -1 : 0;
|
|
if (add(name, d->ops, d->priv, first, last - first + 1, d->secsize,
|
|
disk, schfs) >= 0) {
|
|
kinfo("blkdev: %s: %d MB%s\n", name,
|
|
(int)(((last - first + 1) * d->secsize) >> 20),
|
|
schfs > 0 ? ", SchFS" : schfs < 0 ? ", EFI system" : "");
|
|
found++;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// The PC partition table, for disks that have one instead. Primary entries
|
|
// only; a protective entry (0xEE) means the disk is GPT and was read above.
|
|
//
|
|
// A FAT volume with no partition table -- a "superfloppy", which is how a
|
|
// good many USB sticks leave the factory -- also ends in 55 AA, and its boot
|
|
// code would read as four nonsense entries. Linux tells them apart by the
|
|
// status byte, which in a real entry is 0x00 or 0x80 and in boot code is
|
|
// whatever the code happens to be.
|
|
static int
|
|
fat_type(uint32 type)
|
|
{
|
|
switch (type) {
|
|
case 0x01: case 0x04: case 0x06: case 0x0B: case 0x0C: case 0x0E: case 0xEF:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
scan_mbr(int disk, uchar *sec)
|
|
{
|
|
struct blkdev *d = &devs[disk];
|
|
|
|
if (readsec(d, 0, sec) < 0 || sec[510] != 0x55 || sec[511] != 0xAA)
|
|
return;
|
|
for (int i = 0; i < 4; i++) {
|
|
uchar *e = sec + 446 + i * 16;
|
|
uint32 type = e[4], first = le32(e + 8), n = le32(e + 12);
|
|
// Empty, protective, and extended (which would need its own chain read).
|
|
if ((e[0] != 0x00 && e[0] != 0x80) ||
|
|
type == 0 || type == 0xEE || type == 0x05 || type == 0x0F ||
|
|
n == 0 || first + (uint64)n > d->nsectors)
|
|
continue;
|
|
char name[16];
|
|
partname(name, d->name, i + 1);
|
|
if (add(name, d->ops, d->priv, first, n, d->secsize, disk,
|
|
fat_type(type) ? -1 : 0) >= 0)
|
|
kinfo("blkdev: %s: %d MB, MBR type %x\n", name,
|
|
(int)(((uint64)n * d->secsize) >> 20), type);
|
|
}
|
|
}
|
|
|
|
int
|
|
blkdev_register(const char *name, const struct blkdev_ops *ops, void *priv,
|
|
uint64 nsectors, uint32 secsize)
|
|
{
|
|
if (secsize == 0 || secsize > BSIZE || BSIZE % secsize)
|
|
return -1;
|
|
int dev = add(name, ops, priv, 0, nsectors, secsize, -1, 0);
|
|
if (dev < 0)
|
|
return -1;
|
|
|
|
uchar *sec = (uchar *)kalloc();
|
|
if (sec) {
|
|
if (scan_gpt(dev - BLKDEV_FIRST, sec) < 0)
|
|
scan_mbr(dev - BLKDEV_FIRST, sec);
|
|
kfree((char *)sec);
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
void
|
|
blkdev_unregister(int dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d)
|
|
return;
|
|
int disk = dev - BLKDEV_FIRST;
|
|
|
|
acquire(&blklock);
|
|
for (int i = 0; i < NBLKDEV; i++) {
|
|
if (devs[i].used && (i == disk || devs[i].parent == disk)) {
|
|
devs[i].used = 0;
|
|
kinfo("blkdev: %s removed\n", devs[i].name);
|
|
release(&blklock);
|
|
binval(BLKDEV_FIRST + i);
|
|
acquire(&blklock);
|
|
}
|
|
}
|
|
release(&blklock);
|
|
}
|
|
|
|
// --- I/O ---------------------------------------------------------------------
|
|
|
|
int
|
|
blkdev_present(uint dev)
|
|
{
|
|
return lookup(dev) != 0;
|
|
}
|
|
|
|
// Same contract as iderw(): fill b->data and set B_VALID on a read, write it
|
|
// out and clear B_DIRTY on a write. A failed transfer panics, for the reason
|
|
// usb_storage_rw() gives: bread() has no way to report one, and a buffer that
|
|
// silently holds the wrong bytes is how a filesystem is corrupted.
|
|
void
|
|
blkdev_rw(struct buf *b)
|
|
{
|
|
struct blkdev *d = lookup(b->dev);
|
|
int write = (b->flags & B_DIRTY) != 0;
|
|
|
|
if (!holdingsleep(&b->lock))
|
|
panic("blkdev_rw: buf not locked");
|
|
if (!d)
|
|
panic("blkdev_rw: no such device");
|
|
|
|
uint32 per = BSIZE / d->secsize;
|
|
uint64 lba = b->blockno * per;
|
|
if (lba + per > d->nsectors)
|
|
panic("blkdev_rw: block out of range");
|
|
|
|
if (d->ops->rw(d->priv, d->start + lba, per, b->data, write) < 0) {
|
|
cprintf("blkdev: %s: %s of block %d failed\n", d->name,
|
|
write ? "write" : "read", (int)b->blockno);
|
|
panic("blkdev_rw: transfer failed");
|
|
}
|
|
b->flags |= B_VALID;
|
|
b->flags &= ~B_DIRTY;
|
|
}
|
|
|
|
int
|
|
blkdev_xfer(uint dev, uint64 lba, uint32 count, void *buf, int write)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
|
|
if (!d || count == 0 || (uint64)count * d->secsize > BSIZE ||
|
|
lba + count > d->nsectors || lba + count < lba)
|
|
return -1;
|
|
return d->ops->rw(d->priv, d->start + lba, count, buf, write) < 0 ? -1 : 0;
|
|
}
|
|
|
|
uint64
|
|
blkdev_sectors(uint dev, uint32 *secsize)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d)
|
|
return 0;
|
|
if (secsize)
|
|
*secsize = d->secsize;
|
|
return d->nsectors;
|
|
}
|
|
|
|
int
|
|
blkdev_flush(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d || !d->ops->flush)
|
|
return 0;
|
|
return d->ops->flush(d->priv);
|
|
}
|
|
|
|
int
|
|
blkdev_nth(int i)
|
|
{
|
|
for (int s = 0; s < NBLKDEV; s++)
|
|
if (devs[s].used && i-- == 0)
|
|
return BLKDEV_FIRST + s;
|
|
return -1;
|
|
}
|
|
|
|
const char *
|
|
blkdev_name(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
return d ? d->name : "?";
|
|
}
|
|
|
|
int
|
|
blkdev_is_schfs(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
return d ? d->schfs : 0;
|
|
}
|
|
|
|
// --- /proc/partitions --------------------------------------------------------
|
|
|
|
static int
|
|
put(char *buf, int cap, int off, const char *s)
|
|
{
|
|
while (*s && off < cap)
|
|
buf[off++] = *s++;
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
putu(char *buf, int cap, int off, uint64 v)
|
|
{
|
|
char t[24];
|
|
int n = 0;
|
|
do { t[n++] = '0' + v % 10; v /= 10; } while (v);
|
|
while (n && off < cap)
|
|
buf[off++] = t[--n];
|
|
return off;
|
|
}
|
|
|
|
// putu(), right-aligned in `w` columns, the way printf("%*llu") would.
|
|
static int
|
|
putw(char *buf, int cap, int off, uint64 v, int w)
|
|
{
|
|
int n = 1;
|
|
for (uint64 t = v; t >= 10; t /= 10)
|
|
n++;
|
|
for (; w > n; w--)
|
|
off = put(buf, cap, off, " ");
|
|
return putu(buf, cap, off, v);
|
|
}
|
|
|
|
// /proc/partitions, in Linux's format to the column, because that is what
|
|
// reads it: lsblk's fallback, fdisk -l, every installer and rescue script.
|
|
// Sizes are in 1 KB blocks. The major is 8 for all of them, the one devtmpfs
|
|
// gives every disk here, and the minor is the device number, as in /dev.
|
|
int
|
|
blkdev_info(char *buf, int cap)
|
|
{
|
|
int off = put(buf, cap, 0, "major minor #blocks name\n\n");
|
|
for (int s = 0; s < NBLKDEV; s++) {
|
|
struct blkdev *d = &devs[s];
|
|
if (!d->used)
|
|
continue;
|
|
off = putw(buf, cap, off, 8, 4);
|
|
off = putw(buf, cap, off, BLKDEV_FIRST + s, 8);
|
|
off = putw(buf, cap, off, (d->nsectors * d->secsize) >> 10, 11);
|
|
off = put(buf, cap, off, " ");
|
|
off = put(buf, cap, off, d->name);
|
|
off = put(buf, cap, off, "\n");
|
|
}
|
|
return off;
|
|
}
|