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:
350
src/devicefs.c
Normal file
350
src/devicefs.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user