353 lines
11 KiB
C
353 lines
11 KiB
C
/**
|
|
* @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;
|
|
}
|