|
From: falcovorbis <fal...@us...> - 2024-07-02 04:06:00
|
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "A pseudo Operating System for the Dreamcast.".
The branch, master has been updated
via eb3ed416c8ddf2038376e362a15939b057b37628 (commit)
via 4bccb15070aacb448110b35839265a7bd4ce3db4 (commit)
via 8420766f5fee798a1e35c0f0cfd17e2094606ebd (commit)
via f7ca4fc012173af8d4f0ed0ea992a485463624e2 (commit)
via 96eb72d476bd6c09badd205f9dd79c8f37ba1f0c (commit)
via d847f9262cb80c3977707331dd6908fe4c8be337 (commit)
from d74baddec77b6b00535e065982bf2a80b0ed0ec0 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit eb3ed416c8ddf2038376e362a15939b057b37628
Author: Donald Haase <qu...@ya...>
Date: Tue Jul 2 00:05:19 2024 -0400
Threaded Controller Callbacks v2 (#608)
* Install cont callbacks as worker threads
* Avoid deadlock when cleaning up workers
* Switch to TAILQ to allow prefering individual controllers
* Have worker free itself after being signaled to quit by destroy.
* Prevent thd_join(thd_current) deadlock. Update documentation.
* Patch race in thd_worker_destroy
* Ensure that destroyed threads are removed from the genwait queue
* Ensure that a worker can't start waiting on itself after it was signaled to quit
* Fix typo
* Cache the value of worker->thd in case the worker manages to quit and free itself prior to getting to us calling thd_join. [UAF]
* Prevent iterating through the callback list in an irq in case the irq was triggered while the list was being modified.
* Disable irqs later in the process of shutting down the system. This specifically allows maple_shutdown to gracefully shut down the threads it has set up for button callbacks. This has the effect of also enabling irqs during the other chained shutdowns in snd_shutdown and hardware_shutdown generally.
* Add the matching mutex_unlock for the trylock, as that's how mutexes work. Without this it will deadlock on any callback add or remove including shutdown.
---------
Co-authored-by: QuzarDC <qu...@co...>
Co-authored-by: Falco Girgis <gyr...@gm...>
commit 4bccb15070aacb448110b35839265a7bd4ce3db4
Author: Falco Girgis <gyr...@gm...>
Date: Mon Jul 1 22:34:07 2024 -0500
Added Reentrant Mutex Example (#644)
* Added darc's "reentrant_mutex" example
- This is a reimagining of rustc's "ReentrantLock" which is used
internally by the stdlib to implement println!().
- This code originally exposed a horrible race condition in the kernel
with multiple threads being able to acquire a single mutex.
- Cleaned up the example, commented the hell out of it, turned it into
essentially a regression test as well as a general threading
demonstration.
---------
Co-authored-by: darcagn <da...@pr...>
commit 8420766f5fee798a1e35c0f0cfd17e2094606ebd
Author: Falco Girgis <gyr...@gm...>
Date: Mon Jul 1 22:26:32 2024 -0500
Fixed vmu_beep example to gracefully exit (#650)
* Fixed vmu_beep example to gracefully exit
- We were previously freeing the font resources within a callback
registered as an atexit() handler.
- When we deferred the controller button callback mechanism into another
thread, our call to arch_exit() went from happening in the main thread
to happening in the deferred thread.
- This caused a race condition when resources that the main thread was
still using were ripped away and shut down within the deferred thread
which was the one exiting (and calling the atexit() chain).
- Changed the reset button callback handler to simply set an atomic
boolean which signals for the main thread to exit its run loop.
- Moved shutdown logic to after the run loop in main().
- Added logic to stop playing any active effect upon exit.
- Added #defines for two effects, so they wouldn't be magic numbers.
a. VMU_STOP_EFFECT
b. VMU_DEFAULT_EFFECT
- Added VMU_DEFAULT_EFFECT to initialize the starting effect to a known
good value, so a user (or KOS developer testing) doesn't have to do a
bunch of button presses to get a beep to work. Also less confusing on
the user to have a known good default, so pressing A to beep will
immediately work.
commit f7ca4fc012173af8d4f0ed0ea992a485463624e2
Author: Andy Barajas <and...@gm...>
Date: Mon Jul 1 01:52:53 2024 -0700
Fix pty (#636)
* 'Fixed' pty. Added dup,dup2 and pipe to koslib
* Added pty example to fix issue #224
* Fix the error handling gap in fs_pty_create()
* Implement alternative method to see if fd is a PTY
---------
Co-authored-by: Falco Girgis <gyr...@gm...>
commit 96eb72d476bd6c09badd205f9dd79c8f37ba1f0c
Author: Andy Barajas <and...@gm...>
Date: Sun Jun 30 23:02:58 2024 -0700
/dev rewinddir on close (#645)
commit d847f9262cb80c3977707331dd6908fe4c8be337
Author: Andy Barajas <and...@gm...>
Date: Sun Jun 30 22:07:51 2024 -0700
/rd . & .. are now recognized as directories (#646)
-----------------------------------------------------------------------
Summary of changes:
examples/dreamcast/Makefile | 2 +-
examples/dreamcast/basic/threading/Makefile | 3 +
.../{general => reentrant_mutex}/Makefile | 6 +-
.../threading/reentrant_mutex/reentrant_mutex.c | 206 +++++++++++++++++++++
examples/dreamcast/filesystem/Makefile | 16 ++
.../cdda/basic_cdda => filesystem/pty}/Makefile | 11 +-
examples/dreamcast/filesystem/pty/pty.c | 91 +++++++++
examples/dreamcast/vmu/vmu_beep/beep.c | 51 +++--
include/sys/ioctl.h | 43 +++++
include/sys/termios.h | 48 +++++
kernel/arch/dreamcast/hardware/maple/controller.c | 125 +++++++++++--
.../arch/dreamcast/include/dc/maple/controller.h | 21 +--
kernel/arch/dreamcast/kernel/init.c | 9 +-
kernel/fs/fs.c | 24 ++-
kernel/fs/fs_dev.c | 5 +
kernel/fs/fs_pty.c | 116 +++++++++---
kernel/fs/fs_romdisk.c | 9 +-
kernel/libc/koslib/Makefile | 2 +-
kernel/libc/koslib/dbglog.c | 11 +-
kernel/libc/koslib/dup.c | 13 ++
kernel/libc/koslib/dup2.c | 13 ++
kernel/libc/koslib/pipe.c | 29 +++
kernel/libc/newlib/Makefile | 2 +-
kernel/libc/newlib/newlib_isatty.c | 29 +--
kernel/libc/newlib/newlib_tcgetattr.c | 12 ++
kernel/thread/thread.c | 4 +
kernel/thread/worker.c | 14 +-
27 files changed, 793 insertions(+), 122 deletions(-)
copy examples/dreamcast/basic/threading/{general => reentrant_mutex}/Makefile (77%)
create mode 100644 examples/dreamcast/basic/threading/reentrant_mutex/reentrant_mutex.c
create mode 100644 examples/dreamcast/filesystem/Makefile
copy examples/dreamcast/{sound/cdda/basic_cdda => filesystem/pty}/Makefile (75%)
create mode 100644 examples/dreamcast/filesystem/pty/pty.c
create mode 100644 include/sys/ioctl.h
create mode 100644 include/sys/termios.h
create mode 100644 kernel/libc/koslib/dup.c
create mode 100644 kernel/libc/koslib/dup2.c
create mode 100644 kernel/libc/koslib/pipe.c
create mode 100644 kernel/libc/newlib/newlib_tcgetattr.c
diff --git a/examples/dreamcast/Makefile b/examples/dreamcast/Makefile
index 546a2f86..8afe9ad9 100644
--- a/examples/dreamcast/Makefile
+++ b/examples/dreamcast/Makefile
@@ -6,7 +6,7 @@
#
DIRS = 2ndmix basic libdream kgl hello sound png network vmu conio pvr video \
- lua parallax modem dreameye sd g1ata lightgun keyboard sdl dev rumble \
+ lua parallax modem dreameye filesystem sd g1ata lightgun keyboard sdl dev rumble \
micropython
ifdef KOS_CCPLUS
diff --git a/examples/dreamcast/basic/threading/Makefile b/examples/dreamcast/basic/threading/Makefile
index e76cb63b..ba062f4b 100644
--- a/examples/dreamcast/basic/threading/Makefile
+++ b/examples/dreamcast/basic/threading/Makefile
@@ -13,6 +13,7 @@ all:
$(KOS_MAKE) -C tls
$(KOS_MAKE) -C spinlock_test
$(KOS_MAKE) -C atomics
+ $(KOS_MAKE) -C reentrant_mutex
clean:
$(KOS_MAKE) -C compiler_tls clean
@@ -23,6 +24,7 @@ clean:
$(KOS_MAKE) -C tls clean
$(KOS_MAKE) -C spinlock_test clean
$(KOS_MAKE) -C atomics clean
+ $(KOS_MAKE) -C reentrant_mutex clean
dist:
$(KOS_MAKE) -C compiler_tls dist
@@ -33,3 +35,4 @@ dist:
$(KOS_MAKE) -C tls dist
$(KOS_MAKE) -C spinlock_test dist
$(KOS_MAKE) -C atomics dist
+ $(KOS_MAKE) -C reentrant_mutex dist
diff --git a/examples/dreamcast/basic/threading/general/Makefile b/examples/dreamcast/basic/threading/reentrant_mutex/Makefile
similarity index 77%
copy from examples/dreamcast/basic/threading/general/Makefile
copy to examples/dreamcast/basic/threading/reentrant_mutex/Makefile
index 360dea07..b73332f4 100644
--- a/examples/dreamcast/basic/threading/general/Makefile
+++ b/examples/dreamcast/basic/threading/reentrant_mutex/Makefile
@@ -1,11 +1,11 @@
# KallistiOS ##version##
#
# basic/threading/Makefile
-# (c)2001 Megan Potter
+# Copyright (C) 2024 Eric Fradella
#
-TARGET = general_threading_test.elf
-OBJS = general_threading_test.o
+TARGET = reentrant_mutex.elf
+OBJS = reentrant_mutex.o
all: rm-elf $(TARGET)
diff --git a/examples/dreamcast/basic/threading/reentrant_mutex/reentrant_mutex.c b/examples/dreamcast/basic/threading/reentrant_mutex/reentrant_mutex.c
new file mode 100644
index 00000000..29a3d93f
--- /dev/null
+++ b/examples/dreamcast/basic/threading/reentrant_mutex/reentrant_mutex.c
@@ -0,0 +1,206 @@
+/* KallistiOS ##version##
+
+ reentrant_mutex.c
+
+ Copyright (C) 2024 Eric Fradella
+ Copyright (C) 2024 Falco Girgis
+
+ Concurrency example that creates a "reentrant mutex" (aka recursive mutex)
+ on top of KOS's basic mutex.
+
+ Normally, you would not want to do this, as KOS mutexes can simply be used
+ with "MUTEX_TYPE_RECURSIVE" to achieve the same behavior with less work;
+ however, this is actually the exact mechanism currently used by the Rust
+ standard library to implement such functionality, so it's still a useful
+ demonstration and serves as a validation test for the behavior of KOS's
+ mutexes.
+
+ The referenced Rust implementation is available at
+ https://github.com/rust-lang/rust/blob/4bc39f02/library/std/src/sync/reentrant_lock.rs
+
+ */
+
+#include <kos.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdatomic.h>
+#include <stdnoreturn.h>
+
+/* Number of threads to spawn */
+#define THREAD_COUNT (DBL_MEM? 600 : 300)
+
+/* % chance thread will pass when maybe_pass() called */
+#define THREAD_PASS_CHANCE 75
+
+/* Reentrant mutex we're implementing on top of KOS's normal mutex. */
+typedef struct {
+ mutex_t mutex; /* Regular mutex */
+ _Atomic kthread_t *owner; /* Current mutex owner */
+ unsigned count; /* Reentrant count */
+} reentrant_mutex_t;
+
+/* Initializes our reentrant_mutex structure. */
+static void reentrant_mutex_init(reentrant_mutex_t *rmutex) {
+ mutex_init(&rmutex->mutex, MUTEX_TYPE_NORMAL);
+ atomic_store(&rmutex->owner, NULL);
+ rmutex->count = 0;
+}
+
+/* Uninitializes our reentrant_mutex structure. */
+static void reentrant_mutex_uninit(reentrant_mutex_t *rmutex) {
+ mutex_destroy(&rmutex->mutex);
+}
+
+/* Reports an error for the current thread and exits with failure value. */
+noreturn static void failure(const char *fmt, ...) {
+ char buffer[1024];
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "* * * FAILURE * * *\n");
+ fprintf(stderr, "thread %s: %s\n", thd_get_label(thd_current), buffer);
+
+ exit(EXIT_FAILURE);
+}
+
+/* Locks our reentrant_mutex structure. */
+static void reentrant_mutex_lock(reentrant_mutex_t *rmutex) {
+ /* If we are the owning thread, we must have already acquired the mutex. */
+ if(atomic_load(&rmutex->owner) == (_Atomic kthread_t *)thd_current) {
+ /* Increment the lock count. */
+ rmutex->count++;
+
+ /* Sanity check: mutex better be locked! */
+ if(!mutex_is_locked(&rmutex->mutex))
+ failure("Owns rmutex->mutex but it isn't locked!");
+ }
+ else {
+ kthread_t *expected = NULL;
+
+ /* Attempt to acquire the mutex, since we are not the owning thread. */
+ if(mutex_lock(&rmutex->mutex) < 0)
+ failure("Failed to lock mutex: %s", strerror(errno));
+
+ /* Set the owning thread to this thread. */
+ atomic_compare_exchange_strong(&rmutex->owner,
+ &expected,
+ (_Atomic kthread_t *)thd_current);
+
+ /* This should be impossible if our threading system is working properly. */
+ if(rmutex->count)
+ failure("rmutex->count was %u when it MUST be zero!", rmutex->count);
+
+ rmutex->count++;
+ }
+}
+
+/* Unlocks our reentrant_mutex structure. */
+static void reentrant_mutex_unlock(reentrant_mutex_t *rmutex) {
+ /* We better currently be the owning thread if we're attempting to unlock it! */
+ if(atomic_load(&rmutex->owner) == (_Atomic kthread_t *)thd_current) {
+ /* Decrement the lock counter. */
+ if(atomic_fetch_sub(&rmutex->count, 1) == 1) {
+ /* Clear the owning thread and release the mutex if counter hits 0. */
+ atomic_store(&rmutex->owner, NULL);
+
+ /* Unlock the mutex. */
+ if(mutex_unlock(&rmutex->mutex) < 0)
+ failure("Failed to unlock mutex: %s", strerror(errno));
+ }
+
+ } else {
+ fprintf(stderr, "Error: Thread does not own the mutex\n");
+ }
+}
+
+/* Use the provided probability to determine whether the current thread
+ should pass/yield execution to another thread. */
+static void maybe_pass(void) {
+ unsigned value;
+
+ getentropy(&value, sizeof(value));
+
+ value %= 100;
+ if(value < THREAD_PASS_CHANCE)
+ thd_pass();
+}
+
+/* Mutex controlling access to "shared_variable." */
+static reentrant_mutex_t rmutex;
+/* Variable all threads are fighting over modifying. */
+static int shared_variable = 0;
+
+/* Exec function for each thread. Attempts to acquire the reentrant_mutex
+ multiple times, potentially yielding in between each acquisition,
+ and incrementing the shared variable once while the thread owns the lock. */
+static void *thread_func(void *arg) {
+ printf("Hello from thread %s!\n",
+ thd_get_label(thd_current));
+
+ maybe_pass();
+ reentrant_mutex_lock(&rmutex); /* lock count = 1 */
+
+ maybe_pass();
+ reentrant_mutex_lock(&rmutex); /* lock count = 2 */
+
+ shared_variable++;
+
+ maybe_pass();
+ reentrant_mutex_unlock(&rmutex); /* lock count = 1 */
+
+ maybe_pass();
+ reentrant_mutex_lock(&rmutex); /* lock count = 2 */
+
+ maybe_pass();
+ reentrant_mutex_unlock(&rmutex); /* lock count = 1 */
+
+ maybe_pass();
+ reentrant_mutex_unlock(&rmutex); /* unlocked */
+
+ return NULL;
+}
+
+int main(int argc, const char* argv[]) {
+ kthread_t *threads[THREAD_COUNT];
+
+ /* Initialize our mutex */
+ reentrant_mutex_init(&rmutex);
+
+ /* Spawn a bunch of threads, potentially yielding the main thread after
+ each one gets spawned. */
+ for(size_t i = 0; i < THREAD_COUNT; ++i) {
+ threads[i] = thd_create(false, thread_func, NULL);
+
+ char thd_label[16];
+ snprintf(thd_label, sizeof(thd_label), "%u", i);
+ thd_set_label(threads[i], thd_label);
+
+ maybe_pass();
+ }
+
+ /* Wait for each thread to complete. */
+ for(size_t i = 0; i < THREAD_COUNT; ++i)
+ thd_join(threads[i], NULL);
+
+ /* Ensure our mutex is left in the expected state. */
+ if(rmutex.count || rmutex.owner || mutex_is_locked(&rmutex.mutex))
+ failure("Recursive mutex was left in unexpected state!");
+
+ /* Clean up our mutex. */
+ reentrant_mutex_uninit(&rmutex);
+
+ printf("Shared variable is %d!\n", shared_variable);
+
+ if(shared_variable == THREAD_COUNT)
+ printf("Reentrant lock test completed successfully!\n");
+ else
+ failure("Shared variable != THREAD_COUNT!");
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/dreamcast/filesystem/Makefile b/examples/dreamcast/filesystem/Makefile
new file mode 100644
index 00000000..3233a746
--- /dev/null
+++ b/examples/dreamcast/filesystem/Makefile
@@ -0,0 +1,16 @@
+# KallistiOS ##version##
+#
+# examples/dreamcast/filesystem/Makefile
+# Copyright (C) 2024 Andress Barajas
+#
+
+all:
+ $(KOS_MAKE) -C pty
+
+clean:
+ $(KOS_MAKE) -C pty clean
+
+dist:
+ $(KOS_MAKE) -C pty dist
+
+
diff --git a/examples/dreamcast/sound/cdda/basic_cdda/Makefile b/examples/dreamcast/filesystem/pty/Makefile
similarity index 75%
copy from examples/dreamcast/sound/cdda/basic_cdda/Makefile
copy to examples/dreamcast/filesystem/pty/Makefile
index 22038245..4b6182d5 100644
--- a/examples/dreamcast/sound/cdda/basic_cdda/Makefile
+++ b/examples/dreamcast/filesystem/pty/Makefile
@@ -1,10 +1,12 @@
#
-# Basic CDDA program
-# (c)2002 Megan Potter
+# fs_pty test program
+#
+# Copyright (C) 2024 Andress Barajas
#
-TARGET = basic_cdda.elf
-OBJS = basic_cdda.o
+TARGET = pty.elf
+
+OBJS = pty.o
all: rm-elf $(TARGET)
@@ -25,4 +27,3 @@ run: $(TARGET)
dist: $(TARGET)
-rm -f $(OBJS)
$(KOS_STRIP) $(TARGET)
-
diff --git a/examples/dreamcast/filesystem/pty/pty.c b/examples/dreamcast/filesystem/pty/pty.c
new file mode 100644
index 00000000..3ab0168a
--- /dev/null
+++ b/examples/dreamcast/filesystem/pty/pty.c
@@ -0,0 +1,91 @@
+/* KallistiOS ##version##
+
+ pty.c
+ Copyright (C) 2024 Andress Barajas
+
+ This program demonstrates the creation and usage of a pseudo-terminal (PTY)
+ pair. It involves creating a master and a slave PTY, writing a message to
+ the master PTY, and reading the message from the slave PTY. It also
+ demonstrates handling non-blocking read operations and performs clean-up of
+ resources before exiting.
+*/
+
+#include <kos/fs_pty.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+int main(int argc, char* argv[]) {
+ /* Create a PTY pair */
+ file_t master_fd = NULL, slave_fd = NULL;
+ int retval = EXIT_SUCCESS;
+
+ if(fs_pty_create(NULL, 0, &master_fd, &slave_fd) < 0) {
+ fprintf(stderr, "Error creating PTY pair");
+ goto failure;
+ }
+
+ /* Set non-blocking mode on the slave_fd for testing */
+ int flags = fcntl(slave_fd, F_GETFL, 0);
+ if(fcntl(slave_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ fprintf(stderr, "Error setting O_NONBLOCK");
+ goto failure;
+ }
+
+ /* Write to the master end of the PTY */
+ const char *msg = "Hello from master!";
+ if(write(master_fd, msg, strlen(msg)) < 0) {
+ fprintf(stderr, "Error writing to master PTY");
+ goto failure;
+ }
+
+ /* Read from the slave end of the PTY */
+ char buffer[128];
+ ssize_t bytes_read = read(slave_fd, buffer, sizeof(buffer) - 1);
+ if(bytes_read < 0) {
+ if(errno == EAGAIN) {
+ printf("No data available (non-blocking mode)\n");
+ } else {
+ fprintf(stderr, "Error reading from slave PTY");
+ goto failure;
+ }
+ } else {
+ /* Null-terminate and print the received message */
+ buffer[bytes_read] = '\0';
+ printf("Received message: %s\n", buffer);
+ }
+
+ /* Try and read again */
+ bytes_read = read(slave_fd, buffer, sizeof(buffer) - 1);
+ if(bytes_read < 0) {
+ if(errno == EAGAIN) {
+ printf("No more data available (non-blocking mode)\n");
+ } else {
+ fprintf(stderr, "Error reading from slave PTY");
+ goto failure;
+ }
+ } else if (bytes_read == 0) {
+ printf("No more data to read (EOF)\n");
+ } else {
+ /* Null-terminate and print the received message */
+ buffer[bytes_read] = '\0';
+ printf("Received 2nd message: %s\n", buffer);
+ }
+
+ /* Jump over failure and only do clean-up */
+ goto cleanup;
+
+ /* Set exit code and clean up */
+failure:
+ retval = EXIT_FAILURE;
+
+ /* Clean up resources */
+cleanup:
+ if(master_fd) fs_close(master_fd);
+ if(slave_fd) fs_close(slave_fd);
+
+ printf("DONE!\n");
+
+ return retval;
+}
diff --git a/examples/dreamcast/vmu/vmu_beep/beep.c b/examples/dreamcast/vmu/vmu_beep/beep.c
index ea600717..666eef77 100644
--- a/examples/dreamcast/vmu/vmu_beep/beep.c
+++ b/examples/dreamcast/vmu/vmu_beep/beep.c
@@ -2,8 +2,8 @@
beep.c
Copyright (C) 2004 SinisterTengu
- Copyright (C) 2008 Donald Haase
-
+ Copyright (C) 2008, 2024 Donald Haase
+ Copyright (C) 2024 Falco Girgis
*/
/*
@@ -27,6 +27,7 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
+#include <stdatomic.h>
#include <kos/init.h>
@@ -38,29 +39,39 @@
#include <plx/font.h>
+#define VMU_DEFAULT_EFFECT 0x000065F0
+#define VMU_STOP_EFFECT 0x00000000
-static plx_font_t *fnt;
-static plx_fcxt_t *cxt;
+static atomic_bool quit = false;
-static void cleanup(void) {
- plx_font_destroy(fnt);
- plx_fcxt_destroy(cxt);
+static void on_reset(uint8_t addr, uint32_t btns) {
+ (void)addr; (void)btns;
+ quit = true;
}
int main(int argc, char *argv[]) {
maple_device_t *dev, *vmudev;
cont_state_t *state;
point_t w;
+ plx_font_t *fnt;
+ plx_fcxt_t *cxt;
int i = 0, count = 0;
uint16_t old_buttons = 0, rel_buttons = 0;
uint32_t effect = 0;
- uint8_t n[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; //nibbles
...<truncated>...
hooks/post-receive
--
A pseudo Operating System for the Dreamcast.
|