Files

764 lines
17 KiB
C

/*
* AsterOS Kernel
* Autor: Pavel Kalas
* Rok: 2026
*
*/
/*
* Tento modul implementuje AsterFS nad realnym ATA diskem v QEMU.
* Data se nahravaji pri startu a po kazde zmene se synchronizuji
* do sektoru na druhem IDE disku, aby pretrvala mezi restarty.
*/
#include "storage.h"
#include "string.h"
#define ATA_IO_BASE 0x1F0
#define ATA_CTRL_BASE 0x3F6
#define ATA_DRIVE_SLAVE 0xF0
#define ATA_REG_DATA 0
#define ATA_REG_SECCOUNT0 2
#define ATA_REG_LBA0 3
#define ATA_REG_LBA1 4
#define ATA_REG_LBA2 5
#define ATA_REG_HDDEVSEL 6
#define ATA_REG_COMMAND 7
#define ATA_REG_STATUS 7
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_SR_BSY 0x80
#define ATA_SR_DRQ 0x08
#define ATA_SR_ERR 0x01
#define ASTERFS_MAGIC "ASTERFS1"
#define ASTERFS_VERSION 1U
#define ASTERFS_DISK_START_LBA 1U
typedef struct {
char magic[8];
u32 version;
u32 nodes_used;
u32 reserved;
u8 pad[512 - 8 - 4 - 4 - 4];
} asterfs_superblock_t;
typedef struct {
char name[ASTERFS_NAME_LEN];
u8 is_dir;
u16 size;
u8 reserved;
u8 data[ASTERFS_DATA_LEN];
} __attribute__((packed)) asterfs_disk_node_t;
#define ASTERFS_NODE_BYTES ((usize)sizeof(asterfs_disk_node_t))
#define ASTERFS_NODE_SECTORS (((ASTERFS_MAX_FILES * ASTERFS_NODE_BYTES) + 511) / 512)
_Static_assert(sizeof(asterfs_disk_node_t) == (ASTERFS_NAME_LEN + 1 + 2 + 1 + ASTERFS_DATA_LEN),
"asterfs_disk_node_t layout mismatch");
static asterfs_node_t nodes[ASTERFS_MAX_FILES];
static int nodes_used = 0;
static int disk_ready = 0;
static u8 g_sector_buffer[512];
static u8 g_nodes_blob[ASTERFS_MAX_FILES * ASTERFS_NODE_BYTES];
static inline void outb(unsigned short port, unsigned char value) {
__asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port));
}
static inline unsigned char inb(unsigned short port) {
unsigned char ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
static inline void outw(unsigned short port, unsigned short value) {
__asm__ volatile ("outw %0, %1" : : "a"(value), "Nd"(port));
}
static inline unsigned short inw(unsigned short port) {
unsigned short ret;
__asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
static void ata_delay_400ns(void) {
(void)inb(ATA_CTRL_BASE);
(void)inb(ATA_CTRL_BASE);
(void)inb(ATA_CTRL_BASE);
(void)inb(ATA_CTRL_BASE);
}
static int ata_wait_not_busy(void) {
unsigned int timeout = 200000U;
while (timeout-- > 0) {
unsigned char s = inb(ATA_IO_BASE + ATA_REG_STATUS);
if (s == 0x00 || s == 0xFF) {
return -1;
}
if ((s & ATA_SR_BSY) == 0) {
return 0;
}
}
return -1;
}
static int ata_wait_drq(void) {
unsigned int timeout = 200000U;
while (timeout-- > 0) {
unsigned char s = inb(ATA_IO_BASE + ATA_REG_STATUS);
if (s == 0x00 || s == 0xFF) {
return -1;
}
if (s & ATA_SR_ERR) {
return -1;
}
if ((s & ATA_SR_BSY) == 0 && (s & ATA_SR_DRQ) != 0) {
return 0;
}
}
return -1;
}
static int ata_select_drive_lba(u32 lba) {
if (ata_wait_not_busy() != 0) {
return -1;
}
outb(ATA_IO_BASE + ATA_REG_HDDEVSEL, (unsigned char)(ATA_DRIVE_SLAVE | ((lba >> 24) & 0x0F)));
ata_delay_400ns();
return 0;
}
static int ata_probe_slave(void) {
unsigned char s;
outb(ATA_IO_BASE + ATA_REG_HDDEVSEL, ATA_DRIVE_SLAVE);
ata_delay_400ns();
s = inb(ATA_IO_BASE + ATA_REG_STATUS);
if (s == 0x00 || s == 0xFF) {
return -1;
}
return ata_wait_not_busy();
}
static int ata_read_sector(u32 lba, u8 *buf512) {
int i;
if (!buf512) {
return -1;
}
if (ata_select_drive_lba(lba) != 0) {
return -1;
}
outb(ATA_IO_BASE + ATA_REG_SECCOUNT0, 1);
outb(ATA_IO_BASE + ATA_REG_LBA0, (unsigned char)(lba & 0xFF));
outb(ATA_IO_BASE + ATA_REG_LBA1, (unsigned char)((lba >> 8) & 0xFF));
outb(ATA_IO_BASE + ATA_REG_LBA2, (unsigned char)((lba >> 16) & 0xFF));
outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_READ_PIO);
if (ata_wait_drq() != 0) {
return -1;
}
for (i = 0; i < 256; ++i) {
unsigned short w = inw(ATA_IO_BASE + ATA_REG_DATA);
buf512[i * 2] = (u8)(w & 0xFF);
buf512[i * 2 + 1] = (u8)((w >> 8) & 0xFF);
}
return 0;
}
static int ata_write_sector(u32 lba, const u8 *buf512) {
int i;
if (!buf512) {
return -1;
}
if (ata_select_drive_lba(lba) != 0) {
return -1;
}
outb(ATA_IO_BASE + ATA_REG_SECCOUNT0, 1);
outb(ATA_IO_BASE + ATA_REG_LBA0, (unsigned char)(lba & 0xFF));
outb(ATA_IO_BASE + ATA_REG_LBA1, (unsigned char)((lba >> 8) & 0xFF));
outb(ATA_IO_BASE + ATA_REG_LBA2, (unsigned char)((lba >> 16) & 0xFF));
outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO);
if (ata_wait_drq() != 0) {
return -1;
}
for (i = 0; i < 256; ++i) {
unsigned short w = (unsigned short)buf512[i * 2] | ((unsigned short)buf512[i * 2 + 1] << 8);
outw(ATA_IO_BASE + ATA_REG_DATA, w);
}
outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH);
if (ata_wait_not_busy() != 0) {
return -1;
}
return 0;
}
static void fs_reset_memory(void) {
int i;
for (i = 0; i < ASTERFS_MAX_FILES; ++i) {
nodes[i].name[0] = '\0';
nodes[i].is_dir = 0;
nodes[i].size = 0;
}
nodes_used = 0;
}
static void fs_encode_nodes(void) {
int i;
for (i = 0; i < ASTERFS_MAX_FILES; ++i) {
asterfs_disk_node_t d;
usize off = (usize)i * ASTERFS_NODE_BYTES;
if (off + ASTERFS_NODE_BYTES > sizeof(g_nodes_blob)) {
break;
}
aster_memset(&d, 0, sizeof(d));
aster_memcpy(d.name, nodes[i].name, ASTERFS_NAME_LEN);
d.is_dir = nodes[i].is_dir;
d.size = nodes[i].size;
aster_memcpy(d.data, nodes[i].data, ASTERFS_DATA_LEN);
aster_memcpy(g_nodes_blob + off, &d, ASTERFS_NODE_BYTES);
}
}
static void fs_decode_nodes(void) {
int i;
for (i = 0; i < ASTERFS_MAX_FILES; ++i) {
asterfs_disk_node_t d;
usize off = (usize)i * ASTERFS_NODE_BYTES;
if (off + ASTERFS_NODE_BYTES > sizeof(g_nodes_blob)) {
break;
}
aster_memcpy(&d, g_nodes_blob + off, ASTERFS_NODE_BYTES);
aster_memset(nodes[i].name, 0, ASTERFS_NAME_LEN);
aster_memcpy(nodes[i].name, d.name, ASTERFS_NAME_LEN);
nodes[i].name[ASTERFS_NAME_LEN - 1] = '\0';
if (nodes[i].name[0] != '\0' && nodes[i].name[0] != '/') {
nodes[i].name[0] = '\0';
}
nodes[i].is_dir = d.is_dir;
if (nodes[i].is_dir > 1) {
nodes[i].is_dir = 0;
}
nodes[i].size = d.size;
if (nodes[i].size > ASTERFS_DATA_LEN) {
nodes[i].size = ASTERFS_DATA_LEN;
}
aster_memcpy(nodes[i].data, d.data, ASTERFS_DATA_LEN);
}
}
static int fs_flush_disk(void) {
asterfs_superblock_t super;
unsigned int sector;
if (!disk_ready) {
return 0;
}
aster_memset(&super, 0, sizeof(super));
aster_memcpy(super.magic, ASTERFS_MAGIC, 8);
super.version = ASTERFS_VERSION;
super.nodes_used = (u32)nodes_used;
aster_memset(g_sector_buffer, 0, sizeof(g_sector_buffer));
aster_memcpy(g_sector_buffer, &super, sizeof(super));
if (ata_write_sector(ASTERFS_DISK_START_LBA, g_sector_buffer) != 0) {
return -1;
}
fs_encode_nodes();
for (sector = 0; sector < ASTERFS_NODE_SECTORS; ++sector) {
usize off = (usize)sector * 512;
aster_memset(g_sector_buffer, 0, sizeof(g_sector_buffer));
if (off < sizeof(g_nodes_blob)) {
usize left = sizeof(g_nodes_blob) - off;
usize chunk = left < 512 ? left : 512;
aster_memcpy(g_sector_buffer, g_nodes_blob + off, chunk);
}
if (ata_write_sector(ASTERFS_DISK_START_LBA + 1U + sector, g_sector_buffer) != 0) {
return -1;
}
}
return 0;
}
static int fs_load_disk(void) {
asterfs_superblock_t super;
unsigned int sector;
if (!disk_ready) {
return -1;
}
if (ata_read_sector(ASTERFS_DISK_START_LBA, g_sector_buffer) != 0) {
return -1;
}
aster_memcpy(&super, g_sector_buffer, sizeof(super));
if (aster_strncmp(super.magic, ASTERFS_MAGIC, 8) != 0 || super.version != ASTERFS_VERSION) {
fs_reset_memory();
return fs_flush_disk();
}
for (sector = 0; sector < ASTERFS_NODE_SECTORS; ++sector) {
usize off = (usize)sector * 512;
if (ata_read_sector(ASTERFS_DISK_START_LBA + 1U + sector, g_sector_buffer) != 0) {
return -1;
}
if (off < sizeof(g_nodes_blob)) {
usize left = sizeof(g_nodes_blob) - off;
usize chunk = left < 512 ? left : 512;
aster_memcpy(g_nodes_blob + off, g_sector_buffer, chunk);
}
}
fs_decode_nodes();
if (super.nodes_used > ASTERFS_MAX_FILES) {
nodes_used = ASTERFS_MAX_FILES;
} else {
nodes_used = (int)super.nodes_used;
}
return 0;
}
static int is_root(const char *path) {
return path && path[0] == '/' && path[1] == '\0';
}
static int path_is_valid(const char *path) {
usize len;
if (!path) {
return 0;
}
len = aster_strlen(path);
if (len == 0 || len >= ASTERFS_NAME_LEN) {
return 0;
}
return path[0] == '/';
}
static int path_parent_exists(const char *path) {
int i;
int last = -1;
char parent[ASTERFS_NAME_LEN];
if (!path || path[0] != '/') {
return 0;
}
for (i = 0; path[i] != '\0'; ++i) {
if (path[i] == '/') {
last = i;
}
}
if (last <= 0) {
return 1;
}
if ((usize)last >= ASTERFS_NAME_LEN) {
return 0;
}
aster_memcpy(parent, path, (usize)last);
parent[last] = '\0';
for (i = 0; i < nodes_used; ++i) {
if (nodes[i].is_dir && aster_strcmp(nodes[i].name, parent) == 0) {
return 1;
}
}
return 0;
}
static int path_is_child(const char *parent, const char *path, const char **leaf_start) {
usize parent_len;
const char *rest;
const char *p;
if (!parent || !path || path[0] != '/') {
return 0;
}
if (is_root(parent)) {
if (path[0] != '/' || path[1] == '\0') {
return 0;
}
rest = path + 1;
p = rest;
while (*p && *p != '/') {
++p;
}
if (*p != '\0') {
return 0;
}
*leaf_start = rest;
return 1;
}
parent_len = aster_strlen(parent);
if (aster_strncmp(path, parent, parent_len) != 0) {
return 0;
}
if (path[parent_len] != '/') {
return 0;
}
rest = path + parent_len + 1;
if (*rest == '\0') {
return 0;
}
p = rest;
while (*p && *p != '/') {
++p;
}
if (*p != '\0') {
return 0;
}
*leaf_start = rest;
return 1;
}
void storage_init(void) {
asterfs_init();
}
void asterfs_init(void) {
fs_reset_memory();
disk_ready = (ata_probe_slave() == 0);
if (!disk_ready) {
return;
}
if (fs_load_disk() != 0) {
fs_reset_memory();
(void)fs_flush_disk();
}
}
static int find_node(const char *name) {
int i;
if (!path_is_valid(name)) {
return -1;
}
for (i = 0; i < nodes_used; ++i) {
if (!path_is_valid(nodes[i].name)) {
continue;
}
if (aster_strcmp(nodes[i].name, name) == 0) {
return i;
}
}
return -1;
}
int asterfs_create_file(const char *name) {
usize len;
if (!path_is_valid(name) || is_root(name) || nodes_used >= ASTERFS_MAX_FILES) {
return -1;
}
if (!path_parent_exists(name)) {
return -1;
}
if (find_node(name) >= 0) {
return 0;
}
len = aster_strlen(name);
if (len >= ASTERFS_NAME_LEN) {
return -1;
}
aster_memset(nodes[nodes_used].name, 0, ASTERFS_NAME_LEN);
aster_memcpy(nodes[nodes_used].name, name, len);
nodes[nodes_used].size = 0;
nodes[nodes_used].is_dir = 0;
++nodes_used;
if (fs_flush_disk() != 0) {
--nodes_used;
return -1;
}
return 0;
}
int asterfs_create_dir(const char *name) {
usize len;
if (!path_is_valid(name) || is_root(name) || nodes_used >= ASTERFS_MAX_FILES) {
return -1;
}
if (!path_parent_exists(name)) {
return -1;
}
if (find_node(name) >= 0) {
return 0;
}
len = aster_strlen(name);
if (len >= ASTERFS_NAME_LEN) {
return -1;
}
aster_memset(nodes[nodes_used].name, 0, ASTERFS_NAME_LEN);
aster_memcpy(nodes[nodes_used].name, name, len);
nodes[nodes_used].size = 0;
nodes[nodes_used].is_dir = 1;
++nodes_used;
if (fs_flush_disk() != 0) {
--nodes_used;
return -1;
}
return 0;
}
int asterfs_remove_file(const char *name) {
int idx;
int i;
if (!path_is_valid(name)) {
return -1;
}
idx = find_node(name);
if (idx < 0 || nodes[idx].is_dir) {
return -1;
}
for (i = idx; i < nodes_used - 1; ++i) {
nodes[i] = nodes[i + 1];
}
--nodes_used;
if (fs_flush_disk() != 0) {
return -1;
}
return 0;
}
int asterfs_remove_dir(const char *name) {
int idx;
int i;
const char *leaf;
if (!path_is_valid(name) || is_root(name)) {
return -1;
}
idx = find_node(name);
if (idx < 0 || !nodes[idx].is_dir) {
return -1;
}
for (i = 0; i < nodes_used; ++i) {
if (i == idx) {
continue;
}
if (path_is_child(name, nodes[i].name, &leaf)) {
return -1;
}
}
for (i = idx; i < nodes_used - 1; ++i) {
nodes[i] = nodes[i + 1];
}
--nodes_used;
if (fs_flush_disk() != 0) {
return -1;
}
return 0;
}
int asterfs_write_file(const char *name, const u8 *data, u16 len) {
int idx;
if (!path_is_valid(name) || !data || is_root(name)) {
return -1;
}
idx = find_node(name);
if (idx < 0) {
if (asterfs_create_file(name) != 0) {
return -1;
}
idx = find_node(name);
}
if (idx < 0 || nodes[idx].is_dir) {
return -1;
}
if (len > ASTERFS_DATA_LEN) {
len = ASTERFS_DATA_LEN;
}
{
u8 old_data[ASTERFS_DATA_LEN];
u16 old_size = nodes[idx].size;
aster_memcpy(old_data, nodes[idx].data, ASTERFS_DATA_LEN);
aster_memset(nodes[idx].data, 0, ASTERFS_DATA_LEN);
aster_memcpy(nodes[idx].data, data, len);
nodes[idx].size = len;
if (fs_flush_disk() != 0) {
aster_memcpy(nodes[idx].data, old_data, ASTERFS_DATA_LEN);
nodes[idx].size = old_size;
return -1;
}
}
return len;
}
int asterfs_sync(void) {
return fs_flush_disk();
}
int asterfs_read_file(const char *name, u8 *out, u16 max_len) {
int idx;
u16 len;
if (!path_is_valid(name) || !out || is_root(name)) {
return -1;
}
idx = find_node(name);
if (idx < 0 || nodes[idx].is_dir) {
return -1;
}
len = nodes[idx].size;
if (len > max_len) {
len = max_len;
}
aster_memcpy(out, nodes[idx].data, len);
return len;
}
void asterfs_list(void (*cb)(const char *name, u8 is_dir, u16 size)) {
int i;
if (!cb) {
return;
}
for (i = 0; i < nodes_used; ++i) {
if (!path_is_valid(nodes[i].name)) {
continue;
}
cb(nodes[i].name, nodes[i].is_dir, nodes[i].size);
}
}
int asterfs_get_type(const char *name) {
int idx;
if (is_root(name)) {
return 1;
}
idx = find_node(name);
if (idx < 0) {
return -1;
}
return nodes[idx].is_dir ? 1 : 0;
}
void asterfs_list_dir(const char *path, void (*cb)(const char *name, u8 is_dir, u16 size)) {
int i;
const char *leaf = 0;
char temp[ASTERFS_NAME_LEN];
usize n;
if (!path || !cb) {
return;
}
if (!is_root(path) && !path_is_valid(path)) {
return;
}
for (i = 0; i < nodes_used; ++i) {
if (!path_is_valid(nodes[i].name)) {
continue;
}
if (!path_is_child(path, nodes[i].name, &leaf)) {
continue;
}
n = aster_strlen(leaf);
if (n >= ASTERFS_NAME_LEN) {
n = ASTERFS_NAME_LEN - 1;
}
aster_memcpy(temp, leaf, n);
temp[n] = '\0';
cb(temp, nodes[i].is_dir, nodes[i].size);
}
}