CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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