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

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;
}