From 137aca58851528e77004dc3909826f24bbdbde87 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Mon, 2 Sep 2019 16:28:08 +0300 Subject: [PATCH] backported more fixes from 256boss --- Makefile | 2 +- src/bootdev.c | 325 ++++++++++++++++++ src/bootdev.h | 29 ++ src/floppy.c | 26 ++ src/floppy.h | 44 +++ src/fs.c | 278 +++++++++++++++ src/fs.h | 125 +++++++ src/fsfat.c | 965 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/fsmem.c | 520 +++++++++++++++++++++++++++++ src/libc/dirent.c | 95 ++++++ src/libc/dirent.h | 42 +++ src/libc/file.c | 216 ++++++++++++ src/libc/float.h | 24 ++ src/libc/math.c | 55 +++ src/libc/math.h | 36 ++ src/libc/rand.c | 65 ++++ src/libc/setjmp.c | 28 ++ src/libc/setjmp.h | 26 ++ src/libc/time.c | 165 +++++++++ src/libc/time.h | 52 +++ src/libc/unistd.c | 61 ++++ src/libc/unistd.h | 27 ++ src/mtab.c | 63 ++++ src/mtab.h | 34 ++ src/part.c | 158 +++++++++ src/part.h | 40 +++ src/ptype.h | 83 +++++ src/rtc.c | 123 +++++++ src/rtc.h | 28 ++ 29 files changed, 3734 insertions(+), 1 deletion(-) create mode 100644 src/bootdev.c create mode 100644 src/bootdev.h create mode 100644 src/floppy.c create mode 100644 src/floppy.h create mode 100644 src/fs.c create mode 100644 src/fs.h create mode 100644 src/fsfat.c create mode 100644 src/fsmem.c create mode 100644 src/libc/dirent.c create mode 100644 src/libc/dirent.h create mode 100644 src/libc/file.c create mode 100644 src/libc/float.h create mode 100644 src/libc/math.c create mode 100644 src/libc/math.h create mode 100644 src/libc/rand.c create mode 100644 src/libc/setjmp.c create mode 100644 src/libc/setjmp.h create mode 100644 src/libc/time.c create mode 100644 src/libc/time.h create mode 100644 src/libc/unistd.c create mode 100644 src/libc/unistd.h create mode 100644 src/mtab.c create mode 100644 src/mtab.h create mode 100644 src/part.c create mode 100644 src/part.h create mode 100644 src/ptype.h create mode 100644 src/rtc.c create mode 100644 src/rtc.h diff --git a/Makefile b/Makefile index 6887307..2396eab 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ warn = -pedantic -Wall #opt = -O2 dbg = -g inc = -Isrc -Isrc/libc -Isrc/test -gccopt = -fno-pic -ffreestanding -nostdinc -fno-builtin +gccopt = -fno-pic -ffreestanding -nostdinc -fno-builtin -ffast-math CFLAGS = $(ccarch) -march=i386 $(warn) $(opt) $(dbg) $(gccopt) $(inc) $(def) ASFLAGS = $(asarch) -march=i386 $(dbg) -nostdinc -fno-builtin $(inc) diff --git a/src/bootdev.c b/src/bootdev.c new file mode 100644 index 0000000..b3dd48a --- /dev/null +++ b/src/bootdev.c @@ -0,0 +1,325 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include "bootdev.h" +#include "boot.h" +#include "int86.h" +#include "panic.h" +#include "timer.h" +#include "floppy.h" + +#define FLOPPY_MOTOR_OFF_TIMEOUT 4000 +#define DBG_RESET_ON_FAIL + +struct chs { + unsigned int cyl, head, tsect; +}; + +struct disk_access { + unsigned char pktsize; + unsigned char zero; + uint16_t num_sectors; /* max 127 on some implementations */ + uint16_t boffs, bseg; + uint32_t lba_low, lba_high; +} __attribute__((packed)); + +enum {OP_READ, OP_WRITE}; + +#ifdef DBG_RESET_ON_FAIL +static int bios_reset_dev(int dev); +#endif +static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf); +static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf); +static int get_drive_chs(int dev, struct chs *chs); +static void calc_chs(uint64_t lba, struct chs *chs); + +static int have_bios_ext; +static int bdev_is_floppy; +static int num_cyl, num_heads, num_track_sect; + +void bdev_init(void) +{ + struct chs chs; + struct int86regs regs; + + memset(®s, 0, sizeof regs); + regs.eax = 0x4100; /* function 41h: check int 13h extensions */ + regs.ebx = 0x55aa; + regs.edx = boot_drive_number; + + int86(0x13, ®s); + if((regs.flags & FLAGS_CARRY) || (regs.ecx & 1) == 0) { + printf("BIOS does not support int13h extensions (LBA access)\n"); + have_bios_ext = 0; + + if(get_drive_chs(boot_drive_number, &chs) == -1) { + panic("drive (%d) parameter query failed\n", boot_drive_number); + } + + num_cyl = chs.cyl; + num_heads = chs.head; + num_track_sect = chs.tsect; + + printf("Drive params: %d cyl, %d heads, %d sect/track\n", num_cyl, + num_heads, num_track_sect); + } else { + printf("BIOS supports int13h extensions (LBA access)\n"); + have_bios_ext = 1; + } + + bdev_is_floppy = !(boot_drive_number & 0x80); +} + +#define NRETRIES 3 + +int bdev_read_sect(uint64_t lba, void *buf) +{ + int i; + struct chs chs; + + if(bdev_is_floppy) { + cancel_alarm(floppy_motors_off); + set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off); + } + + if(have_bios_ext) { + return bios_rw_sect_lba(boot_drive_number, lba, 1, OP_READ, buf); + } + + calc_chs(lba, &chs); + + for(i=0; i 0) { + nsect -= rd; + buf = (char*)buf + rd * 512; + lba += rd; + calc_chs(lba, &chs); + } + +#ifdef DBG_RESET_ON_FAIL + bios_reset_dev(boot_drive_number); +#endif + } + return -1; +} + +int bdev_write_range(uint64_t lba, int nsect, void *buf) +{ + struct chs chs; + + if(bdev_is_floppy) { + cancel_alarm(floppy_motors_off); + set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off); + } + + if(have_bios_ext) { + return bios_rw_sect_lba(boot_drive_number, lba, nsect, OP_WRITE, buf); + } + + calc_chs(lba, &chs); + return bios_rw_sect_chs(boot_drive_number, &chs, nsect, OP_WRITE, buf); +} + + +#ifdef DBG_RESET_ON_FAIL +static int bios_reset_dev(int dev) +{ + struct int86regs regs; + + printf("resetting drive %d ...", dev); + + memset(®s, 0, sizeof regs); + regs.edx = dev; + + int86(0x13, ®s); + + printf("%s\n", (regs.flags & FLAGS_CARRY) ? "failed" : "done"); + + return (regs.flags & FLAGS_CARRY) ? -1 : 0; +} +#endif + +static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf) +{ + struct int86regs regs; + struct disk_access *dap = (struct disk_access*)low_mem_buffer; + uint32_t addr = (uint32_t)low_mem_buffer; + uint32_t xaddr = (addr + sizeof *dap + 15) & 0xfffffff0; + void *xbuf = (void*)xaddr; + int func; + + if(op == OP_READ) { + func = 0x42; /* function 42h: extended read sector (LBA) */ + } else { + func = 0x43; /* function 43h: extended write sector (LBA) */ + memcpy(xbuf, buf, nsect * 512); + } + + dap->pktsize = sizeof *dap; + dap->zero = 0; + dap->num_sectors = nsect; + dap->boffs = 0; + dap->bseg = xaddr >> 4; + dap->lba_low = (uint32_t)lba; + dap->lba_high = (uint32_t)(lba >> 32); + + memset(®s, 0, sizeof regs); + regs.eax = func << 8; + regs.ds = addr >> 4; + regs.esi = 0; + regs.edx = dev; + + int86(0x13, ®s); + + if(regs.flags & FLAGS_CARRY) { + return -1; + } + + if(op == OP_READ) { + memcpy(buf, xbuf, nsect * 512); + } + return dap->num_sectors; +} + +/* TODO: fix: probably can't cross track boundaries, clamp nsect accordingly + * and return a short count + */ +static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf) +{ + struct int86regs regs; + uint32_t xaddr = (uint32_t)low_mem_buffer; + int func; + + if(op == OP_READ) { + func = 2; + } else { + func = 3; + memcpy(low_mem_buffer, buf, nsect * 512); + } + + memset(®s, 0, sizeof regs); + regs.eax = (func << 8) | nsect; /* 1 sector */ + regs.es = xaddr >> 4; /* es:bx buffer */ + regs.ecx = ((chs->cyl << 8) & 0xff00) | ((chs->cyl >> 10) & 0xc0) | chs->tsect; + regs.edx = dev | (chs->head << 8); + + int86(0x13, ®s); + + if(regs.flags & FLAGS_CARRY) { + return -1; + } + + if(op == OP_READ) { + memcpy(buf, low_mem_buffer, nsect * 512); + } + return nsect; +} + +static int get_drive_chs(int dev, struct chs *chs) +{ + struct int86regs regs; + + memset(®s, 0, sizeof regs); + regs.eax = 0x800; + regs.edx = dev; + + int86(0x13, ®s); + + if(regs.flags & FLAGS_CARRY) { + return -1; + } + + chs->cyl = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x300)) + 1; + chs->head = (regs.edx >> 8) + 1; + chs->tsect = regs.ecx & 0x3f; + return 0; +} + +static void calc_chs(uint64_t lba, struct chs *chs) +{ + uint32_t lba32, trk; + + if(lba >> 32) { + /* XXX: 64bit ops require libgcc, and I don't want to link that for + * this. CHS I/O is only going to be for floppies and really old systems + * anyway, so there's no point in supporting anything more than LBA32 in + * the translation. + */ + const char *fmt = "calc_chs only supports 32bit LBA. requested: %llx\n" + "If you see this message, please file a bug report at:\n" + " https://github.com/jtsiomb/256boss/issues\n" + " or by email: nuclear@member.fsf.org\n"; + panic(fmt, lba); + } + + lba32 = (uint32_t)lba; + trk = lba32 / num_track_sect; + chs->tsect = (lba32 % num_track_sect) + 1; + chs->cyl = trk / num_heads; + chs->head = trk % num_heads; +} diff --git a/src/bootdev.h b/src/bootdev.h new file mode 100644 index 0000000..3dd45eb --- /dev/null +++ b/src/bootdev.h @@ -0,0 +1,29 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef BOOTDEV_H_ +#define BOOTDEV_H_ + +void bdev_init(void); + +int bdev_read_sect(uint64_t lba, void *buf); +int bdev_write_sect(uint64_t lba, void *buf); + +int bdev_read_range(uint64_t lba, int nsect, void *buf); +int bdev_write_range(uint64_t lba, int nsect, void *buf); + +#endif /* BOOTDEV_H_ */ diff --git a/src/floppy.c b/src/floppy.c new file mode 100644 index 0000000..a524fe6 --- /dev/null +++ b/src/floppy.c @@ -0,0 +1,26 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include "floppy.h" +#include "asmops.h" + +void floppy_motors_off(void) +{ + unsigned char dout = inb(FDC_REG_DOUT); + outb(dout & 0xf, FDC_REG_DOUT); +} diff --git a/src/floppy.h b/src/floppy.h new file mode 100644 index 0000000..f38e284 --- /dev/null +++ b/src/floppy.h @@ -0,0 +1,44 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef FLOPPY_H_ +#define FLOPPY_H_ + +#define FDC_REG_DOUT 0x3f2 +#define FDC_REG_STAT 0x3f4 +#define FDC_REG_DATA 0x3f5 + +#define FDC_STAT_ACTA 0x01 +#define FDC_STAT_ACTB 0x02 +#define FDC_STAT_ACTC 0x04 +#define FDC_STAT_ACTD 0x08 +#define FDC_STAT_BUSY 0x10 +#define FDC_STAT_NODMA 0x20 +#define FDC_STAT_IODIR 0x40 +#define FDC_STAT_RDY 0x80 + +#define FDC_DOUT_SEL(x) (x) +#define FDC_DOUT_RST 0x04 +#define FDC_DOUT_DMA 0x08 +#define FDC_DOUT_MOTA 0x10 +#define FDC_DOUT_MOTB 0x20 +#define FDC_DOUT_MOTC 0x40 +#define FDC_DOUT_MOTD 0x80 + +void floppy_motors_off(void); + +#endif /* FLOPPY_H_ */ diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..2e9cbaf --- /dev/null +++ b/src/fs.c @@ -0,0 +1,278 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include +#include +#include "fs.h" +#include "mtab.h" +#include "panic.h" + +struct filesys *fsfat_create(int dev, uint64_t start, uint64_t size); +struct filesys *fsmem_create(int dev, uint64_t start, uint64_t size); + +static struct filesys *(*createfs[])(int, uint64_t, uint64_t) = { + fsmem_create, + fsfat_create +}; + +struct filesys *fs_mount(int dev, uint64_t start, uint64_t size, struct fs_node *parent) +{ + int i; + struct filesys *fs; + + if(!parent && rootfs) { + printf("fs_mount error: root filesystem already mounted!\n"); + return 0; + } + + for(i=0; imnt = fs; + mtab_add(parent, fs); + } + return fs; + } + } + + printf("failed to mount filesystem dev: %d, start %llu\n", dev, (unsigned long long)start); + return 0; +} + +static char cwdpath[1024]; +static char *cwdpath_end = cwdpath; + +int fs_chdir(const char *path) +{ + struct fs_node *node; + char *uppath = 0; + int uplen; + + if(!path || !*path) { + return -1; + } + + if(strcmp(path, ".") == 0) { + return 0; + } + if(strcmp(path, "..") == 0) { + char *endptr; + + if(cwdpath_end <= cwdpath + 1) { + return -1; + } + + endptr = cwdpath + (cwdpath_end - cwdpath); + while(endptr > cwdpath && *--endptr != '/'); + if(endptr == cwdpath) endptr++; + + uplen = endptr - cwdpath; + uppath = alloca(uplen + 1); + memcpy(uppath, cwdpath, uplen); + uppath[uplen] = 0; + + path = uppath; + } + + if(!(node = fs_open(path, 0))) { + return -1; + } + if(node->type != FSNODE_DIR) { + fs_close(node); + return -1; + } + + if(uppath) { + memcpy(cwdpath, uppath, uplen + 1); + cwdpath_end = cwdpath + uplen; + + } else { + int len = strlen(path); + if(cwdpath_end - cwdpath + len > sizeof cwdpath) { + panic("fs_chdir: path too long: %s\n", path); + } + if(path[0] == '/') { + memcpy(cwdpath, path, len + 1); + cwdpath_end = cwdpath + len; + } else { + if(cwdpath_end > cwdpath + 1) { + *cwdpath_end++ = '/'; + } + memcpy(cwdpath_end, path, len + 1); + cwdpath_end += len; + } + } + + fs_close(cwdnode); + cwdnode = node; + return 0; +} + +char *fs_getcwd(void) +{ + return cwdpath; +} + +/* TODO normalize path */ +struct fs_node *fs_open(const char *path, unsigned int flags) +{ + struct filesys *fs; + struct fs_node *node; + + if(!path || !*path) { + return 0; + } + + if(*path == '/') { + fs = rootfs; + } else { + if(!cwdnode) return 0; + fs = cwdnode->fs; + } + + if(!(node = fs->fsop->open(fs, path, flags))) { + return 0; + } + return node; +} + +int fs_close(struct fs_node *node) +{ + struct fs_operations *fsop; + + if(!node) return -1; + + fsop = node->fs->fsop; + fsop->close(node); + return 0; +} + +int fs_rename(struct fs_node *node, const char *name) +{ + struct fs_operations *fsop = node->fs->fsop; + return fsop->rename(node, name); +} + +int fs_remove(struct fs_node *node) +{ + struct fs_operations *fsop = node->fs->fsop; + return fsop->remove(node); +} + +long fs_filesize(struct fs_node *node) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_FILE) { + return -1; + } + return fsop->fsize(node); +} + +int fs_seek(struct fs_node *node, int offs, int whence) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_FILE) { + return -1; + } + return fsop->seek(node, offs, whence); +} + +long fs_tell(struct fs_node *node) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_FILE) { + return -1; + } + return fsop->tell(node); +} + +int fs_read(struct fs_node *node, void *buf, int sz) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_FILE) { + return -1; + } + return fsop->read(node, buf, sz); +} + +int fs_write(struct fs_node *node, void *buf, int sz) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_FILE) { + return -1; + } + return fsop->write(node, buf, sz); +} + +int fs_rewinddir(struct fs_node *node) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_DIR) { + return -1; + } + return fsop->rewinddir(node); +} + +struct fs_dirent *fs_readdir(struct fs_node *node) +{ + struct fs_operations *fsop = node->fs->fsop; + + if(node->type != FSNODE_DIR) { + return 0; + } + return fsop->readdir(node); +} + +/* fs utility functions */ +char *fs_path_skipsep(char *s) +{ + while(*s == '/') s++; + return s; +} + +char *fs_path_next(char *s, char *namebuf, int bufsz) +{ + int len; + char *ptr; + + ptr = s = fs_path_skipsep(s); + + while(*ptr && *ptr != '/') ptr++; + + if(namebuf) { + len = ptr - s; + if(len >= bufsz) len = bufsz - 1; + + memcpy(namebuf, s, len); + namebuf[len] = 0; + } + + return fs_path_skipsep(ptr); +} diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..f05f29a --- /dev/null +++ b/src/fs.h @@ -0,0 +1,125 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef FS_H_ +#define FS_H_ + +#include + +/* device ids for virtual filesystems */ +enum { + DEV_FLOPPY0 = 0, + DEV_FLOPPY1 = 1, + DEV_HDD0 = 0x80, + DEV_HDD1 = 0x81, + DEV_MEMDISK = 0x10000 +}; + +enum { + FSTYPE_MEM, + FSTYPE_FAT, + + NUM_FSTYPES +}; + +enum { FSNODE_FILE, FSNODE_DIR }; + +enum { FSSEEK_SET, FSSEEK_CUR, FSSEEK_END }; + +enum { + FSO_CREATE = 1, + FSO_DIR = 2, + FSO_EXCL = 4 +}; + +struct filesys; +struct fs_node; +struct fs_dirent; + +struct fs_operations { + void (*destroy)(struct filesys *fs); + + struct fs_node *(*open)(struct filesys *fs, const char *path, unsigned int flags); + void (*close)(struct fs_node *node); + + long (*fsize)(struct fs_node *node); + int (*seek)(struct fs_node *node, int offs, int whence); + long (*tell)(struct fs_node *node); + int (*read)(struct fs_node *node, void *buf, int sz); + int (*write)(struct fs_node *node, void *buf, int sz); + + int (*rewinddir)(struct fs_node *node); + struct fs_dirent *(*readdir)(struct fs_node *node); + + int (*rename)(struct fs_node *node, const char *name); + int (*remove)(struct fs_node *node); +}; + +struct filesys { + int type; + char *name; + struct fs_operations *fsop; + void *data; +}; + +struct fs_node { + struct filesys *fs; + int type; + void *data; + + struct filesys *mnt; +}; + +struct fs_dirent { + char *name; + void *data; + int type; + long fsize; +}; + +struct filesys *rootfs; +struct fs_node *cwdnode; /* current working directory node */ + +struct filesys *fs_mount(int dev, uint64_t start, uint64_t size, struct fs_node *parent); + +int fs_chdir(const char *path); +char *fs_getcwd(void); + +struct fs_node *fs_open(const char *path, unsigned int flags); +int fs_close(struct fs_node *node); + +int fs_rename(struct fs_node *node, const char *name); +int fs_remove(struct fs_node *node); + +long fs_filesize(struct fs_node *node); +int fs_seek(struct fs_node *node, int offs, int whence); +long fs_tell(struct fs_node *node); +int fs_read(struct fs_node *node, void *buf, int sz); +int fs_write(struct fs_node *node, void *buf, int sz); + +int fs_rewinddir(struct fs_node *node); +struct fs_dirent *fs_readdir(struct fs_node *node); + +/* fs utility functions */ +char *fs_path_skipsep(char *s); + +/* copies the current name into the namebuf, and returns a pointer to the + * start of the next path component. + */ +char *fs_path_next(char *s, char *namebuf, int bufsz); + +#endif /* FS_H_ */ diff --git a/src/fsfat.c b/src/fsfat.c new file mode 100644 index 0000000..d0b347a --- /dev/null +++ b/src/fsfat.c @@ -0,0 +1,965 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "bootdev.h" +#include "boot.h" +#include "panic.h" + +#define MAX_NAME 195 + +#define DIRENT_UNUSED 0xe5 + +#define DENT_IS_NULL(dent) (((unsigned char*)(dent))[0] == 0) +#define DENT_IS_UNUSED(dent) (((unsigned char*)(dent))[0] == DIRENT_UNUSED) + +#define ATTR_RO 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLID 0x08 +#define ATTR_DIR 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LFN 0xf + + +enum { FAT12, FAT16, FAT32, EXFAT }; +static const char *typestr[] = { "fat12", "fat16", "fat32", "exfat" }; + +struct fat_dirent; +struct fat_dir; + +struct fatfs { + int type; + int dev; + uint64_t start_sect; + uint32_t size; + int cluster_size; + int fat_size; + uint32_t fat_sect; + uint32_t root_sect; + int root_size; + uint32_t first_data_sect; + uint32_t num_data_sect; + uint32_t num_clusters; + char label[12]; + + void *fat; + struct fat_dir *rootdir; + unsigned int clust_mask; + int clust_shift; +}; + +struct bparam { + uint8_t jmp[3]; + unsigned char fmtid[8]; + uint16_t sect_bytes; + uint8_t cluster_size; + uint16_t reserved_sect; + uint8_t num_fats; + uint16_t num_dirent; + uint16_t num_sectors; + uint8_t medium; + uint16_t fat_size; + uint16_t track_size; + uint16_t num_heads; + uint32_t num_hidden; + uint32_t num_sectors32; +} __attribute__((packed)); + +struct bparam_ext16 { + uint8_t driveno; + uint8_t ntflags; + uint8_t signature; + uint32_t volume_id; + char label[11]; + char sysid[8]; +} __attribute__((packed)); + +struct bparam_ext32 { + uint32_t fat_size; + uint16_t flags; + uint16_t version; + uint32_t root_clust; + uint16_t fsinfo_sect; + uint16_t backup_boot_sect; + char junk[12]; + uint8_t driveno; + uint8_t ntflags; + uint8_t signature; + uint32_t volume_id; + char label[11]; + char sysid[8]; +} __attribute__((packed)); + +struct fat_dirent { + char name[11]; + uint8_t attr; + uint8_t junk; + uint8_t ctime_tenths; + uint16_t ctime_halfsec; + uint16_t ctime_date; + uint16_t atime_date; + uint16_t first_cluster_high; /* 0 for FAT12 and FAT16 */ + uint16_t mtime_hsec; + uint16_t mtime_date; + uint16_t first_cluster_low; + uint32_t size_bytes; +} __attribute__((packed)); + +struct fat_lfnent { + uint8_t seq; + uint16_t part1[5]; + uint8_t attr; + uint8_t type; + uint8_t csum; + uint16_t part2[6]; + uint16_t zero; + uint16_t part3[2]; +} __attribute__((packed)); + + +struct fat_dir { + struct fatfs *fatfs; + + struct fat_dirent *ent; + int max_nent; + + struct fs_dirent *fsent; + int fsent_size; + int cur_ent; + + int ref; +}; + +struct fat_file { + struct fat_dirent ent; + int32_t first_clust; + int64_t cur_pos; + int32_t cur_clust; /* cluster number corresponding to cur_pos */ + + char *clustbuf; + int buf_valid; +}; + + +static void destroy(struct filesys *fs); + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags); +static void close(struct fs_node *node); +static long fsize(struct fs_node *node); +static int seek(struct fs_node *node, int offs, int whence); +static long tell(struct fs_node *node); +static int read(struct fs_node *node, void *buf, int sz); +static int write(struct fs_node *node, void *buf, int sz); +static int rewinddir(struct fs_node *node); +static struct fs_dirent *readdir(struct fs_node *node); +static int rename(struct fs_node *node, const char *name); +static int remove(struct fs_node *node); + +static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent); +static void parse_dir_entries(struct fat_dir *dir); +static void free_dir(struct fat_dir *dir); + +static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent); +static void free_file(struct fat_file *file); + +static int read_sectors(int dev, uint64_t sidx, int count, void *sect); +static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust); +static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf); +static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name); + +static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr); +static int32_t next_cluster(struct fatfs *fatfs, int32_t addr); +static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust); + +/* static void dbg_printdir(struct fat_dirent *dir, int max_entries); */ +static void clean_trailws(char *s); + +static struct fs_operations fs_fat_ops = { + destroy, + open, close, + + fsize, + seek, tell, + read, write, + + rewinddir, readdir, + + rename, remove +}; + +static unsigned char sectbuf[512]; +static int max_sect_once; + +struct filesys *fsfat_create(int dev, uint64_t start, uint64_t size) +{ + int num_read; + char *endp, *ptr; + struct filesys *fs; + struct fatfs *fatfs; + struct fat_dir *rootdir; + struct bparam *bpb; + struct bparam_ext16 *bpb16; + struct bparam_ext32 *bpb32; + + max_sect_once = ((unsigned char*)0xa0000 - low_mem_buffer) / 512; + /* some BIOS implementations have a maximum limit of 127 sectors */ + if(max_sect_once > 127) max_sect_once = 127; + + if(read_sectors(dev, start, 1, sectbuf) == -1) { + return 0; + } + bpb = (struct bparam*)sectbuf; + bpb16 = (struct bparam_ext16*)(sectbuf + sizeof *bpb); + bpb32 = (struct bparam_ext32*)(sectbuf + sizeof *bpb); + + if(bpb->jmp[0] != 0xeb || bpb->jmp[2] != 0x90) { + return 0; + } + if(bpb->sect_bytes != 512) { + return 0; + } + + if(!(fatfs = malloc(sizeof *fatfs))) { + panic("FAT: create failed to allocate memory for the fat filesystem data\n"); + } + memset(fatfs, 0, sizeof *fatfs); + fatfs->dev = dev < 0 ? boot_drive_number : dev; + fatfs->start_sect = start; + fatfs->size = bpb->num_sectors ? bpb->num_sectors : bpb->num_sectors32; + fatfs->cluster_size = bpb->cluster_size; + fatfs->fat_size = bpb->fat_size ? bpb->fat_size : bpb32->fat_size; + fatfs->fat_sect = bpb->reserved_sect; + fatfs->root_sect = fatfs->fat_sect + fatfs->fat_size * bpb->num_fats; + fatfs->root_size = (bpb->num_dirent * sizeof(struct fat_dirent) + 511) / 512; + fatfs->first_data_sect = bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size; + fatfs->num_data_sect = fatfs->size - (bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size); + fatfs->num_clusters = fatfs->num_data_sect / fatfs->cluster_size; + + if(fatfs->num_clusters < 4085) { + fatfs->type = FAT12; + } else if(fatfs->num_clusters < 65525) { + fatfs->type = FAT16; + } else if(fatfs->num_clusters < 268435445) { + fatfs->type = FAT32; + } else { + fatfs->type = EXFAT; + } + + switch(fatfs->type) { + case FAT16: + memcpy(fatfs->label, bpb16->label, sizeof bpb16->label); + break; + + case FAT32: + case EXFAT: + fatfs->root_sect = bpb32->root_clust / fatfs->cluster_size; + fatfs->root_size = 0; + memcpy(fatfs->label, bpb32->label, sizeof bpb32->label); + break; + + default: + break; + } + + endp = fatfs->label + sizeof fatfs->label - 2; + while(endp >= fatfs->label && isspace(*endp)) { + *endp-- = 0; + } + + /* read the FAT */ + if(!(fatfs->fat = malloc(fatfs->fat_size * 512))) { + panic("FAT: failed to allocate memory for the FAT (%lu bytes)\n", (unsigned long)fatfs->fat_size * 512); + } + ptr = fatfs->fat; + num_read = 0; + while(num_read < fatfs->fat_size) { + int count = fatfs->fat_size - num_read; + if(count > max_sect_once) count = max_sect_once; + + read_sectors(dev, fatfs->start_sect + fatfs->fat_sect + num_read, count, ptr); + ptr += count * 512; + num_read += count; + } + + /* open root directory */ + if(fatfs->type == FAT32) { + struct fat_dirent ent; + ent.attr = ATTR_DIR; + ent.first_cluster_low = bpb32->root_clust; + ent.first_cluster_high = bpb32->root_clust >> 16; + if(!(rootdir = load_dir(fatfs, &ent))) { + panic("FAT: failed to load FAT32 root directory\n"); + } + + } else { + if(!(rootdir = malloc(sizeof *rootdir))) { + panic("FAT: failed to allocate root directory structure\n"); + } + rootdir->fatfs = fatfs; + + rootdir->max_nent = fatfs->root_size * 512 / sizeof(struct fat_dirent); + if(!(rootdir->ent = malloc(fatfs->root_size * 512))) { + panic("FAT: failed to allocate memory for the root directory\n"); + } + ptr = (char*)rootdir->ent; + + num_read = 0; + while(num_read < fatfs->root_size) { + int count = fatfs->root_size - num_read; + if(count > max_sect_once) count = max_sect_once; + + read_sectors(dev, fatfs->start_sect + fatfs->root_sect + num_read, count, ptr); + ptr += count * 512; + num_read += count; + } + + parse_dir_entries(rootdir); + } + rootdir->ref = 1; + fatfs->rootdir = rootdir; + + /* assume cluster_size is a power of two */ + fatfs->clust_mask = (fatfs->cluster_size * 512) - 1; + fatfs->clust_shift = 0; + while((1 << fatfs->clust_shift) < (fatfs->cluster_size * 512)) { + fatfs->clust_shift++; + } + + /* fill generic fs structure */ + if(!(fs = malloc(sizeof *fs))) { + panic("FAT: create failed to allocate memory for the filesystem structure\n"); + } + fs->type = FSTYPE_FAT; + fs->name = fatfs->label; + fs->fsop = &fs_fat_ops; + fs->data = fatfs; + + + printf("opened %s filesystem dev: %x, start: %lld\n", typestr[fatfs->type], fatfs->dev, start); + if(fatfs->label[0]) { + printf(" volume label: %s\n", fatfs->label); + } + + return fs; +} + +static void destroy(struct filesys *fs) +{ + struct fatfs *fatfs = fs->data; + free(fatfs); + free(fs); +} + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags) +{ + char name[MAX_NAME]; + struct fatfs *fatfs = fs->data; + struct fat_dir *dir, *newdir; + struct fs_dirent *dent; + struct fat_dirent *fatdent; + struct fs_node *node; + + if(path[0] == '/') { + dir = fatfs->rootdir; + path = fs_path_skipsep((char*)path); + } else { + if(cwdnode->fs->type != FSTYPE_FAT) { + return 0; + } + dir = cwdnode->data; + } + + while(*path) { + if(!dir) { + /* we have more path components, yet the last one wasn't a dir */ + errno = ENOTDIR; + return 0; + } + + path = fs_path_next((char*)path, name, sizeof name); + + if(name[0] == '.' && name[1] == 0) { + continue; + } + + if(!(dent = find_entry(dir, name))) { + errno = ENOENT; + return 0; + } + fatdent = dent->data; + + if((fatdent->first_cluster_low | fatdent->first_cluster_high) == 0) { + if(fatdent->attr == ATTR_DIR) { + /* ".." entries back to the root directory seem to have a 0 + * cluster address as a special case + */ + newdir = fatfs->rootdir; + } else { + return 0; /* but we can't have 0-address files (right?) */ + } + } else { + newdir = fatdent->attr == ATTR_DIR ? load_dir(fatfs, fatdent) : 0; + } + if(dir != fatfs->rootdir && dir != cwdnode->data) { + free_dir(dir); + } + dir = newdir; + } + + + if(!(node = malloc(sizeof *node))) { + panic("FAT: open failed to allocate fs_node structure\n"); + } + node->fs = fs; + if(dir) { + if(dir == fatfs->rootdir) { + if(!(newdir = malloc(sizeof *newdir))) { + panic("FAT: failed to allocate directory structure\n"); + } + *newdir = *dir; + dir = newdir; + dir->ref = 0; + } + node->type = FSNODE_DIR; + node->data = dir; + dir->cur_ent = 0; + dir->ref++; + } else { + node->type = FSNODE_FILE; + if(!(node->data = init_file(fatfs, fatdent))) { + panic("FAT: failed to allocate file entry structure\n"); + } + } + + return node; +} + +static void close(struct fs_node *node) +{ + switch(node->type) { + case FSNODE_FILE: + free_file(node->data); + break; + + case FSNODE_DIR: + free_dir(node->data); + break; + + default: + panic("FAT: close node is not a file nor a dir\n"); + } + + free(node); +} + +static long fsize(struct fs_node *node) +{ + struct fat_file *file; + + if(node->type != FSNODE_FILE) { + return -1; + } + file = node->data; + return file->ent.size_bytes; +} + +static int seek(struct fs_node *node, int offs, int whence) +{ + struct fatfs *fatfs; + struct fat_file *file; + int64_t new_pos; + unsigned int cur_clust_idx, new_clust_idx; + + if(node->type != FSNODE_FILE) { + return -1; + } + + fatfs = node->fs->data; + file = node->data; + + switch(whence) { + case FSSEEK_SET: + new_pos = offs; + break; + + case FSSEEK_CUR: + new_pos = file->cur_pos + offs; + break; + + case FSSEEK_END: + new_pos = file->ent.size_bytes + offs; + break; + + default: + return -1; + } + + if(new_pos < 0) new_pos = 0; + + cur_clust_idx = file->cur_pos >> fatfs->clust_shift; + new_clust_idx = new_pos >> fatfs->clust_shift; + /* if the new position does not fall in the same cluster as the previous one + * re-calculate cur_clust + */ + if(new_clust_idx != cur_clust_idx) { + if(new_clust_idx < cur_clust_idx) { + file->cur_clust = find_cluster(fatfs, new_clust_idx, file->first_clust); + } else { + file->cur_clust = find_cluster(fatfs, new_clust_idx - cur_clust_idx, file->cur_clust); + } + file->buf_valid = 0; + } + file->cur_pos = new_pos; + return 0; +} + +static long tell(struct fs_node *node) +{ + struct fat_file *file; + + if(!node || node->type != FSNODE_FILE) { + return -1; + } + + file = node->data; + return file->cur_pos; +} + +static int read(struct fs_node *node, void *buf, int sz) +{ + struct fatfs *fatfs; + struct fat_file *file; + char *bufptr = buf; + int num_read = 0; + int offs, len, buf_left, rd_left; + unsigned int cur_clust_idx, new_clust_idx; + + if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) { + return -1; + } + + fatfs = node->fs->data; + file = node->data; + + if(file->cur_clust < 0) { + return 0; /* EOF */ + } + + cur_clust_idx = file->cur_pos >> fatfs->clust_shift; + + while(num_read < sz) { + if(!file->buf_valid) { + read_cluster(fatfs, file->cur_clust, file->clustbuf); + file->buf_valid = 1; + } + + offs = file->cur_pos & fatfs->clust_mask; + buf_left = fatfs->cluster_size * 512 - offs; + rd_left = sz - num_read; + len = buf_left < rd_left ? buf_left : rd_left; + + if(file->cur_pos + len > file->ent.size_bytes) { + len = file->ent.size_bytes - file->cur_pos; + } + + memcpy(bufptr, file->clustbuf + offs, len); + num_read += len; + bufptr += len; + + file->cur_pos += len; + if(file->cur_pos >= file->ent.size_bytes) { + file->cur_clust = -1; + file->buf_valid = 0; + break; /* reached EOF */ + } + + new_clust_idx = file->cur_pos >> fatfs->clust_shift; + if(new_clust_idx != cur_clust_idx) { + file->buf_valid = 0; + if((file->cur_clust = next_cluster(fatfs, file->cur_clust)) < 0) { + break; /* reached EOF */ + } + cur_clust_idx = new_clust_idx; + } + } + return num_read; +} + +static int write(struct fs_node *node, void *buf, int sz) +{ + return -1; /* TODO */ +} + +static int rewinddir(struct fs_node *node) +{ + struct fat_dir *dir; + + if(node->type != FSNODE_DIR) { + return -1; + } + + dir = node->data; + dir->cur_ent = 0; + return 0; +} + +static struct fs_dirent *readdir(struct fs_node *node) +{ + struct fat_dir *dir; + + if(node->type != FSNODE_DIR) { + return 0; + } + + dir = node->data; + if(dir->cur_ent >= dir->fsent_size) { + return 0; + } + + return dir->fsent + dir->cur_ent++; +} + +static int rename(struct fs_node *node, const char *name) +{ + return -1; /* TODO */ +} + +static int remove(struct fs_node *node) +{ + errno = EPERM; + return -1; /* TODO */ +} + +static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent) +{ + int32_t addr; + struct fat_dir *dir; + char *buf = 0; + int bufsz = 0; + + if(dent->attr != ATTR_DIR) return 0; + + addr = dent->first_cluster_low; + if(fs->type >= FAT32) { + addr |= (uint32_t)dent->first_cluster_high << 16; + } + + do { + int prevsz = bufsz; + bufsz += fs->cluster_size * 512; + if(!(buf = realloc(buf, bufsz))) { + panic("FAT: failed to allocate cluster buffer (%d bytes)\n", bufsz); + } + + if(read_cluster(fs, addr, buf + prevsz) == -1) { + printf("load_dir: failed to read cluster: %lu\n", (unsigned long)addr); + free(buf); + return 0; + } + } while((addr = next_cluster(fs, addr)) >= 0); + + if(!(dir = malloc(sizeof *dir))) { + panic("FAT: failed to allocate directory structure\n"); + } + dir->fatfs = fs; + dir->ent = (struct fat_dirent*)buf; + dir->max_nent = bufsz / sizeof *dir->ent; + dir->cur_ent = 0; + dir->ref = 0; + + parse_dir_entries(dir); + return dir; +} + +static void parse_dir_entries(struct fat_dir *dir) +{ + int i; + struct fat_dirent *dent, *prev_dent; + struct fs_dirent *eptr; + char entname[MAX_NAME]; + + /* create an fs_dirent array with one element for each actual entry + * (disregarding volume labels, and LFN entries). + */ + if(!(dir->fsent = malloc(dir->max_nent * sizeof *dir->fsent))) { + panic("FAT: failed to allocate dirent array\n"); + } + eptr = dir->fsent; + dent = dir->ent; + prev_dent = dent - 1; + + for(i=0; imax_nent; i++) { + if(DENT_IS_NULL(dent)) break; + + if(!DENT_IS_UNUSED(dent) && dent->attr != ATTR_VOLID && dent->attr != ATTR_LFN) { + if(dent_filename(dent, prev_dent, entname) > 0) { + if(!(eptr->name = malloc(strlen(entname) + 1))) { + panic("FAT: failed to allocate dirent name\n"); + } + strcpy(eptr->name, entname); + eptr->data = dent; + eptr->type = dent->attr == ATTR_DIR ? FSNODE_DIR : FSNODE_FILE; + eptr->fsize = dent->size_bytes; + eptr++; + } + } + if(dent->attr != ATTR_LFN) { + prev_dent = dent; + } + dent++; + } + dir->fsent_size = eptr - dir->fsent; + dir->cur_ent = 0; +} + +static void free_dir(struct fat_dir *dir) +{ + int i; + struct fat_dir *root = dir->fatfs->rootdir; + + if(dir) { + if(--dir->ref > 0) return; + + if(dir->ent != root->ent) { + free(dir->ent); + if(dir->fsent) { + for(i=0; ifsent_size; i++) { + free(dir->fsent[i].name); + } + free(dir->fsent); + } + } + free(dir); + } +} + +static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent) +{ + struct fat_file *file; + + if(!(file = calloc(1, sizeof *file))) { + panic("FAT: failed to allocate file structure\n"); + } + if(!(file->clustbuf = malloc(fatfs->cluster_size * 512))) { + panic("FAT: failed to allocate file cluster buffer\n"); + } + file->ent = *dent; + file->first_clust = dent->first_cluster_low | ((int32_t)dent->first_cluster_high << 16); + file->cur_clust = file->first_clust; + return file; +} + +static void free_file(struct fat_file *file) +{ + if(file) { + free(file->clustbuf); + free(file); + } +} + +static int read_sectors(int dev, uint64_t sidx, int count, void *sect) +{ + if(dev == -1 || dev == boot_drive_number) { + if(bdev_read_range(sidx, count, sect) == -1) { + return -1; + } + return 0; + } + + printf("BUG: reading sectors from drives other than the boot drive not implemented yet\n"); + return -1; +} + +static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust) +{ + char *ptr = clust; + uint64_t saddr = (uint64_t)(addr - 2) * fatfs->cluster_size + fatfs->first_data_sect + fatfs->start_sect; + + if(read_sectors(fatfs->dev, saddr, fatfs->cluster_size, ptr) == -1) { + return -1; + } + return 0; +} + +static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf) +{ + int len = 0; + char *ptr = buf; + struct fat_lfnent *lfn = (struct fat_lfnent*)(dent - 1); + + if(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN) { + /* found a long filename entry, use that */ + do { + uint16_t ustr[14], *uptr = ustr; + memcpy(uptr, lfn->part1, sizeof lfn->part1); + uptr += sizeof lfn->part1 / sizeof *lfn->part1; + memcpy(uptr, lfn->part2, sizeof lfn->part2); + uptr += sizeof lfn->part2 / sizeof *lfn->part2; + memcpy(uptr, lfn->part3, sizeof lfn->part3); + ustr[13] = 0; + + uptr = ustr; + while(*uptr) { + *ptr++ = *(char*)uptr++; + len++; + } + *ptr = 0; + + if(uptr < ustr + 13 || (lfn->seq & 0xf0) == 0x40) break; + lfn -= 1; + } while(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN); + + } else { + /* regular 8.3 filename */ + memcpy(buf, dent->name, 8); + buf[8] = 0; + clean_trailws(buf); + if(!buf[0]) return 0; + + if(dent->name[8] && dent->name[8] != ' ') { + ptr = buf + strlen(buf); + *ptr++ = '.'; + memcpy(ptr, dent->name + 8, 3); + ptr[3] = 0; + clean_trailws(ptr); + } + + len = strlen(buf); + } + return len; +} + +static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name) +{ + int i; + struct fs_dirent *dent = dir->fsent; + + for(i=0; ifsent_size; i++) { + if(strcasecmp(dent->name, name) == 0) { + return dent; + } + dent++; + } + return 0; +} + +static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr) +{ + uint32_t res = 0xffffffff; + + switch(fatfs->type) { + case FAT12: + { + uint32_t idx = addr + addr / 2; + res = ((uint16_t*)fatfs->fat)[idx]; + + if(idx & 1) { + res >>= 4; /* odd entries end up on the high 12 bits */ + } else { + res &= 0xfff; /* even entries end up on the low 12 bits */ + } + } + break; + + case FAT16: + res = ((uint16_t*)fatfs->fat)[addr]; + break; + + case FAT32: + case EXFAT: + res = ((uint32_t*)fatfs->fat)[addr]; + break; + + default: + break; + } + + return res; +} + +static int32_t next_cluster(struct fatfs *fatfs, int32_t addr) +{ + uint32_t fatval = read_fat(fatfs, addr); + + if(fatval == 0) return -1; + + switch(fatfs->type) { + case FAT12: + if(fatval >= 0xff8) return -1; + break; + + case FAT16: + if(fatval >= 0xfff8) return -1; + break; + + case FAT32: + case EXFAT: /* XXX ? */ + if(fatval >= 0xffffff8) return -1; + break; + + default: + break; + } + + return fatval; +} + +static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust) +{ + while(count-- > 0 && (clust = next_cluster(fatfs, clust)) >= 0); + return clust; +} + +/* +static void dbg_printdir(struct fat_dirent *dir, int max_entries) +{ + char name[MAX_NAME]; + struct fat_dirent *prev = dir - 1; + struct fat_dirent *end = max_entries > 0 ? dir + max_entries : 0; + + while(!DENT_IS_NULL(dir) && (!end || dir < end)) { + if(!DENT_IS_UNUSED(dir) && dir->attr != ATTR_VOLID && dir->attr != ATTR_LFN) { + if(dent_filename(dir, prev, name) > 0) { + printf("%s%c\n", name, dir->attr == ATTR_DIR ? '/' : ' '); + } + } + if(dir->attr != ATTR_LFN) { + prev = dir; + } + dir++; + } +} +*/ + +static void clean_trailws(char *s) +{ + char *p; + + if(!s || !*s) return; + + p = s + strlen(s) - 1; + while(p >= s && isspace(*p)) p--; + p[1] = 0; +} diff --git a/src/fsmem.c b/src/fsmem.c new file mode 100644 index 0000000..e4cbdd1 --- /dev/null +++ b/src/fsmem.c @@ -0,0 +1,520 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "panic.h" + +#define MAX_NAME 120 + +struct memfs_node; +struct memfs_file; +struct memfs_dir; + +struct memfs { + struct memfs_node *rootdir; +}; + +struct memfs_dir { + struct memfs_node *clist, *ctail; + struct memfs_node *cur; +}; + +struct odir { + struct memfs_dir *dir; + struct memfs_node *cur; + struct fs_dirent dent; +}; + +struct memfs_file { + char *data; + long size, max_size; +}; + +struct ofile { + struct memfs_file *file; + long cur_pos; +}; + +struct memfs_node { + union { + struct memfs_file file; + struct memfs_dir dir; + }; + int type; + char name[MAX_NAME + 4]; + struct memfs_node *parent; + struct memfs_node *next; + struct fs_node *fsnode; /* we need it for crossing mounts in fs_open */ +}; + + +static void destroy(struct filesys *fs); + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags); +static void close(struct fs_node *node); +static long fsize(struct fs_node *node); +static int seek(struct fs_node *node, int offs, int whence); +static long tell(struct fs_node *node); +static int read(struct fs_node *node, void *buf, int sz); +static int write(struct fs_node *node, void *buf, int sz); +static int rewinddir(struct fs_node *node); +static struct fs_dirent *readdir(struct fs_node *node); +static int rename(struct fs_node *node, const char *name); +static int remove(struct fs_node *node); + +static struct fs_node *create_fsnode(struct filesys *fs, struct memfs_node *n); + +static struct memfs_node *alloc_node(int type); +static void free_node(struct memfs_node *node); + +static struct memfs_node *find_entry(struct memfs_node *dir, const char *name); +static void add_child(struct memfs_node *dir, struct memfs_node *n); + + +static struct fs_operations fs_mem_ops = { + destroy, + open, close, + + fsize, + seek, tell, + read, write, + + rewinddir, readdir, + + rename, remove +}; + + +struct filesys *fsmem_create(int dev, uint64_t start, uint64_t size) +{ + struct filesys *fs; + struct memfs *memfs; + + if(dev != DEV_MEMDISK) { + return 0; + } + + if(!(memfs = malloc(sizeof *memfs))) { + panic("MEMFS: create failed to allocate memory\n"); + } + if(!(memfs->rootdir = alloc_node(FSNODE_DIR))) { + panic("MEMFS: failed to allocate root dir\n"); + } + + if(!(fs = malloc(sizeof *fs))) { + panic("MEMFS: failed to allocate memory for the filesystem structure\n"); + } + fs->type = FSTYPE_MEM; + fs->name = 0; + fs->fsop = &fs_mem_ops; + fs->data = memfs; + + return fs; +} + +static void destroy(struct filesys *fs) +{ + struct memfs *memfs = fs->data; + free_node((struct memfs_node*)memfs->rootdir); + free(memfs); + free(fs); +} + +static struct fs_node *open_mount(struct filesys *fs, const char *path, unsigned int flags) +{ + char *newpath; + + newpath = alloca(strlen(path) + 2); + newpath[0] = '/'; + strcpy(newpath + 1, path); + + return fs->fsop->open(fs, newpath, flags); +} + +#define NODE_IS_MNTPT(n) ((n)->fsnode && (n)->fsnode->mnt) + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags) +{ + struct memfs_node *node, *parent; + struct memfs *memfs = fs->data; + char name[MAX_NAME + 1]; + + if(path[0] == '/') { + node = memfs->rootdir; + path = fs_path_skipsep((char*)path); + } else { + if(cwdnode->fs->type != FSTYPE_MEM) { + return 0; + } + node = (struct memfs_node*)((struct odir*)cwdnode->data)->dir; + } + assert(node->type == FSNODE_DIR); + + while(*path) { + if(node->type != FSNODE_DIR) { + /* we have more path components, yet the last one wasn't a dir */ + errno = ENOTDIR; + return 0; + } + /* check if it's another filesystem hanging off this directory, and if + * so, recursively call that open function to complete the operation + */ + if(NODE_IS_MNTPT(node)) { + return open_mount(node->fsnode->mnt, path, flags); + } + + path = fs_path_next((char*)path, name, sizeof name); + parent = node; + + if(!(node = find_entry(node, name))) { + if(*path || !(flags & FSO_CREATE)) { + errno = ENOENT; + return 0; + } + /* create and add */ + if(!(node = alloc_node((flags & FSO_DIR) ? FSNODE_DIR : FSNODE_FILE))) { + errno = ENOMEM; + return 0; + } + strcpy(node->name, name); + add_child(parent, node); + return create_fsnode(fs, node); + } + } + + /* we need to check for mount points here too, because the check in the loop + * above is not going to be reached when the mount point is the last part of + * the path string (for instance opendir("/mnt/foo")) + */ + if(NODE_IS_MNTPT(node)) { + return open_mount(node->fsnode->mnt, path, flags); + } + + if(flags & FSO_EXCL) { + errno = EEXIST; + return 0; + } + return create_fsnode(fs, node); +} + +static struct fs_node *create_fsnode(struct filesys *fs, struct memfs_node *n) +{ + struct fs_node *fsn; + struct ofile *of; + struct odir *od; + + if(!(fsn = calloc(1, sizeof *fsn))) { + errno = ENOMEM; + return 0; + } + + if(n->type == FSNODE_FILE) { + if(!(of = malloc(sizeof *of))) { + errno = ENOMEM; + free(fsn); + return 0; + } + of->file = &n->file; + of->cur_pos = 0; + fsn->data = of; + } else { + if(!(od = malloc(sizeof *od))) { + errno = ENOMEM; + free(fsn); + return 0; + } + od->dir = &n->dir; + od->cur = n->dir.clist; + fsn->data = od; + } + + if(!n->fsnode) { + n->fsnode = fsn; + } + + fsn->fs = fs; + fsn->type = n->type; + return fsn; +} + +static void close(struct fs_node *node) +{ + if(!node) return; + + free(node->data); /* free the copy of memfs_node allocated by create_fsnode */ + free(node); +} + +static long fsize(struct fs_node *node) +{ + struct ofile *of; + if(!node || node->type != FSNODE_FILE) { + return -1; + } + of = node->data; + return of->file->size; +} + +static int seek(struct fs_node *node, int offs, int whence) +{ + struct ofile *of; + long new_pos; + + if(!node || node->type != FSNODE_FILE) { + return -1; + } + + of = node->data; + + switch(whence) { + case FSSEEK_SET: + new_pos = offs; + break; + + case FSSEEK_CUR: + new_pos = of->cur_pos + offs; + break; + + case FSSEEK_END: + new_pos = of->file->size + offs; + break; + + default: + return -1; + } + + if(new_pos < 0) new_pos = 0; + + of->cur_pos = new_pos; + return 0; +} + +static long tell(struct fs_node *node) +{ + struct ofile *of; + + if(!node || node->type != FSNODE_FILE) { + return -1; + } + of = node->data; + return of->cur_pos; +} + +static int read(struct fs_node *node, void *buf, int sz) +{ + struct ofile *of; + + if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) { + return -1; + } + + of = node->data; + + if(sz > of->file->size - of->cur_pos) { + sz = of->file->size - of->cur_pos; + } + memcpy(buf, of->file->data + of->cur_pos, sz); + of->cur_pos += sz; + return sz; +} + +static int write(struct fs_node *node, void *buf, int sz) +{ + struct ofile *of; + int total_sz, new_max_sz; + void *tmp; + + if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) { + return -1; + } + + of = node->data; + total_sz = of->cur_pos + sz; + if(total_sz > of->file->max_size) { + if(total_sz < of->file->max_size * 2) { + new_max_sz = of->file->max_size * 2; + } else { + new_max_sz = total_sz; + } + if(!(tmp = realloc(of->file->data, new_max_sz))) { + errno = ENOSPC; + return -1; + } + of->file->data = tmp; + of->file->max_size = new_max_sz; + } + + memcpy(of->file->data + of->cur_pos, buf, sz); + of->cur_pos += sz; + if(of->cur_pos > of->file->size) of->file->size = of->cur_pos; + return sz; +} + +static int rewinddir(struct fs_node *node) +{ + struct odir *od; + + if(!node || node->type != FSNODE_DIR) { + return -1; + } + + od = node->data; + od->cur = od->dir->clist; + return 0; +} + +static struct fs_dirent *readdir(struct fs_node *node) +{ + struct odir *od; + struct memfs_node *n; + struct fs_dirent *fsd; + + if(!node || node->type != FSNODE_DIR) { + return 0; + } + + od = node->data; + fsd = &od->dent; + + n = od->cur; + if(!n) return 0; + + od->cur = od->cur->next; + + fsd->name = n->name; + fsd->data = 0; + fsd->type = n->type; + fsd->fsize = n->file.size; + + return fsd; +} + +static int rename(struct fs_node *node, const char *name) +{ + struct memfs_node *n = (struct memfs_node*)((struct odir*)node->data)->dir; + strncpy(n->name, name, MAX_NAME); + n->name[MAX_NAME] = 0; + return 0; +} + +static int remove(struct fs_node *node) +{ + int res = -1; + struct odir *od = 0; + struct ofile *of = 0; + struct memfs_node *n, *par, *prev, dummy; + + if(node->type == FSNODE_DIR) { + od = node->data; + n = (struct memfs_node*)od->dir; + + if(n->dir.clist) { + errno = EEXIST; + return -1; + } + } else { + of = node->data; + n = (struct memfs_node*)of->file; + } + par = n->parent; + + if(!par) { + errno = EBUSY; + return -1; + } + + dummy.next = par->dir.clist; + prev = &dummy; + while(prev->next) { + if(prev->next == n) { + if(par->dir.ctail == n) { + par->dir.ctail = prev; + } + prev->next = n->next; + free_node(n); + res = 0; + break; + } + prev = prev->next; + } + par->dir.clist = dummy.next; + return res; +} + +static struct memfs_node *alloc_node(int type) +{ + struct memfs_node *node; + + if(!(node = calloc(1, sizeof *node))) { + return 0; + } + node->type = type; + return node; +} + +static void free_node(struct memfs_node *node) +{ + if(!node) return; + + switch(node->type) { + case FSNODE_FILE: + free(node->file.data); + break; + + case FSNODE_DIR: + while(node->dir.clist) { + struct memfs_node *n = node->dir.clist; + node->dir.clist = n->next; + free_node(n); + } + break; + } +} + +static struct memfs_node *find_entry(struct memfs_node *dnode, const char *name) +{ + struct memfs_node *n; + + if(strcmp(name, ".") == 0) return dnode; + if(strcmp(name, "..") == 0) return dnode->parent; + + n = dnode->dir.clist; + while(n) { + if(strcasecmp(n->name, name) == 0) { + return n; + } + n = n->next; + } + return 0; +} + +static void add_child(struct memfs_node *dnode, struct memfs_node *n) +{ + if(dnode->dir.clist) { + dnode->dir.ctail->next = n; + dnode->dir.ctail = n; + } else { + dnode->dir.clist = dnode->dir.ctail = n; + } + n->parent = dnode; +} diff --git a/src/libc/dirent.c b/src/libc/dirent.c new file mode 100644 index 0000000..1af59fe --- /dev/null +++ b/src/libc/dirent.c @@ -0,0 +1,95 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include "dirent.h" +#include "fs.h" + +struct DIR { + struct fs_node *fsn; + struct dirent dent; +}; + +DIR *opendir(const char *path) +{ + DIR *dir; + struct fs_node *node; + + if(!path) { + errno = EINVAL; + return 0; + } + + if(!(node = fs_open(path, 0))) { + errno = ENOENT; + return 0; + } + if(node->type != FSNODE_DIR) { + errno = ENOTDIR; + fs_close(node); + return 0; + } + + if(!(dir = malloc(sizeof *dir))) { + errno = ENOMEM; + fs_close(node); + return 0; + } + dir->fsn = node; + + return dir; +} + +int closedir(DIR *dir) +{ + if(!dir) { + errno = EINVAL; + return -1; + } + fs_close(dir->fsn); + free(dir); + return 0; +} + +void rewinddir(DIR *dir) +{ + if(!dir) { + errno = EINVAL; + return; + } + fs_rewinddir(dir->fsn); +} + +struct dirent *readdir(DIR *dir) +{ + struct fs_dirent *fsdent; + + if(!dir) { + errno = EINVAL; + return 0; + } + if(!(fsdent = fs_readdir(dir->fsn))) { + return 0; + } + + strcpy(dir->dent.d_name, fsdent->name); + dir->dent.d_type = fsdent->type == FSNODE_DIR ? DT_DIR : DT_REG; + dir->dent.d_fsize = fsdent->fsize; + return &dir->dent; +} diff --git a/src/libc/dirent.h b/src/libc/dirent.h new file mode 100644 index 0000000..7f0da64 --- /dev/null +++ b/src/libc/dirent.h @@ -0,0 +1,42 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef DIRENT_H_ +#define DIRENT_H_ + +typedef struct DIR DIR; + +enum { + DT_UNKNOWN = 0, + DT_DIR = 4, + DT_REG = 8 +}; + +struct dirent { + char d_name[256]; + unsigned char d_type; + long d_fsize; +}; + +DIR *opendir(const char *path); +int closedir(DIR *dir); + +void rewinddir(DIR *dir); + +struct dirent *readdir(DIR *dir); + +#endif /* DIRENT_H_ */ diff --git a/src/libc/file.c b/src/libc/file.c new file mode 100644 index 0000000..cc73f71 --- /dev/null +++ b/src/libc/file.c @@ -0,0 +1,216 @@ +#ifndef FILE_H_ +#define FILE_H_ + +#include +#include +#include "fs.h" +#include "panic.h" + +enum { + MODE_READ = 1, + MODE_WRITE = 2, + MODE_APPEND = 4, + MODE_CREATE = 8, + MODE_TRUNCATE = 16 +}; + +enum { + STATUS_EOF = 1, + STATUS_ERR = 2 +}; + +struct FILE { + unsigned int mode; + unsigned int status; + struct fs_node *fsn; +}; + +FILE *fopen(const char *path, const char *mode) +{ + FILE *fp; + struct fs_node *node; + unsigned int mflags = 0; + + while(*mode) { + int c = *mode++; + switch(c) { + case 'r': + mflags |= MODE_READ; + if(*mode == '+') { + mflags |= MODE_WRITE; + mode++; + } + break; + case 'w': + mflags |= MODE_WRITE | MODE_TRUNCATE; + if(*mode == '+') { + mflags |= MODE_READ | MODE_CREATE; + mode++; + } + break; + case 'a': + mflags |= MODE_WRITE | MODE_APPEND; + if(*mode == '+') { + mflags |= MODE_READ | MODE_CREATE; + mode++; + } + break; + case 'b': + break; + default: + errno = EINVAL; + return 0; + } + } + + if(!(node = fs_open(path, 0))) { + /* TODO: create */ + errno = ENOENT; /* TODO */ + return 0; + } + if(node->type != FSNODE_FILE) { + errno = EISDIR; + return 0; + } + + if(!(fp = malloc(sizeof *fp))) { + errno = ENOMEM; + return 0; + } + fp->fsn = node; + fp->mode = mflags; + fp->status = 0; + + return fp; +} + +int fclose(FILE *fp) +{ + if(!fp) { + errno = EINVAL; + return -1; + } + + fs_close(fp->fsn); + free(fp); + return 0; +} + +long filesize(FILE *fp) +{ + return fs_filesize(fp->fsn); +} + +int fseek(FILE *fp, long offset, int from) +{ + if(!fp) { + errno = EINVAL; + return -1; + } + if(from < 0 || from > 2) { + errno = EINVAL; + return -1; + } + + fs_seek(fp->fsn, offset, from); + fp->status &= ~STATUS_EOF; + return 0; +} + +void rewind(FILE *fp) +{ + fseek(fp, 0, SEEK_SET); +} + +long ftell(FILE *fp) +{ + if(!fp) { + errno = EINVAL; + return -1; + } + return fs_tell(fp->fsn); +} + +size_t fread(void *buf, size_t size, size_t count, FILE *fp) +{ + int res; + if(!fp) return 0; + if((res = fs_read(fp->fsn, buf, size * count)) == -1) { + fp->status |= STATUS_EOF; + return 0; + } + return res / size; +} + +size_t fwrite(const void *buf, size_t size, size_t count, FILE *fp) +{ + int res; + if(!fp) return 0; + if(!(fp->mode & MODE_WRITE)) { + fp->status |= STATUS_ERR; + return 0; + } + if((res = fs_write(fp->fsn, (void*)buf, size * count)) == -1) { + return 0; + } + return res / size; +} + +int fgetc(FILE *fp) +{ + unsigned char c; + if(fread(&c, 1, 1, fp) < 1) { + return -1; + } + return c; +} + +char *fgets(char *buf, int size, FILE *fp) +{ + int c; + char *s = buf; + + while(--size > 0 && (c = fgetc(fp)) >= 0) { + *s++ = c; + if(c == '\n') break; + } + *s = 0; + return s > buf ? buf : 0; +} + +int fputc(int c, FILE *fp) +{ + if(fp == stdout || fp == stderr) { + return putchar(c); + } + + panic("fputc on anything other than stdout/stderr not implemented yet\n"); + return -1; +} + +int fflush(FILE *fp) +{ + if(fp == stdout || fp == stderr) { + return 0; /* do nothing */ + } + + panic("fflush on anything other than stdout/stderr not implemented yet\n"); + return -1; +} + +int feof(FILE *fp) +{ + return (fp->status & STATUS_EOF) != 0; +} + +int ferror(FILE *fp) +{ + return (fp->status & STATUS_ERR) != 0; +} + +void clearerr(FILE *fp) +{ + fp->status = 0; +} + +#endif /* FILE_H_ */ diff --git a/src/libc/float.h b/src/libc/float.h new file mode 100644 index 0000000..073bf6d --- /dev/null +++ b/src/libc/float.h @@ -0,0 +1,24 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef FLOAT_H_ +#define FLOAT_H_ + +#define FLT_MIN __FLT_MIN__ +#define FLT_MAX __FLT_MAX__ + +#endif /* FLOAT_H_ */ diff --git a/src/libc/math.c b/src/libc/math.c new file mode 100644 index 0000000..3f3e5de --- /dev/null +++ b/src/libc/math.c @@ -0,0 +1,55 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include "math.h" + +static double calc_pow(double x, double y, double precision); + +double pow(double x, double y) +{ + if(y == 0.0 || y == -0.0) { + return 1.0; + } + if(y == 1.0) { + return x; + } + if(y == -INFINITY) { + return fabs(x) < 1.0 ? INFINITY : 0.0; + } + if(y == INFINITY) { + return fabs(x) < 1.0 ? 0.0 : INFINITY; + } + return calc_pow(x, y, 1e-6); +} + +static double calc_pow(double x, double y, double precision) +{ + if(y < 0.0) { + return 1.0 / calc_pow(x, -y, precision); + } + if(y >= 10.0) { + double p = calc_pow(x, y / 2.0, precision / 2.0); + return p * p; + } + if(y >= 1.0) { + return x * calc_pow(x, y - 1.0, precision); + } + if(precision >= 1) { + return __builtin_sqrt(x); + } + return __builtin_sqrt(calc_pow(x, y * 2.0, precision * 2.0)); +} diff --git a/src/libc/math.h b/src/libc/math.h new file mode 100644 index 0000000..e3b0f23 --- /dev/null +++ b/src/libc/math.h @@ -0,0 +1,36 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef MATH_H_ +#define MATH_H_ + +#define INFINITY __builtin_inff() +#define NAN __builtin_nanf + +#define M_PI 3.141592653589793 + +#define sin(x) __builtin_sin(x) +#define cos(x) __builtin_cos(x) +#define tan(x) __builtin_tan(x) +#define fabs(x) __builtin_fabs(x) +#define fmod(x, y) __builtin_fmod(x, y) +#define sqrt(x) __builtin_sqrt(x) +#define atan2(y, x) __builtin_atan2(y, x) + +double pow(double x, double y); + +#endif /* MATH_H_ */ diff --git a/src/libc/rand.c b/src/libc/rand.c new file mode 100644 index 0000000..de159c2 --- /dev/null +++ b/src/libc/rand.c @@ -0,0 +1,65 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +/* random number generator, based on this description of the algorithm + * used by the GNU libc: https://www.mathstat.dal.ca/~selinger/random + */ +#include +#include + +static int init_done; +static int32_t rng[34]; +static int32_t *ptr0, *ptr1; + +int rand(void) +{ + int res; + + if(!init_done) { + srand(1); + } + + *ptr1 += *ptr0; + res = (uint32_t)*ptr1 >> 1; + if(++ptr0 >= rng + 34) ptr0 = rng; + if(++ptr1 >= rng + 34) ptr1 = rng; + + return res; +} + +void srand(unsigned int seed) +{ + int i; + + init_done = 1; + if(seed == 0) seed = 1; + + rng[0] = seed; + for(i=1; i<31; i++) { + rng[i] = (16807 * rng[i - 1]) % RAND_MAX; + if(rng[i] < 0) rng[i] += RAND_MAX; + } + for(i=31; i<34; i++) { + rng[i] = rng[i - 31]; + } + ptr0 = rng + 3; + ptr1 = rng + 31; + + for(i=34; i<344; i++) { + rand(); + } +} diff --git a/src/libc/setjmp.c b/src/libc/setjmp.c new file mode 100644 index 0000000..517563b --- /dev/null +++ b/src/libc/setjmp.c @@ -0,0 +1,28 @@ +/* +256boss - bootable launcher for 256byte intros +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include + +int setjmp(jmp_buf buf) +{ + return __builtin_setjmp(buf); +} + +void longjmp(jmp_buf buf, int val) +{ + __builtin_longjmp(buf, 1); +} diff --git a/src/libc/setjmp.h b/src/libc/setjmp.h new file mode 100644 index 0000000..c92e93d --- /dev/null +++ b/src/libc/setjmp.h @@ -0,0 +1,26 @@ +/* +256boss - bootable launcher for 256byte intros +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef SETJMP_H_ +#define SETJMP_H_ + +typedef unsigned long jmp_buf[5]; + +int setjmp(jmp_buf buf); +void longjmp(jmp_buf buf, int val); + +#endif /* SETJMP_H_ */ diff --git a/src/libc/time.c b/src/libc/time.c new file mode 100644 index 0000000..ecff673 --- /dev/null +++ b/src/libc/time.c @@ -0,0 +1,165 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include "time.h" +#include "rtc.h" +#include "timer.h" +#include "config.h" + +#define MINSEC 60 +#define HOURSEC (60 * MINSEC) +#define DAYSEC (24 * HOURSEC) +#define YEARDAYS(x) (is_leap_year(x) ? 366 : 365) + +/* 1-1-1970 was a thursday */ +#define EPOCH_WDAY 4 + +static int is_leap_year(int yr); + +static int mdays[2][12] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +static char *wday[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static char *mon[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + + +time_t time(time_t *tp) +{ + time_t res = start_time + nticks / TICK_FREQ_HZ; + + if(tp) *tp = res; + return res; +} + +char *asctime(struct tm *tm) +{ + static char buf[64]; + return asctime_r(tm, buf); +} + +char *asctime_r(struct tm *tm, char *buf) +{ + sprintf(buf, "%s %s %d %02d:%02d:%02d %d\n", wday[tm->tm_wday], + mon[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, + tm->tm_sec, tm->tm_year + 1900); + return buf; +} + +time_t mktime(struct tm *tm) +{ + int i, num_years = tm->tm_year - 70; + int year = 1970; + int days = day_of_year(tm->tm_year + 1900, tm->tm_mon, tm->tm_mday - 1); + + /* set correct yearday */ + tm->tm_yday = days; + + for(i=0; itm_wday = (days + EPOCH_WDAY) % 7; + + return (time_t)days * DAYSEC + tm->tm_hour * HOURSEC + + tm->tm_min * MINSEC + tm->tm_sec; +} + +struct tm *gmtime(time_t *tp) +{ + static struct tm tm; + return gmtime_r(tp, &tm); +} + +struct tm *gmtime_r(time_t *tp, struct tm *tm) +{ + int year, days, leap, yrdays; + time_t t; + + year = 1970; + days = *tp / DAYSEC; + t = *tp % DAYSEC; + + tm->tm_wday = (days + EPOCH_WDAY) % 7; + + while(days >= (yrdays = YEARDAYS(year))) { + days -= yrdays; + year++; + } + tm->tm_year = year - 1900; + tm->tm_yday = days; + + leap = is_leap_year(year); + tm->tm_mon = 0; + while(days >= mdays[leap][tm->tm_mon]) { + days -= mdays[leap][tm->tm_mon++]; + } + + tm->tm_mday = days + 1; + + tm->tm_hour = t / HOURSEC; + t %= HOURSEC; + tm->tm_min = t / MINSEC; + tm->tm_sec = t % MINSEC; + return tm; +} + +struct tm *localtime(time_t *tp) +{ + static struct tm tm; + return localtime_r(tp, &tm); +} + +struct tm *localtime_r(time_t *tp, struct tm *tm) +{ + time_t t = *tp + timezone; + return gmtime_r(&t, tm); +} + +int day_of_year(int year, int mon, int day) +{ + int i, yday, leap; + + leap = is_leap_year(year) ? 1 : 0; + yday = day; + + for(i=0; i + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef TIME_H_ +#define TIME_H_ + +typedef long time_t; + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +#define TZOFFS(x) ((x) * 3600) +long timezone; + +time_t time(time_t *tp); +char *asctime(struct tm *tm); +char *asctime_r(struct tm *tm, char *buf); + +time_t mktime(struct tm *tm); +struct tm *gmtime(time_t *tp); +struct tm *gmtime_r(time_t *tp, struct tm *tm); +struct tm *localtime(time_t *tp); +struct tm *localtime_r(time_t *tp, struct tm *tm); + +/* non-standard helpers */ +int day_of_year(int year, int mon, int day); + + +#endif /* TIME_H_ */ diff --git a/src/libc/unistd.c b/src/libc/unistd.c new file mode 100644 index 0000000..1fc0542 --- /dev/null +++ b/src/libc/unistd.c @@ -0,0 +1,61 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include "unistd.h" +#include "fs.h" + +int chdir(const char *path) +{ + return fs_chdir(path); +} + +char *getcwd(char *buf, int sz) +{ + char *cwd = fs_getcwd(); + int len = strlen(cwd); + if(len + 1 > sz) { + errno = ERANGE; + return 0; + } + memcpy(buf, cwd, len + 1); + return buf; +} + +int mkdir(const char *path, int mode) +{ + struct fs_node *fsn; + + if(!(fsn = fs_open(path, FSO_CREATE | FSO_DIR | FSO_EXCL))) { + return -1; + } + fs_close(fsn); + return 0; +} + +int rmdir(const char *path) +{ + struct fs_node *fsn; + + if(!(fsn = fs_open(path, FSO_DIR))) { + return -1; + } + fs_remove(fsn); + fs_close(fsn); + return 0; +} diff --git a/src/libc/unistd.h b/src/libc/unistd.h new file mode 100644 index 0000000..1ed4cf2 --- /dev/null +++ b/src/libc/unistd.h @@ -0,0 +1,27 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef UNISTD_H_ +#define UNISTD_H_ + +int chdir(const char *path); +char *getcwd(char *buf, int sz); + +int mkdir(const char *path, int mode); +int rmdir(const char *path); + +#endif /* UNISTD_H_ */ diff --git a/src/mtab.c b/src/mtab.c new file mode 100644 index 0000000..3d5f169 --- /dev/null +++ b/src/mtab.c @@ -0,0 +1,63 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include "mtab.h" + +int mtab_add(struct fs_node *node, struct filesys *fs) +{ + struct mount *m; + + if(!(m = malloc(sizeof *m))) { + return -1; + } + m->mpt = node; + m->fs = fs; + m->next = 0; + + if(mnt_list) { + mnt_tail->next = m; + mnt_tail = m; + } else { + mnt_list = mnt_tail = m; + } + mnt_count++; + return 0; +} + +int mtab_remove_node(struct fs_node *node) +{ + int res = -1; + struct mount dummy; + struct mount *prev, *m; + + dummy.next = mnt_list; + prev = &dummy; + + while(prev->next) { + m = prev->next; + if(m->mpt == node) { + prev->next = m->next; + free(m); + res = 0; + break; + } + prev = prev->next; + } + mnt_list = dummy.next; + return res; +} diff --git a/src/mtab.h b/src/mtab.h new file mode 100644 index 0000000..c98aad2 --- /dev/null +++ b/src/mtab.h @@ -0,0 +1,34 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef MTAB_H_ +#define MTAB_H_ + +#include "fs.h" + +struct mount { + struct fs_node *mpt; + struct filesys *fs; + struct mount *next; +}; +struct mount *mnt_list, *mnt_tail; +int mnt_count; + +int mtab_add(struct fs_node *node, struct filesys *fs); +int mtab_remove_node(struct fs_node *node); + +#endif /* MTAB_H_ */ diff --git a/src/part.c b/src/part.c new file mode 100644 index 0000000..df007ee --- /dev/null +++ b/src/part.c @@ -0,0 +1,158 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include "part.h" +#include "boot.h" +#include "bootdev.h" +#include "ptype.h" + +struct part_record { + uint8_t stat; + uint8_t first_head, first_cyl, first_sect; + uint8_t type; + uint8_t last_head, last_cyl, last_sect; + uint32_t first_lba; + uint32_t nsect_lba; +} __attribute__((packed)); + +static int read_sector(int dev, uint64_t sidx); +static const char *ptype_name(int type); + +static unsigned char sectdata[512]; + +#define BOOTSIG_OFFS 510 +#define PTABLE_OFFS 0x1be + +#define BOOTSIG 0xaa55 + +#define IS_MBR (sidx == 0) +#define IS_FIRST_EBR (!IS_MBR && (first_ebr_offs == 0)) + +int read_partitions(int dev, struct partition *ptab, int ptabsz) +{ + int i, num_rec, nparts = 0; + int num_bootrec = 0; + struct partition *part = ptab; + struct part_record *prec; + uint64_t sidx = 0; + uint64_t first_ebr_offs = 0; + + if(ptabsz <= 0) { + ptab = 0; + } + + do { + if(IS_FIRST_EBR) { + first_ebr_offs = sidx; + } + + if(read_sector(dev, sidx) == -1) { + printf("failed to read sector %llu\n", (unsigned long long)sidx); + return -1; + } + if(*(uint16_t*)(sectdata + BOOTSIG_OFFS) != BOOTSIG) { + printf("invalid partitionm table, sector %llu has no magic\n", (unsigned long long)sidx); + return -1; + } + prec = (struct part_record*)(sectdata + PTABLE_OFFS); + + /* MBR has 4 records, EBRs have 2 */ + num_rec = IS_MBR ? 4 : 2; + + for(i=0; i 0) { + sidx = 0; + break; + } + continue; + } + + /* ignore extended partitions and setup sector index to read the + * next logical partition. + */ + if(prec[i].type == PTYPE_EXT || prec[i].type == PTYPE_EXT_LBA) { + /* all EBR start fields are relative to the first EBR offset */ + sidx = first_ebr_offs + prec[i].first_lba; + continue; + } + + /* found a proper partition */ + nparts++; + + if(ptab) { + part->attr = prec[i].type; + + if(prec[i].stat & 0x80) { + part->attr |= PART_ACT_BIT; + } + if(IS_MBR) { + part->attr |= PART_PRIM_BIT; + } + part->start_sect = prec[i].first_lba + first_ebr_offs; + part->size_sect = prec[i].nsect_lba; + part++; + } + } + + num_bootrec++; + } while(sidx > 0 && (!ptab || nparts < ptabsz)); + + return nparts; +} + +void print_partition_table(struct partition *ptab, int npart) +{ + int i; + struct partition *p = ptab; + + printf("Found %d partitions\n", npart); + for(i=0; iattr) ? '*' : ' '); + printf("(%s) %-20s ", PART_IS_PRIM(p->attr) ? "pri" : "log", ptype_name(PART_TYPE(p->attr))); + printf("start: %-10llu ", (unsigned long long)p->start_sect); + printf("size: %-10llu\n", (unsigned long long)p->size_sect); + p++; + } +} + +static int read_sector(int dev, uint64_t sidx) +{ + if(dev == -1 || dev == boot_drive_number) { + if(bdev_read_sect(sidx, sectdata) == -1) { + return -1; + } + return 0; + } + + printf("BUG: reading partitions of drives other than the boot drive not implemented yet\n"); + return -1; +} + +static const char *ptype_name(int type) +{ + int i; + + for(i=0; i + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef PART_H_ +#define PART_H_ + +#include + +#define PART_ACT_BIT (1 << 9) +#define PART_PRIM_BIT (1 << 10) + +#define PART_TYPE(attr) ((attr) & 0xff) +#define PART_IS_ACT(attr) ((attr) & PART_ACT_BIT) +#define PART_IS_PRIM(attr) ((attr) & PART_PRIM_BIT) + +struct partition { + uint32_t start_sect; + uint32_t size_sect; + + unsigned int attr; +}; + +int read_partitions(int dev, struct partition *ptab, int ptabsz); +void print_partition_table(struct partition *ptab, int npart); + +#endif /* PART_H_ */ diff --git a/src/ptype.h b/src/ptype.h new file mode 100644 index 0000000..4a40bfd --- /dev/null +++ b/src/ptype.h @@ -0,0 +1,83 @@ +#ifndef PTYPE_H_ +#define PTYPE_H_ + + +#define PTYPE_EXT 0x5 +#define PTYPE_EXT_LBA 0xf + + +#define PTYPES_SIZE (sizeof partypes / sizeof *partypes) + +struct { + int type; + const char *name; +} partypes[] = { + {0, "empty"}, + {0x01, "fat12"}, + {0x02, "xenix root"}, + {0x03, "xenix usr"}, + {0x04, "fat16 (small)"}, + {0x05, "extended"}, + {0x06, "fat16"}, + {0x07, "hpfs/ntfs"}, + {0x08, "aix"}, + {0x09, "aix bootable"}, + {0x0a, "os/2 boot manager"}, + {0x0b, "fat32 (chs)"}, + {0x0c, "fat32 (lba)"}, + {0x0e, "fat16 (lba)"}, + {0x0f, "extended (lba)"}, + {0x11, "hidden fat12"}, + {0x12, "compaq diagnostics"}, + {0x14, "hidden fat16 (small)"}, + {0x16, "hidden fat16"}, + {0x17, "hidden hpfs/ntfs"}, + {0x1b, "hidden fat32"}, + {0x1c, "hidden fat32 (lba)"}, + {0x1d, "hidden fat16 (lba)"}, + {0x24, "nec dos"}, + {0x27, "windows recovery"}, + {0x39, "plan 9"}, + {0x3c, "partition magic"}, + {0x4d, "qnx"}, + {0x4e, "qnx 2nd"}, + {0x4f, "qnx 3rd"}, + {0x52, "cp/m"}, + {0x63, "hurd/sysv"}, + {0x64, "netware 286"}, + {0x65, "netware 386"}, + {0x80, "minix (old)"}, + {0x81, "minix"}, + {0x82, "linux swap/solaris"}, + {0x83, "linux"}, + {0x84, "windows suspend"}, + {0x85, "linux extended"}, + {0x86, "ntfs volume?"}, + {0x87, "ntfs volume?"}, + {0x88, "linux plaintext"}, + {0x8e, "linux lvm"}, + {0x9f, "bsd/os"}, + {0xa0, "laptop diagnostic"}, + {0xa5, "freebsd slice"}, + {0xa6, "openbsd slice"}, + {0xa7, "nextstep"}, + {0xa8, "darwin ufs"}, + {0xa9, "netbsd slice"}, + {0xab, "darwin boot"}, + {0xaf, "hfs/hfs+"}, + {0xb7, "bsdi"}, + {0xb8, "bsdi swap"}, + {0xbe, "solaris boot"}, + {0xbf, "solaris"}, + {0xde, "dell diagnostic"}, + {0xeb, "beos"}, + {0xee, "gpt"}, + {0xef, "efi (fat)"}, + {0xf0, "linux/pa-risc boot"}, + {0xf2, "dos secondary"}, + {0xfb, "vmware vmfs"}, + {0xfc, "vmware vmkcore"}, + {0xfd, "linux raid auto"} +}; + +#endif /* PTYPE_H_ */ diff --git a/src/rtc.c b/src/rtc.c new file mode 100644 index 0000000..40eca9a --- /dev/null +++ b/src/rtc.c @@ -0,0 +1,123 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include "rtc.h" + +/* CMOS I/O ports */ +#define PORT_CTL 0x70 +#define PORT_DATA 0x71 + +/* CMOS RTC registers */ +#define REG_SEC 0 +#define REG_ALARM_SEC 1 +#define REG_MIN 2 +#define REG_ALARM_MIN 3 +#define REG_HOUR 4 +#define REG_ALARM_HOUR 5 +#define REG_WEEKDAY 6 +#define REG_DAY 7 +#define REG_MONTH 8 +#define REG_YEAR 9 +#define REG_STATA 10 +#define REG_STATB 11 +#define REG_STATC 12 +#define REG_STATD 13 + +#define STATA_BUSY (1 << 7) +#define STATB_24HR (1 << 1) +#define STATB_BIN (1 << 2) + +#define HOUR_PM_BIT (1 << 7) + +#define BCD_TO_BIN(x) ((((x) >> 4) & 0xf) * 10 + ((x) & 0xf)) + +static void read_rtc(struct tm *tm); +static int read_reg(int reg); + + +void init_rtc(void) +{ + struct tm tm; + + read_rtc(&tm); + start_time = mktime(&tm); + + printf("System real-time clock: %s", asctime(&tm)); +} + + +static void read_rtc(struct tm *tm) +{ + int statb, pm; + + /* wait for any clock updates to finish */ + while(read_reg(REG_STATA) & STATA_BUSY); + + tm->tm_sec = read_reg(REG_SEC); + tm->tm_min = read_reg(REG_MIN); + tm->tm_hour = read_reg(REG_HOUR); + tm->tm_mday = read_reg(REG_DAY); + tm->tm_mon = read_reg(REG_MONTH); + tm->tm_year = read_reg(REG_YEAR); + + /* in 12hour mode, bit 7 means post-meridiem */ + pm = tm->tm_hour & HOUR_PM_BIT; + tm->tm_hour &= ~HOUR_PM_BIT; + + /* convert to binary if needed */ + statb = read_reg(REG_STATB); + if(!(statb & STATB_BIN)) { + tm->tm_sec = BCD_TO_BIN(tm->tm_sec); + tm->tm_min = BCD_TO_BIN(tm->tm_min); + tm->tm_hour = BCD_TO_BIN(tm->tm_hour); + tm->tm_mday = BCD_TO_BIN(tm->tm_mday); + tm->tm_mon = BCD_TO_BIN(tm->tm_mon); + tm->tm_year = BCD_TO_BIN(tm->tm_year); + } + + /* make the year an offset from 1900 */ + if(tm->tm_year < 100) { + tm->tm_year += 100; + } else { + tm->tm_year -= 1900; + } + + /* if tm_hour is in 12h mode, convert to 24h */ + if(!(statb & STATB_24HR)) { + if(tm->tm_hour == 12) { + tm->tm_hour = 0; + } + if(pm) { + tm->tm_hour += 12; + } + } + + tm->tm_mon -= 1; /* we want months to start from 0 */ +} + +static int read_reg(int reg) +{ + unsigned char val; + outb(reg, PORT_CTL); + iodelay(); + val = inb(PORT_DATA); + iodelay(); + return val; +} diff --git a/src/rtc.h b/src/rtc.h new file mode 100644 index 0000000..0d267dd --- /dev/null +++ b/src/rtc.h @@ -0,0 +1,28 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef _RTC_H_ +#define _RTC_H_ + +#include + +/* the time read from rtc during init */ +time_t start_time; + +void init_rtc(void); + +#endif /* _RTC_H_ */ -- 1.7.10.4