diff options
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | lib/Makefile | 1 | ||||
-rw-r--r-- | lib/libc/src/hyra/socket.c | 13 | ||||
-rw-r--r-- | lib/libgfx/Makefile | 1 | ||||
-rw-r--r-- | lib/libgfx/include/libgfx/draw.h | 44 | ||||
-rw-r--r-- | lib/libgfx/include/libgfx/gfx.h | 11 | ||||
-rw-r--r-- | lib/libgfx/src/draw.c | 206 | ||||
-rw-r--r-- | lib/liboda/Makefile | 29 | ||||
-rw-r--r-- | lib/liboda/include/liboda/input.h | 74 | ||||
-rw-r--r-- | lib/liboda/include/liboda/oda.h | 128 | ||||
-rw-r--r-- | lib/liboda/include/liboda/odavar.h | 59 | ||||
-rw-r--r-- | lib/liboda/include/liboda/types.h | 41 | ||||
-rw-r--r-- | lib/liboda/src/input.c | 88 | ||||
-rw-r--r-- | lib/liboda/src/oda.c | 77 | ||||
-rw-r--r-- | lib/liboda/src/window.c | 461 | ||||
-rw-r--r-- | share/docs/lib/liboda.md | 229 | ||||
-rw-r--r-- | sys/include/sys/sched.h | 2 | ||||
-rw-r--r-- | sys/include/sys/socket.h | 17 | ||||
-rw-r--r-- | sys/include/sys/syscall.h | 1 | ||||
-rw-r--r-- | sys/kern/kern_sched.c | 86 | ||||
-rw-r--r-- | sys/kern/kern_socket.c | 211 | ||||
-rw-r--r-- | sys/kern/kern_syscall.c | 1 |
22 files changed, 1750 insertions, 31 deletions
@@ -77,6 +77,7 @@ Documentation will be in the form of comments throughout the codebase and can al - ``share/man/*``: Man pages - ``share/contrib``: Information on contributing - ``share/docs/kernel``: Kernel documentation +- ``share/docs/lib``: Library documentation Hyra running on bare metal: -------------- diff --git a/lib/Makefile b/lib/Makefile index 4ad104b..fc77815 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -7,3 +7,4 @@ ARGS = -I$(ROOT)/builddeps LDSCRIPT=$(LDSCRIPT) USRDIR=$(USRDIR) ROOT=$(ROOT) all: make -C libc/ $(ARGS) make -C libgfx/ $(ARGS) + make -C liboda/ $(ARGS) diff --git a/lib/libc/src/hyra/socket.c b/lib/libc/src/hyra/socket.c index b3039f6..2a62541 100644 --- a/lib/libc/src/hyra/socket.c +++ b/lib/libc/src/hyra/socket.c @@ -71,3 +71,16 @@ connect(int socket, const struct sockaddr *address, socklen_t len) { return syscall(SYS_connect, socket, (uintptr_t)address, len); } + +int +setsockopt(int sockfd, int level, int name, const void *v, socklen_t len) +{ + return syscall( + SYS_setsockopt, + sockfd, + level, + name, + (uintptr_t)v, + len + ); +} diff --git a/lib/libgfx/Makefile b/lib/libgfx/Makefile index 1f866ac..12fdd9a 100644 --- a/lib/libgfx/Makefile +++ b/lib/libgfx/Makefile @@ -4,7 +4,6 @@ CFILES = $(shell find src/ -name "*.c") OBJ = $(CFILES:.c=.o) all: headers $(OBJ) build/libgfx.a - echo "----------------------------------------" echo $(USRDIR) mv build/libgfx.a $(USRDIR)/lib/ cp -r include/ $(USRDIR)/include/ diff --git a/lib/libgfx/include/libgfx/draw.h b/lib/libgfx/include/libgfx/draw.h index 60d2b24..4140593 100644 --- a/lib/libgfx/include/libgfx/draw.h +++ b/lib/libgfx/include/libgfx/draw.h @@ -34,7 +34,19 @@ #include <libgfx/gfx.h> /* Shape types */ -#define SHAPE_SQUARE 0x00000000 +#define SHAPE_SQUARE 0x00000000 +#define SHAPE_SQUARE_BORDER 0x00000001 + +/* Basic color defines */ +#define GFX_BLACK 0x000000 +#define GFX_RED 0xFF0000 +#define GFX_GREEN 0x00FF00 +#define GFX_BLUE 0x0000FF +#define GFX_WHITE 0xFFFFFF +#define GFX_PURPLE 0x800080 +#define GFX_YELLOW 0xFFFF00 +#define GFX_DARK 0x1D2021 +#define GFX_AQUA 0x427B58 /* * Default shape initializer, something that @@ -78,7 +90,37 @@ struct gfx_shape { dimm_t height; }; +/* + * A point or single pixel that + * may be plotted onto the screen. + * + * @x,y: Position of the point on the screen + * @rgb: Color of the point (RGB) + */ +struct gfx_point { + scrpos_t x, y; + color_t rgb; +}; + +/* + * Represents a rectangular region on + * the screen. + * + * @x,y: Position of this region on the screen + * @width: Region width + * @heght: Region height + */ +struct gfx_region { + scrpos_t x, y; + dimm_t width; + dimm_t height; +}; + int gfx_draw_shape(struct gfx_ctx *ctx, const struct gfx_shape *shape); +int gfx_plot_point(struct gfx_ctx *ctx, const struct gfx_point *point); + +int gfx_copy_region(struct gfx_ctx *ctx, struct gfx_region *r, scrpos_t x, scrpos_t y); +color_t gfx_get_pix(struct gfx_ctx *ctx, uint32_t x, uint32_t y); __always_inline static inline size_t gfx_io_index(struct gfx_ctx *ctx, scrpos_t x, scrpos_t y) diff --git a/lib/libgfx/include/libgfx/gfx.h b/lib/libgfx/include/libgfx/gfx.h index 3468571..67a1006 100644 --- a/lib/libgfx/include/libgfx/gfx.h +++ b/lib/libgfx/include/libgfx/gfx.h @@ -48,17 +48,6 @@ typedef uint32_t pixel_t; typedef pixel_t color_t; /* - * Basic color defines - */ -#define GFX_BLACK 0x000000 -#define GFX_RED 0xFF0000 -#define GFX_GREEN 0x00FF00 -#define GFX_BLUE 0x0000FF -#define GFX_WHITE 0xFFFFFF -#define GFX_PURPLE 0x800080 -#define GFX_YELLOW 0xFFFF00 - -/* * Represents cartesian x/y values */ typedef uint32_t cartpos_t; diff --git a/lib/libgfx/src/draw.c b/lib/libgfx/src/draw.c index 49f2f53..4df64a8 100644 --- a/lib/libgfx/src/draw.c +++ b/lib/libgfx/src/draw.c @@ -33,6 +33,39 @@ #include <libgfx/draw.h> /* + * See if a pixel is within the bounds of + * the screen. + * + * @ctx: Graphics context pointer + * @x: X position to check + * @y: Y position to check + * + * Returns 0 if within bounds, otherwise + * a negative value. + */ +static int +gfx_pixel_bounds(struct gfx_ctx *ctx, uint32_t x, uint32_t y) +{ + scrpos_t scr_width, scr_height; + struct fbattr fbdev; + + if (ctx == NULL) { + return -1; + } + + /* Grab screen dimensions */ + fbdev = ctx->fbdev; + scr_width = fbdev.width; + scr_height = fbdev.height; + + if (x >= scr_width || y >= scr_height) { + return -1; + } + + return 0; +} + +/* * Draw a classic square onto the screen. * * @ctx: Graphics context @@ -42,33 +75,142 @@ static int gfx_draw_square(struct gfx_ctx *ctx, const struct gfx_shape *shape) { struct fbattr fbdev; + struct gfx_point p; off_t idx; - scrpos_t scr_width, scr_height; scrpos_t x, y; if (ctx == NULL || shape == NULL) { return -EINVAL; } - /* Grab screen dimensions */ - fbdev = ctx->fbdev; - scr_width = fbdev.width; - scr_height = fbdev.height; - for (x = shape->x; x < shape->x + shape->width; ++x) { for (y = shape->y; y < shape->y + shape->height; ++y) { - if (x >= scr_width || y >= scr_height) { - break; + p.x = x; + p.y = y; + p.rgb = shape->color; + gfx_plot_point(ctx, &p); + } + } + return 0; +} + +/* + * Draw a bordered square onto the screen. + * + * @ctx: Graphics context pointer + * @shape: Bordered square to draw + */ +static int +gfx_draw_bsquare(struct gfx_ctx *ctx, const struct gfx_shape *shape) +{ + struct gfx_point p; + scrpos_t x_i, y_i; + scrpos_t x_f, y_f; + scrpos_t x, y; + + if (ctx == NULL || shape == NULL) { + return -EINVAL; + } + + x_i = shape->x; + y_i = shape->y; + x_f = shape->x + shape->width; + y_f = shape->y + shape->height; + + /* + * Draw an unfilled square. + * + * If we are at the `y_i' or `y_f' position, draw + * pixels from 'x_i' to 'x_f'. If we are away + * from the `y_i' position, draw two pixels, + * one at `x_i' and the other at `x_f' for that + * current 'y' value. + */ + for (y = y_i; y < y_f; ++y) { + for (x = x_i; x < x_f; ++x) { + p.x = x; + p.y = y; + p.rgb = shape->color; + + /* Origin y, draw entire width */ + if (y == y_i || y == y_f - 1) { + gfx_plot_point(ctx, &p); + continue; } - idx = gfx_io_index(ctx, x, y); - ctx->io[idx] = shape->color; + p.x = x_i; + gfx_plot_point(ctx, &p); + + p.x = x_f - 1; + gfx_plot_point(ctx, &p); } } + return 0; } /* + * Plot a single pixel (aka point) onto + * the screen. + * + * @ctx: The graphics context pointer + * @point: Point to plot + * + * Returns 0 on success, otherwise a less + * than zero value. + */ +int +gfx_plot_point(struct gfx_ctx *ctx, const struct gfx_point *point) +{ + uint32_t index; + + if (ctx == NULL || point == NULL) { + return -EINVAL; + } + + /* + * Is this even a valid point on the screen for + * us to plot on? + */ + if (gfx_pixel_bounds(ctx, point->x, point->y) < 0) { + return -1; + } + + /* Plot it !! */ + index = gfx_io_index(ctx, point->x, point->y); + ctx->io[index] = point->rgb; + return 0; +} + +/* + * Grab the RGB value of a single pixel on + * the scren. + * + * @ctx: Graphics context pointer + * @x: X position to sample + * @y: Y position to sample + */ +color_t +gfx_get_pix(struct gfx_ctx *ctx, uint32_t x, uint32_t y) +{ + const color_t ERROR_COLOR = GFX_BLACK; + uint32_t index; + + /* The 'ctx' argument is required */ + if (ctx == NULL) { + return ERROR_COLOR; + } + + /* Are we within bounds of the screen */ + if (gfx_pixel_bounds(ctx, x, y) < 0) { + return ERROR_COLOR; + } + + index = gfx_io_index(ctx, x, y); + return ctx->io[index]; +} + +/* * Draw a shape onto the screen * * @ctx: libgfx graphics context @@ -90,7 +232,51 @@ gfx_draw_shape(struct gfx_ctx *ctx, const struct gfx_shape *shape) switch (shape->type) { case SHAPE_SQUARE: return gfx_draw_square(ctx, shape); + case SHAPE_SQUARE_BORDER: + return gfx_draw_bsquare(ctx, shape); } return -1; } + +/* + * Copy a region on one part of a screen to + * another part of a screen. + * + * @ctx: Graphics context pointer + * @r: Region to copy + * @x: X position for copy dest + * @y: Y position for copy dest + */ +int +gfx_copy_region(struct gfx_ctx *ctx, struct gfx_region *r, scrpos_t x, scrpos_t y) +{ + struct gfx_point point; + color_t pixel; + scrpos_t src_cx, src_cy; + dimm_t w, h; + + if (ctx == NULL || r == NULL) { + return -EINVAL; + } + + w = r->width; + h = r->height; + + for (int xoff = 0; xoff < w; ++xoff) { + for (int yoff = 0; yoff < h; ++yoff) { + /* Source position */ + src_cx = r->x + xoff; + src_cy = r->y + yoff; + + /* Plot the new pixel */ + pixel = gfx_get_pix(ctx, src_cx, src_cy); + point.x = x + xoff; + point.y = y + yoff; + point.rgb = pixel; + gfx_plot_point(ctx, &point); + } + } + + return 0; +} diff --git a/lib/liboda/Makefile b/lib/liboda/Makefile new file mode 100644 index 0000000..5b4022c --- /dev/null +++ b/lib/liboda/Makefile @@ -0,0 +1,29 @@ +CFLAGS = -c -fno-stack-protector -nostdlib -static \ + -Iinclude/ -I$(USRDIR)/include/ +CFILES = $(shell find src/ -name "*.c") +OBJ = $(CFILES:.c=.o) + +all: headers $(OBJ) build/liboda.a + echo $(USRDIR) + mv build/liboda.a $(USRDIR)/lib/ + cp -r include/ $(USRDIR)/include/ + +build/liboda.a: + mkdir -p build/ + ar rcs build/liboda.a $(OBJ) + +%.o: %.c + $(CC) $(CFLAGS) -Iinclude/ $< -o $@ + +.PHONY: headers +headers: + cp -rf include/* $(USRDIR)/include/ + +.PHONY: +build/: + mkdir -p build/ + +.PHONY: clean +clean: + rm -f $(OBJ) + rm -rf build/ diff --git a/lib/liboda/include/liboda/input.h b/lib/liboda/include/liboda/input.h new file mode 100644 index 0000000..c74a304 --- /dev/null +++ b/lib/liboda/include/liboda/input.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBODA_INPUT_H +#define LIBODA_INPUT_H + +#include <stdint.h> +#include <stddef.h> + +/* + * Macros to help extract scancode and + * character + */ +#define ODA_SCANCODE(KEY) ((KEY) >> 8) +#define ODA_KEYCHAR(KEY) ((char )(KEY) & 0xFF) + +/* + * Key defines + */ +#define ODA_KEY_OTHER 0x0000 +#define ODA_KEY_ESCAPE 0x0001 +#define ODA_KEY_TAB 0x0002 +#define ODA_KEY_BACKSPACE 0x0003 + +/* + * Represents a key press + * + * @type: Key types (see ODA_KEY_*) + * @scancode: Scancode + * @ch: Character + */ +struct oda_key { + uint16_t type; + uint8_t scancode; + char ch; +}; + +/* + * ODA keyboard object for managing keyboard + * input. + */ +struct oda_kbd { + int(*handle_keyev)(struct oda_kbd *kbd, struct oda_key *key); +}; + +int oda_kbd_dispatch(struct oda_kbd *kbd); + +#endif /* !LIBODA_INPUT_H */ diff --git a/lib/liboda/include/liboda/oda.h b/lib/liboda/include/liboda/oda.h new file mode 100644 index 0000000..9d96f2f --- /dev/null +++ b/lib/liboda/include/liboda/oda.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBODA_ODA_H +#define LIBODA_ODA_H 1 + +#include <sys/queue.h> +#include <stdint.h> +#include <stddef.h> +#include <liboda/types.h> +#include <libgfx/gfx.h> +#include <libgfx/draw.h> + +/* + * ODA representation of a window. + * + * @wid: Window ID (identifies the window) + * @surface: Window surface descriptor + * @session: Session this window belongs to + */ +struct oda_window { + odawid_t wid; + struct gfx_shape surface; + struct oda_state *session; + TAILQ_ENTRY(oda_window) link; +}; + +/* + * ODA session + * + * @winq: Window queue + * @gctx: Graphics context + * @cookie: State cookie (ODA_COOKIE) + */ +struct oda_state { + TAILQ_HEAD(, oda_window) winq; + struct gfx_ctx gctx; + uint32_t cookie; +}; + +/* + * ODA window attributes. Arguments to be + * passed to oda_window_new() + * + * @session: Current ODA session / state + * @parent: Window parent (NULL for root) + * @pg: Background color (0xRRGGBB) + * @x,y: Window position + * @w,h: Window width [w] and height [h] + */ +struct oda_wattr { + struct oda_state *session; + struct oda_window *parent; + odacolor_t bg; + odapos_t x, y; + odadimm_t w, h; +}; + +/* + * Arguments for oda_movewin() are stored + * within this structure to minimize the + * number of arguments within the function + * signature. + * + * @wp: Window to be moved + * @to_x: X position to move window to + * @to_y: Y position to move window to + */ +struct oda_movewin { + struct oda_window *wp; + odapos_t to_x; + odapos_t to_y; +}; + +/* + * A pixel point that can be plotted + * onto a window. + * + * @x,y: Point position + * @rgb: Color (RGB) + * @window: Window this will be plotted to + * + * Just set x, y, the color (rgb) then point it + * to a window! + */ +struct oda_point { + odapos_t x, y; + odacolor_t rgb; + struct oda_window *window; +}; + +int oda_reqwin(struct oda_wattr *params, struct oda_window **res); +int oda_termwin(struct oda_state *state, struct oda_window *win); + +int oda_plotwin(struct oda_state *state, const struct oda_point *point); +int oda_movewin(struct oda_state *state, struct oda_movewin *params); +int oda_start_win(struct oda_state *state, struct oda_window *win); + +int oda_init(struct oda_state *res); +int oda_shutdown(struct oda_state *state); + +#endif /* !LIBODA_ODA_H */ diff --git a/lib/liboda/include/liboda/odavar.h b/lib/liboda/include/liboda/odavar.h new file mode 100644 index 0000000..d2dbe2e --- /dev/null +++ b/lib/liboda/include/liboda/odavar.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBODA_ODAVAR_H +#define LIBODA_ODAVAR_H + +#include <sys/param.h> +#include <sys/errno.h> +#include <liboda/oda.h> + +/* + * Default window attributes + */ +#define DEFAULT_WIN_HEIGHT 200 +#define DEFAULT_WIN_WIDTH 150 + +/* + * Verify that ODA structures have been properly + * initialized before usage to prevent undefined + * behaviour. + */ +#define ODA_COOKIE 0xFAFECAD + +/* + * Verify an ODA cookie - internal usage + */ +__always_inline static inline int +oda_cookie_verify(struct oda_state *state) +{ + return (state->cookie == ODA_COOKIE) ? 0 : -EFAULT; +} + +#endif /* !LIBODA_ODAVAR_H */ diff --git a/lib/liboda/include/liboda/types.h b/lib/liboda/include/liboda/types.h new file mode 100644 index 0000000..ec12330 --- /dev/null +++ b/lib/liboda/include/liboda/types.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBODA_TYPE_H +#define LIBODA_TYPE_H + +#include <stdint.h> + +typedef uint32_t odapos_t; /* X/Y positions */ +typedef uint32_t odapix_t; /* RGB pixel */ +typedef odapix_t odacolor_t; /* RGB color */ +typedef uint32_t odadimm_t; /* Dimensions */ +typedef uint32_t odawid_t; /* Window ID */ + +#endif /* !LIBODA_TYPE_H */ diff --git a/lib/liboda/src/input.c b/lib/liboda/src/input.c new file mode 100644 index 0000000..797a9d4 --- /dev/null +++ b/lib/liboda/src/input.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/ascii.h> +#include <sys/errno.h> +#include <sys/param.h> +#include <liboda/oda.h> +#include <liboda/input.h> +#include <stdint.h> +#include <stdio.h> + +/* + * Convert key scancode/char values to fixed + * ODA key constants + */ +static inline uint16_t +oda_map_key(const struct oda_key *key) +{ + uint16_t type = ODA_KEY_OTHER; + + switch (key->ch) { + case ASCII_ESC: + type = ODA_KEY_ESCAPE; + break; + case ASCII_HT: + type = ODA_KEY_TAB; + break; + case ASCII_BS: + type = ODA_KEY_BACKSPACE; + break; + } + + return type; +} + +/* + * Dispatch keyboard events. This is typically + * called in an event loop so that keyboard events + * are handled per iteration. + * + * @kbd: Keyboard to monitor + */ +int +oda_kbd_dispatch(struct oda_kbd *kbd) +{ + struct oda_key key; + int input; + + if (kbd == NULL) { + return -EINVAL; + } + + /* Attempt to grab the input */ + if ((input = getchar()) < 0) { + return -EAGAIN; + } + + key.scancode = ODA_SCANCODE(input); + key.ch = ODA_KEYCHAR(input); + key.type = oda_map_key(&key); + return kbd->handle_keyev(kbd, &key); +} diff --git a/lib/liboda/src/oda.c b/lib/liboda/src/oda.c new file mode 100644 index 0000000..0eef523 --- /dev/null +++ b/lib/liboda/src/oda.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/errno.h> +#include <stdio.h> +#include <libgfx/gfx.h> +#include <liboda/oda.h> +#include <liboda/odavar.h> + +#define oda_log(fmt, ...) printf("oda: " fmt, ##__VA_ARGS__) + +/* + * Initialize the OSMORA Display Architecture + * (ODA) library. + * + * @res: Initialized ODA state result + * + * Returns 0 on success, otherwise a less than + * zero value. + */ +int +oda_init(struct oda_state *res) +{ + int error; + + /* Ensure the argument is valid */ + if (res == NULL) { + return -EINVAL; + } + + /* + * If this state has already been initialized, + * assume programmer error / undefined behaviour + * and let them know. + */ + if (oda_cookie_verify(res) == 0) { + oda_log("oda_init: 'res' already initialized\n"); + return -EBUSY; + } + + /* Initialize the graphics context */ + error = gfx_init(&res->gctx); + if (error != 0) { + oda_log("oda_init: could not init graphics context\n"); + return error; + } + + TAILQ_INIT(&res->winq); + res->cookie = ODA_COOKIE; + return 0; +} diff --git a/lib/liboda/src/window.c b/lib/liboda/src/window.c new file mode 100644 index 0000000..216b106 --- /dev/null +++ b/lib/liboda/src/window.c @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/errno.h> +#include <sys/queue.h> +#include <stdlib.h> +#include <string.h> +#include <liboda/oda.h> +#include <liboda/odavar.h> +#include <liboda/types.h> +#include <libgfx/gfx.h> +#include <libgfx/draw.h> + +/* + * The window cache is used to reduce how many + * calls to malloc() and free() are made during + * window creation and destruction. + */ +static TAILQ_HEAD(, oda_window) wcache; +static uint32_t wcache_cookie = 0; +static odawid_t next_wid = 1; + +/* + * Pop a window from the window cache. + * Returns NULL there are no more windows. + */ +static struct oda_window * +oda_window_pop(void) +{ + struct oda_window *wdp; + + if (wcache_cookie != ODA_COOKIE) { + TAILQ_INIT(&wcache); + wcache_cookie = ODA_COOKIE; + return NULL; + } + + wdp = TAILQ_FIRST(&wcache); + TAILQ_REMOVE(&wcache, wdp, link); + return wdp; +} + +/* + * Place a window into the window cache. + */ +static void +oda_window_cache(struct oda_window *wdp) +{ + /* Ensure arg is valid */ + if (wdp == NULL) { + return; + } + + if (wcache_cookie != ODA_COOKIE) { + TAILQ_INIT(&wcache); + wcache_cookie = ODA_COOKIE; + } + + TAILQ_INSERT_TAIL(&wcache, wdp, link); +} + +/* + * Allocate an ODP window + * + * Returns NULL on failure + */ +static struct oda_window * +oda_window_alloc(void) +{ + struct oda_window *wdp; + + /* + * First check if there are any entries + * we can grab from the window cache. + */ + wdp = oda_window_pop(); + if (wdp != NULL) { + return wdp; + } + + /* Allocate a new window */ + wdp = malloc(sizeof(*wdp)); + if (wdp == NULL) { + return NULL; + } + + memset(wdp, 0, sizeof(*wdp)); + wdp->wid = next_wid++; + return wdp; +} + +/* + * Release a given ODA window descriptor + * + * @wdp: Window to free + */ +static void +oda_window_release(struct oda_state *state, struct oda_window *wdp) +{ + if (wdp == NULL) { + return; + } + + /* + * It is probably a good idea to ensure previous + * state other than the old window ID is reset + * and zeroed. + */ + wdp->session = NULL; + memset(&wdp->surface, 0, sizeof(wdp->surface)); + + /* + * Now we can remove this window from the list + * of windows we are tracking and add it to the + * cache. + */ + TAILQ_REMOVE(&state->winq, wdp, link); + oda_window_cache(wdp); +} + +/* + * Check if a point is within the bounds of + * a surface. + * + * @wp: Surface to check with point + * @point: Point to check with surface + * + * Returns 0 if the check has passed, + * otherwise a less than zero value. + */ +static int +oda_check_point(struct oda_window *wp, struct oda_point *point) +{ + struct gfx_shape *surf; + scrpos_t win_startx; + scrpos_t win_starty; + scrpos_t win_endx; + scrpos_t win_endy; + + /* Compute start positions */ + surf = &wp->surface; + win_startx = surf->x; + win_starty = surf->y; + + /* Compute end positions */ + win_endx = surf->x + surf->width; + win_endy = surf->y + surf->height; + + /* Check X bounds */ + if (point->x < win_startx || point->x > win_endx) { + return -1; + } + + /* Check Y bounds */ + if (point->y < win_starty || point->y > win_endy) { + return -1; + } + + /* All good */ + return 0; +} + +/* + * Clean up after ourselves and release + * each entry of the wcache. + * + * Returns 0 on success. + */ +static int +oda_free_wcache(void) +{ + struct oda_window *wdp, *next; + + if (wcache_cookie != ODA_COOKIE) { + return -1; + } + + /* + * Go through each entry and call free() + * on them. + */ + wdp = TAILQ_FIRST(&wcache); + while (wdp != NULL) { + next = TAILQ_NEXT(wdp, link); + free(wdp); + wdp = next; + } + return 0; +} + +/* + * Plot a pixel onto a window + * + * @state: ODA state pointer + * @point: Point to plot + * + * Returns 0 on success, otherwise a less than + * zero value. + * + * XXX: The x/y params in the 'point' argument must be + * relative to the start of the window. In other words, + * (0,0) refers to the top left corner of the window. + */ +int +oda_plotwin(struct oda_state *state, const struct oda_point *point) +{ + struct gfx_point pixel; + struct oda_point point_new; + struct oda_window *window; + struct gfx_shape *surf; + odapos_t plotx, ploty; + int error; + + if (state == NULL || point == NULL) { + return -EINVAL; + } + + /* Validate cookie */ + if ((error = oda_cookie_verify(state)) != 0) { + return error; + } + + /* Try to grab the window */ + if ((window = point->window) == NULL) { + return -EINVAL; + } + + surf = &window->surface; + plotx = surf->x + point->x; + ploty = surf->y + point->y; + + /* + * We are going to need to transform the coordinates + * as they are supposed to be coming in relative to + * the window bounds, e.g., (0,0) being the top left + * corner of a window. + */ + point_new = *point; + point_new.x = plotx; + point_new.y = ploty; + + /* Initialize the pixel to plot */ + pixel.x = plotx; + pixel.y = ploty; + pixel.rgb = point->rgb; + + /* Is the point within bounds? */ + error = oda_check_point(window, &point_new); + if (error < 0) { + return error; + } + + gfx_plot_point(&state->gctx, &pixel); + return 0; +} + +/* + * Request a window from the OSMORA Display + * Architecture (ODA). + * + * @params: Arguments + * @res: Resulting pointer for new window + * + * Returns 0 on success, otherwise a less than + * zero value. + */ +int +oda_reqwin(struct oda_wattr *params, struct oda_window **res) +{ + struct oda_window *wp; + struct gfx_shape *surf; + struct oda_state *session; + int error; + + if (params == NULL || res == NULL) { + return -EINVAL; + } + + /* Try to grab the current session */ + if ((session = params->session) == NULL) { + return -EIO; + } + + /* Verify that cookie! */ + if ((error = oda_cookie_verify(session)) != 0) { + return error; + } + + /* Allocate a new window */ + wp = oda_window_alloc(); + if (wp == NULL) { + return -ENOMEM; + } + + /* Initialize the window */ + memset(wp, 0, sizeof(*wp)); + wp->session = session; + TAILQ_INSERT_TAIL(&session->winq, wp, link); + + /* Fix up width/height params */ + if (params->w == 0) + params->w = DEFAULT_WIN_WIDTH; + if (params->h == 0) + params->h = DEFAULT_WIN_HEIGHT; + + /* Initialize the window surface */ + surf = &wp->surface; + surf->color = params->bg; + surf->x = params->x; + surf->y = params->y; + surf->width = params->w; + surf->height = params->h; + surf->type = SHAPE_SQUARE; + *res = wp; + return 0; +} + +/* + * Move a window to a new position on the + * screen. + * + * @state: ODA state pointer + * @params: Arguments to this function + */ +int +oda_movewin(struct oda_state *state, struct oda_movewin *params) +{ + struct oda_window *win; + struct gfx_shape *wsurf; + struct gfx_region r; + odadimm_t w, h; + odapos_t x, y, x_f, y_f; + odapos_t to_x, to_y; + uint32_t i = 0; + int error; + + /* Make sure arguments are valid */ + if (state == NULL || params == NULL) { + return -EINVAL; + } + + /* We need the window */ + if ((win = params->wp) == NULL) { + return -EINVAL; + } + + /* Verify state cookie */ + if ((error = oda_cookie_verify(state)) != 0) { + return error; + } + + wsurf = &win->surface; + to_x = params->to_x; + to_y = params->to_y; + + r.x = wsurf->x; + r.y = wsurf->y; + r.width = wsurf->width; + r.height = wsurf->height; + + /* + * We will copy the window to the new location and + * fill where the old window was with GFX_BLACK. + * + * TODO: Handle overlapping of windows + */ + gfx_copy_region(&state->gctx, &r, to_x, to_y); + wsurf->x = to_x; + wsurf->y = to_y; + return 0; +} + +/* + * Register a window into the current ODA state. + * Everytime a compositor requests a window, we + * must keep track of it. + * + * @state: ODA state pointer + * @win: Pointer of window to register + */ +int +oda_start_win(struct oda_state *state, struct oda_window *win) +{ + int error; + + if (state == NULL || win == NULL) { + return -EINVAL; + } + + /* Make sure the state is valid */ + if ((error = oda_cookie_verify(state)) != 0) { + return error; + } + + gfx_draw_shape(&state->gctx, &win->surface); + return 0; +} + +/* + * Terminate a running window + * + * @state: ODA state pointer + * @win: Win pointer + * + * Returns 0 on success, otherwise a less than + * zero value. + * + * TODO: Cleanup screen + */ +int +oda_termwin(struct oda_state *state, struct oda_window *win) +{ + int error; + + if (state == NULL || win == NULL) { + return -EINVAL; + } + + /* Validate the cookie */ + if ((error = oda_cookie_verify(state)) != 0) { + return error; + } + + oda_window_release(state, win); + return 0; +} + +/* + * Shutdown the ODA library + */ +int +oda_shutdown(struct oda_state *state) +{ + return oda_free_wcache(); +} diff --git a/share/docs/lib/liboda.md b/share/docs/lib/liboda.md new file mode 100644 index 0000000..e5345a4 --- /dev/null +++ b/share/docs/lib/liboda.md @@ -0,0 +1,229 @@ +# The OSMORA Display Architecture (ODA) + +Written by Ian M. Moffett + +## Introduction + +The OSMORA Display Architecture (ODA) is a protocol describing how +a compositor should create and manage graphical windows. A graphical +session in Hyra consists of a compositor, window management system and +management of user input. + +There are many existing display architectures out there. Take for instace, the X11 +protocol. X11 for example, has the concept of an "X server" in which window managers +or certain graphical programs would connect to as a means of performing interprocess +communication (IPC). The idea is that X will service graphics related requests from +the window manager or compositor. + +While this works just fine, the highly centralized nature of X11 or similar protocols +may complicate the flexibility of the graphics stack. On the other hand with ODA, a +compositor links with the ODA library and becomes the server for window managers running +on the system. The idea of ODA is to minimize complexity while preserving flexibility. + +Additionally, the compositor should provide its own API for use by window management +software. + +## Scope + +This document serves to describe common OSMORA Display Architecture (ODA) concepts +as well as providing basic example C sources showcasing how compositors and window +managers may interface with the described APIs for various needs. + +## Terminology + +### OSMORA Display Architecture (ODA): + +OSMORA protocol defining common interfaces for C2W and +W2C interactions. + +### Compositor to window (C2W): + +Describes the direction of communication originating from +a compositor and directed towards a specific window: + +``COMPOSITOR -> WINDOW`` + +### Window to compositor (W2C): + +Describes the direction of communication originating from +a specific window to a compositor running on the system: + +``WINDOW -> COMPOSITOR`` + +## Architecture + +``` ++-------------+ +| LIBGFX | ++-------------+ + ^ + | linked with libgfx + V ++-------------+ +| COMPOSITOR | ++-------------+ <---+ signal + | | | | + | | | | c2w: <winop: e.g., close> + | | | | w2c: <winop to accept: e.g., close> + WIN WIN WIN <---+ +``` + +### C2W signal flow: + +``` +-- CLOSE SIGNAL EXAMPLE -- + +WINDOW RECEIVES CLOSE SIGNAL + | + ECHO BACK TO COMPOSITOR + | + YES ---+--- NO + | | + | V + | nothing happens + | + V + window is destroyed by compositor +``` + +## Libgfx + +The Hyra userspace includes ``libgfx`` which is a low-level graphics library aimed +to facilitate drawing on the screen and performing various graphical operations while +decoupling from the concept of compositors, windows and the ODA as a whole. In other words, +libgfx has no knowledge of anything outside of the framebuffer and itself. + +The following is an example of how one may draw a yellow square at +x/y (30,0): + +```c +#include <libgfx/gfx.h> /* Common routines/defs */ +#include <libgfx/draw.h> /* Drawing related routines/defs */ + +int +main(void) +{ + struct gfx_ctx ctx; + struct gfx_shape sh = GFX_SHAPE_DEFAULT; + int error; + + /* Set the x/y and color */ + sh.x = 30; + sh.y = 0; + sh.color = GFX_YELLOW + + error = gfx_init(&ctx); + if (error < 0) { + printf("gfx_init returned %d\n", error); + return error; + } + + /* Draw the square and cleanup */ + gfx_draw_shape(&ctx, &sh); + gfx_cleanup(&ctx); + return 0; +} +``` + +## Liboda + +The Hyra userspace includes the ``liboda`` library which includes various +interfaces conforming to the OSMORA Display Architecture (ODA). + +### Linking a compositor with liboda + +In order for an ODA compliant compositor to reference library +symbols for ``liboda``, it should use the following linker flags: + +``... -loda -logfx`` + +### ODA Session + +For the ODA library to keep track of state, it relies on an ``oda_state`` +structure defined in ``liboda/oda.h``. Additionally, in order for any ODA +library calls to be made, the compositor must initialize the library with +``oda_init()`` like in the following example: + +```c +#include <liboda/oda.h> + +struct oda_state state; +int error; + +/* Returns 0 on success */ +error = oda_init(&state); +... +``` + +Upon failure of ``oda_init()`` a negative POSIX errno value +is returned (see ``sys/errno.h``). + +### Using liboda to request windows + +A compositor may request windows from the ODA by using +``oda_reqwin()`` like in the following example: + +```c +#include <liboda/oda.h> + +... +struct oda_wattr wattr; +struct oda_state state; + +... + +wattr.session = &state; +wattr.parent = NULL; +wattr.bg = GFX_YELLOW; +wattr.x = 200; +wattr.y = 150; +wattr.w = 120; +wattr.h = 300; + +/* Returns 0 on success */ +error = oda_reqwin(&wattr, &win); +``` + +Arguments passed to ``oda_reqwin()`` are first stored in a ``struct oda_wattr`` +structure to minimize the number of parameters used in the function signature. + +Upon failure of ``oda_reqwin()`` a negative POSIX errno value +is returned (see ``sys/errno.h``). + +### Input management + +The liboda library additionally provides an input API that facilitates +management of user input (e.g., keyboard). + +(See ``liboda/input.h``) + +#### The ``oda_key`` structure + +ODA provides a standard description of keys received from a keyboard +device. Keyboard input is represented by the ``oda_key`` structure shown +below: + +```c +struct oda_key { + uint16_t type; + uint8_t scancode; + char ch; + ... +}; +``` + +The ``type`` field describes the key type, valid key types can be +found within the ``ODA_KEY_*`` definitions in ``liboda/input.h``. + +The ``scancode`` field contains the raw keyboard scancode received +from the device. + +The ``ch`` field contains the ASCII representation of the input received +from the device. + +#### The ``oda_kbd`` structure + +ODA represents keyboard devices through the ``oda_kbd`` structure found +in ``liboda/input.h``. This structure contains callbacks that are set up +by the compositor to be invoked when their respective keyboard related +event occurs. diff --git a/sys/include/sys/sched.h b/sys/include/sys/sched.h index 7a859b2..19ceb7e 100644 --- a/sys/include/sys/sched.h +++ b/sys/include/sys/sched.h @@ -33,6 +33,7 @@ #include <sys/proc.h> #include <sys/cdefs.h> #include <sys/limits.h> +#include <sys/time.h> /* * Scheduler CPU information @@ -66,6 +67,7 @@ void sched_stat(struct sched_stat *statp); void sched_init(void); void sched_yield(void); +void sched_suspend(struct proc *td, const struct timeval *tv); void sched_detach(struct proc *td); __dead void sched_enter(void); diff --git a/sys/include/sys/socket.h b/sys/include/sys/socket.h index c82ae4e..1a33108 100644 --- a/sys/include/sys/socket.h +++ b/sys/include/sys/socket.h @@ -69,6 +69,10 @@ typedef uint32_t socklen_t; /* Socket types */ #define SOCK_STREAM 1 +/* Socket option names */ +#define SO_RCVTIMEO 0 /* Max time recv(2) waits */ +#define _SO_MAX 1 /* Max socket options */ + struct sockaddr_un { sa_family_t sun_family; char sun_path[108]; @@ -149,12 +153,22 @@ struct cmsg_list { uint8_t is_init : 1; }; +/* + * Socket option that may be applied to + * sockets on the system. + */ +struct sockopt { + socklen_t len; + char data[]; +}; + struct ksocket { int sockfd; union { struct sockaddr sockaddr; struct sockaddr_un un; }; + struct sockopt *opt[_SO_MAX]; struct proc *owner; struct cmsg_list cmsg_list; struct sockbuf buf; @@ -170,10 +184,13 @@ scret_t sys_send(struct syscall_args *scargs); scret_t sys_recvmsg(struct syscall_args *scargs); scret_t sys_sendmsg(struct syscall_args *scargs); +scret_t sys_setsockopt(struct syscall_args *scargs); #endif /* _KERNEL */ int socket(int domain, int type, int protocol); int bind(int sockfd, const struct sockaddr *addr, socklen_t len); + +int setsockopt(int sockfd, int level, int name, const void *v, socklen_t len); int connect(int sockfd, const struct sockaddr *addr, socklen_t len); ssize_t send(int sockfd, const void *buf, size_t size, int flags); diff --git a/sys/include/sys/syscall.h b/sys/include/sys/syscall.h index 9798d80..f53db8f 100644 --- a/sys/include/sys/syscall.h +++ b/sys/include/sys/syscall.h @@ -66,6 +66,7 @@ #define SYS_sendmsg 25 #define SYS_recvmsg 26 #define SYS_connect 27 +#define SYS_setsockopt 28 #if defined(_KERNEL) /* Syscall return value and arg type */ diff --git a/sys/kern/kern_sched.c b/sys/kern/kern_sched.c index 23a1ebb..9c5e215 100644 --- a/sys/kern/kern_sched.c +++ b/sys/kern/kern_sched.c @@ -315,6 +315,92 @@ proc_unpin(struct proc *td) td->flags &= ~PROC_PINNED; } +/* + * Suspend a process for a specified amount + * of time. This calling process will yield for + * the amount of time specified in 'tv' + * + * @td: Process to suspend (NULL for current) + * @tv: Time value to use + * + * XXX: 'tv' being NULL is equivalent to calling + * sched_detach() + */ +void +sched_suspend(struct proc *td, const struct timeval *tv) +{ + struct timer tmr; + const time_t USEC_PER_SEC = 1000000; + ssize_t usec; + time_t usec_cur, usec_tmp; + bool have_timer = true; + tmrr_status_t tmr_status; + + if (td == NULL) + td = this_td(); + if (__unlikely(td == NULL)) + return; + + if (tv == NULL) { + sched_detach(td); + return; + } + + /* + * Now, we need a generic timer so that we can compute + * how much time has elapsed since this process has + * requested to be suspended. However, we cannot assume + * that it would be present. If the lookup fails, all we + * can do is try to estimate how much time went by which + * works fine too, just not as accurate. + */ + tmr_status = req_timer(TIMER_GP, &tmr); + if (tmr_status != TMRR_SUCCESS) { + have_timer = false; + } + + /* We need microsecond precision */ + if (tmr.get_time_sec == NULL) { + have_timer = false; + } + + /* + * Compute the max time in microseconds that + * we will wait. We are using both tv->tv_sec + * and tv->tv_usec + */ + usec = tv->tv_usec; + usec += tv->tv_sec * USEC_PER_SEC; + usec_cur = (have_timer) ? tmr.get_time_usec() : 0; + + for (;;) { + sched_yield(); + + /* + * If we have a timer in our paws, compute how much + * time went by. Otherwise we estimate by subtracting + * the scheduler quantum. + * + * XXX: The timing here works decently as intended. However, + * it would be nice to smoothen out any jitter. Such can + * probably be done by subtracting 'usec' by the exponential + * moving average of 'usec_tmp' rather than the raw original + * value. + */ + if (have_timer) { + usec_tmp = (tmr.get_time_usec() - usec_cur); + } else { + usec_tmp = DEFAULT_TIMESLICE_USEC; + } + + /* We are done here! */ + usec -= usec_tmp; + if (usec <= 0) { + break; + } + } +} + void sched_init(void) { diff --git a/sys/kern/kern_socket.c b/sys/kern/kern_socket.c index 1717f5a..d0fbe19 100644 --- a/sys/kern/kern_socket.c +++ b/sys/kern/kern_socket.c @@ -31,6 +31,7 @@ #include <sys/sio.h> #include <sys/systm.h> #include <sys/proc.h> +#include <sys/time.h> #include <sys/namei.h> #include <sys/sched.h> #include <sys/errno.h> @@ -47,6 +48,17 @@ static struct vops socket_vops; /* + * This table maps socket option names to + * lengths of their underlying structure. + * + * This is used for bounds/length checking within + * setsockopt() + */ +static size_t sockopt_lentab[_SO_MAX] = { + [ SO_RCVTIMEO ] = sizeof(struct timeval) +}; + +/* * Get a kernel socket structure from a * file descriptor. * @@ -101,6 +113,7 @@ static int socket_reclaim(struct vnode *vp) { struct ksocket *ksock; + struct sockopt *opt; /* Is this even a socket? */ if (vp->type != VSOCK) { @@ -112,6 +125,15 @@ socket_reclaim(struct vnode *vp) return -EIO; } + /* Free up any used options */ + for (int i = 0; i < _SO_MAX; ++i) { + opt = ksock->opt[i]; + if (opt != NULL) { + dynfree(opt); + ksock->opt[i] = NULL; + } + } + fd_close(ksock->sockfd); mutex_free(ksock->mtx); dynfree(ksock); @@ -185,6 +207,46 @@ connect_domain(int sockfd, struct ksocket *ksock, struct sockaddr_un *un) } /* + * Wait until data is received for the + * recv() function. + * + * @sockfd: Socket we are waiting on + * + * Returns zero on success, otherwise a less + * than zero value is returned. + */ +static int +socket_rx_wait(int sockfd) +{ + struct ksocket *ksock; + struct sockopt *opt; + struct timeval tv; + int error; + + if (ksock == NULL) { + return -EINVAL; + } + + error = get_ksock(sockfd, &ksock); + if (error < 0) { + return error; + } + + /* + * If the socket does not have this option set, + * we will assume that there is no timeout value. + */ + opt = ksock->opt[SO_RCVTIMEO]; + if (opt == NULL) { + return 0; + } + + memcpy(&tv, opt->data, opt->len); + sched_suspend(NULL, &tv); + return 0; +} + +/* * Send data to socket - POSIX send(2) core * * @sockfd: File descriptor that backs this socket @@ -254,7 +316,7 @@ send(int sockfd, const void *buf, size_t size, int flags) * @size: Size of the buffer * @flags: Optional flags * - * Returns zero on success, otherwise a less + * Returns length on success, otherwise a less * than zero errno. */ ssize_t @@ -264,6 +326,7 @@ recv(int sockfd, void *buf, size_t len, int flags) struct sockbuf *sbuf; struct netbuf *netbuf; size_t head; + ssize_t retval = len; int error; /* Length cannot be zero */ @@ -283,7 +346,8 @@ recv(int sockfd, void *buf, size_t len, int flags) if (netbuf->len == 0) { sbuf->head = 0; sbuf->tail = 0; - return -EAGAIN; + retval = -EAGAIN; + goto done; } if (len > netbuf->len) { @@ -292,10 +356,10 @@ recv(int sockfd, void *buf, size_t len, int flags) head = sbuf->head; memcpy(buf, &netbuf->data[head], len); - sbuf->head = (sbuf->head + len) % NETBUF_LEN; +done: mutex_release(ksock->mtx); - return len; + return retval; } /* @@ -322,6 +386,7 @@ socket(int domain, int type, int protocol) goto fail; } + memset(ksock, 0, sizeof(*ksock)); sbuf = &ksock->buf; sbuf->head = 0; sbuf->tail = 0; @@ -394,6 +459,68 @@ bind(int sockfd, const struct sockaddr *addr, socklen_t len) } /* + * Set socket options - POSIX setsockopt(3) core + * + * @sockfd: File descriptor of socket + * @level: Protocol level + * @v: Options value + * @len: Length of data pointed to by 'v' + */ +int +setsockopt(int sockfd, int level, int name, const void *v, socklen_t len) +{ + struct ksocket *ksock; + struct sockopt *opt; + size_t exp_len; + int error; + + /* Must have a valid fd */ + if (sockfd < 0) { + return -EBADF; + } + + /* Ensure value and length are valid */ + if (v == NULL || len == 0) { + return -EINVAL; + } + + /* Verify the name */ + if (name >= _SO_MAX) { + return -EINVAL; + } + + /* Grab a new socket */ + if ((error = get_ksock(sockfd, &ksock)) < 0) { + return error; + } + + /* Clamp the input length as needed */ + exp_len = sockopt_lentab[name]; + if (len > exp_len) { + len = exp_len; + } + + /* + * Here we will grab the socket options. If it is + * NULL, we'll need to allocate one. + */ + if ((opt = ksock->opt[name]) == NULL) { + opt = dynalloc(sizeof(*opt) + len); + + if (opt == NULL) { + return -ENOMEM; + } + + opt->len = len; + ksock->opt[name] = opt; + } + + memcpy(opt->data, v, len); + opt->len = len; + return 0; +} + +/* * Connect to a socket * * @sockfd: File descriptor to connect @@ -614,10 +741,29 @@ sys_recv(struct syscall_args *scargs) return -ENOBUFS; } - do { + for (;;) { error = recv(sockfd, buf, len, flags); - sched_yield(); - } while (error == -EAGAIN); + if (error <= 0 && error != -EAGAIN) { + break; + } + + /* + * Wait for data to be ready on the socket. + * If a less than zero value is returned, don't + * handle timeouts. + */ + error = socket_rx_wait(sockfd); + if (error < 0) { + continue; + } + + /* Try one more time, obey timeout */ + error = recv(sockfd, buf, len, flags); + if (error == -EAGAIN) { + return error; + } + break; + } if (error < 0) { pr_error("sys_recv: recv() fail (fd=%d)\n", sockfd); @@ -776,7 +922,7 @@ sys_sendmsg(struct syscall_args *scargs) } /* - * connnect(3) syscall + * connect(3) syscall * * arg0: sockfd * arg1: address @@ -807,6 +953,55 @@ sys_connect(struct syscall_args *scargs) return connect(sockfd, sockaddr, len); } +/* + * POSIX setsockopt(3) syscall + * + * arg0: sockfd + * arg1: level + * arg2: name + * arg3: data + * arg4: len + */ +scret_t +sys_setsockopt(struct syscall_args *scargs) +{ + int sockfd = scargs->arg0; + int level = scargs->arg1; + int name = scargs->arg2; + void *u_data = (void *)scargs->arg3; + socklen_t len = scargs->arg4; + void *data; + size_t exp_len; + int retval; + + /* Verify that the name is correct */ + if (name >= _SO_MAX) { + return -EINVAL; + } + + /* Clamp length as needed */ + exp_len = sockopt_lentab[name]; + if (len > exp_len) { + len = exp_len; + } + + data = dynalloc(len); + if (data == NULL) { + return -ENOMEM; + } + + /* Grab data from userland */ + retval = copyin(u_data, data, len); + if (retval < 0) { + dynfree(data); + return retval; + } + + retval = setsockopt(sockfd, level, name, data, len); + dynfree(data); + return retval; +} + static struct vops socket_vops = { .read = NULL, .write = NULL, diff --git a/sys/kern/kern_syscall.c b/sys/kern/kern_syscall.c index 576b7aa..ba70b12 100644 --- a/sys/kern/kern_syscall.c +++ b/sys/kern/kern_syscall.c @@ -68,6 +68,7 @@ scret_t(*g_sctab[])(struct syscall_args *) = { sys_sendmsg, /* SYS_sendmsg */ sys_recvmsg, /* SYS_recvmsg */ sys_connect, /* SYS_connect */ + sys_setsockopt, /* SYS_setsockopt */ }; const size_t MAX_SYSCALLS = NELEM(g_sctab); |