From 20bf0fccfd683edbe684ad0221a4ebfb0bb17138 Mon Sep 17 00:00:00 2001 From: FlareCoding Date: Sun, 22 Jun 2025 14:38:31 -0700 Subject: [PATCH 01/28] began developing the stellux display manager and userland graphics library --- userland/Makefile | 2 + userland/apps/stlxdm/Makefile | 4 +- userland/apps/stlxdm/include/stlxdm.h | 23 + userland/apps/stlxdm/src/stlxdm.c | 353 +++++-------- userland/lib/Makefile | 11 +- userland/lib/libstlxgfx/Makefile | 52 ++ .../lib/libstlxgfx/include/stlxgfx/font.h | 47 ++ .../include/stlxgfx/internal}/stb_truetype.h | 0 .../include/stlxgfx/internal/stlxgfx_ctx.h | 27 + .../lib/libstlxgfx/include/stlxgfx/stlxgfx.h | 47 ++ .../lib/libstlxgfx/include/stlxgfx/surface.h | 159 ++++++ userland/lib/libstlxgfx/src/font.c | 146 +++++ userland/lib/libstlxgfx/src/stlxgfx.c | 38 ++ userland/lib/libstlxgfx/src/surface.c | 499 ++++++++++++++++++ 14 files changed, 1197 insertions(+), 211 deletions(-) create mode 100644 userland/lib/libstlxgfx/Makefile create mode 100644 userland/lib/libstlxgfx/include/stlxgfx/font.h rename userland/{apps/stlxdm/include => lib/libstlxgfx/include/stlxgfx/internal}/stb_truetype.h (100%) create mode 100644 userland/lib/libstlxgfx/include/stlxgfx/internal/stlxgfx_ctx.h create mode 100644 userland/lib/libstlxgfx/include/stlxgfx/stlxgfx.h create mode 100644 userland/lib/libstlxgfx/include/stlxgfx/surface.h create mode 100644 userland/lib/libstlxgfx/src/font.c create mode 100644 userland/lib/libstlxgfx/src/stlxgfx.c create mode 100644 userland/lib/libstlxgfx/src/surface.c diff --git a/userland/Makefile b/userland/Makefile index f74476a..6ab6e7e 100644 --- a/userland/Makefile +++ b/userland/Makefile @@ -38,6 +38,7 @@ export DEBUG_FLAGS := -g -O2 export WARNING_FLAGS := -Wall -Wextra -Werror # Include paths +export LIBSTLXGFX_INCLUDE_DIR := $(USERLAND_ROOT)/lib/libstlxgfx/include export INCLUDE_FLAGS := -I$(USERLAND_INCLUDE_DIR) -I$(USERLAND_SYSROOT)/include # Standard C flags @@ -53,6 +54,7 @@ export CXXFLAGS_COMMON := $(ARCH_FLAGS) $(DEBUG_FLAGS) $(WARNING_FLAGS) $(INCLUD # ========================= export LDFLAGS_COMMON := -L$(USERLAND_LIB_DIR) -L$(USERLAND_SYSROOT)/lib export LIBS_COMMON := -lstlibc -lc +export LIBS_STLXGFX := -lstlxgfx export LINKER_SCRIPT := $(USERLAND_ROOT)/userland.ld export LDFLAGS_EXE := -T$(LINKER_SCRIPT) $(LDFLAGS_COMMON) diff --git a/userland/apps/stlxdm/Makefile b/userland/apps/stlxdm/Makefile index cdf47a1..edb5961 100644 --- a/userland/apps/stlxdm/Makefile +++ b/userland/apps/stlxdm/Makefile @@ -18,9 +18,9 @@ OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) TARGET := $(USERLAND_BIN_DIR)/$(APP_NAME) # Use exported flags from parent with local include directory -CFLAGS := $(CFLAGS_COMMON) -I$(INC_DIR) +CFLAGS := $(CFLAGS_COMMON) -I$(INC_DIR) -I$(LIBSTLXGFX_INCLUDE_DIR) LDFLAGS := $(LDFLAGS_EXE) -LIBS := $(LIBS_COMMON) +LIBS := $(LIBS_COMMON) $(LIBS_STLXGFX) # Default target all: $(TARGET) diff --git a/userland/apps/stlxdm/include/stlxdm.h b/userland/apps/stlxdm/include/stlxdm.h index 9f3595d..7348c03 100644 --- a/userland/apps/stlxdm/include/stlxdm.h +++ b/userland/apps/stlxdm/include/stlxdm.h @@ -24,4 +24,27 @@ struct gfx_framebuffer_info { uint32_t format; // Pixel format }; +// ========================= +// Framebuffer Functions +// ========================= + +/** + * Get framebuffer information from the kernel + * @param fb_info - pointer to structure to fill with framebuffer info + * @return 0 on success, negative on error + */ +int stlxdm_get_framebuffer_info(struct gfx_framebuffer_info* fb_info); + +/** + * Map the framebuffer into userspace + * @return pointer to mapped framebuffer on success, NULL on error + */ +uint8_t* stlxdm_map_framebuffer(void); + +/** + * Unmap the framebuffer from userspace + * @return 0 on success, negative on error + */ +int stlxdm_unmap_framebuffer(void); + #endif // STLXDM_H diff --git a/userland/apps/stlxdm/src/stlxdm.c b/userland/apps/stlxdm/src/stlxdm.c index ad60407..48a43a5 100644 --- a/userland/apps/stlxdm/src/stlxdm.c +++ b/userland/apps/stlxdm/src/stlxdm.c @@ -7,253 +7,192 @@ #include #include #include "stlxdm.h" +#include -// Define STB TrueType implementation -#define STB_TRUETYPE_IMPLEMENTATION +int stlxdm_get_framebuffer_info(struct gfx_framebuffer_info* fb_info) { + if (!fb_info) { + printf("ERROR: NULL framebuffer info pointer\n"); + return -1; + } + + long result = syscall2(SYS_GRAPHICS_FRAMEBUFFER_OP, GFX_OP_GET_INFO, (uint64_t)fb_info); + if (result != 0) { + printf("ERROR: Failed to get framebuffer info: %ld\n", result); + return -1; + } + + printf("STLXDM] Framebuffer info: %ux%u, %u BPP, pitch=%u, size=%u\n", + fb_info->width, fb_info->height, fb_info->bpp, fb_info->pitch, fb_info->size); + + return 0; +} -// Suppress warnings for STB TrueType library -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +uint8_t* stlxdm_map_framebuffer(void) { + long map_result = syscall1(SYS_GRAPHICS_FRAMEBUFFER_OP, GFX_OP_MAP_FRAMEBUFFER); + if (map_result <= 0) { + printf("ERROR: Failed to map framebuffer: %ld\n", map_result); + return NULL; + } + + printf("[STLXDM] Framebuffer mapped at address: 0x%lx\n", map_result); + return (uint8_t*)map_result; +} -#include "stb_truetype.h" +int stlxdm_unmap_framebuffer(void) { + long result = syscall1(SYS_GRAPHICS_FRAMEBUFFER_OP, GFX_OP_UNMAP_FRAMEBUFFER); + if (result != 0) { + printf("ERROR: Failed to unmap framebuffer: %ld\n", result); + return -1; + } + + printf("Framebuffer unmapped successfully\n"); + return 0; +} -#pragma GCC diagnostic pop +// ====================== // +// Main Entry Point // +// ====================== // int main() { printf("StelluxOS Display Manager (STLXDM) - Initializing...\n"); - - const char* font_path = "/initrd/res/fonts/UbuntuMono-Regular.ttf"; - - // Load TTF font file - int fd = open(font_path, O_RDONLY); - if (fd < 0) { - printf("ERROR: Failed to open font file: %s (errno: %d - %s)\n", - font_path, errno, strerror(errno)); - return 1; - } - - // Get file size - off_t file_size = lseek(fd, 0, SEEK_END); - if (file_size < 0) { - printf("ERROR: Failed to get font file size (errno: %d - %s)\n", - errno, strerror(errno)); - close(fd); + + // Initialize framebuffer first + struct gfx_framebuffer_info fb_info; + if (stlxdm_get_framebuffer_info(&fb_info) != 0) { return 1; } - if (lseek(fd, 0, SEEK_SET) < 0) { - printf("ERROR: Failed to seek to beginning of font file (errno: %d - %s)\n", - errno, strerror(errno)); - close(fd); + uint8_t* framebuffer = stlxdm_map_framebuffer(); + if (!framebuffer) { return 1; } - // Allocate memory for font data - void* font_data = malloc(file_size); - if (!font_data) { - printf("ERROR: Failed to allocate %ld bytes for font data\n", file_size); - close(fd); + // Initialize graphics library + stlxgfx_context_t* gfx_ctx = stlxgfx_init(STLXGFX_MODE_DISPLAY_MANAGER); + if (!gfx_ctx) { + printf("ERROR: Failed to initialize graphics library\n"); + stlxdm_unmap_framebuffer(); return 1; } - - // Read font file - ssize_t total_read = 0; - char* buffer = (char*)font_data; - - while (total_read < file_size) { - ssize_t bytes_read = read(fd, buffer + total_read, file_size - total_read); - if (bytes_read < 0) { - printf("ERROR: Failed to read from font file (errno: %d - %s)\n", - errno, strerror(errno)); - free(font_data); - close(fd); - return 1; - } - if (bytes_read == 0) break; - total_read += bytes_read; - } - - // Basic TTF validation - if (total_read >= 4) { - uint8_t* data = (uint8_t*)font_data; - if (!((data[0] == 0x00 && data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x00) || - (data[0] == 'O' && data[1] == 'T' && data[2] == 'T' && data[3] == 'O'))) { - printf("WARNING: Font file may not be a valid TTF/OpenType font\n"); - } - } - - close(fd); - // Initialize graphics system - // Get framebuffer info - struct gfx_framebuffer_info fb_info; - long result = syscall2(SYS_GRAPHICS_FRAMEBUFFER_OP, GFX_OP_GET_INFO, (uint64_t)&fb_info); - if (result != 0) { - printf("ERROR: Failed to get framebuffer info: %ld\n", result); - free(font_data); + const char* font_path = "/initrd/res/fonts/UbuntuMono-Regular.ttf"; + if (stlxgfx_dm_load_font(gfx_ctx, font_path) != 0) { + printf("ERROR: Failed to load stlxgfx font\n"); + stlxgfx_cleanup(gfx_ctx); + stlxdm_unmap_framebuffer(); return 1; } - printf("Framebuffer: %ux%u, %u BPP\n", fb_info.width, fb_info.height, fb_info.bpp); - - // Map framebuffer - long map_result = syscall1(SYS_GRAPHICS_FRAMEBUFFER_OP, GFX_OP_MAP_FRAMEBUFFER); - if (map_result <= 0) { - printf("ERROR: Failed to map framebuffer: %ld\n", map_result); - free(font_data); - return 1; + stlxgfx_font_metrics_t metrics; + if (stlxgfx_dm_get_font_metrics(gfx_ctx, &metrics) == 0) { + printf("Font metrics retrieved successfully!\n"); } - // Set up rendering - uint8_t* framebuffer = (uint8_t*)map_result; - uint32_t width = fb_info.width; - uint32_t height = fb_info.height; - uint32_t bytes_per_pixel = fb_info.bpp / 8; - uint32_t pitch = fb_info.pitch; - - // Helper macro for pixel writing - #define write_pixel(x, y, color) do { \ - if ((x) < width && (y) < height) { \ - uint8_t* pixel = framebuffer + ((y) * pitch) + ((x) * bytes_per_pixel); \ - uint8_t r = ((color) >> 16) & 0xFF; \ - uint8_t g = ((color) >> 8) & 0xFF; \ - uint8_t b = (color) & 0xFF; \ - if (bytes_per_pixel == 3) { \ - pixel[0] = b; pixel[1] = g; pixel[2] = r; \ - } else if (bytes_per_pixel == 4) { \ - pixel[0] = b; pixel[1] = g; pixel[2] = r; pixel[3] = 0; \ - } \ - } \ - } while(0) - - // Clear screen - for (uint32_t y = 0; y < height; y++) { - for (uint32_t x = 0; x < width; x++) { - write_pixel(x, y, 0x202020); - } + stlxgfx_text_size_t text_size; + if (stlxgfx_get_text_size(gfx_ctx, "Hello from StelluxOS!", 32, &text_size) == 0) { + printf("Text size calculated successfully!\n"); } - // Draw colored squares in corners - for (uint32_t y = 50; y < 150 && y < height; y++) { - for (uint32_t x = 50; x < 150 && x < width; x++) { - write_pixel(x, y, 0xFF0000); // Red - top-left + // Create compositor surface matching framebuffer format + stlxgfx_surface_t* compositor_surface = stlxgfx_dm_create_surface(gfx_ctx, fb_info.width, fb_info.height, stlxgfx_detect_gop_format(fb_info.bpp)); + if (compositor_surface) { + printf("Surface created successfully! %ux%u, pitch=%u\n", + compositor_surface->width, compositor_surface->height, compositor_surface->pitch); + + uint8_t bpp = stlxgfx_get_bpp_for_format(compositor_surface->format); + printf("Surface format BPP: %u\n", bpp); + + // Test drawing primitives + printf("[STLXDM] Testing drawing primitives...\n"); + + // Clear surface with dark gray background + if (stlxgfx_clear_surface(compositor_surface, 0xFF202020) == 0) { + printf(" Clear surface test passed\n"); } - } - - for (uint32_t y = 50; y < 150 && y < height; y++) { - for (uint32_t x = width - 150; x < width - 50 && x < width; x++) { - write_pixel(x, y, 0x00FF00); // Green - top-right + + // Test colored rectangles in corners + if (stlxgfx_fill_rect(compositor_surface, 50, 50, 100, 100, 0xFFFF0000) == 0) { // Red top-left + printf(" Fill rect (red) test passed\n"); } - } - - for (uint32_t y = height - 150; y < height - 50 && y < height; y++) { - for (uint32_t x = 50; x < 150 && x < width; x++) { - write_pixel(x, y, 0x0000FF); // Blue - bottom-left + + if (stlxgfx_fill_rect(compositor_surface, fb_info.width - 150, 50, 100, 100, 0xFF00FF00) == 0) { // Green top-right + printf(" Fill rect (green) test passed\n"); } - } - - for (uint32_t y = height - 150; y < height - 50 && y < height; y++) { - for (uint32_t x = width - 150; x < width - 50 && x < width; x++) { - write_pixel(x, y, 0xFFFFFF); // White - bottom-right + + if (stlxgfx_fill_rect(compositor_surface, 50, fb_info.height - 150, 100, 100, 0xFF0000FF) == 0) { // Blue bottom-left + printf(" Fill rect (blue) test passed\n"); } - } - - // Draw RGB gradient in center - uint32_t gradient_start_y = height / 2 - 50; - uint32_t gradient_end_y = height / 2 + 50; - for (uint32_t y = gradient_start_y; y < gradient_end_y && y < height; y++) { - for (uint32_t x = 200; x < width - 200 && x < width; x++) { - uint32_t red = (255 * (width - 200 - x)) / (width - 400); - uint32_t blue = (255 * x) / (width - 400); - uint32_t color = (red << 16) | blue; - write_pixel(x, y, color); + + if (stlxgfx_fill_rect(compositor_surface, fb_info.width - 150, fb_info.height - 150, 100, 100, 0xFFFFFFFF) == 0) { // White bottom-right + printf(" Fill rect (white) test passed\n"); } - } - - // Draw checkerboard pattern - for (uint32_t y = height / 2 - 100; y < height / 2 + 100 && y < height; y++) { - for (uint32_t x = 50; x < 250 && x < width; x++) { - int checker = ((x / 20) + (y / 20)) % 2; - uint32_t color = checker ? 0x808080 : 0xC0C0C0; - write_pixel(x, y, color); + + // Test rectangle outlines + if (stlxgfx_draw_rect(compositor_surface, 200, 100, 200, 150, 0xFFFFFF00) == 0) { // Yellow outline + printf(" Draw rect outline test passed\n"); } - } - - // Draw diagonal stripes - for (uint32_t y = height / 2 - 100; y < height / 2 + 100 && y < height; y++) { - for (uint32_t x = width - 250; x < width - 50 && x < width; x++) { - int stripe = ((x - (width - 250)) + y) / 15 % 2; - uint32_t color = stripe ? 0xFF00FF : 0x00FFFF; - write_pixel(x, y, color); + + // Test rounded rectangles + uint32_t center_x = fb_info.width / 2; + uint32_t center_y = fb_info.height / 2; + + if (stlxgfx_fill_rounded_rect(compositor_surface, center_x - 100, center_y - 50, 200, 100, 20, 0xFF800080) == 0) { // Purple rounded rect + printf(" Fill rounded rect test passed\n"); } - } - - // Initialize TTF font rendering - stbtt_fontinfo font; - if (!stbtt_InitFont(&font, font_data, 0)) { - printf("ERROR: Failed to initialize STB TrueType font\n"); - free(font_data); - return 1; - } - - // Set up font parameters - float scale = stbtt_ScaleForPixelHeight(&font, 32.0f); - int ascent, descent, lineGap; - stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap); - - // Render text - const char* text = "Hello from StelluxOS!"; - int text_x = 180, text_y = height / 2 + 140; - - for (int i = 0; text[i]; i++) { - int codepoint = text[i]; - // Get character metrics - int advance, lsb; - stbtt_GetCodepointHMetrics(&font, codepoint, &advance, &lsb); + if (stlxgfx_draw_rounded_rect(compositor_surface, center_x - 120, center_y - 70, 240, 140, 25, 0xFF00FFFF) == 0) { // Cyan rounded outline + printf(" Draw rounded rect outline test passed\n"); + } + + // Test individual pixels + for (int i = 0; i < 20; i++) { + uint32_t color = 0xFF000000 | (i * 12) | ((i * 8) << 8) | ((i * 15) << 16); + stlxgfx_draw_pixel(compositor_surface, 300 + i * 2, 200, color); + } + printf(" Draw pixel test passed\n"); + + // Test text rendering + if (stlxgfx_render_text(gfx_ctx, compositor_surface, "StelluxOS Display Manager", + center_x - 150, center_y + 80, 24, 0xFFFFFFFF) == 0) { + printf(" Text rendering test passed\n"); + } - // Get character bitmap - int char_width, char_height, xoff, yoff; - unsigned char* bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, codepoint, - &char_width, &char_height, &xoff, &yoff); + if (stlxgfx_render_text(gfx_ctx, compositor_surface, "Graphics Library v0.1", + center_x - 100, center_y + 110, 18, 0xFF00FFFF) == 0) { + printf(" Text rendering (smaller) test passed\n"); + } - if (bitmap && char_width > 0 && char_height > 0) { - // Draw character to framebuffer - int char_x = text_x + (int)(lsb * scale) + xoff; - int char_y = text_y + (int)(ascent * scale) + yoff; - - for (int py = 0; py < char_height; py++) { - for (int px = 0; px < char_width; px++) { - int fb_x = char_x + px; - int fb_y = char_y + py; - - if (fb_x >= 0 && fb_x < (int)width && - fb_y >= 0 && fb_y < (int)height) { - - unsigned char alpha = bitmap[py * char_width + px]; - if (alpha > 0) { - uint32_t color = ((uint32_t)alpha << 16) | ((uint32_t)alpha << 8) | (uint32_t)alpha; - write_pixel((uint32_t)fb_x, (uint32_t)fb_y, color); - } - } - } + // Test gradient effect + printf(" Creating gradient effect...\n"); + for (uint32_t y = center_y - 20; y < center_y + 20; y++) { + for (uint32_t x = center_x - 80; x < center_x + 80; x++) { + uint32_t red = (255 * (x - (center_x - 80))) / 160; + uint32_t blue = (255 * (y - (center_y - 20))) / 40; + uint32_t color = 0xFF000000 | (red << 16) | blue; + stlxgfx_draw_pixel(compositor_surface, x, y, color); } - - stbtt_FreeBitmap(bitmap, NULL); } + printf(" Gradient effect test passed\n"); - // Advance to next character - text_x += (int)(advance * scale); + // Copy compositor surface to framebuffer + printf("[STLXDM] Copying surface to framebuffer...\n"); + if (stlxgfx_blit_surface_to_buffer(compositor_surface, framebuffer, fb_info.pitch) == 0) { + printf(" Surface to framebuffer blit successful!\n"); + printf("[STLXDM] Display should now show graphics test pattern\n"); + } else { + printf(" ✗ Failed to blit surface to framebuffer\n"); + } + + stlxgfx_dm_destroy_surface(gfx_ctx, compositor_surface); + } else { + printf("Failed to create compositor surface!\n"); } - #undef write_pixel - printf("Graphics rendering complete!\n"); - printf("STLXDM initialization finished - display should be visible\n"); + // Clean up - free(font_data); + stlxgfx_cleanup(gfx_ctx); + stlxdm_unmap_framebuffer(); return 0; } diff --git a/userland/lib/Makefile b/userland/lib/Makefile index b123275..c574d0b 100644 --- a/userland/lib/Makefile +++ b/userland/lib/Makefile @@ -5,18 +5,25 @@ # Sub-libraries STLIBC_DIR := stlibc +LIBSTLXGFX_DIR := libstlxgfx # Default target - build all libraries -all: stlibc +all: stlibc libstlxgfx # Build stlibc stlibc: @echo "Building stlibc..." @$(MAKE) -C $(STLIBC_DIR) +# Build libstlxgfx +libstlxgfx: + @echo "Building libstlxgfx..." + @$(MAKE) -C $(LIBSTLXGFX_DIR) + # Clean all libraries clean: @echo "Cleaning libraries..." @$(MAKE) -C $(STLIBC_DIR) clean + @$(MAKE) -C $(LIBSTLXGFX_DIR) clean -.PHONY: all stlibc clean +.PHONY: all stlibc libstlxgfx clean diff --git a/userland/lib/libstlxgfx/Makefile b/userland/lib/libstlxgfx/Makefile new file mode 100644 index 0000000..5b08edc --- /dev/null +++ b/userland/lib/libstlxgfx/Makefile @@ -0,0 +1,52 @@ +# ================================ +# STLXGFX Graphics Library Makefile +# ================================ +# Uses exported variables from parent Makefiles + +# Library configuration +LIB_NAME := stlxgfx +SRC_DIR := src +INC_DIR := include +BUILD_DIR := build +OBJ_DIR := $(BUILD_DIR)/obj + +# Source files and objects +SOURCES := $(wildcard $(SRC_DIR)/*.c) +OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) + +# Output library (static) +TARGET := $(USERLAND_LIB_DIR)/lib$(LIB_NAME).a + +# Use exported flags from parent with local include directory +CFLAGS := $(CFLAGS_COMMON) -I$(INC_DIR) + +# Default target +all: $(TARGET) + +# Build the static library +$(TARGET): $(OBJECTS) + @mkdir -p $(dir $@) + @echo "[AR] Creating static library $@" + @$(AR) rcs $@ $(OBJECTS) + @$(RANLIB) $@ + @echo "Built lib$(LIB_NAME).a successfully" + +# Compile C source files +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(dir $@) + @echo "[CC] Compiling $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +# Clean build artifacts +clean: + @echo "Cleaning lib$(LIB_NAME)..." + @rm -rf $(BUILD_DIR) + @rm -f $(TARGET) + +# Install headers (copy to a common include location if needed) +install-headers: + @mkdir -p $(USERLAND_INCLUDE_DIR) + @cp -r $(INC_DIR)/* $(USERLAND_INCLUDE_DIR)/ + @echo "Installed $(LIB_NAME) headers" + +.PHONY: all clean install-headers diff --git a/userland/lib/libstlxgfx/include/stlxgfx/font.h b/userland/lib/libstlxgfx/include/stlxgfx/font.h new file mode 100644 index 0000000..8388823 --- /dev/null +++ b/userland/lib/libstlxgfx/include/stlxgfx/font.h @@ -0,0 +1,47 @@ +#ifndef STLXGFX_FONT_H +#define STLXGFX_FONT_H + +#include +#include + +typedef struct stlxgfx_context stlxgfx_context_t; + +typedef struct { + int ascent; + int descent; + int line_gap; +} stlxgfx_font_metrics_t; + +typedef struct { + int width; + int height; +} stlxgfx_text_size_t; + +/** + * Load a font file (Display Manager only) + * @param ctx - graphics context + * @param font_path - path to TTF font file + * @return 0 on success, negative on error + */ +int stlxgfx_dm_load_font(stlxgfx_context_t* ctx, const char* font_path); + +/** + * Get font vertical metrics (Display Manager) + * @param ctx - graphics context + * @param metrics - output font metrics + * @return 0 on success, negative on error + */ +int stlxgfx_dm_get_font_metrics(stlxgfx_context_t* ctx, stlxgfx_font_metrics_t* metrics); + +/** + * Calculate text size for given string and font size + * @param ctx - graphics context + * @param text - null-terminated string + * @param font_size - font size in pixels + * @param size - output text dimensions + * @return 0 on success, negative on error + */ +int stlxgfx_get_text_size(stlxgfx_context_t* ctx, const char* text, + int font_size, stlxgfx_text_size_t* size); + +#endif // STLXGFX_FONT_H \ No newline at end of file diff --git a/userland/apps/stlxdm/include/stb_truetype.h b/userland/lib/libstlxgfx/include/stlxgfx/internal/stb_truetype.h similarity index 100% rename from userland/apps/stlxdm/include/stb_truetype.h rename to userland/lib/libstlxgfx/include/stlxgfx/internal/stb_truetype.h diff --git a/userland/lib/libstlxgfx/include/stlxgfx/internal/stlxgfx_ctx.h b/userland/lib/libstlxgfx/include/stlxgfx/internal/stlxgfx_ctx.h new file mode 100644 index 0000000..fc858fd --- /dev/null +++ b/userland/lib/libstlxgfx/include/stlxgfx/internal/stlxgfx_ctx.h @@ -0,0 +1,27 @@ +#ifndef STLXGFX_CTX_H +#define STLXGFX_CTX_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#include +#pragma GCC diagnostic pop + +#include + +#define STLXGFX_PAGE_SIZE 0x1000 + +// Internal context structure definition +struct stlxgfx_context { + stlxgfx_mode_t mode; + int initialized; + + // Font management (DM mode only) + void* font_data; + size_t font_data_size; + stbtt_fontinfo font_info; + int font_loaded; +}; + +#endif // STLXGFX_CTX_H \ No newline at end of file diff --git a/userland/lib/libstlxgfx/include/stlxgfx/stlxgfx.h b/userland/lib/libstlxgfx/include/stlxgfx/stlxgfx.h new file mode 100644 index 0000000..62615bf --- /dev/null +++ b/userland/lib/libstlxgfx/include/stlxgfx/stlxgfx.h @@ -0,0 +1,47 @@ +#ifndef STLXGFX_H +#define STLXGFX_H + +#include + +// ========================= +// Library Version +// ========================= +#define STLXGFX_VERSION_MAJOR 0 +#define STLXGFX_VERSION_MINOR 1 +#define STLXGFX_VERSION_PATCH 0 + +// ========================= +// Core Types +// ========================= + +typedef enum { + STLXGFX_MODE_APPLICATION, + STLXGFX_MODE_DISPLAY_MANAGER +} stlxgfx_mode_t; + +typedef struct stlxgfx_context stlxgfx_context_t; + +// ========================= +// Library Initialization +// ========================= + +/** + * Initialize the graphics library + * @param mode - operation mode (application or display manager) + * @return context pointer or NULL on failure + */ +stlxgfx_context_t* stlxgfx_init(stlxgfx_mode_t mode); + +/** + * Clean up and free library resources + * @param ctx - context to cleanup + */ +void stlxgfx_cleanup(stlxgfx_context_t* ctx); + +// ========================= +// Component Headers +// ========================= +#include "stlxgfx/font.h" +#include "stlxgfx/surface.h" + +#endif // STLXGFX_H \ No newline at end of file diff --git a/userland/lib/libstlxgfx/include/stlxgfx/surface.h b/userland/lib/libstlxgfx/include/stlxgfx/surface.h new file mode 100644 index 0000000..9a5b829 --- /dev/null +++ b/userland/lib/libstlxgfx/include/stlxgfx/surface.h @@ -0,0 +1,159 @@ +#ifndef STLXGFX_SURFACE_H +#define STLXGFX_SURFACE_H + +#include +#include + +typedef struct stlxgfx_context stlxgfx_context_t; + +typedef enum { + STLXGFX_FORMAT_RGB24, // 24-bit RGB (R,G,B) + STLXGFX_FORMAT_BGR24, // 24-bit BGR (B,G,R) - common on GOP + STLXGFX_FORMAT_ARGB32, // 32-bit ARGB (A,R,G,B) + STLXGFX_FORMAT_BGRA32 // 32-bit BGRA (B,G,R,A) - common on GOP +} stlxgfx_pixel_format_t; + +typedef struct { + uint32_t width, height; + uint32_t pitch; // bytes per row + stlxgfx_pixel_format_t format; + uint8_t pixels[]; // flexible array member - pixel data follows +} stlxgfx_surface_t; + +/** + * Get bits per pixel for a pixel format + * @param format - pixel format + * @return bits per pixel, 0 on error + */ +uint8_t stlxgfx_get_bpp_for_format(stlxgfx_pixel_format_t format); + +/** + * Detect GOP pixel format from bits per pixel + * @param bpp - bits per pixel from GOP + * @return best matching pixel format + */ +stlxgfx_pixel_format_t stlxgfx_detect_gop_format(uint8_t bpp); + +/** + * Create a surface (Display Manager only) + * @param ctx - graphics context + * @param width - surface width in pixels + * @param height - surface height in pixels + * @param format - pixel format + * @return surface pointer or NULL on error + */ +stlxgfx_surface_t* stlxgfx_dm_create_surface(stlxgfx_context_t* ctx, + uint32_t width, uint32_t height, + stlxgfx_pixel_format_t format); + +/** + * Destroy a surface (Display Manager only) + * @param ctx - graphics context + * @param surface - surface to destroy + */ +void stlxgfx_dm_destroy_surface(stlxgfx_context_t* ctx, stlxgfx_surface_t* surface); + +// ========================= +// Drawing Primitives +// ========================= + +/** + * Draw a single pixel to surface + * @param surface - target surface + * @param x, y - pixel coordinates + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_draw_pixel(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, uint32_t color); + +/** + * Clear entire surface with solid color + * @param surface - target surface + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_clear_surface(stlxgfx_surface_t* surface, uint32_t color); + +/** + * Fill rectangle with solid color + * @param surface - target surface + * @param x, y - top-left corner + * @param width, height - rectangle dimensions + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_fill_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t color); + +/** + * Draw rectangle outline + * @param surface - target surface + * @param x, y - top-left corner + * @param width, height - rectangle dimensions + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_draw_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t color); + +/** + * Fill rounded rectangle with solid color + * @param surface - target surface + * @param x, y - top-left corner + * @param width, height - rectangle dimensions + * @param radius - corner radius in pixels + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_fill_rounded_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t radius, uint32_t color); + +/** + * Draw rounded rectangle outline + * @param surface - target surface + * @param x, y - top-left corner + * @param width, height - rectangle dimensions + * @param radius - corner radius in pixels + * @param color - color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_draw_rounded_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t radius, uint32_t color); + +/** + * Render text to surface using loaded font + * @param ctx - graphics context (must have font loaded) + * @param surface - target surface + * @param text - null-terminated string + * @param x, y - text position (baseline) + * @param font_size - font size in pixels + * @param color - text color in 0xAARRGGBB format + * @return 0 on success, negative on error + */ +int stlxgfx_render_text(stlxgfx_context_t* ctx, stlxgfx_surface_t* surface, + const char* text, uint32_t x, uint32_t y, + uint32_t font_size, uint32_t color); + +/** + * Blit (copy) one surface to another with format conversion + * @param src - source surface + * @param src_x, src_y - source position + * @param dst - destination surface + * @param dst_x, dst_y - destination position + * @param width, height - copy dimensions + * @return 0 on success, negative on error + */ +int stlxgfx_blit_surface(stlxgfx_surface_t* src, uint32_t src_x, uint32_t src_y, + stlxgfx_surface_t* dst, uint32_t dst_x, uint32_t dst_y, + uint32_t width, uint32_t height); + +/** + * Blit surface directly to memory buffer (same format/dimensions assumed) + * @param surface - source surface + * @param buffer - destination buffer + * @param buffer_pitch - destination buffer pitch in bytes + * @return 0 on success, negative on error + */ +int stlxgfx_blit_surface_to_buffer(stlxgfx_surface_t* surface, uint8_t* buffer, uint32_t buffer_pitch); + +#endif // STLXGFX_SURFACE_H \ No newline at end of file diff --git a/userland/lib/libstlxgfx/src/font.c b/userland/lib/libstlxgfx/src/font.c new file mode 100644 index 0000000..9fc4193 --- /dev/null +++ b/userland/lib/libstlxgfx/src/font.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int stlxgfx_dm_load_font(stlxgfx_context_t* ctx, const char* font_path) { + if (!ctx || !ctx->initialized || ctx->mode != STLXGFX_MODE_DISPLAY_MANAGER) { + printf("STLXGFX: Font loading only available in Display Manager mode\n"); + return -1; + } + + if (!font_path) { + printf("STLXGFX: Invalid font path\n"); + return -1; + } + + // Open font file + int fd = open(font_path, O_RDONLY); + if (fd < 0) { + printf("STLXGFX: Failed to open font file: %s (errno: %d - %s)\n", + font_path, errno, strerror(errno)); + return -1; + } + + // Get file size + off_t file_size = lseek(fd, 0, SEEK_END); + if (file_size < 0) { + printf("STLXGFX: Failed to get font file size (errno: %d - %s)\n", + errno, strerror(errno)); + close(fd); + return -1; + } + + if (lseek(fd, 0, SEEK_SET) < 0) { + printf("STLXGFX: Failed to seek to beginning of font file (errno: %d - %s)\n", + errno, strerror(errno)); + close(fd); + return -1; + } + + // Allocate memory for font data + void* font_data = malloc(file_size); + if (!font_data) { + printf("STLXGFX: Failed to allocate %ld bytes for font data\n", file_size); + close(fd); + return -1; + } + + // Read font file + ssize_t total_read = 0; + char* buffer = (char*)font_data; + + while (total_read < file_size) { + ssize_t bytes_read = read(fd, buffer + total_read, file_size - total_read); + if (bytes_read < 0) { + printf("STLXGFX: Failed to read from font file (errno: %d - %s)\n", + errno, strerror(errno)); + free(font_data); + close(fd); + return -1; + } + if (bytes_read == 0) break; + total_read += bytes_read; + } + + close(fd); + + // Basic TTF validation + if (total_read >= 4) { + uint8_t* data = (uint8_t*)font_data; + if (!((data[0] == 0x00 && data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x00) || + (data[0] == 'O' && data[1] == 'T' && data[2] == 'T' && data[3] == 'O'))) { + printf("STLXGFX: WARNING - Font file may not be a valid TTF/OpenType font\n"); + } + } + + // Initialize STB TrueType + if (!stbtt_InitFont(&ctx->font_info, font_data, 0)) { + printf("STLXGFX: Failed to initialize STB TrueType font\n"); + free(font_data); + return -1; + } + + // Clean up old font data if any + if (ctx->font_data) { + free(ctx->font_data); + } + + // Store font data + ctx->font_data = font_data; + ctx->font_data_size = total_read; + ctx->font_loaded = 1; + + return 0; +} + +int stlxgfx_dm_get_font_metrics(stlxgfx_context_t* ctx, stlxgfx_font_metrics_t* metrics) { + if (!ctx || !ctx->initialized || ctx->mode != STLXGFX_MODE_DISPLAY_MANAGER) { + return -1; + } + + if (!ctx->font_loaded || !metrics) { + return -1; + } + + stbtt_GetFontVMetrics(&ctx->font_info, &metrics->ascent, &metrics->descent, &metrics->line_gap); + + printf("STLXGFX: Font metrics - ascent: %d, descent: %d, line_gap: %d\n", + metrics->ascent, metrics->descent, metrics->line_gap); + + return 0; +} + +int stlxgfx_get_text_size(stlxgfx_context_t* ctx, const char* text, + int font_size, stlxgfx_text_size_t* size) { + if (!ctx || !ctx->initialized || ctx->mode != STLXGFX_MODE_DISPLAY_MANAGER) { + return -1; + } + + if (!ctx->font_loaded || !text || !size) { + return -1; + } + + float scale = stbtt_ScaleForPixelHeight(&ctx->font_info, font_size); + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&ctx->font_info, &ascent, &descent, &line_gap); + + size->height = (int)((ascent - descent) * scale); + size->width = 0; + + // Calculate total width + for (int i = 0; text[i]; i++) { + int advance, lsb; + stbtt_GetCodepointHMetrics(&ctx->font_info, text[i], &advance, &lsb); + size->width += (int)(advance * scale); + } + + printf("STLXGFX: Text '%s' size at %dpx: %dx%d\n", text, font_size, size->width, size->height); + + return 0; +} + diff --git a/userland/lib/libstlxgfx/src/stlxgfx.c b/userland/lib/libstlxgfx/src/stlxgfx.c new file mode 100644 index 0000000..8638659 --- /dev/null +++ b/userland/lib/libstlxgfx/src/stlxgfx.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include "stlxgfx/stlxgfx.h" + +// Define STB TrueType implementation before including context header +#define STB_TRUETYPE_IMPLEMENTATION +#include + +stlxgfx_context_t* stlxgfx_init(stlxgfx_mode_t mode) { + stlxgfx_context_t* ctx = malloc(sizeof(stlxgfx_context_t)); + if (!ctx) { + return NULL; + } + + memset(ctx, 0, sizeof(stlxgfx_context_t)); + ctx->mode = mode; + ctx->initialized = 1; + + printf("STLXGFX: Initialized in %s mode\n", + mode == STLXGFX_MODE_DISPLAY_MANAGER ? "Display Manager" : "Application"); + + return ctx; +} + +void stlxgfx_cleanup(stlxgfx_context_t* ctx) { + if (!ctx) return; + + printf("STLXGFX: Cleaning up context\n"); + + // Clean up font data + if (ctx->font_data) { + free(ctx->font_data); + } + + ctx->initialized = 0; + free(ctx); +} diff --git a/userland/lib/libstlxgfx/src/surface.c b/userland/lib/libstlxgfx/src/surface.c new file mode 100644 index 0000000..1e1d159 --- /dev/null +++ b/userland/lib/libstlxgfx/src/surface.c @@ -0,0 +1,499 @@ +#include +#include +#include +#include "stlxgfx/surface.h" +#include "stlxgfx/internal/stlxgfx_ctx.h" + +static inline void write_pixel_to_buffer(uint8_t* pixel, stlxgfx_pixel_format_t format, uint32_t color) { + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + uint8_t a = (color >> 24) & 0xFF; + + switch (format) { + case STLXGFX_FORMAT_RGB24: + pixel[0] = r; pixel[1] = g; pixel[2] = b; + break; + case STLXGFX_FORMAT_BGR24: + pixel[0] = b; pixel[1] = g; pixel[2] = r; + break; + case STLXGFX_FORMAT_ARGB32: + pixel[0] = a; pixel[1] = r; pixel[2] = g; pixel[3] = b; + break; + case STLXGFX_FORMAT_BGRA32: + pixel[0] = b; pixel[1] = g; pixel[2] = r; pixel[3] = a; + break; + } +} + +static inline uint32_t read_pixel_from_buffer(const uint8_t* pixel, stlxgfx_pixel_format_t format) { + switch (format) { + case STLXGFX_FORMAT_RGB24: + return 0xFF000000 | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; + case STLXGFX_FORMAT_BGR24: + return 0xFF000000 | (pixel[2] << 16) | (pixel[1] << 8) | pixel[0]; + case STLXGFX_FORMAT_ARGB32: + return (pixel[0] << 24) | (pixel[1] << 16) | (pixel[2] << 8) | pixel[3]; + case STLXGFX_FORMAT_BGRA32: + return (pixel[3] << 24) | (pixel[2] << 16) | (pixel[1] << 8) | pixel[0]; + } + return 0; +} + +static inline void alpha_blend_pixel(uint8_t* dst_pixel, stlxgfx_pixel_format_t format, uint32_t src_color) { + uint32_t dst_color = read_pixel_from_buffer(dst_pixel, format); + + uint8_t src_a = (src_color >> 24) & 0xFF; + if (src_a == 0) return; // Fully transparent + if (src_a == 255) { // Fully opaque + write_pixel_to_buffer(dst_pixel, format, src_color); + return; + } + + // Alpha blending: dst = src * src_a + dst * (1 - src_a) + uint8_t src_r = (src_color >> 16) & 0xFF; + uint8_t src_g = (src_color >> 8) & 0xFF; + uint8_t src_b = src_color & 0xFF; + + uint8_t dst_r = (dst_color >> 16) & 0xFF; + uint8_t dst_g = (dst_color >> 8) & 0xFF; + uint8_t dst_b = dst_color & 0xFF; + uint8_t dst_a = (dst_color >> 24) & 0xFF; + + uint8_t inv_src_a = 255 - src_a; + uint8_t final_r = (src_r * src_a + dst_r * inv_src_a) / 255; + uint8_t final_g = (src_g * src_a + dst_g * inv_src_a) / 255; + uint8_t final_b = (src_b * src_a + dst_b * inv_src_a) / 255; + uint8_t final_a = src_a + (dst_a * inv_src_a) / 255; + + uint32_t final_color = (final_a << 24) | (final_r << 16) | (final_g << 8) | final_b; + write_pixel_to_buffer(dst_pixel, format, final_color); +} + +uint8_t stlxgfx_get_bpp_for_format(stlxgfx_pixel_format_t format) { + switch (format) { + case STLXGFX_FORMAT_RGB24: + case STLXGFX_FORMAT_BGR24: + return 24; + case STLXGFX_FORMAT_ARGB32: + case STLXGFX_FORMAT_BGRA32: + return 32; + default: + return 0; // error + } +} + +stlxgfx_pixel_format_t stlxgfx_detect_gop_format(uint8_t bpp) { + switch (bpp) { + case 24: + return STLXGFX_FORMAT_BGR24; // Common on QEMU + case 32: + return STLXGFX_FORMAT_BGRA32; // Common on real hardware + default: + printf("STLXGFX: Unknown GOP BPP %u, defaulting to BGRA32\n", bpp); + return STLXGFX_FORMAT_BGRA32; // Safe default + } +} + +stlxgfx_surface_t* stlxgfx_dm_create_surface(stlxgfx_context_t* ctx, + uint32_t width, uint32_t height, + stlxgfx_pixel_format_t format) { + if (!ctx || !ctx->initialized || ctx->mode != STLXGFX_MODE_DISPLAY_MANAGER) { + printf("STLXGFX: Surface creation only available in Display Manager mode\n"); + return NULL; + } + + if (width == 0 || height == 0) { + printf("STLXGFX: Invalid surface dimensions %ux%u\n", width, height); + return NULL; + } + + uint8_t bpp = stlxgfx_get_bpp_for_format(format); + if (bpp == 0) { + printf("STLXGFX: Invalid pixel format %d\n", format); + return NULL; + } + + // Calculate memory requirements + uint32_t pitch = width * (bpp / 8); + size_t pixel_data_size = height * pitch; + size_t total_size = sizeof(stlxgfx_surface_t) + pixel_data_size; + + // Round up to page boundary + const size_t page_size = STLXGFX_PAGE_SIZE; + size_t aligned_size = (total_size + page_size - 1) & ~(page_size - 1); + + // Allocate page-aligned memory + stlxgfx_surface_t* surface = malloc(aligned_size); + if (!surface) { + printf("STLXGFX: Failed to allocate %zu bytes for surface\n", aligned_size); + return NULL; + } + + // Initialize surface metadata + surface->width = width; + surface->height = height; + surface->pitch = pitch; + surface->format = format; + + // Clear pixel data to black + memset(surface->pixels, 0, pixel_data_size); + + printf("STLXGFX: Created surface %ux%u (%u BPP, pitch=%u, %zu bytes, aligned to %zu)\n", + width, height, bpp, pitch, pixel_data_size, aligned_size); + + return surface; +} + +void stlxgfx_dm_destroy_surface(stlxgfx_context_t* ctx, stlxgfx_surface_t* surface) { + if (!ctx || !surface) { + return; + } + + if (ctx->mode != STLXGFX_MODE_DISPLAY_MANAGER) { + printf("STLXGFX: Surface destruction only available in Display Manager mode\n"); + return; + } + + printf("STLXGFX: Destroying surface %ux%u\n", surface->width, surface->height); + free(surface); +} + +int stlxgfx_draw_pixel(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, uint32_t color) { + if (!surface) { + return -1; + } + + if (x >= surface->width || y >= surface->height) { + return -1; // Out of bounds + } + + uint8_t bytes_per_pixel = stlxgfx_get_bpp_for_format(surface->format) / 8; + uint8_t* pixel = surface->pixels + (y * surface->pitch) + (x * bytes_per_pixel); + + write_pixel_to_buffer(pixel, surface->format, color); + return 0; +} + +int stlxgfx_clear_surface(stlxgfx_surface_t* surface, uint32_t color) { + if (!surface) { + return -1; + } + + uint8_t bytes_per_pixel = stlxgfx_get_bpp_for_format(surface->format) / 8; + + // Fast path for solid colors without alpha + if ((color & 0xFF000000) == 0xFF000000) { + // Check if we can use memset (all bytes same) + uint8_t test_pixel[4] = { 0 }; + write_pixel_to_buffer(test_pixel, surface->format, color); + + int can_memset = 1; + for (int i = 1; i < bytes_per_pixel; i++) { + if (test_pixel[i] != test_pixel[0]) { + can_memset = 0; + break; + } + } + + if (can_memset) { + size_t total_bytes = surface->height * surface->pitch; + memset(surface->pixels, test_pixel[0], total_bytes); + return 0; + } + } + + // General case: fill pixel by pixel + for (uint32_t y = 0; y < surface->height; y++) { + uint8_t* row = surface->pixels + (y * surface->pitch); + for (uint32_t x = 0; x < surface->width; x++) { + write_pixel_to_buffer(row + (x * bytes_per_pixel), surface->format, color); + } + } + + return 0; +} + +int stlxgfx_fill_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t color) { + if (!surface) { + return -1; + } + + // Clip rectangle to surface bounds + if (x >= surface->width || y >= surface->height) { + return 0; // Completely outside + } + + uint32_t end_x = x + width; + uint32_t end_y = y + height; + + if (end_x > surface->width) end_x = surface->width; + if (end_y > surface->height) end_y = surface->height; + + width = end_x - x; + height = end_y - y; + + if (width == 0 || height == 0) { + return 0; // Nothing to draw + } + + uint8_t bytes_per_pixel = stlxgfx_get_bpp_for_format(surface->format) / 8; + + // Fill row by row + for (uint32_t row = y; row < end_y; row++) { + uint8_t* row_start = surface->pixels + (row * surface->pitch) + (x * bytes_per_pixel); + for (uint32_t col = 0; col < width; col++) { + write_pixel_to_buffer(row_start + (col * bytes_per_pixel), surface->format, color); + } + } + + return 0; +} + +int stlxgfx_draw_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t color) { + if (!surface || width == 0 || height == 0) { + return -1; + } + + // Draw four edges + if (stlxgfx_fill_rect(surface, x, y, width, 1, color) != 0) return -1; // Top + if (height > 1) { + if (stlxgfx_fill_rect(surface, x, y + height - 1, width, 1, color) != 0) return -1; // Bottom + } + if (height > 2) { + if (stlxgfx_fill_rect(surface, x, y + 1, 1, height - 2, color) != 0) return -1; // Left + if (width > 1) { + if (stlxgfx_fill_rect(surface, x + width - 1, y + 1, 1, height - 2, color) != 0) return -1; // Right + } + } + + return 0; +} + +int stlxgfx_fill_rounded_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t radius, uint32_t color) { + if (!surface || width == 0 || height == 0) { + return -1; + } + + if (radius == 0) { + return stlxgfx_fill_rect(surface, x, y, width, height, color); + } + + // Clamp radius to reasonable bounds + uint32_t max_radius = (width < height ? width : height) / 2; + if (radius > max_radius) radius = max_radius; + + // Fill the main rectangular areas + if (stlxgfx_fill_rect(surface, x + radius, y, width - 2 * radius, height, color) != 0) return -1; + if (stlxgfx_fill_rect(surface, x, y + radius, radius, height - 2 * radius, color) != 0) return -1; + if (stlxgfx_fill_rect(surface, x + width - radius, y + radius, radius, height - 2 * radius, color) != 0) return -1; + + // Fill rounded corners using circle algorithm + int r2 = radius * radius; + for (uint32_t dy = 0; dy < radius; dy++) { + for (uint32_t dx = 0; dx < radius; dx++) { + int dist2 = dx * dx + dy * dy; + if (dist2 <= r2) { + // Fill all four corners + stlxgfx_draw_pixel(surface, x + radius - 1 - dx, y + radius - 1 - dy, color); // Top-left + stlxgfx_draw_pixel(surface, x + width - radius + dx, y + radius - 1 - dy, color); // Top-right + stlxgfx_draw_pixel(surface, x + radius - 1 - dx, y + height - radius + dy, color); // Bottom-left + stlxgfx_draw_pixel(surface, x + width - radius + dx, y + height - radius + dy, color); // Bottom-right + } + } + } + + return 0; +} + +int stlxgfx_draw_rounded_rect(stlxgfx_surface_t* surface, uint32_t x, uint32_t y, + uint32_t width, uint32_t height, uint32_t radius, uint32_t color) { + if (!surface || width == 0 || height == 0) { + return -1; + } + + if (radius == 0) { + return stlxgfx_draw_rect(surface, x, y, width, height, color); + } + + // Clamp radius to reasonable bounds + uint32_t max_radius = (width < height ? width : height) / 2; + if (radius > max_radius) radius = max_radius; + + // Draw the straight edges + if (stlxgfx_fill_rect(surface, x + radius, y, width - 2 * radius, 1, color) != 0) return -1; // Top + if (stlxgfx_fill_rect(surface, x + radius, y + height - 1, width - 2 * radius, 1, color) != 0) return -1; // Bottom + if (stlxgfx_fill_rect(surface, x, y + radius, 1, height - 2 * radius, color) != 0) return -1; // Left + if (stlxgfx_fill_rect(surface, x + width - 1, y + radius, 1, height - 2 * radius, color) != 0) return -1; // Right + + // Draw rounded corners using circle outline algorithm + int r2_outer = radius * radius; + int r2_inner = (radius - 1) * (radius - 1); + + for (uint32_t dy = 0; dy < radius; dy++) { + for (uint32_t dx = 0; dx < radius; dx++) { + int dist2 = dx * dx + dy * dy; + if (dist2 <= r2_outer && dist2 > r2_inner) { + // Draw all four corners + stlxgfx_draw_pixel(surface, x + radius - 1 - dx, y + radius - 1 - dy, color); // Top-left + stlxgfx_draw_pixel(surface, x + width - radius + dx, y + radius - 1 - dy, color); // Top-right + stlxgfx_draw_pixel(surface, x + radius - 1 - dx, y + height - radius + dy, color); // Bottom-left + stlxgfx_draw_pixel(surface, x + width - radius + dx, y + height - radius + dy, color); // Bottom-right + } + } + } + + return 0; +} + +int stlxgfx_render_text(stlxgfx_context_t* ctx, stlxgfx_surface_t* surface, + const char* text, uint32_t x, uint32_t y, + uint32_t font_size, uint32_t color) { + if (!ctx || !ctx->initialized || !ctx->font_loaded || !surface || !text) { + return -1; + } + + float scale = stbtt_ScaleForPixelHeight(&ctx->font_info, font_size); + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&ctx->font_info, &ascent, &descent, &line_gap); + + int baseline_y = y + (int)(ascent * scale); + int current_x = x; + + for (int i = 0; text[i]; i++) { + int codepoint = text[i]; + + // Get character metrics + int advance, lsb; + stbtt_GetCodepointHMetrics(&ctx->font_info, codepoint, &advance, &lsb); + + // Get character bitmap + int char_width, char_height, xoff, yoff; + unsigned char* bitmap = stbtt_GetCodepointBitmap(&ctx->font_info, scale, scale, codepoint, + &char_width, &char_height, &xoff, &yoff); + + if (bitmap && char_width > 0 && char_height > 0) { + // Draw character to surface + int char_x = current_x + (int)(lsb * scale) + xoff; + int char_y = baseline_y + yoff; + + for (int py = 0; py < char_height; py++) { + for (int px = 0; px < char_width; px++) { + int surface_x = char_x + px; + int surface_y = char_y + py; + + if (surface_x >= 0 && surface_x < (int)surface->width && + surface_y >= 0 && surface_y < (int)surface->height) { + + unsigned char alpha = bitmap[py * char_width + px]; + if (alpha > 0) { + // Create color with alpha + uint32_t text_color = (color & 0x00FFFFFF) | ((uint32_t)alpha << 24); + + uint8_t bytes_per_pixel = stlxgfx_get_bpp_for_format(surface->format) / 8; + uint8_t* pixel = surface->pixels + (surface_y * surface->pitch) + (surface_x * bytes_per_pixel); + + alpha_blend_pixel(pixel, surface->format, text_color); + } + } + } + } + + stbtt_FreeBitmap(bitmap, NULL); + } + + // Advance to next character + current_x += (int)(advance * scale); + } + + return 0; +} + +int stlxgfx_blit_surface(stlxgfx_surface_t* src, uint32_t src_x, uint32_t src_y, + stlxgfx_surface_t* dst, uint32_t dst_x, uint32_t dst_y, + uint32_t width, uint32_t height) { + if (!src || !dst) { + return -1; + } + + // Clip to source bounds + if (src_x >= src->width || src_y >= src->height) { + return 0; // Completely outside source + } + + uint32_t src_end_x = src_x + width; + uint32_t src_end_y = src_y + height; + + if (src_end_x > src->width) { + width = src->width - src_x; + src_end_x = src->width; + } + if (src_end_y > src->height) { + height = src->height - src_y; + src_end_y = src->height; + } + + // Clip to destination bounds + if (dst_x >= dst->width || dst_y >= dst->height) { + return 0; // Completely outside destination + } + + uint32_t dst_end_x = dst_x + width; + uint32_t dst_end_y = dst_y + height; + + if (dst_end_x > dst->width) { + width = dst->width - dst_x; + dst_end_x = dst->width; + } + if (dst_end_y > dst->height) { + height = dst->height - dst_y; + dst_end_y = dst->height; + } + + if (width == 0 || height == 0) { + return 0; // Nothing to copy + } + + uint8_t src_bpp = stlxgfx_get_bpp_for_format(src->format) / 8; + uint8_t dst_bpp = stlxgfx_get_bpp_for_format(dst->format) / 8; + + // Fast path: same format + if (src->format == dst->format) { + for (uint32_t y = 0; y < height; y++) { + uint8_t* src_row = src->pixels + ((src_y + y) * src->pitch) + (src_x * src_bpp); + uint8_t* dst_row = dst->pixels + ((dst_y + y) * dst->pitch) + (dst_x * dst_bpp); + memcpy(dst_row, src_row, width * src_bpp); + } + } else { + // Format conversion required + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + uint8_t* src_pixel = src->pixels + ((src_y + y) * src->pitch) + ((src_x + x) * src_bpp); + uint8_t* dst_pixel = dst->pixels + ((dst_y + y) * dst->pitch) + ((dst_x + x) * dst_bpp); + + uint32_t color = read_pixel_from_buffer(src_pixel, src->format); + write_pixel_to_buffer(dst_pixel, dst->format, color); + } + } + } + + return 0; +} + +int stlxgfx_blit_surface_to_buffer(stlxgfx_surface_t* surface, uint8_t* buffer, uint32_t buffer_pitch) { + if (!surface || !buffer) { + return -1; + } + + // Copy row by row + for (uint32_t y = 0; y < surface->height; y++) { + uint8_t* src_row = surface->pixels + (y * surface->pitch); + uint8_t* dst_row = buffer + (y * buffer_pitch); + memcpy(dst_row, src_row, surface->pitch); + } + + return 0; +} \ No newline at end of file From 5475fe54171e501e4d46add343ef140867c357f1 Mon Sep 17 00:00:00 2001 From: FlareCoding Date: Mon, 23 Jun 2025 00:15:50 -0700 Subject: [PATCH 02/28] made huge progress on the display manager and implemented kernel support for unix domain sockets using SOCK_STREAM functionality --- kernel/include/net/unix_socket.h | 195 +++++++++++ kernel/include/net/unix_socket_buffer.h | 131 ++++++++ kernel/include/net/unix_socket_manager.h | 119 +++++++ kernel/include/process/process_env.h | 1 + kernel/include/syscall/handlers/sys_net.h | 13 + kernel/include/syscall/handlers/sys_time.h | 9 + kernel/include/syscall/syscalls.h | 40 ++- kernel/src/boot/init.cpp | 8 + kernel/src/net/unix_socket.cpp | 369 +++++++++++++++++++++ kernel/src/net/unix_socket_buffer.cpp | 195 +++++++++++ kernel/src/net/unix_socket_manager.cpp | 204 ++++++++++++ kernel/src/syscall/handlers/sys_io.cpp | 80 ++++- kernel/src/syscall/handlers/sys_net.cpp | 251 ++++++++++++++ kernel/src/syscall/handlers/sys_time.cpp | 86 +++++ kernel/src/syscall/syscalls.cpp | 8 + userland/apps/shell/src/shell.c | 35 +- userland/apps/stlxdm/src/stlxdm.c | 213 +++++++----- userland/lib/libstlxgfx/src/font.c | 2 - 18 files changed, 1861 insertions(+), 98 deletions(-) create mode 100644 kernel/include/net/unix_socket.h create mode 100644 kernel/include/net/unix_socket_buffer.h create mode 100644 kernel/include/net/unix_socket_manager.h create mode 100644 kernel/include/syscall/handlers/sys_net.h create mode 100644 kernel/include/syscall/handlers/sys_time.h create mode 100644 kernel/src/net/unix_socket.cpp create mode 100644 kernel/src/net/unix_socket_buffer.cpp create mode 100644 kernel/src/net/unix_socket_manager.cpp create mode 100644 kernel/src/syscall/handlers/sys_net.cpp create mode 100644 kernel/src/syscall/handlers/sys_time.cpp diff --git a/kernel/include/net/unix_socket.h b/kernel/include/net/unix_socket.h new file mode 100644 index 0000000..01cfc0a --- /dev/null +++ b/kernel/include/net/unix_socket.h @@ -0,0 +1,195 @@ +#ifndef UNIX_SOCKET_H +#define UNIX_SOCKET_H + +#include +#include +#include +#include +#include +#include + +// Uncomment the line below to enable verbose Unix socket debugging +// #define STELLUX_UNIX_SOCKET_DEBUG + +#ifdef STELLUX_UNIX_SOCKET_DEBUG +#include +#define UNIX_SOCKET_TRACE(...) kprint(__VA_ARGS__) +#else +#define UNIX_SOCKET_TRACE(...) do { } while(0) +#endif + +namespace net { + +/** + * @enum unix_socket_state + * @brief Represents the state of a Unix socket. + */ +enum class unix_socket_state : uint32_t { + INVALID = 0, // Socket is invalid/uninitialized + CREATED, // Socket created but not bound/connected + BOUND, // Server socket bound to path + LISTENING, // Server socket listening for connections + CONNECTING, // Client socket attempting to connect + CONNECTED, // Connected socket (client or accepted connection) + DISCONNECTED, // Socket disconnected but not closed + CLOSED // Socket closed and resources freed +}; + +/** + * @class unix_stream_socket + * @brief Unix domain stream socket implementation. + * + * Provides reliable, ordered, connection-based communication between processes + * on the same machine using filesystem paths as addresses. + */ +class unix_stream_socket { +public: + /** + * @brief Constructs a new Unix stream socket. + */ + unix_stream_socket(); + + /** + * @brief Destructor - cleans up socket resources. + */ + ~unix_stream_socket(); + + // Non-copyable, non-movable for thread safety + unix_stream_socket(const unix_stream_socket&) = delete; + unix_stream_socket& operator=(const unix_stream_socket&) = delete; + unix_stream_socket(unix_stream_socket&&) = delete; + unix_stream_socket& operator=(unix_stream_socket&&) = delete; + + /** + * @brief Binds the socket to a filesystem path. + * + * Creates a socket file at the specified path that clients can connect to. + * Only server sockets need to bind to a path. + * + * @param path Filesystem path to bind to (e.g., "/tmp/my_socket") + * @return 0 on success, negative error code on failure + */ + int bind(const kstl::string& path); + + /** + * @brief Puts the socket into listening mode. + * + * Marks the socket as passive, ready to accept incoming connections. + * Must be called after bind() and before accept(). + * + * @param backlog Maximum number of pending connections (default: 5) + * @return 0 on success, negative error code on failure + */ + int listen(int backlog = 5); + + /** + * @brief Accepts an incoming connection (blocking). + * + * Blocks until a client connects, then returns a new socket for that connection. + * The original socket remains in listening state for more connections. + * + * @return Shared pointer to new socket for the connection, or nullptr on error + */ + kstl::shared_ptr accept(); + + /** + * @brief Connects to a server socket (blocking). + * + * Attempts to establish a connection to a server socket at the given path. + * Blocks until connection is established or fails. + * + * @param path Path to connect to + * @return 0 on success, negative error code on failure + */ + int connect(const kstl::string& path); + + /** + * @brief Reads data from the socket (blocking). + * + * Blocks until data is available or the connection is closed. + * For stream sockets, may return fewer bytes than requested. + * + * @param buffer Buffer to read data into + * @param size Maximum number of bytes to read + * @return Number of bytes read, 0 for EOF, negative for error + */ + ssize_t read(void* buffer, size_t size); + + /** + * @brief Writes data to the socket. + * + * Attempts to write data to the connected peer. May write fewer bytes + * than requested if the peer's receive buffer is full. + * + * @param data Data to write + * @param size Number of bytes to write + * @return Number of bytes written, negative for error + */ + ssize_t write(const void* data, size_t size); + + /** + * @brief Closes the socket. + * + * Closes the socket and releases all associated resources. + * Any pending operations will be interrupted. + * + * @return 0 on success, negative error code on failure + */ + int close(); + + /** + * @brief Registers this socket with the manager (for server sockets). + * + * Must be called after bind() for server sockets to make them discoverable. + * This is a separate step to avoid shared_ptr issues in bind(). + * + * @param self Shared pointer to this socket instance + * @return 0 on success, negative error code on failure + */ + int register_with_manager(kstl::shared_ptr self); + + // State and property getters + unix_socket_state get_state() const { return m_state.load(); } + const kstl::string& get_path() const { return m_path; } + bool is_server() const { return m_is_server; } + bool is_connected() const { return get_state() == unix_socket_state::CONNECTED; } + bool is_listening() const { return get_state() == unix_socket_state::LISTENING; } + +private: + // Core socket state + atomic m_state = atomic(unix_socket_state::CREATED); + kstl::string m_path; // Bound path (for server sockets) + bool m_is_server = false; // True if this is a server socket + int m_backlog = 0; // Listen backlog size + + // Connection management + kstl::shared_ptr m_peer; // Connected peer socket + kstl::vector> m_pending_connections; // Pending accept queue + + // Data buffers + kstl::shared_ptr m_recv_buffer; // Incoming data buffer + kstl::shared_ptr m_send_buffer; // Outgoing data buffer + + // Synchronization + mutable mutex m_socket_lock = mutex(); // Protects socket state + mutex m_accept_lock = mutex(); // Protects pending connections + + // Private helper methods + void _change_state(unix_socket_state new_state); + bool _can_accept() const; + bool _can_read() const; + bool _can_write() const; + void _cleanup_resources(); + void _setup_buffers(); + int _add_pending_connection(kstl::shared_ptr client); + kstl::shared_ptr _get_pending_connection(); + void _set_peer(kstl::shared_ptr peer); + + // Allow manager to access private members for connection setup + friend class unix_socket_manager; +}; + +} // namespace net + +#endif // UNIX_SOCKET_H + diff --git a/kernel/include/net/unix_socket_buffer.h b/kernel/include/net/unix_socket_buffer.h new file mode 100644 index 0000000..44a1ef6 --- /dev/null +++ b/kernel/include/net/unix_socket_buffer.h @@ -0,0 +1,131 @@ +#ifndef UNIX_SOCKET_BUFFER_H +#define UNIX_SOCKET_BUFFER_H + +#include +#include +#include + +namespace net { + +/** + * @class unix_socket_buffer + * @brief Thread-safe circular buffer optimized for Unix stream socket data transfer. + * + * This buffer provides efficient, thread-safe data transfer between socket endpoints. + * It uses a fixed-size circular buffer with atomic operations for size tracking + * and mutex protection for buffer operations. + */ +class unix_socket_buffer { +public: + static constexpr size_t DEFAULT_BUFFER_SIZE = 8192; // 8KB default + + /** + * @brief Constructs a socket buffer with specified capacity. + * @param capacity Buffer size in bytes (must be > 0) + */ + explicit unix_socket_buffer(size_t capacity = DEFAULT_BUFFER_SIZE); + + /** + * @brief Destructor - frees allocated buffer memory. + */ + ~unix_socket_buffer(); + + // Non-copyable, non-movable for thread safety + unix_socket_buffer(const unix_socket_buffer&) = delete; + unix_socket_buffer& operator=(const unix_socket_buffer&) = delete; + unix_socket_buffer(unix_socket_buffer&&) = delete; + unix_socket_buffer& operator=(unix_socket_buffer&&) = delete; + + /** + * @brief Writes data to the buffer (non-blocking). + * + * Attempts to write as much data as possible to the buffer without blocking. + * If the buffer is full, only partial data may be written. + * + * @param data Pointer to data to write + * @param size Number of bytes to write + * @return Number of bytes actually written (0 if buffer is full) + */ + size_t write(const void* data, size_t size); + + /** + * @brief Reads data from the buffer (non-blocking). + * + * Attempts to read as much data as available from the buffer without blocking. + * If the buffer is empty, returns 0. + * + * @param buffer Buffer to read data into + * @param size Maximum number of bytes to read + * @return Number of bytes actually read (0 if buffer is empty) + */ + size_t read(void* buffer, size_t size); + + /** + * @brief Checks if data is available for reading. + * @return True if at least one byte is available for reading + */ + bool has_data() const; + + /** + * @brief Checks if buffer has space for writing. + * @return True if at least one byte of space is available + */ + bool has_space() const; + + /** + * @brief Gets the number of bytes available for reading. + * @return Number of bytes that can be read + */ + size_t available_bytes() const; + + /** + * @brief Gets the number of bytes available for writing. + * @return Number of bytes that can be written + */ + size_t free_space() const; + + /** + * @brief Gets the total capacity of the buffer. + * @return Buffer capacity in bytes + */ + size_t capacity() const { return m_capacity; } + + /** + * @brief Clears all data from the buffer. + * + * Resets the buffer to empty state. This operation is thread-safe. + */ + void clear(); + +private: + uint8_t* m_buffer; // Circular buffer memory + size_t m_capacity; // Total buffer capacity + size_t m_head; // Write position (producer index) + size_t m_tail; // Read position (consumer index) + atomic m_size; // Current number of bytes in buffer + mutable mutex m_lock = mutex(); // Protects buffer operations + + /** + * @brief Calculates the next index in the circular buffer. + * @param index Current index + * @return Next index, wrapping around if necessary + */ + size_t _next_index(size_t index) const; + + /** + * @brief Calculates available contiguous write space from current head. + * @return Number of contiguous bytes that can be written + */ + size_t _contiguous_write_space() const; + + /** + * @brief Calculates available contiguous read space from current tail. + * @return Number of contiguous bytes that can be read + */ + size_t _contiguous_read_space() const; +}; + +} // namespace net + +#endif // UNIX_SOCKET_BUFFER_H + diff --git a/kernel/include/net/unix_socket_manager.h b/kernel/include/net/unix_socket_manager.h new file mode 100644 index 0000000..6e6775a --- /dev/null +++ b/kernel/include/net/unix_socket_manager.h @@ -0,0 +1,119 @@ +#ifndef UNIX_SOCKET_MANAGER_H +#define UNIX_SOCKET_MANAGER_H + +#include +#include +#include +#include + +namespace net { +/** + * @class unix_socket_manager + * @brief Manages Unix domain sockets and their filesystem bindings. + * + * Singleton class that handles socket registration, lookup, and connection + * establishment between client and server sockets. Provides a centralized + * registry for all Unix domain sockets in the system. + */ +class unix_socket_manager { +public: + /** + * @brief Gets the singleton instance of the socket manager. + * @return Reference to the singleton instance + */ + static unix_socket_manager& get(); + + /** + * @brief Initializes the socket manager. + * + * Must be called once during kernel initialization before using sockets. + * Sets up internal data structures. + */ + void init(); + + /** + * @brief Registers a socket with a filesystem path. + * + * Associates a server socket with a filesystem path so clients can find it. + * The path must be unique - only one socket can be bound to each path. + * + * @param path Filesystem path (e.g., "/tmp/my_socket") + * @param socket Socket to register + * @return True on success, false if path already exists or manager not initialized + */ + bool register_socket(const kstl::string& path, kstl::shared_ptr socket); + + /** + * @brief Unregisters a socket from a filesystem path. + * + * Removes the association between a path and socket, making the path + * available for other sockets to bind to. + * + * @param path Filesystem path to unregister + * @return True on success, false if path not found or manager not initialized + */ + bool unregister_socket(const kstl::string& path); + + /** + * @brief Finds a socket by filesystem path. + * + * Looks up a server socket that is bound to the specified path. + * Used by clients to find servers to connect to. + * + * @param path Filesystem path to search for + * @return Socket pointer or nullptr if not found or manager not initialized + */ + kstl::shared_ptr find_socket(const kstl::string& path); + + /** + * @brief Creates a new Unix stream socket. + * + * Factory method that creates a properly initialized socket instance. + * + * @return New socket instance + */ + kstl::shared_ptr create_socket(); + + /** + * @brief Gets the number of registered sockets. + * @return Number of sockets currently registered with paths + */ + size_t get_socket_count() const; + + /** + * @brief Checks if the manager is initialized. + * @return True if initialized, false otherwise + */ + bool is_initialized() const { return m_initialized; } + + /** + * @brief Cleans up all registered sockets. + * + * Used during system shutdown to clean up all socket resources. + * Should only be called during kernel shutdown. + */ + void cleanup(); + +private: + static unix_socket_manager s_singleton; + + unix_socket_manager() = default; + + // Non-copyable, non-movable + unix_socket_manager(const unix_socket_manager&) = delete; + unix_socket_manager& operator=(const unix_socket_manager&) = delete; + unix_socket_manager(unix_socket_manager&&) = delete; + unix_socket_manager& operator=(unix_socket_manager&&) = delete; + + bool m_initialized = false; // Manager initialization state + kstl::hashmap> m_bound_sockets; // Path -> Socket mapping + mutable mutex m_manager_lock = mutex(); // Protects manager state + + // Private helper methods + bool _is_valid_path(const kstl::string& path) const; + void _log_socket_operation(const char* operation, const kstl::string& path, bool success) const; +}; +} // namespace net + +#endif // UNIX_SOCKET_MANAGER_H + diff --git a/kernel/include/process/process_env.h b/kernel/include/process/process_env.h index 25f9e29..f557597 100644 --- a/kernel/include/process/process_env.h +++ b/kernel/include/process/process_env.h @@ -80,6 +80,7 @@ enum class handle_type : uint32_t { SEMAPHORE, // Semaphore handle EVENT, // Event handle SHARED_MEM, // Shared memory handle + UNIX_SOCKET, // Unix domain socket handle }; /** diff --git a/kernel/include/syscall/handlers/sys_net.h b/kernel/include/syscall/handlers/sys_net.h new file mode 100644 index 0000000..8ebc106 --- /dev/null +++ b/kernel/include/syscall/handlers/sys_net.h @@ -0,0 +1,13 @@ +#ifndef SYS_NET_H +#define SYS_NET_H + +#include + +// Declare all network-related syscall handlers +DECLARE_SYSCALL_HANDLER(socket); +DECLARE_SYSCALL_HANDLER(connect); +DECLARE_SYSCALL_HANDLER(accept); +DECLARE_SYSCALL_HANDLER(bind); +DECLARE_SYSCALL_HANDLER(listen); + +#endif // SYS_NET_H diff --git a/kernel/include/syscall/handlers/sys_time.h b/kernel/include/syscall/handlers/sys_time.h new file mode 100644 index 0000000..7a3696c --- /dev/null +++ b/kernel/include/syscall/handlers/sys_time.h @@ -0,0 +1,9 @@ +#ifndef SYS_TIME_H +#define SYS_TIME_H + +#include + +// Declare all time-related syscall handlers +DECLARE_SYSCALL_HANDLER(nanosleep); + +#endif // SYS_TIME_H diff --git a/kernel/include/syscall/syscalls.h b/kernel/include/syscall/syscalls.h index f9c16b9..f243320 100644 --- a/kernel/include/syscall/syscalls.h +++ b/kernel/include/syscall/syscalls.h @@ -2,20 +2,26 @@ #define SYSCALL_H #include -#define ENOSYS 1 // Invalid system call number -#define EINVAL 22 // Invalid argument -#define EFAULT 14 // Bad address -#define ENOMEM 12 // Out of memory -#define EACCES 13 // Invalid access -#define ENOTTY 25 // Invalid tty -#define ENOENT 2 // No such file or directory -#define EEXIST 17 // File exists -#define EISDIR 21 // Is a directory -#define EMFILE 24 // Too many open files -#define EIO 5 // I/O error -#define EBADF 9 // Bad file descriptor -#define ESPIPE 29 // Illegal seek -#define ENOPRIV 72 // Invalid privilege permissions +#define ENOSYS 1 // Invalid system call number +#define ENOENT 2 // No such file or directory +#define EIO 5 // I/O error +#define EBADF 9 // Bad file descriptor +#define ENOMEM 12 // Out of memory +#define EACCES 13 // Invalid access +#define EFAULT 14 // Bad address +#define EEXIST 17 // File exists +#define EISDIR 21 // Is a directory +#define EINVAL 22 // Invalid argument +#define EMFILE 24 // Too many open files +#define ENOTTY 25 // Invalid tty +#define ESPIPE 29 // Illegal seek +#define EPIPE 32 // Broken pipe +#define EADDRINUSE 98 // Address already in use +#define EAFNOSUPPORT 102 // Address family not supported +#define ENOTCONN 107 // Transport endpoint is not connected +#define ECONNREFUSED 111 // Connection refused +#define EPROTONOSUPPORT 135 // Protocol not supported +#define ENOPRIV 720 // Invalid privilege permissions #define SYSCALL_SYS_READ 0 #define SYSCALL_SYS_WRITE 1 @@ -27,7 +33,13 @@ #define SYSCALL_SYS_BRK 12 #define SYSCALL_SYS_IOCTL 16 #define SYSCALL_SYS_WRITEV 20 +#define SYSCALL_SYS_NANOSLEEP 35 #define SYSCALL_SYS_GETPID 39 +#define SYSCALL_SYS_SOCKET 41 +#define SYSCALL_SYS_CONNECT 42 +#define SYSCALL_SYS_ACCEPT 43 +#define SYSCALL_SYS_BIND 49 +#define SYSCALL_SYS_LISTEN 50 #define SYSCALL_SYS_EXIT 60 #define SYSCALL_SYS_SET_THREAD_AREA 158 #define SYSCALL_SYS_SET_TID_ADDRESS 218 diff --git a/kernel/src/boot/init.cpp b/kernel/src/boot/init.cpp index 1f24d39..2a9b6af 100644 --- a/kernel/src/boot/init.cpp +++ b/kernel/src/boot/init.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #ifdef BUILD_UNIT_TESTS @@ -129,6 +130,10 @@ void load_initrd() { auto& vfs = fs::virtual_filesystem::get(); vfs.mount("/", kstl::make_shared()); + // Create a /tmp/ directory + vfs.create("/tmp", fs::vfs_node_type::directory, 0755); + + // Load the initial ramdisk into /initrd fs::load_cpio_initrd(reinterpret_cast(vaddr), mod_size, "/initrd"); } @@ -182,6 +187,9 @@ void init(unsigned int magic, void* mbi) { // Load the initrd if it's available load_initrd(); + // Initialize the Unix Domain Socket subsystem + net::unix_socket_manager::get().init(); + // Calibrate architecture-specific CPU timer to a tickrate of 4ms kernel_timer::calibrate_cpu_timer(4); diff --git a/kernel/src/net/unix_socket.cpp b/kernel/src/net/unix_socket.cpp new file mode 100644 index 0000000..f91ea04 --- /dev/null +++ b/kernel/src/net/unix_socket.cpp @@ -0,0 +1,369 @@ +#include +#include +#include +#include +#include + +namespace net { + +unix_stream_socket::unix_stream_socket() { + _setup_buffers(); +} + +unix_stream_socket::~unix_stream_socket() { + // Only close if not already closed to avoid double-close deadlock + if (m_state.load() != unix_socket_state::CLOSED) { + close(); + } +} + +void unix_stream_socket::_setup_buffers() { + m_recv_buffer = kstl::shared_ptr(new unix_socket_buffer()); + m_send_buffer = kstl::shared_ptr(new unix_socket_buffer()); +} + +void unix_stream_socket::_change_state(unix_socket_state new_state) { + unix_socket_state old_state = m_state.exchange(new_state); + __unused old_state; + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Socket state changed: %u -> %u\n", + static_cast(old_state), static_cast(new_state)); +} + +bool unix_stream_socket::_can_accept() const { + return m_state.load() == unix_socket_state::LISTENING && m_is_server; +} + +bool unix_stream_socket::_can_read() const { + unix_socket_state state = m_state.load(); + return state == unix_socket_state::CONNECTED || state == unix_socket_state::DISCONNECTED; +} + +bool unix_stream_socket::_can_write() const { + unix_socket_state state = m_state.load(); + return state == unix_socket_state::CONNECTED; +} + +void unix_stream_socket::_cleanup_resources() { + // NOTE: This method assumes m_socket_lock is already held by the caller + + // Clear peer connection + m_peer = kstl::shared_ptr(nullptr); + + // Clear pending connections + { + mutex_guard accept_guard(m_accept_lock); + m_pending_connections.clear(); + } + + // Clear buffers + if (m_recv_buffer) { + m_recv_buffer->clear(); + } + if (m_send_buffer) { + m_send_buffer->clear(); + } +} + +int unix_stream_socket::_add_pending_connection(kstl::shared_ptr client) { + mutex_guard guard(m_accept_lock); + + if (static_cast(m_pending_connections.size()) >= m_backlog) { + return -ECONNREFUSED; // Queue is full + } + + m_pending_connections.push_back(client); + return 0; +} + +kstl::shared_ptr unix_stream_socket::_get_pending_connection() { + mutex_guard guard(m_accept_lock); + + if (m_pending_connections.empty()) { + return kstl::shared_ptr(nullptr); + } + + auto client = m_pending_connections[0]; + m_pending_connections.erase(0); + return client; +} + +void unix_stream_socket::_set_peer(kstl::shared_ptr peer) { + mutex_guard guard(m_socket_lock); + m_peer = peer; +} + +int unix_stream_socket::bind(const kstl::string& path) { + mutex_guard guard(m_socket_lock); + + if (m_state.load() != unix_socket_state::CREATED) { + return -EINVAL; // Socket already bound or in use + } + + if (path.empty()) { + return -EINVAL; // Invalid path + } + + m_path = path; + m_is_server = true; + _change_state(unix_socket_state::BOUND); + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Socket bound to path: %s (registration deferred)\n", path.c_str()); + return 0; +} + +int unix_stream_socket::listen(int backlog) { + mutex_guard guard(m_socket_lock); + + if (m_state.load() != unix_socket_state::BOUND) { + return -EINVAL; // Must bind before listening + } + + if (backlog <= 0) { + backlog = 5; // Default backlog + } + + m_backlog = backlog; + _change_state(unix_socket_state::LISTENING); + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Socket listening with backlog: %d\n", backlog); + return 0; +} + +kstl::shared_ptr unix_stream_socket::accept() { + if (!_can_accept()) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Cannot accept on this socket\n"); + return kstl::shared_ptr(nullptr); + } + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Waiting for incoming connections...\n"); + + // Block until a connection is available + while (true) { + auto client = _get_pending_connection(); + if (client) { + // Create a new socket for this connection + auto connection_socket = kstl::shared_ptr(new unix_stream_socket()); + if (!connection_socket) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Failed to create connection socket\n"); + return kstl::shared_ptr(nullptr); + } + + // Set up the bidirectional connection + connection_socket->m_peer = client; + client->m_peer = connection_socket; + + // Both sockets are now connected + connection_socket->_change_state(unix_socket_state::CONNECTED); + client->_change_state(unix_socket_state::CONNECTED); + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Accepted connection\n"); + return connection_socket; + } + + // No pending connections, yield and try again + sched::yield(); + + // Check if we're still listening + if (!_can_accept()) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Socket no longer accepting connections\n"); + return kstl::shared_ptr(nullptr); + } + } +} + +int unix_stream_socket::connect(const kstl::string& path) { + mutex_guard guard(m_socket_lock); + + if (m_state.load() != unix_socket_state::CREATED) { + return -EINVAL; // Socket already connected or in use + } + + if (path.empty()) { + return -EINVAL; // Invalid path + } + + _change_state(unix_socket_state::CONNECTING); + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Attempting to connect to: %s\n", path.c_str()); + + // Find the server socket via socket manager + auto& manager = unix_socket_manager::get(); + auto server = manager.find_socket(path); + if (!server) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Server socket not found at path: %s\n", path.c_str()); + _change_state(unix_socket_state::CREATED); + return -ECONNREFUSED; + } + + // For now, directly add ourselves to the server's pending queue + // The shared_ptr issue will be resolved when sockets are created via manager + int result = server->_add_pending_connection(kstl::shared_ptr(this)); + if (result != 0) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Failed to add to server's pending queue: %d\n", result); + _change_state(unix_socket_state::CREATED); + return result; + } + + // Wait for the server to accept us (our state will change to CONNECTED) + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Waiting for server to accept connection...\n"); + while (m_state.load() == unix_socket_state::CONNECTING) { + sched::yield(); + } + + if (m_state.load() == unix_socket_state::CONNECTED) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Successfully connected to: %s\n", path.c_str()); + return 0; + } else { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Connection failed or was rejected\n"); + _change_state(unix_socket_state::CREATED); + return -ECONNREFUSED; + } +} + +ssize_t unix_stream_socket::read(void* buffer, size_t size) { + if (!buffer || size == 0) { + return -EINVAL; + } + + unix_socket_state current_state = m_state.load(); + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] read() called, socket state: %u\n", static_cast(current_state)); + + // If socket is not connected and not disconnected, it's an error + if (current_state != unix_socket_state::CONNECTED && + current_state != unix_socket_state::DISCONNECTED) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] read() returning ENOTCONN for state %u\n", static_cast(current_state)); + return -ENOTCONN; + } + + if (!m_recv_buffer) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] read() returning EBADF - no recv buffer\n"); + return -EBADF; // Invalid socket state + } + + // Check if we have any data to read + if (m_recv_buffer->has_data()) { + // Data is available, read it + size_t bytes_read = m_recv_buffer->read(buffer, size); + return static_cast(bytes_read); + } + + // No data available + if (current_state == unix_socket_state::DISCONNECTED) { + // Peer has disconnected and no more data - return EOF + return 0; + } + + // Still connected but no data - block until data arrives or disconnection + while (true) { + current_state = m_state.load(); + + // Check for disconnection + if (current_state == unix_socket_state::DISCONNECTED) { + // Check one more time for any remaining data + if (m_recv_buffer->has_data()) { + size_t bytes_read = m_recv_buffer->read(buffer, size); + return static_cast(bytes_read); + } + return 0; // EOF - peer disconnected and no more data + } + + // Check if connection is broken + if (current_state != unix_socket_state::CONNECTED) { + return -ENOTCONN; + } + + // Check for new data + if (m_recv_buffer->has_data()) { + size_t bytes_read = m_recv_buffer->read(buffer, size); + return static_cast(bytes_read); + } + + // No data available, yield and try again + sched::yield(); + } +} + +ssize_t unix_stream_socket::write(const void* data, size_t size) { + if (!data || size == 0) { + return -EINVAL; + } + + if (!_can_write()) { + return -ENOTCONN; + } + + // Get peer's receive buffer + auto peer = m_peer; + if (!peer || !peer->m_recv_buffer) { + return -EPIPE; // Broken pipe + } + + // Try to write to peer's receive buffer + size_t bytes_written = peer->m_recv_buffer->write(data, size); + + if (bytes_written == 0 && size > 0) { + // Peer's buffer is full, this is a partial write scenario + // For now, we'll return 0 to indicate no bytes written + // In a full implementation, we might want to block or return EAGAIN + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Warning: Peer buffer full, no bytes written\n"); + return 0; + } + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Wrote %llu bytes to peer\n", bytes_written); + return static_cast(bytes_written); +} + +int unix_stream_socket::close() { + mutex_guard guard(m_socket_lock); + + unix_socket_state current_state = m_state.load(); + if (current_state == unix_socket_state::CLOSED) { + return 0; // Already closed + } + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Closing socket (path: %s)\n", + m_path.empty() ? "none" : m_path.c_str()); + + // Unregister from manager if we're a bound server socket + if (m_is_server && !m_path.empty()) { + auto& manager = unix_socket_manager::get(); + manager.unregister_socket(m_path); + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Unregistered socket from manager\n"); + } + + // Notify peer of disconnection + if (m_peer) { + m_peer->_change_state(unix_socket_state::DISCONNECTED); + m_peer->m_peer = kstl::shared_ptr(nullptr); + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Notified peer of disconnection\n"); + } + + _cleanup_resources(); + _change_state(unix_socket_state::CLOSED); + + return 0; +} + +int unix_stream_socket::register_with_manager(kstl::shared_ptr self) { + if (!m_is_server || m_path.empty()) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Only bound server sockets can be registered\n"); + return -EINVAL; + } + + if (m_state.load() != unix_socket_state::BOUND) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Socket must be bound before registration\n"); + return -EINVAL; + } + + auto& manager = unix_socket_manager::get(); + if (!manager.register_socket(m_path, self)) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Error: Failed to register socket with manager\n"); + return -EADDRINUSE; + } + + UNIX_SOCKET_TRACE("[UNIX_SOCKET] Socket registered with manager at path: %s\n", m_path.c_str()); + return 0; +} + +} // namespace net \ No newline at end of file diff --git a/kernel/src/net/unix_socket_buffer.cpp b/kernel/src/net/unix_socket_buffer.cpp new file mode 100644 index 0000000..5178856 --- /dev/null +++ b/kernel/src/net/unix_socket_buffer.cpp @@ -0,0 +1,195 @@ +#include +#include +#include + +namespace net { + +unix_socket_buffer::unix_socket_buffer(size_t capacity) + : m_capacity(capacity), m_head(0), m_tail(0), m_size(0) { + + if (capacity == 0) { + kprint("[UNIX_SOCKET] Error: Buffer capacity cannot be zero\n"); + m_capacity = DEFAULT_BUFFER_SIZE; + } + + m_buffer = new uint8_t[m_capacity]; + if (!m_buffer) { + kprint("[UNIX_SOCKET] Error: Failed to allocate buffer of size %llu\n", m_capacity); + // This is a critical error - we can't function without a buffer + m_capacity = 0; + return; + } + + // Initialize buffer to zero for debugging + memset(m_buffer, 0, m_capacity); +} + +unix_socket_buffer::~unix_socket_buffer() { + if (m_buffer) { + delete[] m_buffer; + m_buffer = nullptr; + } +} + +size_t unix_socket_buffer::write(const void* data, size_t size) { + if (!data || size == 0 || !m_buffer) { + return 0; + } + + mutex_guard guard(m_lock); + + size_t current_size = m_size.load(); + size_t available_space = m_capacity - current_size; + + if (available_space == 0) { + return 0; // Buffer is full + } + + // Limit write size to available space + size_t bytes_to_write = (size > available_space) ? available_space : size; + const uint8_t* src = static_cast(data); + size_t bytes_written = 0; + + // Handle circular buffer wrapping - may need to write in two parts + while (bytes_written < bytes_to_write) { + size_t contiguous_space = _contiguous_write_space(); + if (contiguous_space == 0) { + break; // Should not happen, but safety check + } + + size_t chunk_size = bytes_to_write - bytes_written; + if (chunk_size > contiguous_space) { + chunk_size = contiguous_space; + } + + // Copy data to buffer + memcpy(m_buffer + m_head, src + bytes_written, chunk_size); + + // Update head position + m_head = (m_head + chunk_size) % m_capacity; + bytes_written += chunk_size; + } + + // Update size atomically + m_size.fetch_add(bytes_written); + + return bytes_written; +} + +size_t unix_socket_buffer::read(void* buffer, size_t size) { + if (!buffer || size == 0 || !m_buffer) { + return 0; + } + + mutex_guard guard(m_lock); + + size_t current_size = m_size.load(); + if (current_size == 0) { + return 0; // Buffer is empty + } + + // Limit read size to available data + size_t bytes_to_read = (size > current_size) ? current_size : size; + uint8_t* dest = static_cast(buffer); + size_t bytes_read = 0; + + // Handle circular buffer wrapping - may need to read in two parts + while (bytes_read < bytes_to_read) { + size_t contiguous_data = _contiguous_read_space(); + if (contiguous_data == 0) { + break; // Should not happen, but safety check + } + + size_t chunk_size = bytes_to_read - bytes_read; + if (chunk_size > contiguous_data) { + chunk_size = contiguous_data; + } + + // Copy data from buffer + memcpy(dest + bytes_read, m_buffer + m_tail, chunk_size); + + // Update tail position + m_tail = (m_tail + chunk_size) % m_capacity; + bytes_read += chunk_size; + } + + // Update size atomically + m_size.fetch_sub(bytes_read); + + return bytes_read; +} + +bool unix_socket_buffer::has_data() const { + return m_size.load() > 0; +} + +bool unix_socket_buffer::has_space() const { + return m_size.load() < m_capacity; +} + +size_t unix_socket_buffer::available_bytes() const { + return m_size.load(); +} + +size_t unix_socket_buffer::free_space() const { + return m_capacity - m_size.load(); +} + +void unix_socket_buffer::clear() { + mutex_guard guard(m_lock); + + m_head = 0; + m_tail = 0; + m_size.store(0); + + // Optional: Clear buffer memory for security + if (m_buffer) { + memset(m_buffer, 0, m_capacity); + } +} + +size_t unix_socket_buffer::_next_index(size_t index) const { + return (index + 1) % m_capacity; +} + +size_t unix_socket_buffer::_contiguous_write_space() const { + size_t current_size = m_size.load(); + size_t available_space = m_capacity - current_size; + + if (available_space == 0) { + return 0; + } + + // If head is before tail, we can write until we reach tail + // If head is at or after tail, we can write until end of buffer + if (m_head < m_tail) { + return m_tail - m_head; + } else { + // Write to end of buffer, unless we would wrap to tail + size_t space_to_end = m_capacity - m_head; + if (m_tail == 0 && current_size > 0) { + // Special case: can't write to position 0 if tail is there + return space_to_end - 1; + } + return space_to_end; + } +} + +size_t unix_socket_buffer::_contiguous_read_space() const { + size_t current_size = m_size.load(); + + if (current_size == 0) { + return 0; + } + + // If tail is before head, we can read until we reach head + // If tail is at or after head, we can read until end of buffer + if (m_tail < m_head) { + return m_head - m_tail; + } else { + // Read to end of buffer + return m_capacity - m_tail; + } +} + +} // namespace net diff --git a/kernel/src/net/unix_socket_manager.cpp b/kernel/src/net/unix_socket_manager.cpp new file mode 100644 index 0000000..bc9a62c --- /dev/null +++ b/kernel/src/net/unix_socket_manager.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include + +namespace net { + +// Error codes for Unix socket manager +#define ENOTINIT 125 // Manager not initialized +#define EADDRINUSE 98 // Address already in use +#define ENOENT 2 // No such file or directory +#define EINVAL 22 // Invalid argument +#define ECONNREFUSED 111 // Connection refused + +unix_socket_manager unix_socket_manager::s_singleton; + +unix_socket_manager& unix_socket_manager::get() { + return s_singleton; +} + +void unix_socket_manager::init() { + mutex_guard guard(m_manager_lock); + + if (m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Warning: Manager already initialized\n"); + return; + } + + // Initialize the hashmap + m_bound_sockets = kstl::hashmap>(); + + m_initialized = true; +} + +bool unix_socket_manager::register_socket(const kstl::string& path, + kstl::shared_ptr socket) { + if (!m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Manager not initialized\n"); + return false; + } + + if (!socket) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Cannot register null socket\n"); + return false; + } + + if (!_is_valid_path(path)) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Invalid path: %s\n", path.c_str()); + return false; + } + + mutex_guard guard(m_manager_lock); + + // Check if path is already in use + if (m_bound_sockets.find(path)) { + _log_socket_operation("register", path, false); + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Path already in use: %s\n", path.c_str()); + return false; + } + + // Register the socket + bool success = m_bound_sockets.insert(path, socket); + _log_socket_operation("register", path, success); + + if (!success) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Failed to register socket at path: %s\n", path.c_str()); + } + + return success; +} + +bool unix_socket_manager::unregister_socket(const kstl::string& path) { + if (!m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Manager not initialized\n"); + return false; + } + + if (!_is_valid_path(path)) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Invalid path: %s\n", path.c_str()); + return false; + } + + mutex_guard guard(m_manager_lock); + + bool success = m_bound_sockets.remove(path); + _log_socket_operation("unregister", path, success); + + if (!success) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Warning: Path not found for unregistration: %s\n", path.c_str()); + } + + return success; +} + +kstl::shared_ptr unix_socket_manager::find_socket(const kstl::string& path) { + if (!m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Manager not initialized\n"); + return kstl::shared_ptr(nullptr); + } + + if (!_is_valid_path(path)) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Invalid path: %s\n", path.c_str()); + return kstl::shared_ptr(nullptr); + } + + mutex_guard guard(m_manager_lock); + + auto* socket_ptr = m_bound_sockets.get(path); + if (socket_ptr) { + _log_socket_operation("find", path, true); + return *socket_ptr; + } + + _log_socket_operation("find", path, false); + return kstl::shared_ptr(nullptr); +} + +kstl::shared_ptr unix_socket_manager::create_socket() { + if (!m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Manager not initialized\n"); + return kstl::shared_ptr(nullptr); + } + + // Create a new socket instance + auto socket = kstl::shared_ptr(new unix_stream_socket()); + if (!socket) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Error: Failed to create socket\n"); + return kstl::shared_ptr(nullptr); + } + + return socket; +} + +size_t unix_socket_manager::get_socket_count() const { + if (!m_initialized) { + return 0; + } + + mutex_guard guard(m_manager_lock); + return m_bound_sockets.size(); +} + +void unix_socket_manager::cleanup() { + mutex_guard guard(m_manager_lock); + + if (!m_initialized) { + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] Warning: Manager not initialized, nothing to cleanup\n"); + return; + } + + // Get all keys before clearing (to avoid iterator invalidation) + auto paths = m_bound_sockets.keys(); + + // Close all registered sockets + for (const auto& path : paths) { + auto* socket_ptr = m_bound_sockets.get(path); + if (socket_ptr && *socket_ptr) { + (*socket_ptr)->close(); + } + } + + // Clear the hashmap + m_bound_sockets = kstl::hashmap>(); + + m_initialized = false; +} + +bool unix_socket_manager::_is_valid_path(const kstl::string& path) const { + // Basic path validation + if (path.empty()) { + return false; + } + + // Unix socket paths should start with '/' for absolute paths + // or be relative paths (not starting with '/') + // For now, accept any non-empty path + const char* path_str = path.c_str(); + size_t len = strlen(path_str); + + // Check for reasonable length limits + if (len > 255) { // Typical filesystem path limit + return false; + } + + // Check for null bytes within the path + for (size_t i = 0; i < len; i++) { + if (path_str[i] == '\0') { + return false; + } + } + + return true; +} + +void unix_socket_manager::_log_socket_operation(const char* operation, + const kstl::string& path, + bool success) const { + const char* status = success ? "SUCCESS" : "FAILED"; + __unused status; __unused operation; __unused path; + UNIX_SOCKET_TRACE("[UNIX_SOCKET_MGR] %s operation %s for path: %s\n", + operation, status, path.c_str()); +} + +} // namespace net \ No newline at end of file diff --git a/kernel/src/syscall/handlers/sys_io.cpp b/kernel/src/syscall/handlers/sys_io.cpp index 1d8fd64..44376e6 100644 --- a/kernel/src/syscall/handlers/sys_io.cpp +++ b/kernel/src/syscall/handlers/sys_io.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include DECLARE_SYSCALL_HANDLER(write) { int handle = static_cast(arg1); @@ -27,7 +29,10 @@ DECLARE_SYSCALL_HANDLER(write) { switch (hentry->type) { case handle_type::OUTPUT_STREAM: { // Write to serial port (stdout behavior) - serial::write(serial::g_kernel_uart_port, buf, count); + char hack[512] = { 0 }; + memcpy(hack, buf, count < 512 ? count : 511); + kprint(hack); + // serial::write(serial::g_kernel_uart_port, buf, count); bytes_written = count; break; } @@ -51,6 +56,24 @@ DECLARE_SYSCALL_HANDLER(write) { } break; } + case handle_type::UNIX_SOCKET: { + // Write to Unix domain socket + auto socket_ptr = reinterpret_cast*>(hentry->object); + if (!socket_ptr || !(*socket_ptr)) { + return -EBADF; + } + + auto socket = *socket_ptr; + + // Let the socket's write method handle state checking + int result = socket->write(buf, count); + if (result < 0) { + return result; // Error (already negative) + } + + bytes_written = static_cast(result); + break; + } default: { return -EBADF; // Unsupported handle type } @@ -116,6 +139,26 @@ DECLARE_SYSCALL_HANDLER(read) { break; } + case handle_type::UNIX_SOCKET: { + // Read from Unix domain socket + auto socket_ptr = reinterpret_cast*>(hentry->object); + if (!socket_ptr || !(*socket_ptr)) { + return -EBADF; + } + + auto socket = *socket_ptr; + + // Let the socket's read method handle state checking and EOF logic + // It can read from both CONNECTED and DISCONNECTED states appropriately + int result = socket->read(buf, count); + if (result < 0) { + return result; + } + + bytes_read = static_cast(result); + break; + } + default: { return -EBADF; // Unsupported handle type } @@ -251,6 +294,22 @@ DECLARE_SYSCALL_HANDLER(close) { } break; } + case handle_type::UNIX_SOCKET: { + // Clean up Unix domain socket + auto socket_ptr = reinterpret_cast*>(hentry->object); + if (socket_ptr) { + auto socket = *socket_ptr; + if (socket) { + // Close the socket - this will disconnect peers and clean up state + // The socket's close() method handles manager unregistration automatically + socket->close(); + } + + // Clean up the shared_ptr object itself + delete socket_ptr; + } + break; + } default: { return -EBADF; // Invalid handle type } @@ -399,7 +458,10 @@ DECLARE_SYSCALL_HANDLER(writev) { switch (hentry->type) { case handle_type::OUTPUT_STREAM: { // Write to serial port (stdout behavior) - serial::write(serial::g_kernel_uart_port, reinterpret_cast(k_iov.iov_base), k_iov.iov_len); + //serial::write(serial::g_kernel_uart_port, reinterpret_cast(k_iov.iov_base), k_iov.iov_len); + char hack[512] = { 0 }; + memcpy(hack, k_iov.iov_base, k_iov.iov_len < 512 ? k_iov.iov_len : 511); + kprint(hack); n = k_iov.iov_len; break; } @@ -425,6 +487,20 @@ DECLARE_SYSCALL_HANDLER(writev) { break; } + case handle_type::UNIX_SOCKET: { + // Write to Unix domain socket + auto socket_ptr = reinterpret_cast*>(hentry->object); + if (!socket_ptr || !(*socket_ptr)) { + return -EBADF; + } + + auto socket = *socket_ptr; + + // Let the socket's write method handle state checking + n = socket->write(k_iov.iov_base, k_iov.iov_len); + break; + } + default: return -EBADF; // Unsupported handle type } diff --git a/kernel/src/syscall/handlers/sys_net.cpp b/kernel/src/syscall/handlers/sys_net.cpp new file mode 100644 index 0000000..512426e --- /dev/null +++ b/kernel/src/syscall/handlers/sys_net.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include + +// Socket address structures +struct sockaddr_un { + uint16_t sun_family; // AF_UNIX + char sun_path[108]; // pathname +}; + +// Socket family constants +#define AF_UNIX 1 + +// Socket type constants +#define SOCK_STREAM 1 + +DECLARE_SYSCALL_HANDLER(socket) { + /* ----------------------------------------------------------------- + * Linux-style calling convention + * arg1 = int domain (AF_UNIX) + * arg2 = int type (SOCK_STREAM) + * arg3 = int protocol (0) + * -----------------------------------------------------------------*/ + + int domain = static_cast(arg1); + int type = static_cast(arg2); + int protocol = static_cast(arg3); + + // Validate socket parameters + if (domain != AF_UNIX) { + SYSCALL_TRACE("socket(%d, %d, %d) = -EAFNOSUPPORT\n", domain, type, protocol); + return -EAFNOSUPPORT; // Address family not supported + } + + if (type != SOCK_STREAM) { + SYSCALL_TRACE("socket(%d, %d, %d) = -EPROTONOSUPPORT\n", domain, type, protocol); + return -EPROTONOSUPPORT; // Protocol not supported + } + + if (protocol != 0) { + SYSCALL_TRACE("socket(%d, %d, %d) = -EPROTONOSUPPORT\n", domain, type, protocol); + return -EPROTONOSUPPORT; // Protocol not supported + } + + // Create socket via manager + auto& manager = net::unix_socket_manager::get(); + auto socket = manager.create_socket(); + if (!socket) { + SYSCALL_TRACE("socket(%d, %d, %d) = -ENOMEM\n", domain, type, protocol); + return -ENOMEM; + } + + // Create shared_ptr wrapper for handle storage + auto* socket_ptr = new kstl::shared_ptr(socket); + if (!socket_ptr) { + SYSCALL_TRACE("socket(%d, %d, %d) = -ENOMEM\n", domain, type, protocol); + return -ENOMEM; + } + + // Add to process handle table + auto& handles = current->get_env()->handles; + size_t handle = handles.add_handle(handle_type::UNIX_SOCKET, socket_ptr); + if (handle == SIZE_MAX) { + delete socket_ptr; + SYSCALL_TRACE("socket(%d, %d, %d) = -EMFILE\n", domain, type, protocol); + return -EMFILE; // Too many open files + } + + SYSCALL_TRACE("socket(%d, %d, %d) = %d\n", domain, type, protocol, static_cast(handle)); + return static_cast(handle); +} + +DECLARE_SYSCALL_HANDLER(bind) { + /* ----------------------------------------------------------------- + * Linux-style calling convention + * arg1 = int sockfd + * arg2 = const struct sockaddr* addr + * arg3 = socklen_t addrlen + * -----------------------------------------------------------------*/ + + int sockfd = static_cast(arg1); + const sockaddr_un* addr = reinterpret_cast(arg2); + uint32_t addrlen = static_cast(arg3); + + // Validate parameters + if (!addr) { + return -EFAULT; + } + + if (addrlen < sizeof(sockaddr_un) || addr->sun_family != AF_UNIX) { + return -EINVAL; + } + + // Get socket handle + handle_entry* hentry = current->get_env()->handles.get_handle(sockfd); + if (!hentry || hentry->type != handle_type::UNIX_SOCKET) { + return -EBADF; + } + + auto* socket_ptr = static_cast*>(hentry->object); + if (!socket_ptr || !*socket_ptr) { + return -EBADF; + } + + // Extract path from address + kstl::string path(addr->sun_path); + + // Bind socket + int result = (*socket_ptr)->bind(path); + if (result == 0) { + // Register with manager after successful bind + int reg_result = (*socket_ptr)->register_with_manager(*socket_ptr); + if (reg_result != 0) { + (*socket_ptr)->close(); // Clean up on registration failure + result = reg_result; + } + } + + SYSCALL_TRACE("bind(%d, {sun_family=AF_UNIX, sun_path=\"%s\"}, %u) = %d\n", + sockfd, addr->sun_path, addrlen, result); + return result; +} + +DECLARE_SYSCALL_HANDLER(listen) { + /* ----------------------------------------------------------------- + * Linux-style calling convention + * arg1 = int sockfd + * arg2 = int backlog + * -----------------------------------------------------------------*/ + + int sockfd = static_cast(arg1); + int backlog = static_cast(arg2); + + // Get socket handle + handle_entry* hentry = current->get_env()->handles.get_handle(sockfd); + if (!hentry || hentry->type != handle_type::UNIX_SOCKET) { + return -EBADF; + } + + auto* socket_ptr = static_cast*>(hentry->object); + if (!socket_ptr || !*socket_ptr) { + return -EBADF; + } + + // Listen on socket + int result = (*socket_ptr)->listen(backlog); + + SYSCALL_TRACE("listen(%d, %d) = %d\n", sockfd, backlog, result); + return result; +} + +DECLARE_SYSCALL_HANDLER(accept) { + /* ----------------------------------------------------------------- + * Linux-style calling convention + * arg1 = int sockfd + * arg2 = struct sockaddr* addr (optional, can be NULL) + * arg3 = socklen_t* addrlen (optional, can be NULL) + * -----------------------------------------------------------------*/ + + int sockfd = static_cast(arg1); + sockaddr_un* addr = reinterpret_cast(arg2); + uint32_t* addrlen = reinterpret_cast(arg3); + + // Get socket handle + handle_entry* hentry = current->get_env()->handles.get_handle(sockfd); + if (!hentry || hentry->type != handle_type::UNIX_SOCKET) { + return -EBADF; + } + + auto* socket_ptr = static_cast*>(hentry->object); + if (!socket_ptr || !*socket_ptr) { + return -EBADF; + } + + // Accept connection (blocking) + auto client_socket = (*socket_ptr)->accept(); + if (!client_socket) { + SYSCALL_TRACE("accept(%d, %p, %p) = -EINVAL\n", sockfd, addr, addrlen); + return -EINVAL; + } + + // Create shared_ptr wrapper for the new connection + auto* client_socket_ptr = new kstl::shared_ptr(client_socket); + if (!client_socket_ptr) { + SYSCALL_TRACE("accept(%d, %p, %p) = -ENOMEM\n", sockfd, addr, addrlen); + return -ENOMEM; + } + + // Add to process handle table + auto& handles = current->get_env()->handles; + size_t client_handle = handles.add_handle(handle_type::UNIX_SOCKET, client_socket_ptr); + if (client_handle == SIZE_MAX) { + delete client_socket_ptr; + SYSCALL_TRACE("accept(%d, %p, %p) = -EMFILE\n", sockfd, addr, addrlen); + return -EMFILE; + } + + // Fill in client address if requested + if (addr && addrlen) { + addr->sun_family = AF_UNIX; + addr->sun_path[0] = '\0'; // Anonymous socket for client + *addrlen = sizeof(sockaddr_un); + } + + SYSCALL_TRACE("accept(%d, %p, %p) = %d\n", sockfd, addr, addrlen, static_cast(client_handle)); + return static_cast(client_handle); +} + +DECLARE_SYSCALL_HANDLER(connect) { + /* ----------------------------------------------------------------- + * Linux-style calling convention + * arg1 = int sockfd + * arg2 = const struct sockaddr* addr + * arg3 = socklen_t addrlen + * -----------------------------------------------------------------*/ + + int sockfd = static_cast(arg1); + const sockaddr_un* addr = reinterpret_cast(arg2); + uint32_t addrlen = static_cast(arg3); + + // Validate parameters + if (!addr) { + return -EFAULT; + } + + if (addrlen < sizeof(sockaddr_un) || addr->sun_family != AF_UNIX) { + return -EINVAL; + } + + // Get socket handle + handle_entry* hentry = current->get_env()->handles.get_handle(sockfd); + if (!hentry || hentry->type != handle_type::UNIX_SOCKET) { + return -EBADF; + } + + auto* socket_ptr = static_cast*>(hentry->object); + if (!socket_ptr || !*socket_ptr) { + return -EBADF; + } + + // Extract path from address + kstl::string path(addr->sun_path); + + // Connect to server + int result = (*socket_ptr)->connect(path); + + SYSCALL_TRACE("connect(%d, {sun_family=AF_UNIX, sun_path=\"%s\"}, %u) = %d\n", + sockfd, addr->sun_path, addrlen, result); + return result; +} \ No newline at end of file diff --git a/kernel/src/syscall/handlers/sys_time.cpp b/kernel/src/syscall/handlers/sys_time.cpp new file mode 100644 index 0000000..4d0ad84 --- /dev/null +++ b/kernel/src/syscall/handlers/sys_time.cpp @@ -0,0 +1,86 @@ +#include +#include