diff --git a/nx/include/switch.h b/nx/include/switch.h index 8b7e87dc..69550034 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -51,6 +51,7 @@ extern "C" { #include "switch/services/acc.h" #include "switch/services/apm.h" #include "switch/services/applet.h" +#include "switch/services/async.h" #include "switch/services/audin.h" #include "switch/services/audout.h" #include "switch/services/audren.h" diff --git a/nx/include/switch/services/async.h b/nx/include/switch/services/async.h new file mode 100644 index 00000000..72f9beb9 --- /dev/null +++ b/nx/include/switch/services/async.h @@ -0,0 +1,112 @@ +/** + * @file async.h + * @brief NS/NIM IAsync* IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../applets/error.h" +#include "../kernel/event.h" + +/// AsyncValue +typedef struct { + Service s; ///< IAsyncValue + Event event; ///< Event with autoclear=false. +} AsyncValue; + +/// AsyncResult +typedef struct { + Service s; ///< IAsyncResult + Event event; ///< Event with autoclear=false. +} AsyncResult; + +///@name IAsyncValue +///@{ + +/** + * @brief Close a \ref AsyncValue. + * @note When the object is initialized, this uses \ref asyncValueCancel then \ref asyncValueWait with timeout=U64_MAX. + * @param a \ref AsyncValue + */ +void asyncValueClose(AsyncValue *a); + +/** + * @brief Waits for the async operation to finish using the specified timeout. + * @param a \ref AsyncValue + * @param[in] timeout Timeout in nanoseconds. U64_MAX for no timeout. + */ +Result asyncValueWait(AsyncValue *a, u64 timeout); + +/** + * @brief Gets the value size. + * @param a \ref AsyncValue + * @param[out] size Output size. + */ +Result asyncValueGetSize(AsyncValue *a, u64 *size); + +/** + * @brief Gets the value. + * @note Prior to using the cmd, this uses \ref asyncResultWait with timeout=U64_MAX. + * @param a \ref AsyncValue + * @param[out] buffer Output buffer. + * @param[in] size Output buffer size. + */ +Result asyncValueGet(AsyncValue *a, void* buffer, size_t size); + +/** + * @brief Cancels the async operation. + * @note Used automatically by \ref asyncValueClose. + * @param a \ref AsyncValue + */ +Result asyncValueCancel(AsyncValue *a); + +/** + * @brief Gets the \ref ErrorContext. + * @param a \ref AsyncValue + * @param[out] context \ref ErrorContext + */ +Result asyncValueGetErrorContext(AsyncValue *a, ErrorContext *context); + +///@} + +///@name IAsyncResult +///@{ + +/** + * @brief Close a \ref AsyncResult. + * @note When the object is initialized, this uses \ref asyncResultCancel then \ref asyncResultWait with timeout=U64_MAX. + * @param a \ref AsyncResult + */ +void asyncResultClose(AsyncResult *a); + +/** + * @brief Waits for the async operation to finish using the specified timeout. + * @param a \ref AsyncResult + * @param[in] timeout Timeout in nanoseconds. U64_MAX for no timeout. + */ +Result asyncResultWait(AsyncResult *a, u64 timeout); + +/** + * @brief Gets the Result. + * @note Prior to using the cmd, this uses \ref asyncResultWait with timeout=U64_MAX. + * @param a \ref AsyncResult + */ +Result asyncResultGet(AsyncResult *a); + +/** + * @brief Cancels the async operation. + * @note Used automatically by \ref asyncResultClose. + * @param a \ref AsyncResult + */ +Result asyncResultCancel(AsyncResult *a); + +/** + * @brief Gets the \ref ErrorContext. + * @param a \ref AsyncResult + * @param[out] context \ref ErrorContext + */ +Result asyncResultGetErrorContext(AsyncResult *a, ErrorContext *context); + +///@} + diff --git a/nx/source/services/async.c b/nx/source/services/async.c new file mode 100644 index 00000000..b9c19987 --- /dev/null +++ b/nx/source/services/async.c @@ -0,0 +1,115 @@ +#include "sf/service.h" +#include "runtime/hosversion.h" +#include "services/async.h" +#include "applets/error.h" + +static Result _asyncCmdNoIO(Service* srv, u32 cmd_id) { + return serviceDispatch(srv, cmd_id); +} + +static Result _asyncCmdNoInOutU64(Service* srv, u64 *out, u32 cmd_id) { + return serviceDispatchOut(srv, cmd_id, *out); +} + +static Result _asyncCmdNoInOutBuf(Service* srv, void* buffer, size_t size, u32 cmd_id) { + return serviceDispatch(srv, cmd_id, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { buffer, size } }, + ); +} + +// IAsyncValue + +void asyncValueClose(AsyncValue *a) { + if (serviceIsActive(&a->s)) { + asyncValueCancel(a); // Official sw ignores rc from this prior to waiting on the event. + asyncValueWait(a, U64_MAX); + } + + serviceClose(&a->s); + eventClose(&a->event); +} + +Result asyncValueWait(AsyncValue *a, u64 timeout) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return eventWait(&a->event, timeout); +} + +Result asyncValueGetSize(AsyncValue *a, u64 *size) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _asyncCmdNoInOutU64(&a->s, size, 0); +} + +Result asyncValueGet(AsyncValue *a, void* buffer, size_t size) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + Result rc = asyncValueWait(a, U64_MAX); + if (R_SUCCEEDED(rc)) rc = _asyncCmdNoInOutBuf(&a->s, buffer, size, 1); + return rc; +} + +Result asyncValueCancel(AsyncValue *a) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _asyncCmdNoIO(&a->s, 2); +} + +Result asyncValueGetErrorContext(AsyncValue *a, ErrorContext *context) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _asyncCmdNoInOutBuf(&a->s, context, sizeof(*context), 3); +} + +// AsyncResult + +void asyncResultClose(AsyncResult *a) { + if (serviceIsActive(&a->s)) { + asyncResultCancel(a); // Official sw ignores rc from this prior to waiting on the event. + asyncResultWait(a, U64_MAX); + } + + serviceClose(&a->s); + eventClose(&a->event); +} + +Result asyncResultWait(AsyncResult *a, u64 timeout) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return eventWait(&a->event, timeout); +} + +Result asyncResultGet(AsyncResult *a) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + Result rc = asyncResultWait(a, U64_MAX); + if (R_SUCCEEDED(rc)) rc = _asyncCmdNoIO(&a->s, 0); + return rc; +} + +Result asyncResultCancel(AsyncResult *a) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _asyncCmdNoIO(&a->s, 1); +} + +Result asyncResultGetErrorContext(AsyncResult *a, ErrorContext *context) { + if (!serviceIsActive(&a->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _asyncCmdNoInOutBuf(&a->s, context, sizeof(*context), 2); +} +