diff --git a/nx/include/switch/services/nifm.h b/nx/include/switch/services/nifm.h index b28cbb20..f1b67d31 100644 --- a/nx/include/switch/services/nifm.h +++ b/nx/include/switch/services/nifm.h @@ -8,6 +8,7 @@ #pragma once #include "../types.h" #include "../sf/service.h" +#include "../kernel/event.h" typedef enum { NifmServiceType_User = 0, ///< Initializes nifm:u. @@ -28,6 +29,25 @@ typedef enum { NifmInternetConnectionStatus_Connected = 4, ///< Internet is connected. } NifmInternetConnectionStatus; +typedef enum { + NifmRequestState_Invalid = 0, ///< Error. + NifmRequestState_Unknown1 = 1, ///< Not yet submitted or error. + NifmRequestState_OnHold = 2, ///< OnHold + NifmRequestState_Available = 3, ///< Available + NifmRequestState_Unknown4 = 4, ///< Unknown + NifmRequestState_Unknown5 = 5, ///< Unknown +} NifmRequestState; + +/// Request +typedef struct { + Service s; ///< IRequest + Event event_request_state; ///< First Event from cmd GetSystemEventReadableHandles, autoclear=true. Signaled when the RequestState changes. + Event event1; ///< Second Event from cmd GetSystemEventReadableHandles. + + NifmRequestState request_state; ///< \ref NifmRequestState from the GetRequestState cmd. + Result res; ///< Result from the GetResult cmd. +} NifmRequest; + /// ClientId typedef struct { u32 id; ///< ClientId @@ -140,6 +160,13 @@ Service* nifmGetServiceSession_GeneralService(void); */ NifmClientId nifmGetClientId(void); +/** + * @brief CreateRequest + * @param[out] r \ref NifmRequest + * @param[in] autoclear Event autoclear to use for NifmRequest::event1, a default of true can be used for this. + */ +Result nifmCreateRequest(NifmRequest* r, bool autoclear); + /** * @brief GetCurrentNetworkProfile * @param[out] profile \ref NifmNetworkProfileData @@ -201,3 +228,46 @@ bool nifmIsAnyInternetRequestAccepted(NifmClientId id); Result nifmIsAnyForegroundRequestAccepted(bool* out); Result nifmPutToSleep(void); Result nifmWakeUp(void); + +///@name IRequest +///@{ + +/** + * @brief Close a \ref NifmRequest. + * @param r \ref NifmRequest + */ +void nifmRequestClose(NifmRequest* r); + +/** + * @brief GetRequestState + * @param r \ref NifmRequest + * @param[out] out \ref NifmRequestState + */ +Result nifmGetRequestState(NifmRequest* r, NifmRequestState *out); + +/** + * @brief GetResult + * @param r \ref NifmRequest + */ +Result nifmGetResult(NifmRequest* r); + +/** + * @brief Cancel + * @param r \ref NifmRequest + */ +Result nifmRequestCancel(NifmRequest* r); + +/** + * @brief Submit + * @param r \ref NifmRequest + */ +Result nifmRequestSubmit(NifmRequest* r); + +/** + * @brief SubmitAndWait + * @param r \ref NifmRequest + */ +Result nifmRequestSubmitAndWait(NifmRequest* r); + +///@} + diff --git a/nx/source/services/nifm.c b/nx/source/services/nifm.c index 7ecab2a9..8756de5b 100644 --- a/nx/source/services/nifm.c +++ b/nx/source/services/nifm.c @@ -11,6 +11,8 @@ static Service g_nifmIGS; static Result _nifmCreateGeneralService(Service* srv_out); static Result _nifmCreateGeneralServiceOld(Service* srv_out); +static Result _nifmRequestGetSystemEventReadableHandles(NifmRequest* r, bool autoclear); + NX_GENERATE_SERVICE_GUARD_PARAMS(nifm, (NifmServiceType service_type), (service_type)); Result _nifmInitialize(NifmServiceType service_type) { @@ -83,6 +85,11 @@ static Result _nifmCmdNoInOutBool(Service* srv, bool *out, u32 cmd_id) { return rc; } +static Result _nifmCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { + serviceAssumeDomain(srv); + return serviceDispatchOut(srv, cmd_id, *out); +} + static Result _nifmCmdInU8NoOut(Service* srv, u8 inval, u64 cmd_id) { serviceAssumeDomain(srv); return serviceDispatchIn(srv, cmd_id, inval); @@ -162,6 +169,38 @@ NifmClientId nifmGetClientId(void) { return id; } +static Result _nifmCreateRequest(Service* srv_out, s32 inval) { + serviceAssumeDomain(&g_nifmIGS); + return serviceDispatchIn(&g_nifmIGS, 4, inval, + .out_num_objects = 1, + .out_objects = srv_out, + ); +} + +Result nifmCreateRequest(NifmRequest* r, bool autoclear) { + Result rc=0; + + memset(r, 0, sizeof(*r)); + + rc = _nifmCreateRequest(&r->s, 0x2); + + if (R_SUCCEEDED(rc)) { + rc = _nifmRequestGetSystemEventReadableHandles(r, autoclear); + + if (R_FAILED(rc)) { + serviceAssumeDomain(&r->s); + serviceClose(&r->s); + } + } + + if (R_SUCCEEDED(rc)) { + r->request_state = NifmRequestState_Unknown1; + r->res = MAKERESULT(110, 311); + } + + return rc; +} + Result nifmGetCurrentNetworkProfile(NifmNetworkProfileData *profile) { NifmSfNetworkProfileData tmp={0}; serviceAssumeDomain(&g_nifmIGS); @@ -274,3 +313,119 @@ Result nifmPutToSleep(void) { Result nifmWakeUp(void) { return _nifmCmdNoIO(&g_nifmIGS, 24); } + +// IRequest + +void nifmRequestClose(NifmRequest* r) { + eventClose(&r->event1); + eventClose(&r->event_request_state); + + serviceAssumeDomain(&r->s); + serviceClose(&r->s); +} + +static Result _nifmRequestGetSystemEventReadableHandles(NifmRequest* r, bool autoclear) { + Result rc=0; + Handle tmp_handles[2] = {INVALID_HANDLE, INVALID_HANDLE}; + + serviceAssumeDomain(&r->s); + rc = serviceDispatch(&r->s, 2, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy, SfOutHandleAttr_HipcCopy }, + .out_handles = tmp_handles, + ); + + if (R_SUCCEEDED(rc)) { + eventLoadRemote(&r->event_request_state, tmp_handles[0], true); + eventLoadRemote(&r->event1, tmp_handles[1], autoclear); + } + + return rc; +} + +static void _nifmUpdateState(NifmRequest* r) { + Result rc=0; + u32 tmp=0; + + rc = _nifmCmdNoInOutU32(&r->s, &tmp, 0); // GetRequestState + r->request_state = R_SUCCEEDED(rc) ? tmp : 0; // sdknso ignores error other than this. + + rc = _nifmCmdNoIO(&r->s, 1); // GetResult + r->res = rc; +} + +Result nifmGetRequestState(NifmRequest* r, NifmRequestState *out) { + if (!serviceIsActive(&r->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (R_FAILED(eventWait(&r->event_request_state, 0))) { + if (out) *out = r->request_state; + return 0; + } + + // sdknso would clear the event here, but it's autoclear anyway. + + _nifmUpdateState(r); + if (out) *out = r->request_state; + + return 0; +} + +Result nifmGetResult(NifmRequest* r) { + if (!serviceIsActive(&r->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (R_FAILED(eventWait(&r->event_request_state, 0))) return r->res; + + // sdknso would clear the event here, but it's autoclear anyway. + + _nifmUpdateState(r); + return r->res; +} + +Result nifmRequestCancel(NifmRequest* r) { + if (!serviceIsActive(&r->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _nifmCmdNoIO(&r->s, 3); +} + +Result nifmRequestSubmit(NifmRequest* r) { + Result rc=0; + NifmRequestState tmp; + + if (!serviceIsActive(&r->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + rc = nifmGetRequestState(r, &tmp); + + if (R_SUCCEEDED(rc) && (tmp == NifmRequestState_Unknown1 || tmp == NifmRequestState_OnHold || tmp == NifmRequestState_Available || tmp == NifmRequestState_Unknown5)) { + _nifmCmdNoIO(&r->s, 4); // Submit (sdknso ignores error) + _nifmUpdateState(r); + } + + return rc; +} + +Result nifmRequestSubmitAndWait(NifmRequest* r) { + Result rc=0; + NifmRequestState tmp; + + rc = nifmRequestSubmit(r); + if (R_FAILED(rc)) return rc; + + while(1) { + rc = nifmGetRequestState(r, &tmp); + if (R_FAILED(rc)) return rc; + + if (tmp != NifmRequestState_OnHold) return rc; + + if (R_SUCCEEDED(eventWait(&r->event_request_state, 10000000000ULL))) break; + } + + // sdknso would clear the event here, but it's autoclear anyway. + + _nifmUpdateState(r); + + return rc; +} +