#include "disk.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bios.h"
#include "memory.h"
#include "tty.h"
#include "x86/boot.h"
#include "x86/mbr.h"
#include "x86/regs.h"
#define MBR_SECTOR_SIZE 512
#define BIOS_MAX_SECTORS 64
#define BOUNCE_SIZE 8192
typedef struct {
u16 drive;
u16 sector_size;
u16 flags;
size_t root_base;
size_t root_size;
bool root_found;
u8 root_part;
boot_ext2_t fs;
u8 bounce[BOUNCE_SIZE];
} bios_disk_t;
static bios_disk_t bios_disk = {
.sector_size = MBR_SECTOR_SIZE,
};
static bool read_lba(void *dest, size_t lba, u16 sectors) {
if (!dest || !sectors) {
return false;
}
u32 real_dest = (u32)REAL_OFFSET(dest) | ((u32)REAL_SEG(dest) << 16);
dap_t dap = {
.size = sizeof(dap_t),
.sectors = sectors,
.destination = real_dest,
.lba = lba,
};
regs32_t r = { 0 };
r.ah = 0x42;
r.dl = bios_disk.drive;
r.esi = (uintptr_t)&dap;
bios_call(0x13, &r, &r);
return !(r.flags & FLAG_CF);
}
int read_disk(void *dest, size_t offset, size_t bytes) {
size_t ss = bios_disk.sector_size;
if (!ss) {
panic("invalid disk sector size");
}
size_t lba = offset / ss;
size_t sector_off = offset % ss;
u16 bounce_sectors = BOUNCE_SIZE / ss;
u16 max_sectors = (u16)min((size_t)BIOS_MAX_SECTORS, (size_t)bounce_sectors);
uint8_t *out = dest;
while (bytes > 0) {
if (bytes > SIZE_MAX - sector_off) {
panic("disk read size overflow");
}
size_t bytes_window = bytes + sector_off;
size_t sectors_window = DIV_ROUND_UP(bytes_window, ss);
u16 sectors = (u16)min(sectors_window, (size_t)max_sectors);
if (!read_lba(bios_disk.bounce, lba, sectors)) {
panic("disk read error");
}
size_t available = (size_t)sectors * ss - sector_off;
size_t to_copy = min(bytes, available);
memcpy(out, bios_disk.bounce + sector_off, to_copy);
out += to_copy;
bytes -= to_copy;
lba += sectors;
sector_off = 0;
}
return 0;
}
static bool read_rootfs_cb(void *dest, size_t offset, size_t bytes, void *ctx) {
(void)ctx;
if (offset > SIZE_MAX - bios_disk.root_base) {
panic("rootfs read offset overflow");
}
read_disk(dest, bios_disk.root_base + offset, bytes);
return true;
}
static bool find_part(mbr_partition_t *out_part, u8 *part_index) {
mbr_t mbr;
read_disk(&mbr, 0, sizeof(mbr_t));
if (mbr.signature != MBR_SIGNATURE) {
panic("MBR has invalid signature");
}
for (size_t i = 0; i < 4; i++) {
mbr_partition_t *partition = &mbr.table.partitions[i];
if (partition->type != MBR_LINUX) {
continue;
}
if (partition->status == MBR_BOOTABLE) {
continue;
}
memcpy(out_part, partition, sizeof(mbr_partition_t));
if (part_index) {
*part_index = (u8)i;
}
return true;
}
return false;
}
static void detect_sector(void) {
disk_params_t params;
memset(¶ms, 0, sizeof(params));
params.size = sizeof(params);
regs32_t r = { 0 };
r.ah = 0x48;
r.dl = bios_disk.drive;
r.esi = (uintptr_t)¶ms;
bios_call(0x13, &r, &r);
if (!(r.flags & FLAG_CF)) {
bios_disk.flags = params.flags;
if (params.bytes_per_sector >= 512) {
bios_disk.sector_size = params.bytes_per_sector;
}
}
}
void disk_init(u16 drive) {
bios_disk.drive = drive;
detect_sector();
log_debug("boot disk=%#x sector=%u", bios_disk.drive, bios_disk.sector_size);
mbr_partition_t part = { 0 };
u8 rootfs_index = 0;
if (!find_part(&part, &rootfs_index)) {
panic("rootfs partition not found");
}
bios_disk.root_found = true;
bios_disk.root_part = rootfs_index;
if (part.lba_first > ((size_t)-1 / MBR_SECTOR_SIZE)) {
panic("rootfs offset too large");
}
if (part.sector_count > ((size_t)-1 / MBR_SECTOR_SIZE)) {
panic("rootfs partition too large");
}
bios_disk.root_base = part.lba_first * MBR_SECTOR_SIZE;
bios_disk.root_size = part.sector_count * MBR_SECTOR_SIZE;
log_debug(
"rootfs partition lba=%u base=%#zx size=%zu",
(unsigned int)part.lba_first,
bios_disk.root_base,
bios_disk.root_size
);
if (!boot_ext2_init(&bios_disk.fs, read_rootfs_cb, NULL, bios_disk.root_size)) {
panic("not an ext2 filesystem");
}
}
bool bios_boot_root_hint(boot_root_hint_t *out) {
if (!out) {
return false;
}
memset(out, 0, sizeof(*out));
if (!bios_disk.root_found) {
return false;
}
out->valid = 1;
out->media = BOOT_MEDIA_DISK;
out->transport = BOOT_TRANSPORT_ATA;
out->part_style = BOOT_PARTSTYLE_MBR;
out->part_index = (u8)(bios_disk.root_part + 1);
out->bios_drive = (u8)(bios_disk.drive & 0xffU);
if (bios_disk.fs.superblock.signature == EXT2_SIGNATURE) {
out->rootfs_uuid_valid = 1;
memcpy(out->rootfs_uuid, bios_disk.fs.superblock.fs_id, sizeof(out->rootfs_uuid));
}
return true;
}
bool stage_rootfs_image(u64 *paddr, u64 *size) {
if (!paddr || !size || !bios_disk.root_size) {
return false;
}
size_t alloc_size = ALIGN(bios_disk.root_size, 0x1000);
if (alloc_size < bios_disk.root_size) {
return false;
}
void *image = mmap_alloc_top(alloc_size, E820_KERNEL, 0x1000, PROTECTED_MODE_TOP);
read_disk(image, bios_disk.root_base, bios_disk.root_size);
if (alloc_size > bios_disk.root_size) {
memset((u8 *)image + bios_disk.root_size, 0, alloc_size - bios_disk.root_size);
}
*paddr = (u64)(uintptr_t)image;
*size = (u64)bios_disk.root_size;
return true;
}
void *read_rootfs(const char *path) {
return boot_ext2_read_file(&bios_disk.fs, path, NULL);
}