From 27a560102054d5c4c349aebc05723817dc5016fd Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Wed, 9 Feb 2022 21:37:56 -0800
Subject: [PATCH 1/6] Change estimated error code when thread spawning fails

---
 src/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 63324fd..77b7311 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,10 +57,10 @@ pub unsafe extern "C" fn pthread_create(
 
     if handle.is_null() {
         // There was some error, but libctru doesn't expose the result.
-        // We assume there was an incorrect parameter (such as too low of a priority).
+        // We assume there was permissions issue (such as too low of a priority).
         // We also need to clean up the closure at this time.
         drop(Box::from_raw(main));
-        return libc::EINVAL;
+        return libc::EPERM;
     }
 
     *native = handle as _;

From ba5c5d787e57ee6c62153a10ff68a70036395407 Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Fri, 11 Feb 2022 21:22:43 -0800
Subject: [PATCH 2/6] Change to more standard priority function interfaces

---
 src/lib.rs | 63 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 40 insertions(+), 23 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 77b7311..0314cb2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,16 +21,9 @@ pub unsafe extern "C" fn pthread_create(
     value: *mut libc::c_void,
 ) -> libc::c_int {
     let attr = attr as *const PThreadAttr;
-
-    let stack_size = (*attr).stack_size.unwrap_or(libc::PTHREAD_STACK_MIN) as ctru_sys::size_t;
-
-    // If no priority value is specified, spawn with the same
-    // priority as the parent thread
-    let priority = (*attr).priority.unwrap_or_else(|| pthread_getpriority());
-
-    // If no affinity is specified, spawn on the default core (determined by
-    // the application's Exheader)
-    let affinity = (*attr).affinity.unwrap_or(-2);
+    let stack_size = (*attr).stack_size as ctru_sys::size_t;
+    let priority = (*attr).priority;
+    let affinity = (*attr).affinity;
 
     extern "C" fn thread_start(main: *mut libc::c_void) {
         unsafe {
@@ -97,9 +90,25 @@ pub unsafe extern "C" fn pthread_getpriority() -> libc::c_int {
 /// Must be less than or equal to the size of `libc::pthread_attr_t`. We assert
 /// this below via static_assertions.
 struct PThreadAttr {
-    stack_size: Option<libc::size_t>,
-    priority: Option<libc::c_int>,
-    affinity: Option<libc::c_int>,
+    stack_size: libc::size_t,
+    priority: libc::c_int,
+    affinity: libc::c_int,
+}
+
+impl Default for PThreadAttr {
+    fn default() -> Self {
+        PThreadAttr {
+            stack_size: libc::PTHREAD_STACK_MIN,
+
+            // If no priority value is specified, spawn with the same
+            // priority as the current thread
+            priority: unsafe { pthread_getpriority() },
+
+            // If no affinity is specified, spawn on the default core (determined by
+            // the application's Exheader)
+            affinity: -2,
+        }
+    }
 }
 
 static_assertions::const_assert!(
@@ -109,11 +118,7 @@ static_assertions::const_assert!(
 #[no_mangle]
 pub unsafe extern "C" fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> libc::c_int {
     let attr = attr as *mut PThreadAttr;
-    *attr = PThreadAttr {
-        stack_size: None,
-        priority: None,
-        affinity: None,
-    };
+    *attr = PThreadAttr::default();
 
     0
 }
@@ -130,18 +135,30 @@ pub unsafe extern "C" fn pthread_attr_setstacksize(
     stack_size: libc::size_t,
 ) -> libc::c_int {
     let attr = attr as *mut PThreadAttr;
-    (*attr).stack_size = Some(stack_size);
+    (*attr).stack_size = stack_size;
 
     0
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn pthread_attr_setpriority(
+pub unsafe extern "C" fn pthread_attr_getschedparam(
+    attr: *const libc::pthread_attr_t,
+    param: *mut libc::sched_param,
+) -> libc::c_int {
+    let attr = attr as *const PThreadAttr;
+    (*param).sched_priority = (*attr).priority;
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn pthread_attr_setschedparam(
     attr: *mut libc::pthread_attr_t,
-    priority: libc::c_int,
+    param: *const libc::sched_param,
 ) -> libc::c_int {
     let attr = attr as *mut PThreadAttr;
-    (*attr).priority = Some(priority);
+    (*attr).priority = (*param).sched_priority;
+
+    // TODO: we could validate the priority here if we wanted?
 
     0
 }
@@ -152,7 +169,7 @@ pub unsafe extern "C" fn pthread_attr_setaffinity(
     affinity: libc::c_int,
 ) -> libc::c_int {
     let attr = attr as *mut PThreadAttr;
-    (*attr).affinity = Some(affinity);
+    (*attr).affinity = affinity;
 
     0
 }

From 717938f682593af9d91cadf0b6a14e3fe8adb420 Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Sat, 12 Feb 2022 19:16:35 -0800
Subject: [PATCH 3/6] Rename affinity to "ideal processor"

This follows the 3dbrew.org wiki naming.

Also added a get version.
---
 src/lib.rs | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 0314cb2..4db289e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,7 +23,7 @@ pub unsafe extern "C" fn pthread_create(
     let attr = attr as *const PThreadAttr;
     let stack_size = (*attr).stack_size as ctru_sys::size_t;
     let priority = (*attr).priority;
-    let affinity = (*attr).affinity;
+    let ideal_processor = (*attr).ideal_processor;
 
     extern "C" fn thread_start(main: *mut libc::c_void) {
         unsafe {
@@ -44,7 +44,7 @@ pub unsafe extern "C" fn pthread_create(
         main as *mut libc::c_void,
         stack_size,
         priority,
-        affinity,
+        ideal_processor,
         false,
     );
 
@@ -92,7 +92,7 @@ pub unsafe extern "C" fn pthread_getpriority() -> libc::c_int {
 struct PThreadAttr {
     stack_size: libc::size_t,
     priority: libc::c_int,
-    affinity: libc::c_int,
+    ideal_processor: libc::c_int,
 }
 
 impl Default for PThreadAttr {
@@ -104,9 +104,9 @@ impl Default for PThreadAttr {
             // priority as the current thread
             priority: unsafe { pthread_getpriority() },
 
-            // If no affinity is specified, spawn on the default core (determined by
-            // the application's Exheader)
-            affinity: -2,
+            // If no processor is specified, spawn on the default core.
+            // (determined by the application's Exheader)
+            ideal_processor: -2,
         }
     }
 }
@@ -164,12 +164,25 @@ pub unsafe extern "C" fn pthread_attr_setschedparam(
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn pthread_attr_setaffinity(
+pub unsafe extern "C" fn pthread_attr_getidealprocessor_np(
+    attr: *const libc::pthread_attr_t,
+    ideal_processor: *mut libc::c_int,
+) -> libc::c_int {
+    let attr = attr as *mut PThreadAttr;
+    (*ideal_processor) = (*attr).ideal_processor;
+
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn pthread_attr_setidealprocessor_np(
     attr: *mut libc::pthread_attr_t,
-    affinity: libc::c_int,
+    ideal_processor: libc::c_int,
 ) -> libc::c_int {
     let attr = attr as *mut PThreadAttr;
-    (*attr).affinity = affinity;
+    (*attr).ideal_processor = ideal_processor;
+
+    // TODO: we could validate the processor ID here if we wanted?
 
     0
 }

From d1815bcb9b750fc1a8a86e02cf944ce801438488 Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Sat, 12 Feb 2022 19:38:30 -0800
Subject: [PATCH 4/6] Add pthread_self and improve thread function error
 handling

---
 src/lib.rs | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 4db289e..4d19f8f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,6 +13,9 @@ pub fn init() {}
 
 // PTHREAD LAYER TO CALL LIBCTRU
 
+/// The main thread's thread ID. It is "null" because libctru didn't spawn it.
+const MAIN_THREAD_ID: libc::pthread_t = 0;
+
 #[no_mangle]
 pub unsafe extern "C" fn pthread_create(
     native: *mut libc::pthread_t,
@@ -39,7 +42,7 @@ pub unsafe extern "C" fn pthread_create(
     let main: *mut Box<dyn FnOnce() -> *mut libc::c_void> =
         Box::into_raw(Box::new(Box::new(move || entrypoint(value))));
 
-    let handle = ctru_sys::threadCreate(
+    let thread = ctru_sys::threadCreate(
         Some(thread_start),
         main as *mut libc::c_void,
         stack_size,
@@ -48,7 +51,7 @@ pub unsafe extern "C" fn pthread_create(
         false,
     );
 
-    if handle.is_null() {
+    if thread.is_null() {
         // There was some error, but libctru doesn't expose the result.
         // We assume there was permissions issue (such as too low of a priority).
         // We also need to clean up the closure at this time.
@@ -56,7 +59,7 @@ pub unsafe extern "C" fn pthread_create(
         return libc::EPERM;
     }
 
-    *native = handle as _;
+    *native = thread as _;
 
     0
 }
@@ -66,7 +69,17 @@ pub unsafe extern "C" fn pthread_join(
     native: libc::pthread_t,
     _value: *mut *mut libc::c_void,
 ) -> libc::c_int {
-    ctru_sys::threadJoin(native as ctru_sys::Thread, u64::MAX);
+    if native == MAIN_THREAD_ID {
+        // This is not a valid thread to join on
+        return libc::EINVAL;
+    }
+
+    let result = ctru_sys::threadJoin(native as ctru_sys::Thread, u64::MAX);
+    if ctru_sys::R_FAILED(result) {
+        // TODO: improve the error code by checking the result further?
+        return libc::EINVAL;
+    }
+
     ctru_sys::threadFree(native as ctru_sys::Thread);
 
     0
@@ -74,11 +87,21 @@ pub unsafe extern "C" fn pthread_join(
 
 #[no_mangle]
 pub unsafe extern "C" fn pthread_detach(thread: libc::pthread_t) -> libc::c_int {
+    if native == MAIN_THREAD_ID {
+        // This is not a valid thread to detach
+        return libc::EINVAL;
+    }
+
     ctru_sys::threadDetach(thread as ctru_sys::Thread);
 
     0
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn pthread_self() -> libc::pthread_t {
+    ctru_sys::threadGetCurrent() as libc::pthread_t
+}
+
 #[no_mangle]
 pub unsafe extern "C" fn pthread_getpriority() -> libc::c_int {
     let mut priority = 0;

From cac16d639cd59111a718062bd3d4991f9a346197 Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Sat, 12 Feb 2022 21:06:27 -0800
Subject: [PATCH 5/6] Replace pthread_getpriority with more standard version
 (and add set fn)

In the case of the main thread we use a hack since there's no nice API
for getting the main thread's handle (see code).
---
 src/lib.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 112 insertions(+), 5 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 4d19f8f..7424de0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -87,7 +87,7 @@ pub unsafe extern "C" fn pthread_join(
 
 #[no_mangle]
 pub unsafe extern "C" fn pthread_detach(thread: libc::pthread_t) -> libc::c_int {
-    if native == MAIN_THREAD_ID {
+    if thread == MAIN_THREAD_ID {
         // This is not a valid thread to detach
         return libc::EINVAL;
     }
@@ -102,11 +102,110 @@ pub unsafe extern "C" fn pthread_self() -> libc::pthread_t {
     ctru_sys::threadGetCurrent() as libc::pthread_t
 }
 
+unsafe fn get_main_thread_handle() -> Result<ctru_sys::Handle, ctru_sys::Result> {
+    // Unfortunately I don't know of any better way to get the main thread's
+    // handle, other than via svcGetThreadList and svcOpenThread.
+    // Experimentally, the main thread ID is always the first in the list.
+    let mut thread_ids = [0; 1];
+    let mut thread_ids_count = 0;
+    let result = ctru_sys::svcGetThreadList(
+        &mut thread_ids_count,
+        thread_ids.as_mut_ptr(),
+        thread_ids.len() as i32,
+        ctru_sys::CUR_PROCESS_HANDLE,
+    );
+    if ctru_sys::R_FAILED(result) {
+        return Err(result);
+    }
+
+    // Get the main thread's handle
+    let main_thread_id = thread_ids[0];
+    let mut main_thread_handle = 0;
+    let result = ctru_sys::svcOpenThread(
+        &mut main_thread_handle,
+        ctru_sys::CUR_PROCESS_HANDLE,
+        main_thread_id,
+    );
+    if ctru_sys::R_FAILED(result) {
+        return Err(result);
+    }
+
+    Ok(main_thread_handle)
+}
+
+unsafe fn get_thread_handle(native: libc::pthread_t) -> Result<ctru_sys::Handle, ctru_sys::Result> {
+    if native == MAIN_THREAD_ID {
+        get_main_thread_handle()
+    } else {
+        Ok(ctru_sys::threadGetHandle(native as ctru_sys::Thread))
+    }
+}
+
 #[no_mangle]
-pub unsafe extern "C" fn pthread_getpriority() -> libc::c_int {
+pub unsafe extern "C" fn pthread_getschedparam(
+    native: libc::pthread_t,
+    policy: *mut libc::c_int,
+    param: *mut libc::sched_param,
+) -> libc::c_int {
+    let handle = match get_thread_handle(native) {
+        Ok(handle) => handle,
+        Err(_) => return libc::ESRCH,
+    };
+
+    if handle == u32::MAX {
+        // The thread has already finished
+        return libc::ESRCH;
+    }
+
     let mut priority = 0;
-    ctru_sys::svcGetThreadPriority(&mut priority, ctru_sys::CUR_THREAD_HANDLE);
-    priority
+    let result = ctru_sys::svcGetThreadPriority(&mut priority, handle);
+    if ctru_sys::R_FAILED(result) {
+        // Some error occurred. This is the only error defined for this
+        // function, so return it. Maybe the thread exited while this function
+        // was exiting?
+        return libc::ESRCH;
+    }
+
+    (*param).sched_priority = priority;
+
+    // SCHED_FIFO is closest to how the cooperative app core works, while
+    // SCHED_RR is closest to how the preemptive sys core works.
+    // However, we don't have an API to get the current processor ID of a chosen
+    // thread (only the current), so we just always return SCHED_FIFO.
+    (*policy) = libc::SCHED_FIFO;
+
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn pthread_setschedparam(
+    native: libc::pthread_t,
+    policy: libc::c_int,
+    param: *const libc::sched_param,
+) -> libc::c_int {
+    if policy != libc::SCHED_FIFO {
+        // We only accept SCHED_FIFO. See the note in pthread_getschedparam.
+        return libc::EINVAL;
+    }
+
+    let handle = match get_thread_handle(native) {
+        Ok(handle) => handle,
+        Err(_) => return libc::EINVAL,
+    };
+
+    if handle == u32::MAX {
+        // The thread has already finished
+        return libc::ESRCH;
+    }
+
+    let result = ctru_sys::svcSetThreadPriority(handle, (*param).sched_priority);
+    if ctru_sys::R_FAILED(result) {
+        // Probably the priority is out of the permissible bounds
+        // TODO: improve the error code by checking the result further?
+        return libc::EPERM;
+    }
+
+    0
 }
 
 /// Internal struct for storing pthread attribute data
@@ -120,12 +219,20 @@ struct PThreadAttr {
 
 impl Default for PThreadAttr {
     fn default() -> Self {
+        let thread_id = unsafe { libc::pthread_self() };
+        let mut policy = 0;
+        let mut sched_param = libc::sched_param { sched_priority: 0 };
+
+        unsafe { libc::pthread_getschedparam(thread_id, &mut policy, &mut sched_param) };
+
+        let priority = sched_param.sched_priority;
+
         PThreadAttr {
             stack_size: libc::PTHREAD_STACK_MIN,
 
             // If no priority value is specified, spawn with the same
             // priority as the current thread
-            priority: unsafe { pthread_getpriority() },
+            priority,
 
             // If no processor is specified, spawn on the default core.
             // (determined by the application's Exheader)

From 88d12c91ad8ae61cbcb14a2c58334a213d392374 Mon Sep 17 00:00:00 2001
From: AzureMarker <mark.drobnak@gmail.com>
Date: Sun, 13 Feb 2022 18:43:00 -0800
Subject: [PATCH 6/6] Implement pthread_getprocessorid_np

---
 src/lib.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/lib.rs b/src/lib.rs
index 7424de0..2741e38 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -208,6 +208,11 @@ pub unsafe extern "C" fn pthread_setschedparam(
     0
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn pthread_getprocessorid_np() -> libc::c_int {
+    ctru_sys::svcGetProcessorID()
+}
+
 /// Internal struct for storing pthread attribute data
 /// Must be less than or equal to the size of `libc::pthread_attr_t`. We assert
 /// this below via static_assertions.