/** * @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 /* 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; }