SchOS/kernel/dev/blkdev.c
Nexoniarz fa7669254e usb: plug and play -- hot-plug, unplug, every interface, real mice
Devices can now arrive and leave after boot. A "usbhotplug" kernel thread
handles root-port changes, which it is told about by Port Status Change
events and also checks for every quarter second. It also handles changes
behind hubs, which each hub reports on its status-change endpoint. An
unplugged device has its endpoints closed and any transfer in flight
failed. Then its drivers' disconnect hooks run, the slot is disabled and
its memory is freed. Everything behind a hub that goes goes with it. This
needed the interrupt handler to file command and endpoint-0 completions
for their waiters, so that enumeration can run while keyboards interrupt.

On real hardware: the controller is taken from the firmware through USB
Legacy Support before reset, with SMIs off. On Intel 7/8/9 series parts
the ports are switched over from EHCI, which is where firmware leaves
them.

Every interface of a device is offered to the class drivers, not just
the first, so a receiver's mouse on interface 1 gets a driver. Configure
Endpoint now copies the slot context from the controller. Before, it
rebuilt it and lost the route string and TT fields of devices behind
hubs.

HID: pointers are read in report protocol from their report descriptor.
That covers buttons, X/Y and wheel, relative or absolute, and report IDs.
Boot protocol is kept as the fallback for mice and for keyboards.
Non-pointer HID interfaces are declined rather than treated as tablets.
USB keyboards get software key repeat. Mass storage handles up to four
sticks, each unregistered from the block layer when it is pulled out,
along with its cached blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
2026-10-22 11:38:13 +02:00

355 lines
9.2 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/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;
}
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_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;
}