Add disk driver
This commit is contained in:
@@ -1,3 +1,69 @@
|
||||
./scripts/build.sh
|
||||
|
||||
qemu-system-i386 -serial stdio -kernel "build/out/kernel"
|
||||
# Create disk image if it doesn't exist
|
||||
if [ ! -f hdd.img ]; then
|
||||
echo "Creating disk image..."
|
||||
qemu-img create -f raw hdd.img 10M
|
||||
|
||||
# Create a proper MBR partition table with one FAT32 partition
|
||||
# Using fdisk commands
|
||||
(
|
||||
echo o # Create new DOS partition table
|
||||
echo n # New partition
|
||||
echo p # Primary partition
|
||||
echo 1 # Partition number 1
|
||||
echo # Default first sector
|
||||
echo # Default last sector (use all space)
|
||||
echo t # Change partition type
|
||||
echo c # FAT32 LBA
|
||||
echo a # Make bootable
|
||||
echo w # Write changes
|
||||
) | fdisk hdd.img >/dev/null 2>&1
|
||||
|
||||
# Format the partition as FAT32
|
||||
# First, get the partition offset using sfdisk
|
||||
OFFSET=$(sfdisk -l hdd.img 2>/dev/null | grep 'hdd.img1' | awk '{print $2}')
|
||||
|
||||
# Check if OFFSET is empty or non-numeric and set default if needed
|
||||
if [ -z "$OFFSET" ] || ! [[ "$OFFSET" =~ ^[0-9]+$ ]]; then
|
||||
OFFSET=2048 # Default first sector for most partition tables
|
||||
fi
|
||||
|
||||
# Create FAT32 filesystem on the partition
|
||||
echo "Formatting partition as FAT32..."
|
||||
mkfs.vfat -F 32 -n "TESTDISK" --offset $OFFSET hdd.img
|
||||
|
||||
echo "Disk image created with FAT32 partition"
|
||||
fi
|
||||
|
||||
# Get the partition offset for file operations
|
||||
OFFSET=$(sfdisk -l hdd.img 2>/dev/null | grep 'hdd.img1' | awk '{print $2}')
|
||||
if [ -z "$OFFSET" ] || ! [[ "$OFFSET" =~ ^[0-9]+$ ]]; then
|
||||
OFFSET=2048
|
||||
fi
|
||||
|
||||
# Copy test-hdd contents into the disk image
|
||||
if [ -d "test-hdd" ]; then
|
||||
echo "Copying test-hdd contents to disk image..."
|
||||
|
||||
# Use mcopy to copy files to the FAT32 partition
|
||||
MTOOLS_SKIP_CHECK=1 mcopy -i hdd.img@@$((OFFSET * 512)) -s test-hdd/* :: || {
|
||||
echo "Warning: mcopy failed, retrying individual files..."
|
||||
for file in test-hdd/*; do
|
||||
if [ -f "$file" ]; then
|
||||
MTOOLS_SKIP_CHECK=1 mcopy -i hdd.img@@$((OFFSET * 512)) "$file" :: && echo "Copied $(basename "$file")"
|
||||
fi
|
||||
done
|
||||
}
|
||||
fi
|
||||
|
||||
# Create CD-ROM image if it doesn't exist
|
||||
if [ ! -f cdrom.iso ]; then
|
||||
mkdir -p /tmp/cdrom_content
|
||||
echo "Test CD-ROM" > /tmp/cdrom_content/readme.txt
|
||||
genisoimage -o cdrom.iso -V "TESTCD" -r -J /tmp/cdrom_content 2>/dev/null || touch cdrom.iso
|
||||
fi
|
||||
|
||||
qemu-system-i386 -serial stdio -kernel "build/out/kernel" \
|
||||
-drive file=hdd.img,if=ide,format=raw,index=0,media=disk \
|
||||
-drive file=cdrom.iso,if=ide,format=raw,index=1,media=cdrom
|
||||
@@ -116,6 +116,33 @@ duk_ret_t native_dword_out(duk_context *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
duk_ret_t native_word_in(duk_context *ctx)
|
||||
{
|
||||
uint16_t port = (uint16_t)duk_to_uint32(ctx, 0);
|
||||
|
||||
uint16_t result;
|
||||
__asm__ volatile("inw %1, %0"
|
||||
: "=a"(result)
|
||||
: "Nd"(port));
|
||||
|
||||
duk_push_uint(ctx, (duk_uint_t)result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
duk_ret_t native_word_out(duk_context *ctx)
|
||||
{
|
||||
uint16_t port = (uint16_t)duk_to_uint32(ctx, 0);
|
||||
|
||||
// Get the value to write (second argument)
|
||||
uint16_t value = (uint16_t)duk_to_uint32(ctx, 1);
|
||||
|
||||
__asm__ volatile("outw %0, %1"
|
||||
:
|
||||
: "a"(value), "Nd"(port));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Global context for IRQ handlers
|
||||
duk_context *global_ctx = NULL;
|
||||
|
||||
@@ -229,6 +256,14 @@ void kmain()
|
||||
duk_push_c_function(ctx, native_dword_out, 2);
|
||||
duk_put_global_string(ctx, "$dwordout");
|
||||
|
||||
// Register native dword in function
|
||||
duk_push_c_function(ctx, native_word_in, 1);
|
||||
duk_put_global_string(ctx, "$wordin");
|
||||
|
||||
// Register native dword out function
|
||||
duk_push_c_function(ctx, native_word_out, 2);
|
||||
duk_put_global_string(ctx, "$wordout");
|
||||
|
||||
// Register native IRQ registration function
|
||||
duk_push_c_function(ctx, native_irq_register, 2);
|
||||
duk_put_global_string(ctx, "$irqregister");
|
||||
|
||||
14
src/link.ld
14
src/link.ld
@@ -26,15 +26,15 @@ SECTIONS
|
||||
*(COMMON)
|
||||
}
|
||||
|
||||
/* Stack grows downward from high memory - place before heap */
|
||||
. = ALIGN(4096);
|
||||
__stack_bottom = .;
|
||||
. = . + 0x100000; /* 1MB stack */
|
||||
__stack_top = .;
|
||||
|
||||
/* Heap for malloc */
|
||||
. = ALIGN(4096);
|
||||
__heap_start = .;
|
||||
. = . + 0x100000; /* 1MB heap */
|
||||
. = . + 0x10000000; /* 256MB heap */
|
||||
__heap_end = .;
|
||||
|
||||
/* Stack grows downward from high memory */
|
||||
. = ALIGN(4096);
|
||||
__stack_bottom = .;
|
||||
. = . + 0x10000; /* 64KB stack */
|
||||
__stack_top = .;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export const Keycode = {
|
||||
Enter: 28,
|
||||
Q: 16,
|
||||
W: 17,
|
||||
E: 18,
|
||||
@@ -25,9 +26,12 @@ export const Keycode = {
|
||||
B: 48,
|
||||
N: 49,
|
||||
M: 50,
|
||||
".": 52,
|
||||
"/": 53,
|
||||
};
|
||||
|
||||
export const RKeycode = {
|
||||
28: "Enter",
|
||||
16: "Q",
|
||||
17: "W",
|
||||
18: "E",
|
||||
@@ -54,4 +58,6 @@ export const RKeycode = {
|
||||
48: "B",
|
||||
49: "N",
|
||||
50: "M",
|
||||
52: ".",
|
||||
53: "/",
|
||||
};
|
||||
|
||||
365
src/os/src/kernel/drivers/dev/ata.ts
Normal file
365
src/os/src/kernel/drivers/dev/ata.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
import { Logger } from "../../../lib/libstd/logger/logger.kmod";
|
||||
import { bytein, byteout } from "../../../lib/libts/byte";
|
||||
import { dwordin } from "../../../lib/libts/dword";
|
||||
import { wordin } from "../../../lib/libts/word";
|
||||
import type { AtaDriveDescriptor } from "../../../types/dev/ata/drive";
|
||||
import type { BlockCache } from "../../../types/utils/block_cache";
|
||||
import {
|
||||
blockCache_block,
|
||||
blockCache_create,
|
||||
blockCache_init,
|
||||
} from "../../utils/block_cache";
|
||||
|
||||
const KDRIVER_DEV_ATA_PRIMARY = 0x1f0;
|
||||
const KDRIVER_DEV_ATA_SECONDARY = 0x170;
|
||||
const KDRIVER_DEV_ATA_PRIMARY_CTL = 0x3f6;
|
||||
const KDRIVER_DEV_ATA_SECONDARY_CTL = 0x376;
|
||||
const KDRIVER_DEV_ATA_CTL_nIEN = 0x02;
|
||||
const KDRIVER_DEV_ATA_MASTER_BIT = 0;
|
||||
const KDRIVER_DEV_ATA_SLAVE_BIT = 1;
|
||||
const KDRIVER_DEV_ATA_DRV_HEAD = 6;
|
||||
const KDRIVER_DEV_ATA_NSECTOR = 2;
|
||||
const KDRIVER_DEV_ATA_SECTOR = 3;
|
||||
const KDRIVER_DEV_ATA_LCYL = 4;
|
||||
const KDRIVER_DEV_ATA_HCYL = 5;
|
||||
const KDRIVER_DEV_ATA_STATUS = 7;
|
||||
const KDRIVER_DEV_ATA_COMMAND = 7;
|
||||
const KDRIVER_DEV_ATA_IDENTIFY = 0xec;
|
||||
const KDRIVER_DEV_ATA_READ_SECTORS = 0x20;
|
||||
const KDRIVER_DEV_ATA_STATUS_ERROR = 0x01;
|
||||
const KDRIVER_DEV_ATA_STATUS_BSY = 0x80;
|
||||
const KDRIVER_DEV_ATA_STATUS_DRQ = 0x08;
|
||||
const KDRIVER_DEV_ATA_DATA = 0;
|
||||
const KDRIVER_DEV_ATA_BLOCK_SIZE = 512;
|
||||
|
||||
let drives: AtaDriveDescriptor[] = [];
|
||||
let cache = blockCache_create();
|
||||
|
||||
export function kdriver_dev_ata_init(): void {}
|
||||
|
||||
export function kdriver_dev_ata_400ns_delay(controller: number) {
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
bytein(controller + KDRIVER_DEV_ATA_STATUS);
|
||||
}
|
||||
|
||||
function ide_string(info: number[], start: number, size: number): string {
|
||||
const buffer = new Array(50);
|
||||
|
||||
const t = info.slice(start);
|
||||
for (let i = 0; i < size; ++i) {
|
||||
const wordIndex = Math.floor(i / 2);
|
||||
const isHighByte = i % 2 === 1;
|
||||
|
||||
buffer[i] = isHighByte ? (t[wordIndex]! >> 8) & 0xff : t[wordIndex]! & 0xff;
|
||||
}
|
||||
|
||||
for (let i = 0; i < size; i += 2) {
|
||||
const c = buffer[i];
|
||||
buffer[i] = buffer[i + 1];
|
||||
buffer[i + 1] = c;
|
||||
}
|
||||
|
||||
let end = size - 1;
|
||||
while (true) {
|
||||
const c = buffer[end];
|
||||
|
||||
if (c > 32 && c < 127) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (end == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
--end;
|
||||
}
|
||||
|
||||
//buffer[end + 1] = 0;
|
||||
|
||||
let target = "";
|
||||
for (let i = 0; i <= end; i++) {
|
||||
target += String.fromCharCode(buffer[i]);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_identify(drive: AtaDriveDescriptor) {
|
||||
byteout(
|
||||
drive.controller + KDRIVER_DEV_ATA_DRV_HEAD,
|
||||
0xa0 | (drive.slave << 4)
|
||||
);
|
||||
kdriver_dev_ata_400ns_delay(drive.controller);
|
||||
|
||||
const floatingBus = bytein(drive.controller + KDRIVER_DEV_ATA_STATUS);
|
||||
if (floatingBus == 0xff) {
|
||||
return;
|
||||
}
|
||||
|
||||
byteout(drive.controller + KDRIVER_DEV_ATA_COMMAND, KDRIVER_DEV_ATA_IDENTIFY);
|
||||
kdriver_dev_ata_400ns_delay(drive.controller);
|
||||
|
||||
if (bytein(drive.controller + KDRIVER_DEV_ATA_STATUS) == 0) return;
|
||||
|
||||
let notAta = false;
|
||||
let iterations = 0;
|
||||
while (1) {
|
||||
const status = bytein(drive.controller + KDRIVER_DEV_ATA_STATUS);
|
||||
iterations++;
|
||||
|
||||
if (status & KDRIVER_DEV_ATA_STATUS_ERROR) {
|
||||
notAta = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
!(status & KDRIVER_DEV_ATA_STATUS_BSY) &&
|
||||
status & KDRIVER_DEV_ATA_STATUS_DRQ
|
||||
)
|
||||
break;
|
||||
|
||||
if (iterations > 100000) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
drive.atapi = false;
|
||||
|
||||
if (notAta) {
|
||||
const cl = bytein(drive.controller + KDRIVER_DEV_ATA_LCYL);
|
||||
const ch = bytein(drive.controller + KDRIVER_DEV_ATA_HCYL);
|
||||
|
||||
if (cl == 0x14 && ch == 0xeb) drive.atapi = true;
|
||||
else if (cl == 0x69 && ch == 0x96) drive.atapi = true;
|
||||
else return;
|
||||
|
||||
byteout(drive.controller + KDRIVER_DEV_ATA_COMMAND, 0xa1);
|
||||
kdriver_dev_ata_400ns_delay(drive.controller);
|
||||
}
|
||||
|
||||
drive.present = true;
|
||||
|
||||
const info = new Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
info[i] = wordin(drive.controller + KDRIVER_DEV_ATA_DATA);
|
||||
}
|
||||
|
||||
drive.model = ide_string(info, 27, 40);
|
||||
drive.serial = ide_string(info, 10, 20);
|
||||
drive.firmware = ide_string(info, 23, 8);
|
||||
|
||||
const sectors = info[0] + 114;
|
||||
drive.size = sectors * 4096;
|
||||
|
||||
Logger.log(
|
||||
"[ATA] Found drive " +
|
||||
drive.model +
|
||||
" (" +
|
||||
drive.serial +
|
||||
") FW: " +
|
||||
drive.firmware +
|
||||
" Size: " +
|
||||
drive.size
|
||||
);
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_numberOfDrives(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_getDrive(n: number) {
|
||||
return drives[n]!;
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_detectDisks(): void {
|
||||
blockCache_init(cache, KDRIVER_DEV_ATA_BLOCK_SIZE, 256);
|
||||
|
||||
drives = new Array<AtaDriveDescriptor>(4);
|
||||
|
||||
drives[0] = {
|
||||
controller: KDRIVER_DEV_ATA_PRIMARY,
|
||||
drive: 0xe0,
|
||||
present: false,
|
||||
slave: KDRIVER_DEV_ATA_MASTER_BIT,
|
||||
atapi: false,
|
||||
model: "",
|
||||
serial: "",
|
||||
firmware: "",
|
||||
size: 0,
|
||||
};
|
||||
drives[1] = {
|
||||
controller: KDRIVER_DEV_ATA_PRIMARY,
|
||||
drive: 0xf0,
|
||||
present: false,
|
||||
slave: KDRIVER_DEV_ATA_SLAVE_BIT,
|
||||
atapi: false,
|
||||
model: "",
|
||||
serial: "",
|
||||
firmware: "",
|
||||
size: 0,
|
||||
};
|
||||
drives[2] = {
|
||||
controller: KDRIVER_DEV_ATA_SECONDARY,
|
||||
drive: 0xe0,
|
||||
present: false,
|
||||
slave: KDRIVER_DEV_ATA_MASTER_BIT,
|
||||
atapi: false,
|
||||
model: "",
|
||||
serial: "",
|
||||
firmware: "",
|
||||
size: 0,
|
||||
};
|
||||
drives[3] = {
|
||||
controller: KDRIVER_DEV_ATA_SECONDARY,
|
||||
drive: 0xf0,
|
||||
present: false,
|
||||
slave: KDRIVER_DEV_ATA_SLAVE_BIT,
|
||||
atapi: false,
|
||||
model: "",
|
||||
serial: "",
|
||||
firmware: "",
|
||||
size: 0,
|
||||
};
|
||||
|
||||
byteout(KDRIVER_DEV_ATA_PRIMARY_CTL, KDRIVER_DEV_ATA_CTL_nIEN);
|
||||
byteout(KDRIVER_DEV_ATA_SECONDARY_CTL, KDRIVER_DEV_ATA_CTL_nIEN);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const drive = drives[i]!;
|
||||
|
||||
kdriver_dev_ata_identify(drive);
|
||||
}
|
||||
|
||||
byteout(KDRIVER_DEV_ATA_PRIMARY_CTL, 0);
|
||||
byteout(KDRIVER_DEV_ATA_SECONDARY_CTL, 0);
|
||||
|
||||
// TODO: INTERRUPTS
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_readSectors(
|
||||
descriptor: AtaDriveDescriptor,
|
||||
start: number,
|
||||
count: number,
|
||||
read: number
|
||||
) {
|
||||
let buffer: number[] = [];
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const sectorNum = start + i;
|
||||
const valid = { valid: false };
|
||||
const block = blockCache_block(
|
||||
cache,
|
||||
(descriptor.controller << 8) + descriptor.drive,
|
||||
sectorNum,
|
||||
valid
|
||||
);
|
||||
|
||||
if (!valid.valid) {
|
||||
// Need to actually read from disk
|
||||
byteout(
|
||||
descriptor.controller + KDRIVER_DEV_ATA_DRV_HEAD,
|
||||
0xe0 | (descriptor.slave << 4) | ((sectorNum >> 24) & 0x0f)
|
||||
);
|
||||
byteout(descriptor.controller + KDRIVER_DEV_ATA_NSECTOR, 1);
|
||||
byteout(descriptor.controller + KDRIVER_DEV_ATA_SECTOR, sectorNum & 0xff);
|
||||
byteout(
|
||||
descriptor.controller + KDRIVER_DEV_ATA_LCYL,
|
||||
(sectorNum >> 8) & 0xff
|
||||
);
|
||||
byteout(
|
||||
descriptor.controller + KDRIVER_DEV_ATA_HCYL,
|
||||
(sectorNum >> 16) & 0xff
|
||||
);
|
||||
byteout(
|
||||
descriptor.controller + KDRIVER_DEV_ATA_COMMAND,
|
||||
KDRIVER_DEV_ATA_READ_SECTORS
|
||||
);
|
||||
|
||||
// Wait for drive to be ready
|
||||
let timeout = 0;
|
||||
while (timeout < 100000) {
|
||||
const status = bytein(descriptor.controller + KDRIVER_DEV_ATA_STATUS);
|
||||
if (
|
||||
!(status & KDRIVER_DEV_ATA_STATUS_BSY) &&
|
||||
status & KDRIVER_DEV_ATA_STATUS_DRQ
|
||||
) {
|
||||
break;
|
||||
}
|
||||
timeout++;
|
||||
}
|
||||
|
||||
// Read 256 words (512 bytes)
|
||||
for (let j = 0; j < 256; j++) {
|
||||
const word = wordin(descriptor.controller + KDRIVER_DEV_ATA_DATA);
|
||||
block[j * 2] = word & 0xff;
|
||||
block[j * 2 + 1] = (word >> 8) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the block to the output buffer
|
||||
for (let n = 0; n < block.length; n++) {
|
||||
buffer.push(block[n]!);
|
||||
}
|
||||
|
||||
read += 512;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_fsDriver_read(
|
||||
data: AtaDriveDescriptor,
|
||||
count: number,
|
||||
offset: number
|
||||
) {
|
||||
if (count % KDRIVER_DEV_ATA_BLOCK_SIZE != 0) throw new Error("Invalid count");
|
||||
if (offset % KDRIVER_DEV_ATA_BLOCK_SIZE != 0)
|
||||
throw new Error("Invalid offset");
|
||||
|
||||
const sectors = count / KDRIVER_DEV_ATA_BLOCK_SIZE;
|
||||
const start = offset / KDRIVER_DEV_ATA_BLOCK_SIZE;
|
||||
|
||||
const buffer = kdriver_dev_ata_readSectors(data, start, sectors, 0);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function kdriver_dev_ata_partition_fsDriver_read(
|
||||
data: any,
|
||||
count: number,
|
||||
offset: number
|
||||
) {
|
||||
if (count % KDRIVER_DEV_ATA_BLOCK_SIZE != 0) throw new Error("Invalid count");
|
||||
if (offset % KDRIVER_DEV_ATA_BLOCK_SIZE != 0)
|
||||
throw new Error("Invalid offset");
|
||||
|
||||
const partition = data;
|
||||
const disk = partition.disk as { descriptor: AtaDriveDescriptor };
|
||||
|
||||
const sectors = count / KDRIVER_DEV_ATA_BLOCK_SIZE;
|
||||
const start = partition.lbaStart + offset / KDRIVER_DEV_ATA_BLOCK_SIZE;
|
||||
|
||||
const buffer = kdriver_dev_ata_readSectors(
|
||||
disk.descriptor,
|
||||
start,
|
||||
sectors,
|
||||
0
|
||||
);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export const kdriver_dev_ata_fsDriver = {
|
||||
read: kdriver_dev_ata_fsDriver_read,
|
||||
};
|
||||
|
||||
export const kdriver_dev_ata_partition_fsDriver = {
|
||||
read: kdriver_dev_ata_partition_fsDriver_read,
|
||||
};
|
||||
@@ -11,6 +11,8 @@ export function kdriver_dev_keyboard_handleInterrupt(): void {
|
||||
| keyof typeof Keycode
|
||||
| undefined;
|
||||
|
||||
//Logger.log(String(scancode));
|
||||
|
||||
if (key == undefined) return;
|
||||
|
||||
kmod_terminal_input_handleKeyboardInput(key);
|
||||
|
||||
151
src/os/src/kernel/filesystem/devfs.ts
Normal file
151
src/os/src/kernel/filesystem/devfs.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Path } from "../../lib/libstd/path";
|
||||
import type { AtaDriveDescriptor } from "../../types/dev/ata/drive";
|
||||
import type { DeviceType } from "../../types/dev/device";
|
||||
import type {
|
||||
KFilesystemDevice,
|
||||
KFilesystemDirectReadResponse,
|
||||
KFilesystemDriver,
|
||||
KFilesystemEntity,
|
||||
KFilesystemMemdata,
|
||||
KFilesystemMemdev,
|
||||
KFilesystemStat,
|
||||
} from "../../types/filesystem/fs";
|
||||
|
||||
const devfs_data: KFilesystemMemdev = [];
|
||||
|
||||
export function devfs_driver(): KFilesystemDriver {
|
||||
return {
|
||||
id: "devfs",
|
||||
init: devfs_init,
|
||||
listDir(path: string): (KFilesystemEntity | KFilesystemDevice)[] | null {
|
||||
return devfs_listDir(path);
|
||||
},
|
||||
readFile(path: string): unknown {
|
||||
throw new Error("Cannot read files in devfs.");
|
||||
},
|
||||
writeFile(path: string, content: unknown): void {
|
||||
throw new Error("Cannot write files in devfs.");
|
||||
},
|
||||
createFile(path: string, content: unknown): void {
|
||||
throw new Error("Cannot create files in devfs.");
|
||||
},
|
||||
mkdir(path: string): void {
|
||||
throw new Error("Cannot make directories in devfs.");
|
||||
},
|
||||
stat(path: string): KFilesystemStat | null {
|
||||
return devfs_statEntity(path);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function devfs_init(): void {
|
||||
devfs_data.push({
|
||||
name: "root",
|
||||
path: "/",
|
||||
type: "folder",
|
||||
size: 0,
|
||||
contents: null,
|
||||
});
|
||||
}
|
||||
|
||||
export function devfs_getEntity(
|
||||
path: string
|
||||
): KFilesystemDevice | KFilesystemEntity | null {
|
||||
for (let i = 0; i < devfs_data.length; i++) {
|
||||
const entity = devfs_data[i]!;
|
||||
|
||||
if (entity.path === path) return entity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function devfs_statEntity(path: string): KFilesystemStat | null {
|
||||
const entity = devfs_getEntity(path);
|
||||
if (!entity) return null;
|
||||
|
||||
return {
|
||||
name: entity.name,
|
||||
type: entity.type,
|
||||
size: entity.size,
|
||||
};
|
||||
}
|
||||
|
||||
export function devfs_mkdir(path: string): void {
|
||||
if (devfs_getEntity(path)) return;
|
||||
|
||||
const parent = Path.dirname(path);
|
||||
if (!devfs_getEntity(parent)) return;
|
||||
|
||||
devfs_data.push({
|
||||
name: Path.filename(path),
|
||||
path,
|
||||
type: "folder",
|
||||
size: 0,
|
||||
contents: null,
|
||||
});
|
||||
}
|
||||
|
||||
export function devfs_listDir(
|
||||
path: string
|
||||
): (KFilesystemEntity | KFilesystemDevice)[] | null {
|
||||
const entity = devfs_getEntity(path);
|
||||
if (!entity) return null;
|
||||
|
||||
if (entity.type !== "folder") return null;
|
||||
|
||||
const contents: (KFilesystemEntity | KFilesystemDevice)[] = [];
|
||||
|
||||
for (let i = 0; i < devfs_data.length; i++) {
|
||||
const e = devfs_data[i]!;
|
||||
|
||||
if (Path.dirname(e.path) === path) {
|
||||
contents.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
export function devfs_registerDevice(
|
||||
path: string,
|
||||
type: DeviceType,
|
||||
driver: unknown,
|
||||
data: unknown
|
||||
) {
|
||||
if (devfs_getEntity(path)) return;
|
||||
|
||||
const parent = Path.dirname(path);
|
||||
if (!devfs_getEntity(parent)) return;
|
||||
|
||||
devfs_data.push({
|
||||
name: Path.filename(path),
|
||||
path,
|
||||
type: "file",
|
||||
size: 0,
|
||||
contents: "",
|
||||
dtype: type,
|
||||
driver: driver,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function devfs_getDevice(path: string): KFilesystemDevice | null {
|
||||
const e = devfs_getEntity(path);
|
||||
if (!e) return null;
|
||||
if (!("driver" in e)) return null;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
export function devfs_directRead(
|
||||
path: string,
|
||||
count: number,
|
||||
offset: number
|
||||
): KFilesystemDirectReadResponse | null {
|
||||
const device = devfs_getDevice(path);
|
||||
if (!device) return null;
|
||||
|
||||
//return (device.driver as any).read(device.data, count, offset);
|
||||
return [];
|
||||
}
|
||||
367
src/os/src/kernel/filesystem/fat32.ts
Normal file
367
src/os/src/kernel/filesystem/fat32.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
import { Logger } from "../../lib/libstd/logger/logger.kmod";
|
||||
import type {
|
||||
KFilesystemDriver,
|
||||
KFilesystemEntity,
|
||||
KFilesystemStat,
|
||||
} from "../../types/filesystem/fs";
|
||||
|
||||
interface Fat32BootSector {
|
||||
bytesPerSector: number;
|
||||
sectorsPerCluster: number;
|
||||
reservedSectors: number;
|
||||
numberOfFATs: number;
|
||||
sectorsPerFAT: number;
|
||||
rootCluster: number;
|
||||
totalSectors: number;
|
||||
}
|
||||
|
||||
interface Fat32State {
|
||||
driver: any;
|
||||
data: any;
|
||||
boot: Fat32BootSector;
|
||||
fatStart: number;
|
||||
dataStart: number;
|
||||
}
|
||||
|
||||
interface DirectoryEntry {
|
||||
name: string;
|
||||
extension: string;
|
||||
attributes: number;
|
||||
firstCluster: number;
|
||||
fileSize: number;
|
||||
}
|
||||
|
||||
const ATTR_READ_ONLY = 0x01;
|
||||
const ATTR_HIDDEN = 0x02;
|
||||
const ATTR_SYSTEM = 0x04;
|
||||
const ATTR_VOLUME_ID = 0x08;
|
||||
const ATTR_DIRECTORY = 0x10;
|
||||
const ATTR_ARCHIVE = 0x20;
|
||||
const ATTR_LONG_NAME =
|
||||
ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID;
|
||||
|
||||
export function fat32_driver(driver: any, data: any): KFilesystemDriver {
|
||||
const state: Fat32State = {
|
||||
driver,
|
||||
data,
|
||||
boot: {} as Fat32BootSector,
|
||||
fatStart: 0,
|
||||
dataStart: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
id: "fat32",
|
||||
init() {
|
||||
return fat32_init(state);
|
||||
},
|
||||
listDir(path: string) {
|
||||
return fat32_listDir(state, path);
|
||||
},
|
||||
readFile(path: string) {
|
||||
return fat32_readFile(state, path);
|
||||
},
|
||||
writeFile(path: string, content: unknown) {
|
||||
throw new Error("FAT32 write not implemented");
|
||||
},
|
||||
createFile(path: string, content: unknown) {
|
||||
throw new Error("FAT32 createFile not implemented");
|
||||
},
|
||||
mkdir(path: string) {
|
||||
throw new Error("FAT32 mkdir not implemented");
|
||||
},
|
||||
stat(path: string) {
|
||||
return fat32_stat(state, path);
|
||||
},
|
||||
directRead(path: string, count: number, offset: number) {
|
||||
return fat32_directRead(state, path, count, offset);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function fat32_init(state: Fat32State): void {
|
||||
const bootSector = state.driver.read(state.data, 512, 0);
|
||||
|
||||
state.boot = {
|
||||
bytesPerSector: bootSector[11] | (bootSector[12] << 8),
|
||||
sectorsPerCluster: bootSector[13],
|
||||
reservedSectors: bootSector[14] | (bootSector[15] << 8),
|
||||
numberOfFATs: bootSector[16],
|
||||
sectorsPerFAT:
|
||||
bootSector[36] |
|
||||
(bootSector[37] << 8) |
|
||||
(bootSector[38] << 16) |
|
||||
(bootSector[39] << 24),
|
||||
rootCluster:
|
||||
bootSector[44] |
|
||||
(bootSector[45] << 8) |
|
||||
(bootSector[46] << 16) |
|
||||
(bootSector[47] << 24),
|
||||
totalSectors:
|
||||
bootSector[32] |
|
||||
(bootSector[33] << 8) |
|
||||
(bootSector[34] << 16) |
|
||||
(bootSector[35] << 24),
|
||||
};
|
||||
|
||||
state.fatStart = state.boot.reservedSectors;
|
||||
state.dataStart =
|
||||
state.boot.reservedSectors +
|
||||
state.boot.numberOfFATs * state.boot.sectorsPerFAT;
|
||||
}
|
||||
|
||||
function fat32_clusterToSector(state: Fat32State, cluster: number): number {
|
||||
return state.dataStart + (cluster - 2) * state.boot.sectorsPerCluster;
|
||||
}
|
||||
|
||||
function fat32_readCluster(state: Fat32State, cluster: number): number[] {
|
||||
const sector = fat32_clusterToSector(state, cluster);
|
||||
const size = state.boot.sectorsPerCluster * state.boot.bytesPerSector;
|
||||
return state.driver.read(
|
||||
state.data,
|
||||
size,
|
||||
sector * state.boot.bytesPerSector
|
||||
);
|
||||
}
|
||||
|
||||
function fat32_getNextCluster(state: Fat32State, cluster: number): number {
|
||||
const fatOffset = cluster * 4;
|
||||
const fatSector =
|
||||
state.fatStart + Math.floor(fatOffset / state.boot.bytesPerSector);
|
||||
const entryOffset = fatOffset % state.boot.bytesPerSector;
|
||||
|
||||
const sector = state.driver.read(
|
||||
state.data,
|
||||
state.boot.bytesPerSector,
|
||||
fatSector * state.boot.bytesPerSector
|
||||
);
|
||||
|
||||
const nextCluster =
|
||||
sector[entryOffset] |
|
||||
(sector[entryOffset + 1] << 8) |
|
||||
(sector[entryOffset + 2] << 16) |
|
||||
(sector[entryOffset + 3] << 24);
|
||||
|
||||
return nextCluster & 0x0fffffff;
|
||||
}
|
||||
|
||||
function fat32_parseDirectoryEntry(
|
||||
data: number[],
|
||||
offset: number
|
||||
): DirectoryEntry | null {
|
||||
if (data[offset] === 0x00) return null; // End of directory
|
||||
if (data[offset] === 0xe5) return null; // Deleted entry
|
||||
|
||||
const attr = data[offset + 11];
|
||||
if ((attr & ATTR_LONG_NAME) === ATTR_LONG_NAME) return null; // LFN entry
|
||||
|
||||
let name = "";
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const c = data[offset + i];
|
||||
if (c !== 0x20) name += String.fromCharCode(c);
|
||||
}
|
||||
|
||||
let extension = "";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const c = data[offset + 8 + i];
|
||||
if (c !== 0x20) extension += String.fromCharCode(c);
|
||||
}
|
||||
|
||||
const firstClusterLow = data[offset + 26] | (data[offset + 27] << 8);
|
||||
const firstClusterHigh = data[offset + 20] | (data[offset + 21] << 8);
|
||||
const firstCluster = (firstClusterHigh << 16) | firstClusterLow;
|
||||
|
||||
const fileSize =
|
||||
data[offset + 28] |
|
||||
(data[offset + 29] << 8) |
|
||||
(data[offset + 30] << 16) |
|
||||
(data[offset + 31] << 24);
|
||||
|
||||
return {
|
||||
name: name.trim(),
|
||||
extension: extension.trim(),
|
||||
attributes: attr,
|
||||
firstCluster,
|
||||
fileSize,
|
||||
};
|
||||
}
|
||||
|
||||
function fat32_listDir(
|
||||
state: Fat32State,
|
||||
path: string
|
||||
): KFilesystemEntity[] | null {
|
||||
let cluster: number | undefined;
|
||||
|
||||
if (path === "/") cluster = state.boot.rootCluster;
|
||||
else {
|
||||
const entry = fat32_findEntry(state, path);
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
cluster = entry.firstCluster;
|
||||
}
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries: KFilesystemEntity[] = [];
|
||||
let currentCluster = cluster;
|
||||
|
||||
while (currentCluster < 0x0ffffff8) {
|
||||
const clusterData = fat32_readCluster(state, currentCluster);
|
||||
|
||||
for (let i = 0; i < clusterData.length; i += 32) {
|
||||
const entry = fat32_parseDirectoryEntry(clusterData, i);
|
||||
if (!entry) continue;
|
||||
|
||||
// Skip volume label entries
|
||||
if ((entry.attributes & ATTR_VOLUME_ID) !== 0) continue;
|
||||
|
||||
const fullName = entry.extension
|
||||
? entry.name + "." + entry.extension
|
||||
: entry.name;
|
||||
const isDir = (entry.attributes & ATTR_DIRECTORY) !== 0;
|
||||
|
||||
entries.push({
|
||||
name: fullName,
|
||||
path: path === "/" ? "/" + fullName : path + "/" + fullName,
|
||||
type: isDir ? "folder" : "file",
|
||||
size: entry.fileSize,
|
||||
contents: null,
|
||||
});
|
||||
}
|
||||
|
||||
currentCluster = fat32_getNextCluster(state, currentCluster);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function fat32_findEntry(
|
||||
state: Fat32State,
|
||||
path: string
|
||||
): DirectoryEntry | null {
|
||||
const parts = path.split("/").filter(function (p) {
|
||||
return p.length > 0;
|
||||
});
|
||||
if (parts.length === 0) return null;
|
||||
|
||||
let currentCluster = state.boot.rootCluster;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const targetName = parts[i]!.toUpperCase();
|
||||
let found = false;
|
||||
|
||||
while (currentCluster < 0x0ffffff8) {
|
||||
const clusterData = fat32_readCluster(state, currentCluster);
|
||||
|
||||
for (let j = 0; j < clusterData.length; j += 32) {
|
||||
const entry = fat32_parseDirectoryEntry(clusterData, j);
|
||||
if (!entry) continue;
|
||||
|
||||
const entryName = entry.extension
|
||||
? entry.name + "." + entry.extension
|
||||
: entry.name;
|
||||
|
||||
if (entryName.toUpperCase() === targetName) {
|
||||
if (i === parts.length - 1) return entry;
|
||||
currentCluster = entry.firstCluster;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) break;
|
||||
currentCluster = fat32_getNextCluster(state, currentCluster);
|
||||
}
|
||||
|
||||
if (!found) return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function fat32_readFile(state: Fat32State, path: string): number[] {
|
||||
const entry = fat32_findEntry(state, path);
|
||||
if (!entry || (entry.attributes & ATTR_DIRECTORY) !== 0) {
|
||||
throw new Error("File not found or is a directory");
|
||||
}
|
||||
|
||||
const data: number[] = [];
|
||||
let currentCluster = entry.firstCluster;
|
||||
let remaining = entry.fileSize;
|
||||
|
||||
while (currentCluster < 0x0ffffff8 && remaining > 0) {
|
||||
const clusterData = fat32_readCluster(state, currentCluster);
|
||||
const bytesToCopy = Math.min(remaining, clusterData.length);
|
||||
|
||||
for (let i = 0; i < bytesToCopy; i++) {
|
||||
data.push(clusterData[i]!);
|
||||
}
|
||||
|
||||
remaining -= bytesToCopy;
|
||||
currentCluster = fat32_getNextCluster(state, currentCluster);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function fat32_stat(state: Fat32State, path: string): KFilesystemStat | null {
|
||||
if (path === "/") {
|
||||
return { name: "/", type: "folder", size: 0 };
|
||||
}
|
||||
|
||||
const entry = fat32_findEntry(state, path);
|
||||
if (!entry) return null;
|
||||
|
||||
const fullName = entry.extension
|
||||
? entry.name + "." + entry.extension
|
||||
: entry.name;
|
||||
|
||||
return {
|
||||
name: fullName,
|
||||
type: (entry.attributes & ATTR_DIRECTORY) !== 0 ? "folder" : "file",
|
||||
size: entry.fileSize,
|
||||
};
|
||||
}
|
||||
|
||||
function fat32_directRead(
|
||||
state: Fat32State,
|
||||
path: string,
|
||||
count: number,
|
||||
offset: number
|
||||
): number[] {
|
||||
const entry = fat32_findEntry(state, path);
|
||||
if (!entry || (entry.attributes & ATTR_DIRECTORY) !== 0) {
|
||||
throw new Error("File not found or is a directory");
|
||||
}
|
||||
|
||||
const clusterSize = state.boot.sectorsPerCluster * state.boot.bytesPerSector;
|
||||
let currentCluster = entry.firstCluster;
|
||||
let currentOffset = 0;
|
||||
const data: number[] = [];
|
||||
|
||||
// Skip to the cluster containing the offset
|
||||
while (offset >= currentOffset + clusterSize && currentCluster < 0x0ffffff8) {
|
||||
currentOffset += clusterSize;
|
||||
currentCluster = fat32_getNextCluster(state, currentCluster);
|
||||
}
|
||||
|
||||
let remaining = count;
|
||||
const startOffsetInCluster = offset - currentOffset;
|
||||
|
||||
while (remaining > 0 && currentCluster < 0x0ffffff8) {
|
||||
const clusterData = fat32_readCluster(state, currentCluster);
|
||||
const start = data.length === 0 ? startOffsetInCluster : 0;
|
||||
const bytesToCopy = Math.min(remaining, clusterData.length - start);
|
||||
|
||||
for (let i = start; i < start + bytesToCopy; i++) {
|
||||
data.push(clusterData[i]!);
|
||||
}
|
||||
|
||||
remaining -= bytesToCopy;
|
||||
currentCluster = fat32_getNextCluster(state, currentCluster);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -2,18 +2,23 @@ import { Logger } from "../lib/libstd/logger/logger.kmod";
|
||||
import { charc } from "../lib/libts/byte";
|
||||
import { padStart } from "../lib/libts/string";
|
||||
import { getDate } from "../lib/sys/date";
|
||||
import { kdriver_dev_ata_detectDisks } from "./drivers/dev/ata";
|
||||
import { kdriver_dev_pci_detectDevices } from "./drivers/dev/pci";
|
||||
import {
|
||||
kdriver_etc_serial_read,
|
||||
kdriver_etc_serial_transmit,
|
||||
} from "./drivers/etc/serial";
|
||||
import { devfs_driver, devfs_getDevice } from "./filesystem/devfs";
|
||||
import { fat32_driver } from "./filesystem/fat32";
|
||||
import { sysfs_driver, sysfs_readFile } from "./filesystem/sysfs";
|
||||
import { kmod_disks_detectDisks } from "./modules/disks/disks.kmod";
|
||||
import {
|
||||
kmod_drivers_init,
|
||||
kmod_drivers_register,
|
||||
} from "./modules/drivers/drivers.kmod";
|
||||
import {
|
||||
kmod_filesystem_init,
|
||||
kmod_filesystem_listDir,
|
||||
kmod_filesystem_mount,
|
||||
kmod_filesystem_readFile,
|
||||
} from "./modules/filesystem/filesystem.kmod";
|
||||
@@ -21,6 +26,7 @@ import {
|
||||
kmod_graphics_vga_clear,
|
||||
kmod_graphics_vga_init,
|
||||
kmod_graphics_vga_pushLine,
|
||||
kmod_graphics_vga_setLastLine,
|
||||
} from "./modules/graphics/graphics.kmod";
|
||||
import {
|
||||
kmod_terminal_input_init,
|
||||
@@ -33,9 +39,13 @@ export function kmain() {
|
||||
kmod_drivers_register();
|
||||
Logger.log("[Kernel] Drivers initialized.");
|
||||
|
||||
Logger.log("[Kernel] Initializing VGA module...");
|
||||
kmod_graphics_vga_init();
|
||||
|
||||
Logger.log("[Kernel] Initializing filesystem module...");
|
||||
kmod_filesystem_init();
|
||||
kmod_filesystem_mount("/sys", sysfs_driver);
|
||||
kmod_filesystem_mount("/dev", devfs_driver);
|
||||
|
||||
Logger.log("[Kernel] Initializing terminal module...");
|
||||
kmod_terminal_input_init();
|
||||
@@ -43,11 +53,45 @@ export function kmain() {
|
||||
Logger.log("[Kernel] Initializing PCI devices...");
|
||||
kdriver_dev_pci_detectDevices();
|
||||
|
||||
Logger.log("[Kernel] Initializing VGA module...");
|
||||
kmod_graphics_vga_init();
|
||||
Logger.log("[Kernel] Initializing drives...");
|
||||
kmod_disks_detectDisks();
|
||||
|
||||
kmod_graphics_vga_pushLine("[Kernel] Kernel initialized successfully.");
|
||||
|
||||
kmod_terminal_input_onKeyboardInput(function (keycode) {
|
||||
kmod_graphics_vga_pushLine(keycode);
|
||||
const dev = devfs_getDevice("/hda1");
|
||||
if (!dev) throw new Error("Drive not found");
|
||||
|
||||
kmod_filesystem_mount("/home", function () {
|
||||
return fat32_driver(dev.driver, dev.data);
|
||||
});
|
||||
|
||||
let input = "";
|
||||
let running = false;
|
||||
|
||||
kmod_graphics_vga_setLastLine("> ");
|
||||
|
||||
kmod_terminal_input_onKeyboardInput(function (code) {
|
||||
if (running) return;
|
||||
|
||||
if (code == "Enter") {
|
||||
running = true;
|
||||
kmod_graphics_vga_pushLine("Starting " + input);
|
||||
const f = kmod_filesystem_readFile(input);
|
||||
if (!f) return;
|
||||
|
||||
let app = "";
|
||||
|
||||
for (let i = 0; i < f.length; i++) {
|
||||
app += String.fromCharCode(f[i]!);
|
||||
}
|
||||
|
||||
// EXTREMELY DANGEROUS -- REPLACE WITH SANDBOXING
|
||||
eval("const console = { log: Logger.log } ;" + app);
|
||||
Logger.log("DONE");
|
||||
running = false;
|
||||
} else {
|
||||
input += code.toLowerCase();
|
||||
kmod_graphics_vga_setLastLine("> " + input);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
163
src/os/src/kernel/modules/disks/disks.kmod.ts
Normal file
163
src/os/src/kernel/modules/disks/disks.kmod.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Logger } from "../../../lib/libstd/logger/logger.kmod";
|
||||
import { Path } from "../../../lib/libstd/path";
|
||||
import { cchar, charc } from "../../../lib/libts/byte";
|
||||
import { DeviceType } from "../../../types/dev/device";
|
||||
import { DiskType, type Disk } from "../../../types/filesystem/disk";
|
||||
import {
|
||||
kdriver_dev_ata_detectDisks,
|
||||
kdriver_dev_ata_fsDriver,
|
||||
kdriver_dev_ata_getDrive,
|
||||
kdriver_dev_ata_numberOfDrives,
|
||||
kdriver_dev_ata_partition_fsDriver,
|
||||
kdriver_dev_ata_readSectors,
|
||||
} from "../../drivers/dev/ata";
|
||||
import { devfs_registerDevice } from "../../filesystem/devfs";
|
||||
import { sysfs_mkdir, sysfs_writeFile } from "../../filesystem/sysfs";
|
||||
|
||||
let disks: Disk[] = [];
|
||||
|
||||
export function kmod_disks_detectDisks() {
|
||||
kdriver_dev_ata_detectDisks();
|
||||
|
||||
let cdrom = charc("a");
|
||||
let disk = charc("a");
|
||||
let numOfDisks = 0;
|
||||
|
||||
for (let i = 0; i < kdriver_dev_ata_numberOfDrives(); ++i) {
|
||||
const descriptor = kdriver_dev_ata_getDrive(i);
|
||||
|
||||
if (!descriptor.present) continue;
|
||||
|
||||
let name = "";
|
||||
|
||||
if (descriptor.atapi) {
|
||||
name = "cd" + cchar(cdrom);
|
||||
cdrom++;
|
||||
|
||||
disks.push({
|
||||
dnum: numOfDisks,
|
||||
name: name,
|
||||
descriptor: descriptor,
|
||||
type: DiskType.ATAPI,
|
||||
});
|
||||
|
||||
devfs_registerDevice(
|
||||
"/" + name,
|
||||
DeviceType.BlockDevice,
|
||||
null,
|
||||
descriptor
|
||||
);
|
||||
} else {
|
||||
name = "hd" + cchar(disk);
|
||||
disk++;
|
||||
|
||||
disks.push({
|
||||
dnum: numOfDisks,
|
||||
name: name,
|
||||
descriptor: descriptor,
|
||||
type: DiskType.ATA,
|
||||
});
|
||||
|
||||
devfs_registerDevice(
|
||||
"/" + name,
|
||||
DeviceType.BlockDevice,
|
||||
kdriver_dev_ata_fsDriver,
|
||||
descriptor
|
||||
);
|
||||
|
||||
let part = charc("1");
|
||||
|
||||
const partitions = kmod_disks_getPartitions(disks[numOfDisks]!);
|
||||
|
||||
for (let n = 0; n < partitions.length; n++) {
|
||||
const partition = partitions[n];
|
||||
|
||||
let part_name = name + cchar(part);
|
||||
part++;
|
||||
|
||||
(partition as any).disk = disks[numOfDisks]!;
|
||||
|
||||
devfs_registerDevice(
|
||||
"/" + part_name,
|
||||
DeviceType.BlockDevice,
|
||||
kdriver_dev_ata_partition_fsDriver,
|
||||
partition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const p = Path.join("/ata", name);
|
||||
|
||||
sysfs_mkdir(p);
|
||||
sysfs_writeFile(Path.join(p, "model"), descriptor.model);
|
||||
sysfs_writeFile(Path.join(p, "serial"), descriptor.serial);
|
||||
sysfs_writeFile(Path.join(p, "firmware"), descriptor.firmware);
|
||||
|
||||
numOfDisks++;
|
||||
}
|
||||
}
|
||||
|
||||
export function kmod_disks_getPartitions(disk: Disk) {
|
||||
const sectors = kdriver_dev_ata_readSectors(disk.descriptor, 0, 1, 0);
|
||||
|
||||
const signature = (sectors[511]! << 8) | sectors[510]!;
|
||||
|
||||
if (signature !== 0xaa55) {
|
||||
throw new Error("Invalid MBR signature");
|
||||
}
|
||||
|
||||
const partitions = [];
|
||||
|
||||
// Parse partition table (starts at offset 446, 4 entries of 16 bytes each)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const offset = 446 + i * 16;
|
||||
|
||||
const status = sectors[offset]!;
|
||||
const partitionType = sectors[offset + 4]!;
|
||||
|
||||
// LBA of first sector (little-endian)
|
||||
const lbaStart =
|
||||
sectors[offset + 8]! |
|
||||
(sectors[offset + 9]! << 8) |
|
||||
(sectors[offset + 10]! << 16) |
|
||||
(sectors[offset + 11]! << 24);
|
||||
|
||||
// Number of sectors (little-endian)
|
||||
const sectorCount =
|
||||
sectors[offset + 12]! |
|
||||
(sectors[offset + 13]! << 8) |
|
||||
(sectors[offset + 14]! << 16) |
|
||||
(sectors[offset + 15]! << 24);
|
||||
|
||||
// Skip empty partition entries
|
||||
if (partitionType === 0 || sectorCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
partitions.push({
|
||||
index: i,
|
||||
bootable: status === 0x80,
|
||||
type: partitionType,
|
||||
lbaStart: lbaStart,
|
||||
sectors: sectorCount,
|
||||
size: sectorCount * 512,
|
||||
});
|
||||
|
||||
Logger.log(
|
||||
"[Disk] Partition " +
|
||||
i +
|
||||
": Type=0x" +
|
||||
partitionType.toString(16) +
|
||||
" " +
|
||||
"LBA=" +
|
||||
lbaStart +
|
||||
" Sectors=" +
|
||||
sectorCount +
|
||||
" " +
|
||||
"Bootable=" +
|
||||
(status === 0x80)
|
||||
);
|
||||
}
|
||||
|
||||
return partitions;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { kdriver_dev_ata_init } from "../../drivers/dev/ata";
|
||||
import { kdriver_dev_keyboard_init } from "../../drivers/dev/keyboard";
|
||||
import { kdriver_dev_pci_init } from "../../drivers/dev/pci";
|
||||
import { kdriver_etc_serial_init } from "../../drivers/etc/serial";
|
||||
@@ -6,6 +7,7 @@ const drivers = [
|
||||
kdriver_etc_serial_init,
|
||||
kdriver_dev_pci_init,
|
||||
kdriver_dev_keyboard_init,
|
||||
kdriver_dev_ata_init,
|
||||
];
|
||||
|
||||
export function kmod_drivers_init(): void {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Logger } from "../../../lib/libstd/logger/logger.kmod";
|
||||
import { Path } from "../../../lib/libstd/path";
|
||||
import type {
|
||||
KFilesystemDriver,
|
||||
KFilesystemEntity,
|
||||
@@ -48,24 +49,42 @@ export function kmod_filesystem_stripMountpoint(
|
||||
return p.slice(mount.mountpoint.length) || "/";
|
||||
}
|
||||
|
||||
export function kmod_filesystem_lsMountAsFsEntity() {
|
||||
let out: KFilesystemEntity[] = [];
|
||||
|
||||
for (let i = 0; i < kmod_filesystem_data.length; i++) {
|
||||
const mount = kmod_filesystem_data[i]!;
|
||||
|
||||
out.push({
|
||||
name: Path.filename(mount.mountpoint),
|
||||
path: mount.mountpoint,
|
||||
type: "folder",
|
||||
size: 0,
|
||||
contents: 0,
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function kmod_filesystem_listDir(
|
||||
path: string
|
||||
): KFilesystemEntity[] | null {
|
||||
const mount = kmod_filesystem_findMount(path);
|
||||
if (!mount) return null;
|
||||
if (!mount) return kmod_filesystem_lsMountAsFsEntity();
|
||||
|
||||
const strippedPath = kmod_filesystem_stripMountpoint(mount, path);
|
||||
|
||||
return mount.driver.listDir(strippedPath);
|
||||
}
|
||||
|
||||
export function kmod_filesystem_readFile(path: string): unknown {
|
||||
export function kmod_filesystem_readFile(path: string): number[] | null {
|
||||
const mount = kmod_filesystem_findMount(path);
|
||||
if (!mount) return null;
|
||||
|
||||
const strippedPath = kmod_filesystem_stripMountpoint(mount, path);
|
||||
|
||||
return mount.driver.readFile(strippedPath);
|
||||
return mount.driver.readFile(strippedPath) as number[];
|
||||
}
|
||||
|
||||
export function kmod_filesystem_writeFile(
|
||||
@@ -109,3 +128,9 @@ export function kmod_filesystem_stat(path: string): KFilesystemStat | null {
|
||||
|
||||
return mount.driver.stat(strippedPath);
|
||||
}
|
||||
|
||||
export function kmod_filesystem_directRead(
|
||||
path: string,
|
||||
count: number,
|
||||
offset: number
|
||||
) {}
|
||||
|
||||
@@ -6,12 +6,17 @@ import {
|
||||
} from "./config";
|
||||
|
||||
let kmod_graphics_vga_lineBuf: string[] = [];
|
||||
let init = false;
|
||||
|
||||
export function kmod_graphics_vga_init() {
|
||||
init = true;
|
||||
|
||||
kmod_graphics_vga_clear();
|
||||
}
|
||||
|
||||
export function kmod_graphics_vga_pushLine(line: string) {
|
||||
if (!init) return;
|
||||
|
||||
kmod_graphics_vga_lineBuf.push(line);
|
||||
|
||||
if (kmod_graphics_vga_lineBuf.length > CFG_KMOD_GRAPHICS_VGA_HEIGHT) {
|
||||
@@ -21,7 +26,21 @@ export function kmod_graphics_vga_pushLine(line: string) {
|
||||
kmod_graphics_vga_printLines();
|
||||
}
|
||||
|
||||
export function kmod_graphics_vga_setLastLine(line: string) {
|
||||
if (!init) return;
|
||||
|
||||
if (kmod_graphics_vga_lineBuf.length === 0) {
|
||||
kmod_graphics_vga_lineBuf.push(line);
|
||||
} else {
|
||||
kmod_graphics_vga_lineBuf[kmod_graphics_vga_lineBuf.length - 1] = line;
|
||||
}
|
||||
|
||||
kmod_graphics_vga_printLines();
|
||||
}
|
||||
|
||||
export function kmod_graphics_vga_printLines() {
|
||||
if (!init) return;
|
||||
|
||||
for (let y = 0; y < CFG_KMOD_GRAPHICS_VGA_HEIGHT; y++) {
|
||||
const line = kmod_graphics_vga_lineBuf[y] || "";
|
||||
|
||||
@@ -49,6 +68,8 @@ export function kmod_graphics_vga_getColorAddr(x: number, y: number): number {
|
||||
}
|
||||
|
||||
export function kmod_graphics_vga_clear() {
|
||||
if (!init) return;
|
||||
|
||||
kmod_graphics_vga_lineBuf = [];
|
||||
|
||||
for (let y = 0; y < CFG_KMOD_GRAPHICS_VGA_HEIGHT; y++) {
|
||||
@@ -64,6 +85,8 @@ export function kmod_graphics_vga_writeChar(
|
||||
char: string,
|
||||
color: number
|
||||
) {
|
||||
if (!init) return;
|
||||
|
||||
const charAddr = kmod_graphics_vga_getCharAddr(x, y);
|
||||
const colorAddr = kmod_graphics_vga_getColorAddr(x, y);
|
||||
|
||||
|
||||
192
src/os/src/kernel/utils/block_cache.ts
Normal file
192
src/os/src/kernel/utils/block_cache.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
interface Block {
|
||||
key: number;
|
||||
hashNext: Block | null;
|
||||
freeNext: Block | null;
|
||||
freePrev: Block | null;
|
||||
payload: number[]; // Array instead of Uint8Array
|
||||
}
|
||||
|
||||
interface BlockCacheState {
|
||||
payloadSize: number;
|
||||
blocks: number;
|
||||
hashTable: (Block | null)[];
|
||||
blocksMemory: Block[];
|
||||
front: Block | null;
|
||||
rear: Block | null;
|
||||
}
|
||||
|
||||
export function blockCache_create(): BlockCacheState {
|
||||
return {
|
||||
payloadSize: 0,
|
||||
blocks: 0,
|
||||
hashTable: [],
|
||||
blocksMemory: [],
|
||||
front: null,
|
||||
rear: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockCache_init(
|
||||
state: BlockCacheState,
|
||||
payloadSize: number,
|
||||
blocks: number
|
||||
): void {
|
||||
state.payloadSize = payloadSize;
|
||||
state.blocks = blocks;
|
||||
|
||||
// Initialize hash table (2x the number of blocks)
|
||||
var hashTableSize = blocks * 2;
|
||||
state.hashTable = [];
|
||||
for (var i = 0; i < hashTableSize; i++) {
|
||||
state.hashTable.push(null);
|
||||
}
|
||||
|
||||
// Allocate blocks in memory
|
||||
state.blocksMemory = [];
|
||||
var previous: Block | null = null;
|
||||
|
||||
for (var i = 0; i < blocks; i++) {
|
||||
var payload: number[] = [];
|
||||
for (var j = 0; j < payloadSize; j++) {
|
||||
payload.push(0);
|
||||
}
|
||||
|
||||
var block: Block = {
|
||||
key: 0,
|
||||
hashNext: null,
|
||||
freeNext: null,
|
||||
freePrev: previous,
|
||||
payload: payload,
|
||||
};
|
||||
|
||||
if (previous) {
|
||||
previous.freeNext = block;
|
||||
}
|
||||
|
||||
state.blocksMemory.push(block);
|
||||
previous = block;
|
||||
}
|
||||
|
||||
state.front = state.blocksMemory[0];
|
||||
state.rear = previous;
|
||||
}
|
||||
|
||||
export function blockCache_blockIfPresent(
|
||||
state: BlockCacheState,
|
||||
deviceOrKey: number,
|
||||
sector?: number
|
||||
): number[] | null {
|
||||
var origKey =
|
||||
sector !== undefined ? deviceOrKey * 65536 + sector : deviceOrKey;
|
||||
|
||||
var key = origKey % (state.blocks * 2);
|
||||
|
||||
if (state.hashTable[key]) {
|
||||
var entry = state.hashTable[key];
|
||||
|
||||
while (entry) {
|
||||
if (entry.key === origKey) {
|
||||
return entry.payload;
|
||||
}
|
||||
entry = entry.hashNext;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function blockCache_block(
|
||||
state: BlockCacheState,
|
||||
deviceOrKey: number,
|
||||
sectorOrValid: number | { valid: boolean },
|
||||
valid?: { valid: boolean }
|
||||
): number[] {
|
||||
var origKey: number;
|
||||
var validRef: { valid: boolean };
|
||||
|
||||
if (valid !== undefined) {
|
||||
// block(state, device, sector, valid)
|
||||
origKey = deviceOrKey * 65536 + (sectorOrValid as number);
|
||||
validRef = valid;
|
||||
} else {
|
||||
// block(state, key, valid)
|
||||
origKey = deviceOrKey;
|
||||
validRef = sectorOrValid as { valid: boolean };
|
||||
}
|
||||
|
||||
var key = origKey % (state.blocks * 2);
|
||||
|
||||
// Try to get from hash table
|
||||
var direct = blockCache_blockIfPresent(state, origKey);
|
||||
if (direct) {
|
||||
validRef.valid = true;
|
||||
return direct;
|
||||
}
|
||||
|
||||
// Allocate new block from front of free list
|
||||
validRef.valid = false;
|
||||
|
||||
if (!state.front) {
|
||||
throw new Error("No free blocks available");
|
||||
}
|
||||
|
||||
var blk = state.front;
|
||||
|
||||
// Remove block from previous hash table entry if it was used
|
||||
if (blk.key) {
|
||||
var previousKey = blk.key % (state.blocks * 2);
|
||||
|
||||
if (state.hashTable[previousKey] === blk) {
|
||||
state.hashTable[previousKey] = blk.hashNext;
|
||||
} else {
|
||||
var entry = state.hashTable[previousKey];
|
||||
var found = false;
|
||||
|
||||
while (entry && entry.hashNext) {
|
||||
if (entry.hashNext === blk) {
|
||||
entry.hashNext = blk.hashNext;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
entry = entry.hashNext;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new Error("The hash table chain did not contain the used block");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert block into hash table
|
||||
blk.key = origKey;
|
||||
|
||||
if (!state.hashTable[key]) {
|
||||
blk.hashNext = null;
|
||||
state.hashTable[key] = blk;
|
||||
} else {
|
||||
// Insert at end of chain
|
||||
var entry = state.hashTable[key]!;
|
||||
|
||||
while (entry.hashNext) {
|
||||
entry = entry.hashNext;
|
||||
}
|
||||
|
||||
entry.hashNext = blk;
|
||||
blk.hashNext = null;
|
||||
}
|
||||
|
||||
// Move block to rear of free list
|
||||
state.front = state.front.freeNext;
|
||||
if (state.front) {
|
||||
state.front.freePrev = null;
|
||||
}
|
||||
|
||||
if (state.rear) {
|
||||
state.rear.freeNext = blk;
|
||||
}
|
||||
blk.freePrev = state.rear;
|
||||
blk.freeNext = null;
|
||||
state.rear = blk;
|
||||
|
||||
return blk.payload;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { charc } from "../../libts/byte";
|
||||
import { kdriver_etc_serial_transmit } from "../../../kernel/drivers/etc/serial";
|
||||
import { kmod_graphics_vga_pushLine } from "../../../kernel/modules/graphics/vga";
|
||||
|
||||
export const Logger = {
|
||||
log(message: string) {
|
||||
kmod_graphics_vga_pushLine(message);
|
||||
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
kdriver_etc_serial_transmit(charc(message[i]!));
|
||||
}
|
||||
|
||||
@@ -9,3 +9,7 @@ export function byteout(port: number, value: number): void {
|
||||
export function charc(char: string): number {
|
||||
return char.charCodeAt(0);
|
||||
}
|
||||
|
||||
export function cchar(code: number): string {
|
||||
return String.fromCharCode(code)[0]!;
|
||||
}
|
||||
|
||||
7
src/os/src/lib/libts/word.ts
Normal file
7
src/os/src/lib/libts/word.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function wordin(w: number): number {
|
||||
return $wordin(w);
|
||||
}
|
||||
|
||||
export function wordout(w: number, value: number): void {
|
||||
$wordout(w, value);
|
||||
}
|
||||
2
src/os/src/types/c/bindings.d.ts
vendored
2
src/os/src/types/c/bindings.d.ts
vendored
@@ -5,6 +5,8 @@ declare function $bytein(port: number): number;
|
||||
declare function $byteout(port: number, value: number): void;
|
||||
declare function $dwordin(port: number): number;
|
||||
declare function $dwordout(port: number, value: number): void;
|
||||
declare function $wordin(port: number): number;
|
||||
declare function $wordout(port: number, value: number): void;
|
||||
declare const $irqregister: (
|
||||
irq: number,
|
||||
handler: (irqNum: number) => void
|
||||
|
||||
11
src/os/src/types/dev/ata/drive.ts
Normal file
11
src/os/src/types/dev/ata/drive.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type AtaDriveDescriptor = {
|
||||
controller: number;
|
||||
drive: number;
|
||||
present: boolean;
|
||||
slave: number;
|
||||
atapi: boolean;
|
||||
model: string;
|
||||
serial: string;
|
||||
firmware: string;
|
||||
size: number;
|
||||
};
|
||||
4
src/os/src/types/dev/device.ts
Normal file
4
src/os/src/types/dev/device.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum DeviceType {
|
||||
BlockDevice,
|
||||
CharDevice,
|
||||
}
|
||||
13
src/os/src/types/filesystem/disk.ts
Normal file
13
src/os/src/types/filesystem/disk.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { AtaDriveDescriptor } from "../dev/ata/drive";
|
||||
|
||||
export type Disk = {
|
||||
dnum: number;
|
||||
type: DiskType;
|
||||
descriptor: AtaDriveDescriptor;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export enum DiskType {
|
||||
ATA,
|
||||
ATAPI,
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { DeviceType } from "../dev/device";
|
||||
|
||||
export type KFilesystemMountdata = KFilesystemMount[];
|
||||
|
||||
export type KFilesystemMount = {
|
||||
@@ -5,6 +7,8 @@ export type KFilesystemMount = {
|
||||
driver: KFilesystemDriver;
|
||||
};
|
||||
|
||||
export type KFilesystemDirectReadResponse = number[];
|
||||
|
||||
export type KFilesystemDriver = {
|
||||
id: string;
|
||||
init: () => void;
|
||||
@@ -14,6 +18,11 @@ export type KFilesystemDriver = {
|
||||
createFile(path: string, content: unknown): void;
|
||||
mkdir(path: string): void;
|
||||
stat(path: string): KFilesystemStat | null;
|
||||
directRead?(
|
||||
path: string,
|
||||
count: number,
|
||||
offset: number
|
||||
): KFilesystemDirectReadResponse;
|
||||
};
|
||||
|
||||
export type KFilesystemMemdata = KFilesystemEntity[];
|
||||
@@ -31,3 +40,11 @@ export type KFilesystemStat = {
|
||||
type: "file" | "folder";
|
||||
size: number;
|
||||
};
|
||||
|
||||
export type KFilesystemDevice = KFilesystemEntity & {
|
||||
dtype: DeviceType;
|
||||
driver: unknown;
|
||||
data: unknown;
|
||||
};
|
||||
|
||||
export type KFilesystemMemdev = (KFilesystemDevice | KFilesystemEntity)[];
|
||||
|
||||
4
src/os/src/types/utils/block_cache.ts
Normal file
4
src/os/src/types/utils/block_cache.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type BlockCache = {
|
||||
payload_size: number;
|
||||
blocks: number;
|
||||
};
|
||||
1
test-hdd/hello.txt
Normal file
1
test-hdd/hello.txt
Normal file
@@ -0,0 +1 @@
|
||||
It works!
|
||||
1
test-hdd/myapp.js
Normal file
1
test-hdd/myapp.js
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Hello from myApp");
|
||||
Reference in New Issue
Block a user