// MIT License, Copyright (c) 2021 Marvin Borner // Independent ext2 loader - mostly copied from Melvix kernel /** * Some general definitions */ typedef signed char s8; typedef unsigned char u8; typedef signed short s16; typedef unsigned short u16; typedef signed int s32; typedef unsigned int u32; #define NULL ((void *)0) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define PACKED __attribute__((packed)) #define assert(exp) \ if (!(exp)) { \ print(__FILE__); \ print(": "); \ print(__func__); \ print(": Bootloader assertion '"); \ print(#exp); \ print("' failed.\n"); \ __asm__ volatile("cli\nhlt"); \ } /** * ATA numbers */ #define BLOCK_SIZE 1024 #define BLOCK_COUNT 256 // BLOCK_SIZE / sizeof(u32) #define SECTOR_SIZE 512 #define SECTOR_COUNT (BLOCK_SIZE / SECTOR_SIZE) #define ATA_PRIMARY_IO 0x1f0 #define ATA_SECONDARY_IO 0x170 #define ATA_PRIMARY 0x00 #define ATA_SECONDARY 0x01 #define ATA_READ 0x00 #define ATA_WRITE 0x013 #define ATA_MASTER 0x00 #define ATA_SLAVE 0x01 #define ATA_SR_BSY 0x80 #define ATA_SR_DRDY 0x40 #define ATA_SR_DF 0x20 #define ATA_SR_DSC 0x10 #define ATA_SR_DRQ 0x08 #define ATA_SR_CORR 0x04 #define ATA_SR_IDX 0x02 #define ATA_SR_ERR 0x01 #define ATA_REG_DATA 0x00 #define ATA_REG_ERROR 0x01 #define ATA_REG_FEATURES 0x01 #define ATA_REG_SECCOUNT0 0x02 #define ATA_REG_LBA0 0x03 #define ATA_REG_LBA1 0x04 #define ATA_REG_LBA2 0x05 #define ATA_REG_HDDEVSEL 0x06 #define ATA_REG_COMMAND 0x07 #define ATA_REG_STATUS 0x07 #define ATA_REG_SECCOUNT1 0x08 #define ATA_REG_LBA3 0x09 #define ATA_REG_LBA4 0x0a #define ATA_REG_LBA5 0x0b #define ATA_REG_CONTROL 0x0c #define ATA_REG_ALTSTATUS 0x0c #define ATA_REG_DEVADDRESS 0x0d #define ATA_CMD_READ_PIO 0x20 #define ATA_CMD_READ_PIO_EXT 0x24 #define ATA_CMD_READ_DMA 0xc8 #define ATA_CMD_READ_DMA_EXT 0x25 #define ATA_CMD_WRITE_PIO 0x30 #define ATA_CMD_WRITE_PIO_EXT 0x34 #define ATA_CMD_WRITE_DMA 0xca #define ATA_CMD_WRITE_DMA_EXT 0x35 #define ATA_CMD_CACHE_FLUSH 0xe7 #define ATA_CMD_CACHE_FLUSH_EXT 0xea #define ATA_CMD_PACKET 0xa0 #define ATA_CMD_IDENTIFY_PACKET 0xa1 #define ATA_CMD_IDENTIFY 0xec #define ATA_IDENT_DEVICETYPE 0 #define ATA_IDENT_CYLINDERS 2 #define ATA_IDENT_HEADS 6 #define ATA_IDENT_SECTORS 12 #define ATA_IDENT_SERIAL 20 #define ATA_IDENT_MODEL 54 #define ATA_IDENT_CAPABILITIES 98 #define ATA_IDENT_FIELDVALID 106 #define ATA_IDENT_MAX_LBA 120 #define ATA_IDENT_COMMANDSETS 164 #define ATA_IDENT_MAX_LBA_EXT 200 /** * ELF stuff */ #define ELF_MAG0 0x7F #define ELF_MAG1 'E' #define ELF_MAG2 'L' #define ELF_MAG3 'F' #define ELF_IDENT_COUNT 16 #define ELF_IDENT_MAG0 0 #define ELF_IDENT_MAG1 1 #define ELF_IDENT_MAG2 2 #define ELF_IDENT_MAG3 3 #define ELF_IDENT_CLASS 4 #define ELF_IDENT_CLASS_NONE 0 #define ELF_IDENT_CLASS_32 1 #define ELF_IDENT_CLASS_64 2 #define ELF_IDENT_DATA 5 #define ELF_IDENT_DATA_NONE 0 #define ELF_IDENT_DATA_LSB 1 #define ELF_IDENT_DATA_MSB 2 #define ELF_IDENT_VERSION 6 #define ELF_IDENT_OSABI 7 #define ELF_IDENT_ABIVERSION 8 #define ELF_IDENT_PAD 9 #define ELF_ETYPE_NONE 0 #define ELF_ETYPE_REL 1 #define ELF_ETYPE_EXEC 2 #define ELF_ETYPE_DYN 3 #define ELF_ETYPE_CORE 4 #define ELF_ETYPE_NUM 5 #define ELF_MACHINE_NONE 0 #define ELF_MACHINE_SPARC 2 #define ELF_MACHINE_386 3 #define ELF_MACHINE_SPARC32PLUS 18 #define ELF_MACHINE_SPARCV9 43 #define ELF_MACHINE_AMD64 62 #define ELF_PROGRAM_TYPE_NULL 0 #define ELF_PROGRAM_TYPE_LOAD 1 #define ELF_PROGRAM_TYPE_DYNAMIC 2 #define ELF_PROGRAM_TYPE_INTERP 3 #define ELF_PROGRAM_TYPE_NOTE 4 #define ELF_PROGRAM_TYPE_SHLIB 5 #define ELF_PROGRAM_TYPE_PHDR 6 #define ELF_PROGRAM_TYPE_TLS 7 #define ELF_PROGRAM_FLAG_X 0x1 #define ELF_PROGRAM_FLAG_W 0x2 #define ELF_PROGRAM_FLAG_R 0x4 #define ELF_SECTION_TYPE_NULL 0 #define ELF_SECTION_TYPE_PROGBITS 1 #define ELF_SECTION_TYPE_SYMTAB 2 #define ELF_SECTION_TYPE_STRTAB 3 #define ELF_SECTION_TYPE_RELA 4 #define ELF_SECTION_TYPE_HASH 5 #define ELF_SECTION_TYPE_DYNAMIC 6 #define ELF_SECTION_TYPE_NOTE 7 #define ELF_SECTION_TYPE_NOBITS 8 #define ELF_SECTION_TYPE_REL 9 #define ELF_SECTION_TYPE_SHLIB 10 #define ELF_SECTION_TYPE_DYNSYM 11 #define ELF_SECTION_TYPE_COUNT 12 #define ELF_SECTION_FLAG_WRITE 0x1 #define ELF_SECTION_FLAG_ALLOC 0x2 #define ELF_SECTION_FLAG_EXEC 0x3 #define ELF_SECTION_FLAG_MERGE 0x10 #define ELF_SECTION_FLAG_STRINGS 0x20 #define ELF_SECTION_FLAG_INFO_LINK 0x40 #define ELF_SECTION_FLAG_LINK_ORDER 0x80 #define ELF_SECTION_FLAG_OS_SPECIAL 0x100 #define ELF_SECTION_FLAG_GROUP 0x200 #define ELF_SECTION_FLAG_TLS 0x400 #define ELF_SECTION_FLAG_COMPRESSED 0x800 #define ELF_BSS ".bss" #define ELF_DATA ".data" #define ELF_DEBUG ".debug" #define ELF_DYNAMIC ".dynamic" #define ELF_DYNSTR ".dynstr" #define ELF_DYNSYM ".dynsym" #define ELF_FINI ".fini" #define ELF_GOT ".got" #define ELF_HASH ".hash" #define ELF_INIT ".init" #define ELF_REL_DATA ".rel.data" #define ELF_REL_FINI ".rel.fini" #define ELF_REL_INIT ".rel.init" #define ELF_REL_DYN ".rel.dyn" #define ELF_REL_RODATA ".rel.rodata" #define ELF_REL_TEXT ".rel.text" #define ELF_RODATA ".rodata" #define ELF_SHSTRTAB ".shstrtab" #define ELF_STRTAB ".strtab" #define ELF_SYMTAB ".symtab" #define ELF_TEXT ".text" struct PACKED elf_header { u8 ident[ELF_IDENT_COUNT]; u16 type; u16 machine; u32 version; u32 entry; u32 phoff; u32 shoff; u32 flags; u16 ehsize; u16 phentsize; u16 phnum; u16 shentsize; u16 shnum; u16 shstrndx; }; struct PACKED elf_program { u32 type; u32 offset; u32 vaddr; u32 paddr; u32 filesz; u32 memsz; u32 flags; u32 align; }; struct PACKED elf_section { u32 name; u32 type; u32 flags; u32 addr; u32 offset; u32 size; u32 link; u32 info; u32 addralign; u32 entsize; }; struct PACKED elf_symbol { u32 name; u32 value; u32 size; u8 info; u8 other; u16 shndx; }; /** * EXT2 numbers/structs */ #define EXT2_BOOT 0 #define EXT2_SUPER 1 #define EXT2_ROOT 2 #define EXT2_MAGIC 0x0000EF53 struct ext2_superblock { u32 total_inodes; u32 total_blocks; u32 su_res_blocks; // Superuser reserved u32 free_blocks; u32 free_inodes; u32 superblock_block_num; u32 log2_block_size; u32 log2_frag_size; u32 blocks_per_group; u32 frags_per_group; u32 inodes_per_group; u32 last_mount_time; u32 last_write_time; u16 mounts_since_fsck; u16 max_mounts_since_fsck; u16 magic; u16 state; // 1 clean; 2 errors u16 error_action; u16 minor_version; u32 last_fsck_time; u32 max_time_since_fsck; u32 creator_os_id; u32 major_version; u16 res_block_uid; u16 res_block_gid; }; struct ext2_bgd { u32 block_bitmap; u32 inode_bitmap; u32 inode_table; u16 free_blocks; u16 free_inodes; u16 used_dirs; u16 pad; u8 bg_reserved[12]; }; struct ext2_inode { u16 mode; u16 uid; u32 size; u32 last_access_time; u32 creation_time; u32 last_modification_time; u32 deletion_time; u16 gid; u16 link_count; u32 blocks; u32 flags; u32 os_specific_val1; u32 block[15]; u32 generation; u32 reserved1; u32 reserved2; u32 fragment_addr; u8 os_specific_val2[12]; }; #define EXT2_INODE_SIZE (sizeof(struct ext2_inode)) struct ext2_dirent { u32 inode_num; u16 total_len; u8 name_len; u8 type_indicator; u8 name[]; }; struct ext2_file { struct ext2_inode inode; u32 pos; u8 block_index; u8 *buf; u32 curr_block_pos; }; /** * Memory */ static u32 heap = 0x0000c000; static void *memcpy(void *dest, const void *src, u32 n) { // Inspired by jgraef at osdev u32 num_dwords = n / 4; u32 num_bytes = n % 4; u32 *dest32 = (u32 *)dest; const u32 *src32 = (const u32 *)src; u8 *dest8 = ((u8 *)dest) + num_dwords * 4; const u8 *src8 = ((const u8 *)src) + num_dwords * 4; __asm__ volatile("rep movsl\n" : "=S"(src32), "=D"(dest32), "=c"(num_dwords) : "S"(src32), "D"(dest32), "c"(num_dwords) : "memory"); for (u32 i = 0; i < num_bytes; i++) dest8[i] = src8[i]; return dest; } static void *memset(void *dest, u32 val, u32 n) { // Inspired by jgraef at osdev u32 uval = val; u32 num_dwords = n / 4; u32 num_bytes = n % 4; u32 *dest32 = (u32 *)dest; u8 *dest8 = ((u8 *)dest) + num_dwords * 4; u8 val8 = (u8)val; u32 val32 = uval | (uval << 8) | (uval << 16) | (uval << 24); __asm__ volatile("rep stosl\n" : "=D"(dest32), "=c"(num_dwords) : "D"(dest32), "c"(num_dwords), "a"(val32) : "memory"); for (u32 i = 0; i < num_bytes; i++) dest8[i] = val8; return dest; } static void *malloc(u32 size) { return (u32 *)(heap += size); } static void *zalloc(u32 size) { void *ret = malloc(size); memset(ret, 0, size); return ret; } static void free(void *ptr) { (void)ptr; } /** * String */ static u32 strlen(const char *str) { const char *s = str; while (*s) s++; return s - str; } static s32 strncmp(const char *s1, const char *s2, u32 n) { const u8 *c1 = (const u8 *)s1; const u8 *c2 = (const u8 *)s2; u8 ch; int d = 0; while (n--) { d = (int)(ch = *c1++) - (int)*c2++; if (d || !ch) break; } return d; } static char *strdup(const char *s) { int l = strlen(s) + 1; char *d = malloc(l); memcpy(d, s, l); return d; } /** * CPU IO */ static u8 inb(u16 port) { u8 value; __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); return value; } static u16 inw(u16 port) { u16 value; __asm__ volatile("inw %1, %0" : "=a"(value) : "Nd"(port)); return value; } static void outb(u16 port, u8 data) { __asm__ volatile("outb %0, %1" ::"a"(data), "Nd"(port)); } /** * Serial */ static void serial_install(void) { outb(0x3f8 + 1, 0x00); outb(0x3f8 + 3, 0x80); outb(0x3f8 + 0, 0x03); outb(0x3f8 + 1, 0x00); outb(0x3f8 + 3, 0x03); outb(0x3f8 + 2, 0xC7); outb(0x3f8 + 4, 0x0B); } static int is_transmit_empty(void) { return inb(0x3f8 + 5) & 0x20; } static void serial_put(char ch) { while (is_transmit_empty() == 0) ; outb(0x3f8, (u8)ch); } static void print(const char *data) { for (u32 i = 0; i < strlen(data); i++) serial_put(data[i]); } /** * IDE/ATA */ static void ide_delay(u16 io) // 400ns { for (int i = 0; i < 4; i++) inb(io + ATA_REG_ALTSTATUS); } static void ide_poll(u16 io) { for (int i = 0; i < 4; i++) inb(io + ATA_REG_ALTSTATUS); u8 status; do { status = inb(io + ATA_REG_STATUS); } while (status & ATA_SR_BSY); do { status = inb(io + ATA_REG_STATUS); /* assert(!(status & ATA_SR_ERR)) */ } while (!(status & ATA_SR_DRQ)); } static u8 ata_read_one(u8 *buf, u32 lba, u8 drive) { u16 io = (drive & ATA_PRIMARY << 1) == ATA_PRIMARY ? ATA_PRIMARY_IO : ATA_SECONDARY_IO; drive = (drive & ATA_SLAVE) == ATA_SLAVE ? ATA_SLAVE : ATA_MASTER; u8 cmd = drive == ATA_MASTER ? 0xe0 : 0xf0; outb(io + ATA_REG_HDDEVSEL, (cmd | (u8)((lba >> 24 & 0x0f)))); outb(io + 1, 0x00); outb(io + ATA_REG_SECCOUNT0, 1); outb(io + ATA_REG_LBA0, (u8)lba); outb(io + ATA_REG_LBA1, (u8)(lba >> 8)); outb(io + ATA_REG_LBA2, (u8)(lba >> 16)); outb(io + ATA_REG_COMMAND, ATA_CMD_READ_PIO); ide_poll(io); for (int i = 0; i < BLOCK_COUNT; i++) { u16 data = inw(io + ATA_REG_DATA); *(u16 *)(buf + i * 2) = data; } ide_delay(io); return 1; } static u32 ata_read(void *buf, u32 lba, u32 sector_count, u8 drive) { u8 *b = buf; // I love bytes, yk for (u32 i = 0; i < sector_count; i++) { ata_read_one(b, lba + i, drive); b += SECTOR_SIZE; } return sector_count; } /** * EXT2 */ static void *buffer_read(u32 block, u8 drive) { void *buf = zalloc(BLOCK_SIZE); ata_read(buf, block * SECTOR_COUNT, SECTOR_COUNT, drive); return buf; } static struct ext2_superblock *get_superblock(u8 drive) { struct ext2_superblock *sb = buffer_read(EXT2_SUPER, drive); assert(sb->magic == EXT2_MAGIC); return sb; } static struct ext2_bgd *get_bgd(u8 drive) { return buffer_read(EXT2_SUPER + 1, drive); } static struct ext2_inode *get_inode(u32 i, struct ext2_inode *in_buf, u8 drive) { struct ext2_superblock *s = get_superblock(drive); assert(s); struct ext2_bgd *b = get_bgd(drive); assert(b); u32 block_group = (i - 1) / s->inodes_per_group; u32 index = (i - 1) % s->inodes_per_group; u32 block = (index * EXT2_INODE_SIZE) / BLOCK_SIZE; b += block_group; u32 *buf = buffer_read(b->inode_table + block, drive); struct ext2_inode *in = (struct ext2_inode *)((u32)buf + (index % (BLOCK_SIZE / EXT2_INODE_SIZE)) * EXT2_INODE_SIZE); memcpy(in_buf, in, sizeof(*in_buf)); free(buf); free(s); free(b - block_group); return in_buf; } static u32 find_inode(const char *name, u32 dir_inode, u8 drive) { if (!dir_inode) return (unsigned)-1; struct ext2_inode i = { 0 }; get_inode(dir_inode, &i, drive); char *buf = malloc(BLOCK_SIZE * i.blocks / 2); memset(buf, 0, BLOCK_SIZE * i.blocks / 2); for (u32 q = 0; q < i.blocks / 2; q++) { char *data = buffer_read(i.block[q], drive); memcpy((u32 *)((u32)buf + q * BLOCK_SIZE), data, BLOCK_SIZE); free(data); } struct ext2_dirent *d = (struct ext2_dirent *)buf; u32 sum = 0; do { // Calculate the 4byte aligned size of each entry sum += d->total_len; if (strlen(name) == d->name_len && strncmp((void *)d->name, name, d->name_len) == 0) { free(buf); return d->inode_num; } d = (struct ext2_dirent *)((u32)d + d->total_len); } while (sum < (1024 * i.blocks / 2)); free(buf); return (unsigned)-1; } static u32 read_indirect(u32 indirect, u32 block_num, u8 drive) { void *data = buffer_read(indirect, drive); u32 ind = *(u32 *)((u32)data + block_num * sizeof(u32)); free(data); return ind; } static s32 read_inode(struct ext2_inode *in, void *buf, u32 offset, u32 count, u8 drive) { if (!in || !buf) return -1; if (in->size == 0) return 0; u32 num_blocks = in->blocks / (BLOCK_SIZE / SECTOR_SIZE) + 1; if (!num_blocks) return -1; u32 first_block = offset / BLOCK_SIZE; u32 last_block = (offset + count) / BLOCK_SIZE; if (last_block >= num_blocks) last_block = num_blocks - 1; u32 first_block_offset = offset % BLOCK_SIZE; u32 remaining = MIN(count, in->size - offset); u32 copied = 0; u32 indirect = 0; u32 blocknum = 0; // TODO: Support triply indirect pointers for (u32 i = first_block; i <= last_block; i++) { if (i < 12) { blocknum = in->block[i]; } else if (i < BLOCK_COUNT + 12) { indirect = in->block[12]; blocknum = read_indirect(indirect, i - 12, drive); } else { indirect = in->block[13]; blocknum = read_indirect(indirect, (i - (BLOCK_COUNT + 12)) / BLOCK_COUNT, drive); blocknum = read_indirect(blocknum, (i - (BLOCK_COUNT + 12)) % BLOCK_COUNT, drive); } char *data = buffer_read(blocknum, drive); u32 block_offset = (i == first_block) ? first_block_offset : 0; u32 byte_count = MIN(BLOCK_SIZE - block_offset, remaining); memcpy((u8 *)buf + copied, data + block_offset, byte_count); copied += byte_count; remaining -= byte_count; free(data); } return copied; } static struct ext2_inode *find_inode_by_path(const char *path, struct ext2_inode *in_buf, u8 drive) { char *path_cp = strdup(path); char *init = path_cp; // For freeing if (path_cp[0] != '/') { free(init); return NULL; } path_cp++; u32 current_inode = EXT2_ROOT; u32 i = 0; while (1) { for (i = 0; path_cp[i] != '/' && path_cp[i] != '\0'; i++) ; if (path_cp[i] == '\0') break; path_cp[i] = '\0'; current_inode = find_inode(path_cp, current_inode, drive); path_cp[i] = '/'; if (current_inode == 0) { free(init); return NULL; } path_cp += i + 1; } u32 inode = find_inode(path_cp, current_inode, drive); free(init); if ((signed)inode <= 0) return NULL; return get_inode(inode, in_buf, drive); } static s32 read(const char *path, void *buf, u32 offset, u32 count, u8 drive) { struct ext2_inode in = { 0 }; if (find_inode_by_path(path, &in, drive) == &in) { return read_inode(&in, buf, offset, count, drive); } else { print("Couldn't find kernel!\n"); return -1; } } /** * ELF */ static s32 elf_load(const char *path, u8 drive) { struct elf_header header = { 0 }; s32 rd = read(path, &header, 0, sizeof(header), drive); if (rd < 0) return rd; if (rd != sizeof(header)) return -1; // Valid? u8 *magic = header.ident; u8 valid_magic = magic[ELF_IDENT_MAG0] == ELF_MAG0 && magic[ELF_IDENT_MAG1] == ELF_MAG1 && magic[ELF_IDENT_MAG2] == ELF_MAG2 && magic[ELF_IDENT_MAG3] == ELF_MAG3 && magic[ELF_IDENT_CLASS] == ELF_IDENT_CLASS_32 && magic[ELF_IDENT_DATA] == ELF_IDENT_DATA_LSB; if (!valid_magic || (header.type != ELF_ETYPE_REL && header.type != ELF_ETYPE_EXEC) || header.version != 1 || header.machine != ELF_MACHINE_386) return -1; // Loop through programs for (u32 i = 0; i < header.phnum; i++) { struct elf_program program = { 0 }; if (read(path, &program, header.phoff + header.phentsize * i, sizeof(program), drive) != sizeof(program)) return -1; if (program.type == ELF_PROGRAM_TYPE_INTERP) return -1; if (program.vaddr == 0 || program.type != ELF_PROGRAM_TYPE_LOAD) continue; if ((u32)read(path, (void *)program.vaddr, program.offset, program.filesz, drive) != program.filesz) return -1; } // Find section string table struct elf_section section_strings = { 0 }; if (read(path, §ion_strings, header.shoff + header.shentsize * header.shstrndx, sizeof(section_strings), drive) != sizeof(section_strings)) return -1; if (section_strings.type != ELF_SECTION_TYPE_STRTAB) return -1; return header.entry; } /** * Let's go! */ struct boot_info { u32 vid; u32 mem; u32 tss; u32 drive; }; int main(struct boot_info *boot) { serial_install(); print("Loaded bootloader!\n"); assert(boot->drive); s32 elf = elf_load("/kernel", boot->drive); assert(elf > 0); void (*kernel)(void *); *(void **)(&kernel) = (void *)elf; print("Loaded kernel!\n"); kernel(boot); print("WTF, kernel returned!\n"); while (1) ; return 0; }