Add sysfs VFS driver, SYS_OPEN/CLOSE syscalls, cat app
Sysfs: - New VFS driver mounted at /sys that lets kernel drivers expose virtual text files via namespace registration - Drivers call sysfs_register(name, ops, ctx) with list/read/write callbacks for their namespace - IDE driver registers 'ide' namespace exposing per-device attributes: model, type, channel, drive, sectors, sector_size - Tested: ls /sys -> ide, ls /sys/ide -> hdd1 cd1, cat /sys/ide/hdd1/model -> QEMU HARDDISK Syscalls: - Added SYS_OPEN (11) and SYS_CLOSE (12) for file I/O from userspace - Extended SYS_READ/SYS_WRITE to handle VFS file descriptors (fd >= 3) - Updated userspace syscalls.h with open()/close() wrappers Apps: - New 'cat' app: reads and displays file contents via open/read/close - Updated 'ls' to accept path argument via ARG1 env var - Updated shell to pass ARG1 env var to external commands
This commit is contained in:
@@ -61,7 +61,7 @@ Once a task is completed, it should be checked off.
|
||||
- [x] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices.
|
||||
- [x] 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.
|
||||
- [x] 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.
|
||||
- [ ] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
|
||||
- [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
|
||||
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device.
|
||||
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
|
||||
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
|
||||
|
||||
41
apps/cat/cat.c
Normal file
41
apps/cat/cat.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file cat.c
|
||||
* @brief Display file contents.
|
||||
*
|
||||
* Reads a file specified as the first argument (from shell)
|
||||
* and prints its contents to stdout. If no argument is given,
|
||||
* prints usage.
|
||||
*
|
||||
* Usage: cat <filepath>
|
||||
*/
|
||||
|
||||
#include "syscalls.h"
|
||||
|
||||
int main(void) {
|
||||
/* Get the file path from the ARGS environment variable.
|
||||
* The shell sets ARGS to the arguments after the command name. */
|
||||
char path[128];
|
||||
if (getenv("ARG1", path, sizeof(path)) < 0) {
|
||||
puts("Usage: cat <file>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Open the file */
|
||||
int32_t fd = open(path, 0);
|
||||
if (fd < 0) {
|
||||
puts("cat: ");
|
||||
puts(path);
|
||||
puts(": open failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Read and print in chunks */
|
||||
char buf[256];
|
||||
int32_t n;
|
||||
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
||||
write(1, buf, (uint32_t)n);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -24,6 +24,8 @@ typedef int int32_t;
|
||||
#define SYS_GETENV 8
|
||||
#define SYS_SETENV 9
|
||||
#define SYS_READDIR 10
|
||||
#define SYS_OPEN 11
|
||||
#define SYS_CLOSE 12
|
||||
|
||||
static inline int32_t syscall0(int num) {
|
||||
int32_t ret;
|
||||
@@ -103,6 +105,25 @@ static inline int32_t readdir(const char *path, uint32_t idx, char *name) {
|
||||
return syscall3(SYS_READDIR, (uint32_t)path, idx, (uint32_t)name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file by path.
|
||||
* @param path File path.
|
||||
* @param flags Open flags (currently unused, pass 0).
|
||||
* @return File descriptor (>= 3) on success, -1 on failure.
|
||||
*/
|
||||
static inline int32_t open(const char *path, uint32_t flags) {
|
||||
return syscall2(SYS_OPEN, (uint32_t)path, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a file descriptor.
|
||||
* @param fd File descriptor.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
static inline int32_t close(int32_t fd) {
|
||||
return syscall1(SYS_CLOSE, (uint32_t)fd);
|
||||
}
|
||||
|
||||
/* Basic string operations for user-space */
|
||||
static inline uint32_t strlen(const char *s) {
|
||||
uint32_t len = 0;
|
||||
|
||||
12
apps/ls/ls.c
12
apps/ls/ls.c
@@ -10,12 +10,14 @@
|
||||
#include "syscalls.h"
|
||||
|
||||
int main(void) {
|
||||
/* Get the current working directory */
|
||||
/* Check for an explicit path argument first */
|
||||
char cwd[128];
|
||||
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
|
||||
/* Default to root if CWD not set */
|
||||
cwd[0] = '/';
|
||||
cwd[1] = '\0';
|
||||
if (getenv("ARG1", cwd, sizeof(cwd)) < 0 || cwd[0] == '\0') {
|
||||
/* No argument; use the current working directory */
|
||||
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
|
||||
cwd[0] = '/';
|
||||
cwd[1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
char name[128];
|
||||
|
||||
13
apps/sh/sh.c
13
apps/sh/sh.c
@@ -105,7 +105,7 @@ static void builtin_help(void) {
|
||||
}
|
||||
|
||||
/** Execute an external command via fork+exec. */
|
||||
static void run_command(const char *cmd) {
|
||||
static void run_command(const char *cmd, const char *arg) {
|
||||
int32_t pid = fork();
|
||||
if (pid < 0) {
|
||||
puts("sh: fork failed\n");
|
||||
@@ -113,7 +113,14 @@ static void run_command(const char *cmd) {
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
/* Child: exec the command */
|
||||
/* Child: set ARG1 if there's an argument */
|
||||
if (arg && *arg) {
|
||||
setenv("ARG1", arg);
|
||||
} else {
|
||||
setenv("ARG1", "");
|
||||
}
|
||||
|
||||
/* exec the command */
|
||||
int32_t ret = exec(cmd);
|
||||
if (ret < 0) {
|
||||
puts("sh: ");
|
||||
@@ -172,7 +179,7 @@ int main(void) {
|
||||
builtin_help();
|
||||
} else {
|
||||
/* External command */
|
||||
run_command(line);
|
||||
run_command(line, arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ add_executable(kernel
|
||||
vfs.c
|
||||
initrd_fs.c
|
||||
devicefs.c
|
||||
sysfs.c
|
||||
ide.c
|
||||
mbr.c
|
||||
env.c
|
||||
|
||||
211
src/ide.c
211
src/ide.c
@@ -15,6 +15,7 @@
|
||||
#include "ide.h"
|
||||
#include "port_io.h"
|
||||
#include "devicefs.h"
|
||||
#include "sysfs.h"
|
||||
#include "driver.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -304,6 +305,213 @@ static devicefs_block_ops_t ide_block_ops = {
|
||||
.sector_count = ide_block_sector_count,
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Sysfs interface for /sys/ide
|
||||
* ================================================================
|
||||
*
|
||||
* Exposes per-device info:
|
||||
* /sys/ide/ → lists device names (hdd1, cd1, ...)
|
||||
* /sys/ide/hdd1/ → lists attributes (model, type, channel, drive,
|
||||
* sectors, sector_size)
|
||||
* /sys/ide/hdd1/model → "QEMU HARDDISK\n"
|
||||
* /sys/ide/hdd1/sectors → "0x00003800\n"
|
||||
*/
|
||||
|
||||
/** Integer to hex string helper for sysfs output. */
|
||||
static int ide_sysfs_hex(uint32_t val, char *buf, uint32_t buf_size) {
|
||||
static const char hex[] = "0123456789ABCDEF";
|
||||
if (buf_size < 12) return -1; /* "0x" + 8 hex + \n + \0 */
|
||||
buf[0] = '0'; buf[1] = 'x';
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
buf[2 + (7 - i)] = hex[(val >> (i * 4)) & 0xF];
|
||||
}
|
||||
buf[10] = '\n'; buf[11] = '\0';
|
||||
return 11;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a path string into first component and remainder.
|
||||
* "hdd1/model" → first="hdd1", rest="model"
|
||||
*/
|
||||
static void ide_split_path(const char *path, char *first, uint32_t fsize,
|
||||
const char **rest) {
|
||||
while (*path == '/') path++;
|
||||
const char *s = path;
|
||||
while (*s && *s != '/') s++;
|
||||
uint32_t len = (uint32_t)(s - path);
|
||||
if (len >= fsize) len = fsize - 1;
|
||||
memcpy(first, path, len);
|
||||
first[len] = '\0';
|
||||
if (*s == '/') s++;
|
||||
*rest = s;
|
||||
}
|
||||
|
||||
/** Attribute names exposed for each device. */
|
||||
static const char *ide_sysfs_attrs[] = {
|
||||
"model", "type", "channel", "drive", "sectors", "sector_size"
|
||||
};
|
||||
#define IDE_SYSFS_NUM_ATTRS 6
|
||||
|
||||
/**
|
||||
* Find an IDE device by its devicefs name (hdd1, cd1, etc.).
|
||||
* Returns the device pointer, or NULL.
|
||||
*/
|
||||
static ide_device_t *ide_find_by_name(const char *name) {
|
||||
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
|
||||
if (!ide_devices[i].present) continue;
|
||||
|
||||
/* Reconstruct the devicefs name for this device */
|
||||
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA) ? "hdd" : "cd";
|
||||
uint32_t cls_len = strlen(cls);
|
||||
if (strncmp(name, cls, cls_len) != 0) continue;
|
||||
|
||||
/* Parse the number suffix */
|
||||
const char *num_str = name + cls_len;
|
||||
uint32_t num = 0;
|
||||
while (*num_str >= '0' && *num_str <= '9') {
|
||||
num = num * 10 + (*num_str - '0');
|
||||
num_str++;
|
||||
}
|
||||
if (*num_str != '\0' || num == 0) continue;
|
||||
|
||||
/* Count how many devices of this class precede idx i */
|
||||
uint32_t count = 0;
|
||||
for (int j = 0; j <= i; j++) {
|
||||
if (!ide_devices[j].present) continue;
|
||||
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA) ? "hdd" : "cd";
|
||||
if (strcmp(cls, jcls) == 0) count++;
|
||||
}
|
||||
if (count == num) return &ide_devices[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sysfs list callback.
|
||||
* path="" → list device names (hdd1, cd1, ...)
|
||||
* path="hdd1" → list attributes
|
||||
*/
|
||||
static int ide_sysfs_list(void *ctx, const char *path, uint32_t idx,
|
||||
sysfs_entry_t *out) {
|
||||
(void)ctx;
|
||||
|
||||
if (path[0] == '\0') {
|
||||
/* List all present devices */
|
||||
uint32_t count = 0;
|
||||
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
|
||||
if (!ide_devices[i].present) continue;
|
||||
if (count == idx) {
|
||||
memset(out, 0, sizeof(sysfs_entry_t));
|
||||
/* Build device name */
|
||||
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA)
|
||||
? "hdd" : "cd";
|
||||
/* Count devices of this class up to and including i */
|
||||
uint32_t cls_count = 0;
|
||||
for (int j = 0; j <= i; j++) {
|
||||
if (!ide_devices[j].present) continue;
|
||||
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA)
|
||||
? "hdd" : "cd";
|
||||
if (strcmp(cls, jcls) == 0) cls_count++;
|
||||
}
|
||||
uint32_t clen = strlen(cls);
|
||||
memcpy(out->name, cls, clen);
|
||||
/* Append number as ASCII (max 1 digit for 4 devs) */
|
||||
out->name[clen] = '0' + (char)cls_count;
|
||||
out->name[clen + 1] = '\0';
|
||||
out->is_dir = 1;
|
||||
return 0;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* List attributes for a specific device */
|
||||
ide_device_t *dev = ide_find_by_name(path);
|
||||
if (!dev) return -1;
|
||||
|
||||
if (idx >= IDE_SYSFS_NUM_ATTRS) return -1;
|
||||
|
||||
memset(out, 0, sizeof(sysfs_entry_t));
|
||||
strncpy(out->name, ide_sysfs_attrs[idx], SYSFS_MAX_NAME - 1);
|
||||
out->is_dir = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sysfs read callback.
|
||||
* path="hdd1/model" → device model string
|
||||
* path="hdd1/sectors" → hex sector count
|
||||
*/
|
||||
static int ide_sysfs_read(void *ctx, const char *path, char *buf,
|
||||
uint32_t buf_size) {
|
||||
(void)ctx;
|
||||
|
||||
/* Split path into device name and attribute name */
|
||||
char dev_name[SYSFS_MAX_NAME];
|
||||
const char *attr;
|
||||
ide_split_path(path, dev_name, sizeof(dev_name), &attr);
|
||||
|
||||
ide_device_t *dev = ide_find_by_name(dev_name);
|
||||
if (!dev) return -1;
|
||||
if (attr[0] == '\0') return -1; /* no attribute specified */
|
||||
|
||||
if (strcmp(attr, "model") == 0) {
|
||||
uint32_t mlen = strlen(dev->model);
|
||||
if (mlen + 2 > buf_size) return -1;
|
||||
memcpy(buf, dev->model, mlen);
|
||||
buf[mlen] = '\n';
|
||||
buf[mlen + 1] = '\0';
|
||||
return (int)(mlen + 1);
|
||||
}
|
||||
if (strcmp(attr, "type") == 0) {
|
||||
const char *t = (dev->type == IDE_TYPE_ATA) ? "ATA\n" :
|
||||
(dev->type == IDE_TYPE_ATAPI) ? "ATAPI\n" : "unknown\n";
|
||||
uint32_t tlen = strlen(t);
|
||||
if (tlen + 1 > buf_size) return -1;
|
||||
memcpy(buf, t, tlen + 1);
|
||||
return (int)tlen;
|
||||
}
|
||||
if (strcmp(attr, "channel") == 0) {
|
||||
const char *c = (dev->channel == 0) ? "primary\n" : "secondary\n";
|
||||
uint32_t clen = strlen(c);
|
||||
if (clen + 1 > buf_size) return -1;
|
||||
memcpy(buf, c, clen + 1);
|
||||
return (int)clen;
|
||||
}
|
||||
if (strcmp(attr, "drive") == 0) {
|
||||
const char *d = (dev->drive == 0) ? "master\n" : "slave\n";
|
||||
uint32_t dlen = strlen(d);
|
||||
if (dlen + 1 > buf_size) return -1;
|
||||
memcpy(buf, d, dlen + 1);
|
||||
return (int)dlen;
|
||||
}
|
||||
if (strcmp(attr, "sectors") == 0) {
|
||||
return ide_sysfs_hex(dev->sector_count, buf, buf_size);
|
||||
}
|
||||
if (strcmp(attr, "sector_size") == 0) {
|
||||
return ide_sysfs_hex(dev->sector_size, buf, buf_size);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sysfs write callback — IDE is read-only for now.
|
||||
*/
|
||||
static int ide_sysfs_write(void *ctx, const char *path, const char *buf,
|
||||
uint32_t size) {
|
||||
(void)ctx; (void)path; (void)buf; (void)size;
|
||||
return -1; /* Read-only */
|
||||
}
|
||||
|
||||
/** Sysfs operations for the IDE namespace. */
|
||||
static sysfs_ops_t ide_sysfs_ops = {
|
||||
.list = ide_sysfs_list,
|
||||
.read = ide_sysfs_read,
|
||||
.write = ide_sysfs_write,
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Driver probe and init
|
||||
* ================================================================ */
|
||||
@@ -382,6 +590,9 @@ static int ide_driver_init(void) {
|
||||
offset_print(" device(s) found\n");
|
||||
}
|
||||
|
||||
/* Register sysfs namespace for IDE information */
|
||||
sysfs_register("ide", &ide_sysfs_ops, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "vfs.h"
|
||||
#include "initrd_fs.h"
|
||||
#include "devicefs.h"
|
||||
#include "sysfs.h"
|
||||
#include "mbr.h"
|
||||
#include "keyboard.h"
|
||||
#include "framebuffer.h"
|
||||
@@ -253,6 +254,10 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
||||
EARLY_PRINT("DEV ");
|
||||
offset_print("Devicefs initialized\n");
|
||||
|
||||
init_sysfs();
|
||||
EARLY_PRINT("SYS ");
|
||||
offset_print("Sysfs initialized\n");
|
||||
|
||||
init_tss();
|
||||
EARLY_PRINT("TSS ");
|
||||
offset_print("TSS initialized\n");
|
||||
|
||||
@@ -61,6 +61,11 @@ static int32_t sys_write(registers_t *regs) {
|
||||
return (int32_t)len;
|
||||
}
|
||||
|
||||
/* VFS file descriptors (fd >= 3) */
|
||||
if (fd >= 3) {
|
||||
return vfs_write(fd, buf, len);
|
||||
}
|
||||
|
||||
return -1; /* Invalid fd */
|
||||
}
|
||||
|
||||
@@ -83,6 +88,11 @@ static int32_t sys_read(registers_t *regs) {
|
||||
return 0; /* No data available */
|
||||
}
|
||||
|
||||
/* VFS file descriptors (fd >= 3) */
|
||||
if (fd >= 3) {
|
||||
return vfs_read(fd, buf, len);
|
||||
}
|
||||
|
||||
return -1; /* Invalid fd */
|
||||
}
|
||||
|
||||
@@ -282,6 +292,29 @@ static int32_t sys_setenv(registers_t *regs) {
|
||||
return env_set(&cur->env, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SYS_OPEN: open a file by path.
|
||||
* EBX = path string, ECX = flags.
|
||||
* Returns file descriptor (>= 3) on success, -1 on failure.
|
||||
*/
|
||||
static int32_t sys_open(registers_t *regs) {
|
||||
const char *path = (const char *)regs->ebx;
|
||||
uint32_t flags = regs->ecx;
|
||||
return (int32_t)vfs_open(path, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SYS_CLOSE: close a file descriptor.
|
||||
* EBX = fd.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
static int32_t sys_close(registers_t *regs) {
|
||||
int fd = (int)regs->ebx;
|
||||
if (fd < 3) return -1; /* Don't close stdin/stdout/stderr */
|
||||
vfs_close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SYS_READDIR: read a directory entry.
|
||||
* EBX = path, ECX = index, EDX = name buffer (128 bytes min).
|
||||
@@ -320,6 +353,8 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||
[SYS_GETENV] = sys_getenv,
|
||||
[SYS_SETENV] = sys_setenv,
|
||||
[SYS_READDIR] = sys_readdir,
|
||||
[SYS_OPEN] = sys_open,
|
||||
[SYS_CLOSE] = sys_close,
|
||||
};
|
||||
|
||||
void syscall_handler(registers_t *regs) {
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
#define SYS_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */
|
||||
#define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */
|
||||
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
|
||||
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
|
||||
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
|
||||
|
||||
/** Total number of system calls. */
|
||||
#define NUM_SYSCALLS 11
|
||||
#define NUM_SYSCALLS 13
|
||||
|
||||
/**
|
||||
* Initialize the system call handler.
|
||||
|
||||
294
src/sysfs.c
Normal file
294
src/sysfs.c
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* @file sysfs.c
|
||||
* @brief System filesystem (sysfs) implementation.
|
||||
*
|
||||
* A VFS driver mounted at /sys that lets kernel drivers expose virtual
|
||||
* text files. Drivers register namespaces (e.g., "ide", "mem") and
|
||||
* provide callbacks for listing entries and reading/writing file content.
|
||||
*
|
||||
* Path structure:
|
||||
* /sys/<namespace>/<subpath...>
|
||||
*
|
||||
* The VFS routes requests through sysfs_readdir, sysfs_finddir, and
|
||||
* sysfs_read/write, which decompose the path and forward it to the
|
||||
* appropriate driver's callbacks.
|
||||
*/
|
||||
|
||||
#include "sysfs.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);
|
||||
|
||||
/* ================================================================
|
||||
* Namespace registry
|
||||
* ================================================================ */
|
||||
|
||||
/** A registered sysfs namespace. */
|
||||
typedef struct sysfs_ns {
|
||||
char name[SYSFS_MAX_NAME]; /**< Namespace name. */
|
||||
sysfs_ops_t *ops; /**< Driver callbacks. */
|
||||
void *ctx; /**< Driver context. */
|
||||
int active; /**< 1 if registered. */
|
||||
} sysfs_ns_t;
|
||||
|
||||
/** Namespace table. */
|
||||
static sysfs_ns_t namespaces[SYSFS_MAX_NAMESPACES];
|
||||
|
||||
/**
|
||||
* Find a namespace by name.
|
||||
* @return Pointer to the namespace, or NULL.
|
||||
*/
|
||||
static sysfs_ns_t *find_ns(const char *name) {
|
||||
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||
if (namespaces[i].active && strcmp(namespaces[i].name, name) == 0) {
|
||||
return &namespaces[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Path helpers
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Extract the first path component from a relative path.
|
||||
*
|
||||
* Given "ide/hdd1/model", sets component="ide" and rest="hdd1/model".
|
||||
* Given "ide", sets component="ide" and rest="".
|
||||
*
|
||||
* @param path Input relative path.
|
||||
* @param component Output buffer for the first component.
|
||||
* @param comp_size Size of component buffer.
|
||||
* @param rest Output pointer to the remainder of the path.
|
||||
*/
|
||||
static void split_first(const char *path, char *component,
|
||||
uint32_t comp_size, const char **rest) {
|
||||
/* Skip leading slashes */
|
||||
while (*path == '/') path++;
|
||||
|
||||
const char *slash = path;
|
||||
while (*slash && *slash != '/') slash++;
|
||||
|
||||
uint32_t len = (uint32_t)(slash - path);
|
||||
if (len >= comp_size) len = comp_size - 1;
|
||||
memcpy(component, path, len);
|
||||
component[len] = '\0';
|
||||
|
||||
/* Skip the slash */
|
||||
if (*slash == '/') slash++;
|
||||
*rest = slash;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* VFS callbacks for /sys
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Read from a sysfs virtual file.
|
||||
*
|
||||
* The node's fs_data points to the sysfs_ns_t.
|
||||
* The node's name encodes the sub-path (set by finddir).
|
||||
*
|
||||
* We store the full sub-path (relative to namespace) in node->name.
|
||||
*/
|
||||
static int32_t sysfs_vfs_read(vfs_node_t *node, uint32_t offset,
|
||||
uint32_t size, void *buf) {
|
||||
if (!node || !node->fs_data) return -1;
|
||||
|
||||
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
|
||||
if (!ns->ops || !ns->ops->read) return -1;
|
||||
|
||||
/* Read the full content into a stack buffer */
|
||||
char content[SYSFS_MAX_CONTENT];
|
||||
int total = ns->ops->read(ns->ctx, node->name, content,
|
||||
sizeof(content));
|
||||
if (total < 0) return -1;
|
||||
|
||||
/* Apply offset */
|
||||
if (offset >= (uint32_t)total) return 0;
|
||||
uint32_t avail = (uint32_t)total - offset;
|
||||
if (size > avail) size = avail;
|
||||
memcpy(buf, content + offset, size);
|
||||
return (int32_t)size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to a sysfs virtual file.
|
||||
*/
|
||||
static int32_t sysfs_vfs_write(vfs_node_t *node, uint32_t offset,
|
||||
uint32_t size, const void *buf) {
|
||||
if (!node || !node->fs_data) return -1;
|
||||
|
||||
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
|
||||
if (!ns->ops || !ns->ops->write) return -1;
|
||||
|
||||
return ns->ops->write(ns->ctx, node->name, (const char *)buf, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read directory entries under /sys or /sys/<namespace>/...
|
||||
*
|
||||
* When the directory node has no fs_data, we're at /sys root → list
|
||||
* namespaces. When fs_data is a namespace, delegate to its list callback.
|
||||
*/
|
||||
static int sysfs_vfs_readdir(vfs_node_t *dir, uint32_t idx,
|
||||
vfs_dirent_t *out) {
|
||||
if (!dir) return -1;
|
||||
|
||||
/* Root of /sys → list registered namespaces */
|
||||
if (dir->fs_data == NULL) {
|
||||
uint32_t count = 0;
|
||||
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||
if (!namespaces[i].active) continue;
|
||||
if (count == idx) {
|
||||
memset(out, 0, sizeof(vfs_dirent_t));
|
||||
strncpy(out->name, namespaces[i].name,
|
||||
VFS_MAX_NAME - 1);
|
||||
out->type = VFS_DIRECTORY;
|
||||
out->inode = (uint32_t)i;
|
||||
return 0;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Subdirectory within a namespace */
|
||||
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
|
||||
if (!ns->ops || !ns->ops->list) return -1;
|
||||
|
||||
sysfs_entry_t entry;
|
||||
int ret = ns->ops->list(ns->ctx, dir->name, idx, &entry);
|
||||
if (ret != 0) return -1;
|
||||
|
||||
memset(out, 0, sizeof(vfs_dirent_t));
|
||||
strncpy(out->name, entry.name, VFS_MAX_NAME - 1);
|
||||
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
|
||||
out->inode = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a child by name inside a sysfs directory.
|
||||
*
|
||||
* At the /sys root (fs_data == NULL), we look up namespace names.
|
||||
* Inside a namespace, we ask the driver's list callback to check
|
||||
* whether the child is a file or directory.
|
||||
*/
|
||||
static int sysfs_vfs_finddir(vfs_node_t *dir, const char *name,
|
||||
vfs_node_t *out) {
|
||||
if (!dir) return -1;
|
||||
|
||||
memset(out, 0, sizeof(vfs_node_t));
|
||||
|
||||
/* Root of /sys → find a namespace */
|
||||
if (dir->fs_data == NULL) {
|
||||
sysfs_ns_t *ns = find_ns(name);
|
||||
if (!ns) return -1;
|
||||
|
||||
strncpy(out->name, "", VFS_MAX_NAME - 1); /* sub-path is "" */
|
||||
out->type = VFS_DIRECTORY;
|
||||
out->fs_data = ns;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Inside a namespace: build sub-path */
|
||||
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
|
||||
if (!ns->ops || !ns->ops->list) return -1;
|
||||
|
||||
/* The dir->name contains the current sub-path (relative to namespace).
|
||||
* The child path is dir->name + "/" + name (or just name if dir->name
|
||||
* is empty). */
|
||||
char child_path[VFS_MAX_NAME];
|
||||
if (dir->name[0] == '\0') {
|
||||
strncpy(child_path, name, VFS_MAX_NAME - 1);
|
||||
child_path[VFS_MAX_NAME - 1] = '\0';
|
||||
} else {
|
||||
/* Manually concatenate dir->name + "/" + name */
|
||||
uint32_t dlen = strlen(dir->name);
|
||||
uint32_t nlen = strlen(name);
|
||||
if (dlen + 1 + nlen >= VFS_MAX_NAME) return -1;
|
||||
memcpy(child_path, dir->name, dlen);
|
||||
child_path[dlen] = '/';
|
||||
memcpy(child_path + dlen + 1, name, nlen);
|
||||
child_path[dlen + 1 + nlen] = '\0';
|
||||
}
|
||||
|
||||
/* Iterate the list callback to see if this child exists */
|
||||
sysfs_entry_t entry;
|
||||
for (uint32_t i = 0; ; i++) {
|
||||
int ret = ns->ops->list(ns->ctx, dir->name, i, &entry);
|
||||
if (ret != 0) break; /* no more entries */
|
||||
if (strcmp(entry.name, name) == 0) {
|
||||
strncpy(out->name, child_path, VFS_MAX_NAME - 1);
|
||||
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
|
||||
out->fs_data = ns;
|
||||
|
||||
/* For files, get the content size */
|
||||
if (!entry.is_dir && ns->ops->read) {
|
||||
char tmp[SYSFS_MAX_CONTENT];
|
||||
int sz = ns->ops->read(ns->ctx, child_path, tmp, sizeof(tmp));
|
||||
out->size = (sz > 0) ? (uint32_t)sz : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; /* child not found */
|
||||
}
|
||||
|
||||
/** VFS operations for /sys. */
|
||||
static vfs_fs_ops_t sysfs_vfs_ops = {
|
||||
.open = NULL,
|
||||
.close = NULL,
|
||||
.read = sysfs_vfs_read,
|
||||
.write = sysfs_vfs_write,
|
||||
.readdir = sysfs_vfs_readdir,
|
||||
.finddir = sysfs_vfs_finddir,
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Public API
|
||||
* ================================================================ */
|
||||
|
||||
void init_sysfs(void) {
|
||||
memset(namespaces, 0, sizeof(namespaces));
|
||||
vfs_mount("/sys", &sysfs_vfs_ops, NULL);
|
||||
offset_print(" Sysfs mounted at /sys\n");
|
||||
}
|
||||
|
||||
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx) {
|
||||
if (!name || !ops) return -1;
|
||||
|
||||
/* Check for duplicate */
|
||||
if (find_ns(name)) {
|
||||
offset_print(" Sysfs: namespace '");
|
||||
offset_print(name);
|
||||
offset_print("' already exists\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Find a free slot */
|
||||
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||
if (!namespaces[i].active) {
|
||||
strncpy(namespaces[i].name, name, SYSFS_MAX_NAME - 1);
|
||||
namespaces[i].name[SYSFS_MAX_NAME - 1] = '\0';
|
||||
namespaces[i].ops = ops;
|
||||
namespaces[i].ctx = ctx;
|
||||
namespaces[i].active = 1;
|
||||
|
||||
offset_print(" Sysfs: registered namespace '");
|
||||
offset_print(name);
|
||||
offset_print("'\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
offset_print(" Sysfs: no free namespace slots\n");
|
||||
return -1;
|
||||
}
|
||||
100
src/sysfs.h
Normal file
100
src/sysfs.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @file sysfs.h
|
||||
* @brief System filesystem (sysfs) interface.
|
||||
*
|
||||
* Allows kernel drivers to expose text/config files to userspace via the
|
||||
* VFS at /sys. Each driver registers a namespace (e.g., "ide") and
|
||||
* provides callbacks for listing, reading, and writing virtual files.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Define a sysfs_ops_t with list/read/write callbacks.
|
||||
* 2. Call sysfs_register("ide", &my_ops, my_context).
|
||||
* 3. Users can then `ls /sys/ide`, `cat /sys/ide/model`, etc.
|
||||
*
|
||||
* File contents are small text strings stored on the stack; the read
|
||||
* callback fills a caller-provided buffer.
|
||||
*/
|
||||
|
||||
#ifndef SYSFS_H
|
||||
#define SYSFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/** Maximum number of sysfs namespaces. */
|
||||
#define SYSFS_MAX_NAMESPACES 32
|
||||
|
||||
/** Maximum namespace name length. */
|
||||
#define SYSFS_MAX_NAME 64
|
||||
|
||||
/** Maximum size for sysfs file content (text buffer). */
|
||||
#define SYSFS_MAX_CONTENT 512
|
||||
|
||||
/**
|
||||
* Sysfs file entry returned by the list callback.
|
||||
*/
|
||||
typedef struct sysfs_entry {
|
||||
char name[SYSFS_MAX_NAME]; /**< File or directory name. */
|
||||
uint8_t is_dir; /**< 1 if directory, 0 if file. */
|
||||
} sysfs_entry_t;
|
||||
|
||||
/**
|
||||
* Callbacks provided by a driver to expose files under its namespace.
|
||||
*
|
||||
* All paths are relative to the namespace root. For a namespace "ide",
|
||||
* a path of "hdd1/model" means the file "model" inside subdirectory "hdd1".
|
||||
*/
|
||||
typedef struct sysfs_ops {
|
||||
/**
|
||||
* List entries in a directory.
|
||||
*
|
||||
* @param ctx Driver context pointer (from registration).
|
||||
* @param path Relative path within the namespace ("" for root).
|
||||
* @param idx Entry index (0-based).
|
||||
* @param out Output entry.
|
||||
* @return 0 on success, -1 when no more entries.
|
||||
*/
|
||||
int (*list)(void *ctx, const char *path, uint32_t idx,
|
||||
sysfs_entry_t *out);
|
||||
|
||||
/**
|
||||
* Read a file's content as a text string.
|
||||
*
|
||||
* @param ctx Driver context pointer.
|
||||
* @param path Relative path to the file within the namespace.
|
||||
* @param buf Output buffer for file content.
|
||||
* @param buf_size Buffer size.
|
||||
* @return Number of bytes written to buf, or -1 on error.
|
||||
*/
|
||||
int (*read)(void *ctx, const char *path, char *buf, uint32_t buf_size);
|
||||
|
||||
/**
|
||||
* Write data to a file.
|
||||
*
|
||||
* @param ctx Driver context pointer.
|
||||
* @param path Relative path to the file within the namespace.
|
||||
* @param buf Input data.
|
||||
* @param size Number of bytes to write.
|
||||
* @return Number of bytes consumed, or -1 on error.
|
||||
*/
|
||||
int (*write)(void *ctx, const char *path, const char *buf, uint32_t size);
|
||||
} sysfs_ops_t;
|
||||
|
||||
/**
|
||||
* Initialize the sysfs and mount it at /sys.
|
||||
*/
|
||||
void init_sysfs(void);
|
||||
|
||||
/**
|
||||
* Register a driver namespace in sysfs.
|
||||
*
|
||||
* After registration, files are accessible under /sys/<name>/.
|
||||
*
|
||||
* @param name Namespace name (e.g., "ide").
|
||||
* @param ops Callback operations.
|
||||
* @param ctx Opaque driver context passed to all callbacks.
|
||||
* @return 0 on success, -1 if the table is full or name is invalid.
|
||||
*/
|
||||
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx);
|
||||
|
||||
#endif /* SYSFS_H */
|
||||
Reference in New Issue
Block a user