storage: AHCI, GPT/MBR partitions, and a disk a PC can boot
q35 stopped with "no root filesystem" because its disk is SATA, like every PC since 2008, and SchOS only had IDE. And SchFS could only sit on a whole raw device, so nothing could boot from a disk that also carried an ESP. - include/blkdev.h, kernel/dev/blkdev.c: a registry of block devices found at probe time, numbered from 4. A disk's GPT (or MBR) is read when it registers, and each partition becomes a device whose block 0 is the partition's first sector. SchFS needs no format change to live in a partition; a Linux SchFS driver will see the same bytes on /dev/sdX2. SchFS has its own GPT type: 47138FA9-B79C-47A6-9F7D-AC1F7F572D5E. - AHCI driver, written from the AHCI 1.3.1 spec: BIOS handoff, COMRESET when the link is down, IDENTIFY, 48-bit DMA read/write, FLUSH. Completion is polled, so the root disk does not depend on interrupt routing. - USB sticks also register as usb0, so their partitions are found. - The root probe tries registered devices between USB and IDE, SchFS-typed partitions first. - make disk / tools/posix/mkdisk.sh: a GPT image with a 64 MB ESP and a SchFS partition, ready to dd to a stick or a disk. Tested in QEMU q35: the old two-disk setup roots on sata1; disk.img as the only SATA disk roots on sata0p2; disk.img as a USB stick (firmware boots from it) roots on usb0p2. Writes survive sync in each case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
parent
7e8d5e2821
commit
d7352c0db0
10 changed files with 1027 additions and 17 deletions
|
|
@ -27,6 +27,7 @@
|
|||
#include "buf.h"
|
||||
#include "x86.h"
|
||||
#include "fsstat.h"
|
||||
#include "blkdev.h"
|
||||
|
||||
struct fsstat fsstat;
|
||||
|
||||
|
|
@ -86,6 +87,12 @@ bdevrw_inner(struct buf *b)
|
|||
usb_storage_rw(b);
|
||||
return;
|
||||
}
|
||||
// SATA, NVMe and every partition: registered at probe time rather than
|
||||
// known at build time. See include/blkdev.h.
|
||||
if(b->dev >= BLKDEV_FIRST){
|
||||
blkdev_rw(b);
|
||||
return;
|
||||
}
|
||||
iderw(b);
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +106,8 @@ bdevflush(uint dev)
|
|||
return 0; // memory has no volatile stage below it
|
||||
if(dev == USBDISK_DEV)
|
||||
return usb_storage_flush();
|
||||
if(dev >= BLKDEV_FIRST)
|
||||
return blkdev_flush(dev);
|
||||
return ideflush(dev);
|
||||
}
|
||||
|
||||
|
|
|
|||
334
kernel/dev/blkdev.c
Normal file
334
kernel/dev/blkdev.c
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
// The block device registry, and the partition tables on what is in it.
|
||||
// See include/blkdev.h.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
#include "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; // GPT says this is a SchFS volume
|
||||
};
|
||||
|
||||
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 skipped. It is FAT, nothing here can mount it,
|
||||
// and registering it would only give the root probe one more thing to try.
|
||||
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) || guid_is(e, ESP_TYPE))
|
||||
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);
|
||||
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 ? ", SchFS" : "");
|
||||
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.
|
||||
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, extended (which would need its own chain read), and
|
||||
// FAT/ESP types: none of them can be SchFS.
|
||||
if (type == 0 || type == 0xEE || type == 0x05 || type == 0x0F ||
|
||||
type == 0xEF || 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, 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;
|
||||
}
|
||||
|
||||
// --- 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_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;
|
||||
}
|
||||
|
||||
int
|
||||
blkdev_info(char *buf, int cap)
|
||||
{
|
||||
int off = put(buf, cap, 0, "dev name MB type\n");
|
||||
for (int s = 0; s < NBLKDEV; s++) {
|
||||
struct blkdev *d = &devs[s];
|
||||
if (!d->used)
|
||||
continue;
|
||||
off = putu(buf, cap, off, BLKDEV_FIRST + s);
|
||||
off = put(buf, cap, off, " ");
|
||||
off = put(buf, cap, off, d->name);
|
||||
for (int pad = 12 - strlen(d->name); pad > 0; pad--)
|
||||
off = put(buf, cap, off, " ");
|
||||
off = putu(buf, cap, off, (d->nsectors * d->secsize) >> 20);
|
||||
off = put(buf, cap, off, d->parent < 0 ? " disk" :
|
||||
d->schfs ? " schfs" : " part");
|
||||
off = put(buf, cap, off, "\n");
|
||||
}
|
||||
return off;
|
||||
}
|
||||
Loading…
Reference in a new issue