Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/HLE/KernelWaitHelpers.h
3186 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#pragma once
19
20
#include <vector>
21
#include <map>
22
#include <algorithm> // std::erase/remove
23
24
#include "Common/CommonTypes.h"
25
#include "Core/CoreTiming.h"
26
#include "Core/HLE/sceKernelThread.h"
27
#include "Core/HLE/ErrorCodes.h"
28
29
namespace HLEKernel
30
{
31
32
// Should be called from the CoreTiming handler for the wait func.
33
template <typename KO, WaitType waitType>
34
inline void WaitExecTimeout(SceUID threadID) {
35
u32 error;
36
SceUID uid = __KernelGetWaitID(threadID, waitType, error);
37
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
38
KO *ko = uid == 0 ? NULL : kernelObjects.Get<KO>(uid, error);
39
if (ko)
40
{
41
if (timeoutPtr != 0)
42
Memory::Write_U32(0, timeoutPtr);
43
44
// This thread isn't waiting anymore, but we'll remove it from waitingThreads later.
45
// The reason is, if it times out, but what it was waiting on is DELETED prior to it
46
// actually running, it will get a DELETE result instead of a TIMEOUT.
47
// So, we need to remember it or we won't be able to mark it DELETE instead later.
48
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
49
__KernelReSchedule("wait timed out");
50
}
51
}
52
53
// Move a thread from the waiting thread list to the paused thread list.
54
// This version is for vectors which contain structs, which must have SceUID threadID and u64 pausedTimeout.
55
// Should not be called directly.
56
template <typename WaitInfoType, typename PauseType>
57
inline bool WaitPauseHelperUpdate(SceUID pauseKey, SceUID threadID, std::vector<WaitInfoType> &waitingThreads, std::map<SceUID, PauseType> &pausedWaits, u64 pauseTimeout) {
58
WaitInfoType waitData = {0};
59
for (size_t i = 0; i < waitingThreads.size(); i++) {
60
WaitInfoType *t = &waitingThreads[i];
61
if (t->threadID == threadID)
62
{
63
waitData = *t;
64
// TODO: Hmm, what about priority/fifo order? Does it lose its place in line?
65
waitingThreads.erase(waitingThreads.begin() + i);
66
break;
67
}
68
}
69
70
if (waitData.threadID != threadID)
71
return false;
72
73
waitData.pausedTimeout = pauseTimeout;
74
pausedWaits[pauseKey] = waitData;
75
return true;
76
}
77
78
// Move a thread from the waiting thread list to the paused thread list.
79
// This version is for a simpler list of SceUIDs. The paused list is a std::map<SceUID, u64>.
80
// Should not be called directly.
81
template <>
82
inline bool WaitPauseHelperUpdate<SceUID, u64>(SceUID pauseKey, SceUID threadID, std::vector<SceUID> &waitingThreads, std::map<SceUID, u64> &pausedWaits, u64 pauseTimeout) {
83
// TODO: Hmm, what about priority/fifo order? Does it lose its place in line?
84
waitingThreads.erase(std::remove(waitingThreads.begin(), waitingThreads.end(), threadID), waitingThreads.end());
85
pausedWaits[pauseKey] = pauseTimeout;
86
return true;
87
}
88
89
// Retrieve the paused wait info from the list, and pop it.
90
// Returns the pausedTimeout value.
91
// Should not be called directly.
92
template <typename WaitInfoType, typename PauseType>
93
inline u64 WaitPauseHelperGet(SceUID pauseKey, SceUID threadID, std::map<SceUID, PauseType> &pausedWaits, WaitInfoType &waitData) {
94
waitData = pausedWaits[pauseKey];
95
u64 waitDeadline = waitData.pausedTimeout;
96
pausedWaits.erase(pauseKey);
97
return waitDeadline;
98
}
99
100
// Retrieve the paused wait info from the list, and pop it.
101
// This version is for a simple std::map paused list.
102
// Should not be called directly.
103
template <>
104
inline u64 WaitPauseHelperGet<SceUID, u64>(SceUID pauseKey, SceUID threadID, std::map<SceUID, u64> &pausedWaits, SceUID &waitData) {
105
waitData = threadID;
106
u64 waitDeadline = pausedWaits[pauseKey];
107
pausedWaits.erase(pauseKey);
108
return waitDeadline;
109
}
110
111
enum WaitBeginEndCallbackResult {
112
// Returned when the thread cannot be found in the waiting threads list.
113
// Only returned for struct types, which have other data than the threadID.
114
WAIT_CB_BAD_WAIT_DATA = -2,
115
// Returned when the wait ID of the thread no longer matches the kernel object.
116
WAIT_CB_BAD_WAIT_ID = -1,
117
// Success, whether that means the wait was paused, deleted, etc.
118
WAIT_CB_SUCCESS = 0,
119
// Success, and resumed waiting. Useful for logging.
120
WAIT_CB_RESUMED_WAIT = 1,
121
// Success, but the wait timed out. Useful for logging.
122
WAIT_CB_TIMED_OUT = 2,
123
};
124
125
// Meant to be called in a registered begin callback function for a wait type.
126
//
127
// The goal of this function is to pause the wait. While inside a callback, waits are released.
128
// Once the callback returns, the wait should be resumed (see WaitEndCallback.)
129
//
130
// This assumes the object has been validated already. The primary purpose is if you need
131
// to use a specific pausedWaits list (for example, sceMsgPipe has two types of waiting per object.)
132
//
133
// In most cases, use the other, simpler version of WaitBeginCallback().
134
template <typename WaitInfoType, typename PauseType>
135
WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, std::vector<WaitInfoType> &waitingThreads, std::map<SceUID, PauseType> &pausedWaits, bool doTimeout = true) {
136
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
137
138
// This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.)
139
// TODO: Handle this better?
140
if (pausedWaits.find(pauseKey) != pausedWaits.end()) {
141
return WAIT_CB_SUCCESS;
142
}
143
144
u64 pausedTimeout = 0;
145
if (doTimeout && waitTimer != -1) {
146
s64 cyclesLeft = CoreTiming::UnscheduleEvent(waitTimer, threadID);
147
pausedTimeout = CoreTiming::GetTicks() + cyclesLeft;
148
}
149
150
if (!WaitPauseHelperUpdate(pauseKey, threadID, waitingThreads, pausedWaits, pausedTimeout)) {
151
return WAIT_CB_BAD_WAIT_DATA;
152
}
153
154
return WAIT_CB_SUCCESS;
155
}
156
157
// Meant to be called in a registered begin callback function for a wait type.
158
//
159
// The goal of this function is to pause the wait. While inside a callback, waits are released.
160
// Once the callback returns, the wait should be resumed (see WaitEndCallback.)
161
//
162
// In the majority of cases, calling this function is sufficient for the BeginCallback handler.
163
template <typename KO, WaitType waitType, typename WaitInfoType>
164
WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer) {
165
u32 error;
166
SceUID uid = __KernelGetWaitID(threadID, waitType, error);
167
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
168
KO *ko = uid == 0 ? NULL : kernelObjects.Get<KO>(uid, error);
169
if (ko) {
170
return WaitBeginCallback(threadID, prevCallbackId, waitTimer, ko->waitingThreads, ko->pausedWaits, timeoutPtr != 0);
171
} else {
172
return WAIT_CB_BAD_WAIT_ID;
173
}
174
}
175
176
// Meant to be called in a registered end callback function for a wait type.
177
//
178
// The goal of this function is to resume the wait, or to complete it if a wait is no longer needed.
179
//
180
// This version allows you to specify the pausedWaits and waitingThreads vectors, primarily for
181
// MsgPipes which have two waiting thread lists. Unlike the matching WaitBeginCallback() function,
182
// this still validates the wait (since it needs other data from the object.)
183
//
184
// In most cases, use the other, simpler version of WaitEndCallback().
185
template <typename KO, WaitType waitType, typename WaitInfoType, typename PauseType, class TryUnlockFunc>
186
WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, TryUnlockFunc TryUnlock, WaitInfoType &waitData, std::vector<WaitInfoType> &waitingThreads, std::map<SceUID, PauseType> &pausedWaits) {
187
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
188
189
// Note: Cancel does not affect suspended semaphore waits, probably same for others.
190
191
u32 error;
192
SceUID uid = __KernelGetWaitID(threadID, waitType, error);
193
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
194
KO *ko = uid == 0 ? NULL : kernelObjects.Get<KO>(uid, error);
195
if (!ko || pausedWaits.find(pauseKey) == pausedWaits.end()) {
196
// TODO: Since it was deleted, we don't know how long was actually left.
197
// For now, we just say the full time was taken.
198
if (timeoutPtr != 0 && waitTimer != -1) {
199
Memory::Write_U32(0, timeoutPtr);
200
}
201
202
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE);
203
return WAIT_CB_SUCCESS;
204
}
205
206
u64 waitDeadline = WaitPauseHelperGet(pauseKey, threadID, pausedWaits, waitData);
207
208
// TODO: Don't wake up if __KernelCurHasReadyCallbacks()?
209
210
bool wokeThreads;
211
// Attempt to unlock.
212
if (TryUnlock(ko, waitData, error, 0, wokeThreads)) {
213
return WAIT_CB_SUCCESS;
214
}
215
216
// We only check if it timed out if it couldn't unlock.
217
s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks();
218
if (cyclesLeft < 0 && waitDeadline != 0) {
219
if (timeoutPtr != 0 && waitTimer != -1) {
220
Memory::Write_U32(0, timeoutPtr);
221
}
222
223
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
224
return WAIT_CB_TIMED_OUT;
225
} else {
226
if (timeoutPtr != 0 && waitTimer != -1) {
227
CoreTiming::ScheduleEvent(cyclesLeft, waitTimer, __KernelGetCurThread());
228
}
229
return WAIT_CB_RESUMED_WAIT;
230
}
231
}
232
233
// Meant to be called in a registered end callback function for a wait type.
234
//
235
// The goal of this function is to resume the wait, or to complete it if a wait is no longer needed.
236
//
237
// The TryUnlockFunc signature should be (choosen due to similarity to existing funcitons):
238
// bool TryUnlock(KO *ko, WaitInfoType waitingThreadInfo, u32 &error, int result, bool &wokeThreads)
239
template <typename KO, WaitType waitType, typename WaitInfoType, class TryUnlockFunc>
240
WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, TryUnlockFunc TryUnlock) {
241
u32 error;
242
SceUID uid = __KernelGetWaitID(threadID, waitType, error);
243
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
244
KO *ko = uid == 0 ? NULL : kernelObjects.Get<KO>(uid, error);
245
// We need the ko for the vectors, but to avoid a null check we validate it here too.
246
if (!ko) {
247
// TODO: Since it was deleted, we don't know how long was actually left.
248
// For now, we just say the full time was taken.
249
if (timeoutPtr != 0 && waitTimer != -1) {
250
Memory::Write_U32(0, timeoutPtr);
251
}
252
253
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE);
254
return WAIT_CB_SUCCESS;
255
}
256
257
WaitInfoType waitData;
258
auto result = WaitEndCallback<KO, waitType>(threadID, prevCallbackId, waitTimer, TryUnlock, waitData, ko->waitingThreads, ko->pausedWaits);
259
if (result == WAIT_CB_RESUMED_WAIT) {
260
// TODO: Should this not go at the end?
261
ko->waitingThreads.push_back(waitData);
262
}
263
return result;
264
}
265
266
// Verify that a thread has not been released from waiting, e.g. by sceKernelReleaseWaitThread().
267
// For a waiting thread info struct.
268
template <typename T>
269
inline bool VerifyWait(const T &waitInfo, WaitType waitType, SceUID uid) {
270
u32 error;
271
SceUID waitID = __KernelGetWaitID(waitInfo.threadID, waitType, error);
272
return waitID == uid && error == 0;
273
}
274
275
// Verify that a thread has not been released from waiting, e.g. by sceKernelReleaseWaitThread().
276
template <>
277
inline bool VerifyWait(const SceUID &threadID, WaitType waitType, SceUID uid) {
278
u32 error;
279
SceUID waitID = __KernelGetWaitID(threadID, waitType, error);
280
return waitID == uid && error == 0;
281
}
282
283
// Resume a thread from waiting for a particular object.
284
template <typename T>
285
inline bool ResumeFromWait(SceUID threadID, WaitType waitType, SceUID uid, T result) {
286
if (VerifyWait(threadID, waitType, uid)) {
287
__KernelResumeThreadFromWait(threadID, result);
288
return true;
289
}
290
return false;
291
}
292
293
// Removes threads that are not waiting anymore from a waitingThreads list.
294
template <typename T>
295
inline void CleanupWaitingThreads(WaitType waitType, SceUID uid, std::vector<T> &waitingThreads) {
296
size_t size = waitingThreads.size();
297
for (size_t i = 0; i < size; ++i) {
298
if (!VerifyWait(waitingThreads[i], waitType, uid)) {
299
// Decrement size and swap what was there with i.
300
if (--size != i) {
301
std::swap(waitingThreads[i], waitingThreads[size]);
302
}
303
// Now we haven't checked the new i, so go back and do i again.
304
--i;
305
}
306
}
307
waitingThreads.resize(size);
308
}
309
310
template <typename T>
311
inline void RemoveWaitingThread(std::vector<T> &waitingThreads, const SceUID threadID) {
312
waitingThreads.erase(std::remove(waitingThreads.begin(), waitingThreads.end(), threadID), waitingThreads.end());
313
}
314
315
};
316
317