Add devicefs VFS driver mounted at /dev

Implement device filesystem subsystem that provides a VFS interface at
/dev for exposing block and character devices. Drivers register devices
via devicefs_register_block() or devicefs_register_char(), and the
devicefs assigns sequential numbers per device class (e.g., hdd1, hdd2).

Features:
- Block device ops: read_sectors, write_sectors, sector_size, sector_count
- Character device ops: read, write
- VFS integration: readdir lists devices, finddir looks up by name
- Byte-offset to sector translation for block device reads/writes
- Auto-numbering: devices named classN where N starts at 1 per class

Also checks off 'ls' task in README.
This commit is contained in:
AI
2026-02-23 13:53:31 +00:00
parent 3512e937ae
commit c12d49dea0
4 changed files with 357 additions and 1 deletions

View File

@@ -57,7 +57,7 @@ Once a task is completed, it should be checked off.
- [x] Implement the fork system call.
- [x] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
- [x] Create a basic shell program `sh`. This shell must be able to start the hello-world app. It must include `cd` as a built-in to change the current working directory.
- [ ] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
- [x] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
- [ ] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices.
- [ ] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver.
- [ ] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.

View File

@@ -19,6 +19,7 @@ add_executable(kernel
cpio.c
vfs.c
initrd_fs.c
devicefs.c
env.c
keyboard.c
interrupts.S

350
src/devicefs.c Normal file
View File

@@ -0,0 +1,350 @@
/**
* @file devicefs.c
* @brief Device filesystem (devicefs) implementation.
*
* Provides a VFS driver mounted at /dev that exposes block and character
* devices. Kernel drivers register devices via devicefs_register_block()
* or devicefs_register_char(), and the devicefs assigns sequential numbers
* per device class (e.g., hdd1, hdd2, cd1).
*
* The VFS interface supports:
* - readdir: lists all registered devices
* - finddir: looks up a device by name
* - read/write: delegates to the device's block or char operations
*/
#include "devicefs.h"
#include "vfs.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/** Device table. */
static devicefs_device_t devices[DEVICEFS_MAX_DEVICES];
/** Number of active devices. */
static uint32_t device_count = 0;
/**
* Find a free slot in the device table.
* @return Index of free slot, or -1 if full.
*/
static int find_free_slot(void) {
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) {
return i;
}
}
return -1;
}
/* ================================================================
* VFS operations for /dev
* ================================================================ */
/**
* Read from a device file.
*
* For block devices, translates byte offset/size to sector reads.
* For character devices, delegates directly to the char read op.
*/
static int32_t devfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
uint32_t sec_size = 512;
if (dev->block_ops->sector_size) {
sec_size = dev->block_ops->sector_size(dev->dev_data);
}
if (sec_size == 0) return -1;
uint32_t start_lba = offset / sec_size;
uint32_t end_byte = offset + size;
uint32_t end_lba = (end_byte + sec_size - 1) / sec_size;
uint32_t num_sectors = end_lba - start_lba;
/* For simplicity, require aligned reads for now */
if (offset % sec_size != 0 || size % sec_size != 0) {
/* Unaligned read: read full sectors, copy partial */
/* TODO: implement unaligned block reads with temp buffer */
return -1;
}
if (dev->block_ops->read_sectors) {
int ret = dev->block_ops->read_sectors(dev->dev_data,
start_lba,
num_sectors,
buf);
return (ret == 0) ? (int32_t)size : -1;
}
return -1;
}
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
if (dev->char_ops->read) {
return dev->char_ops->read(dev->dev_data, size, buf);
}
return -1;
}
return -1;
}
/**
* Write to a device file.
*
* For block devices, translates byte offset/size to sector writes.
* For character devices, delegates directly to the char write op.
*/
static int32_t devfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
uint32_t sec_size = 512;
if (dev->block_ops->sector_size) {
sec_size = dev->block_ops->sector_size(dev->dev_data);
}
if (sec_size == 0) return -1;
if (offset % sec_size != 0 || size % sec_size != 0) {
return -1; /* Require aligned writes */
}
uint32_t start_lba = offset / sec_size;
uint32_t num_sectors = size / sec_size;
if (dev->block_ops->write_sectors) {
int ret = dev->block_ops->write_sectors(dev->dev_data,
start_lba,
num_sectors,
buf);
return (ret == 0) ? (int32_t)size : -1;
}
return -1;
}
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
if (dev->char_ops->write) {
return dev->char_ops->write(dev->dev_data, size, buf);
}
return -1;
}
return -1;
}
/**
* Read a directory entry from /dev.
* Lists all registered devices.
*/
static int devfs_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) {
(void)dir;
uint32_t count = 0;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
out->inode = (uint32_t)i;
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
VFS_BLOCKDEV : VFS_CHARDEV;
return 0;
}
count++;
}
return -1; /* No more entries */
}
/**
* Find a device by name within /dev.
*/
static int devfs_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) {
(void)dir;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].name, name) == 0) {
memset(out, 0, sizeof(vfs_node_t));
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
VFS_BLOCKDEV : VFS_CHARDEV;
out->inode = (uint32_t)i;
out->fs_data = &devices[i];
/* For block devices, compute size from sector count */
if (devices[i].type == DEVICEFS_BLOCK && devices[i].block_ops) {
uint32_t sec_size = 512;
uint32_t sec_count = 0;
if (devices[i].block_ops->sector_size) {
sec_size = devices[i].block_ops->sector_size(devices[i].dev_data);
}
if (devices[i].block_ops->sector_count) {
sec_count = devices[i].block_ops->sector_count(devices[i].dev_data);
}
out->size = sec_count * sec_size;
}
return 0;
}
}
return -1;
}
/** Filesystem operations for /dev. */
static vfs_fs_ops_t devfs_ops = {
.open = NULL,
.close = NULL,
.read = devfs_read,
.write = devfs_write,
.readdir = devfs_readdir,
.finddir = devfs_finddir,
};
/* ================================================================
* Public API
* ================================================================ */
uint32_t devicefs_next_number(const char *class_name) {
uint32_t max_num = 0;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].class_name, class_name) == 0) {
if (devices[i].number >= max_num) {
max_num = devices[i].number + 1;
}
}
}
/* Numbers start at 1 */
return (max_num == 0) ? 1 : max_num;
}
/**
* Format a uint32 as a decimal string into buf.
* Returns pointer to the start of the number within buf.
*/
static char *uint_to_str(uint32_t val, char *buf, int buf_size) {
buf[buf_size - 1] = '\0';
int pos = buf_size - 2;
if (val == 0) {
buf[pos] = '0';
return &buf[pos];
}
while (val > 0 && pos >= 0) {
buf[pos--] = (char)('0' + (val % 10));
val /= 10;
}
return &buf[pos + 1];
}
devicefs_device_t *devicefs_register_block(const char *class_name,
devicefs_block_ops_t *ops,
void *dev_data) {
int slot = find_free_slot();
if (slot < 0) {
offset_print(" DEVICEFS: no free device slots\n");
return NULL;
}
devicefs_device_t *dev = &devices[slot];
memset(dev, 0, sizeof(devicefs_device_t));
/* Copy class name */
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
/* Assign sequential number */
dev->number = devicefs_next_number(class_name);
/* Build full device name: class_name + number */
char num_buf[12];
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
/* Append number string */
uint32_t nlen = strlen(dev->name);
uint32_t slen = strlen(num_str);
if (nlen + slen < DEVICEFS_MAX_DEV_NAME) {
memcpy(dev->name + nlen, num_str, slen + 1);
}
dev->type = DEVICEFS_BLOCK;
dev->block_ops = ops;
dev->dev_data = dev_data;
dev->active = 1;
device_count++;
offset_print(" DEVICEFS: registered block device /dev/");
offset_print(dev->name);
offset_print("\n");
return dev;
}
devicefs_device_t *devicefs_register_char(const char *class_name,
devicefs_char_ops_t *ops,
void *dev_data) {
int slot = find_free_slot();
if (slot < 0) {
offset_print(" DEVICEFS: no free device slots\n");
return NULL;
}
devicefs_device_t *dev = &devices[slot];
memset(dev, 0, sizeof(devicefs_device_t));
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
dev->number = devicefs_next_number(class_name);
char num_buf[12];
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
/* Append number string */
uint32_t nlen2 = strlen(dev->name);
uint32_t slen2 = strlen(num_str);
if (nlen2 + slen2 < DEVICEFS_MAX_DEV_NAME) {
memcpy(dev->name + nlen2, num_str, slen2 + 1);
}
dev->type = DEVICEFS_CHAR;
dev->char_ops = ops;
dev->dev_data = dev_data;
dev->active = 1;
device_count++;
offset_print(" DEVICEFS: registered char device /dev/");
offset_print(dev->name);
offset_print("\n");
return dev;
}
devicefs_device_t *devicefs_find(const char *name) {
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].name, name) == 0) {
return &devices[i];
}
}
return NULL;
}
int init_devicefs(void) {
memset(devices, 0, sizeof(devices));
device_count = 0;
int ret = vfs_mount("/dev", &devfs_ops, NULL);
if (ret != 0) {
offset_print(" DEVICEFS: failed to mount at /dev\n");
return -1;
}
offset_print(" DEVICEFS: mounted at /dev\n");
return 0;
}

View File

@@ -17,6 +17,7 @@
#include "cpio.h"
#include "vfs.h"
#include "initrd_fs.h"
#include "devicefs.h"
#include "keyboard.h"
#include "framebuffer.h"
@@ -247,6 +248,10 @@ void kernel_main(uint32_t magic, uint32_t addr) {
}
}
init_devicefs();
EARLY_PRINT("DEV ");
offset_print("Devicefs initialized\n");
init_tss();
EARLY_PRINT("TSS ");
offset_print("TSS initialized\n");