-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
CPU.cpp
366 lines (312 loc) · 9.23 KB
/
CPU.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/CPU.h"
#include <condition_variable>
#include <mutex>
#include <queue>
#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Core/CPUThreadConfigCallback.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/GDBStub.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "VideoCommon/Fifo.h"
namespace CPU
{
CPUManager::CPUManager(Core::System& system) : m_system(system)
{
}
CPUManager::~CPUManager() = default;
void CPUManager::Init(PowerPC::CPUCore cpu_core)
{
m_system.GetPowerPC().Init(cpu_core);
m_state = State::Stepping;
}
void CPUManager::Shutdown()
{
Stop();
m_system.GetPowerPC().Shutdown();
}
// Requires holding m_state_change_lock
void CPUManager::FlushStepSyncEventLocked()
{
if (!m_state_cpu_step_instruction)
return;
if (m_state_cpu_step_instruction_sync)
{
m_state_cpu_step_instruction_sync->Set();
m_state_cpu_step_instruction_sync = nullptr;
}
m_state_cpu_step_instruction = false;
}
void CPUManager::ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
{
while (!m_pending_jobs.empty())
{
auto callback = m_pending_jobs.front();
m_pending_jobs.pop();
state_lock.unlock();
callback();
state_lock.lock();
}
}
void CPUManager::Run()
{
auto& power_pc = m_system.GetPowerPC();
// Updating the host CPU's rounding mode must be done on the CPU thread.
// We can't rely on PowerPC::Init doing it, since it's called from EmuThread.
PowerPC::RoundingModeUpdated(power_pc.GetPPCState());
std::unique_lock state_lock(m_state_change_lock);
while (m_state != State::PowerDown)
{
m_state_cpu_cvar.wait(state_lock, [this] { return !m_state_paused_and_locked; });
ExecutePendingJobs(state_lock);
CPUThreadConfigCallback::CheckForConfigChanges();
Common::Event gdb_step_sync_event;
switch (m_state)
{
case State::Running:
m_state_cpu_thread_active = true;
state_lock.unlock();
// Adjust PC for JIT when debugging
// SingleStep so that the "continue", "step over" and "step out" debugger functions
// work when the PC is at a breakpoint at the beginning of the block
// If watchpoints are enabled, any instruction could be a breakpoint.
if (power_pc.GetMode() != PowerPC::CoreMode::Interpreter)
{
if (power_pc.GetBreakPoints().IsAddressBreakPoint(power_pc.GetPPCState().pc) ||
power_pc.GetMemChecks().HasAny())
{
m_state = State::Stepping;
PowerPC::CoreMode old_mode = power_pc.GetMode();
power_pc.SetMode(PowerPC::CoreMode::Interpreter);
power_pc.SingleStep();
power_pc.SetMode(old_mode);
m_state = State::Running;
}
}
// Enter a fast runloop
power_pc.RunLoop();
state_lock.lock();
m_state_cpu_thread_active = false;
m_state_cpu_idle_cvar.notify_all();
break;
case State::Stepping:
// Wait for step command.
m_state_cpu_cvar.wait(state_lock, [this, &state_lock, &gdb_step_sync_event] {
ExecutePendingJobs(state_lock);
CPUThreadConfigCallback::CheckForConfigChanges();
state_lock.unlock();
if (GDBStub::IsActive() && GDBStub::HasControl())
{
if (!GDBStub::JustConnected())
GDBStub::SendSignal(GDBStub::Signal::Sigtrap);
GDBStub::ProcessCommands(true);
// If we are still going to step, emulate the fact we just sent a step command
if (GDBStub::HasControl())
{
// Make sure the previous step by gdb was serviced
if (m_state_cpu_step_instruction_sync &&
m_state_cpu_step_instruction_sync != &gdb_step_sync_event)
{
m_state_cpu_step_instruction_sync->Set();
}
m_state_cpu_step_instruction = true;
m_state_cpu_step_instruction_sync = &gdb_step_sync_event;
}
}
state_lock.lock();
return m_state_cpu_step_instruction || !IsStepping();
});
if (!IsStepping())
{
// Signal event if the mode changes.
FlushStepSyncEventLocked();
continue;
}
if (m_state_paused_and_locked)
continue;
// Do step
m_state_cpu_thread_active = true;
state_lock.unlock();
power_pc.SingleStep();
state_lock.lock();
m_state_cpu_thread_active = false;
m_state_cpu_idle_cvar.notify_all();
// Update disasm dialog
FlushStepSyncEventLocked();
Host_UpdateDisasmDialog();
break;
case State::PowerDown:
break;
}
}
state_lock.unlock();
Host_UpdateDisasmDialog();
}
// Requires holding m_state_change_lock
void CPUManager::RunAdjacentSystems(bool running)
{
// NOTE: We're assuming these will not try to call Break or EnableStepping.
m_system.GetFifo().EmulatorState(running);
// Core is responsible for shutting down the sound stream.
if (m_state != State::PowerDown)
AudioCommon::SetSoundStreamRunning(m_system, running);
}
void CPUManager::Stop()
{
// Change state and wait for it to be acknowledged.
// We don't need the stepping lock because State::PowerDown is a priority state which
// will stick permanently.
std::unique_lock state_lock(m_state_change_lock);
m_state = State::PowerDown;
m_state_cpu_cvar.notify_one();
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
RunAdjacentSystems(false);
FlushStepSyncEventLocked();
}
bool CPUManager::IsStepping() const
{
return m_state == State::Stepping;
}
State CPUManager::GetState() const
{
return m_state;
}
const State* CPUManager::GetStatePtr() const
{
return &m_state;
}
void CPUManager::Reset()
{
}
void CPUManager::StepOpcode(Common::Event* event)
{
std::lock_guard state_lock(m_state_change_lock);
// If we're not stepping then this is pointless
if (!IsStepping())
{
if (event)
event->Set();
return;
}
// Potential race where the previous step has not been serviced yet.
if (m_state_cpu_step_instruction_sync && m_state_cpu_step_instruction_sync != event)
m_state_cpu_step_instruction_sync->Set();
m_state_cpu_step_instruction = true;
m_state_cpu_step_instruction_sync = event;
m_state_cpu_cvar.notify_one();
}
// Requires m_state_change_lock
bool CPUManager::SetStateLocked(State s)
{
if (m_state == State::PowerDown)
return false;
m_state = s;
return true;
}
void CPUManager::EnableStepping(bool stepping)
{
std::lock_guard stepping_lock(m_stepping_lock);
std::unique_lock state_lock(m_state_change_lock);
if (stepping)
{
SetStateLocked(State::Stepping);
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
RunAdjacentSystems(false);
}
else if (SetStateLocked(State::Running))
{
m_state_cpu_cvar.notify_one();
RunAdjacentSystems(true);
}
}
void CPUManager::Break()
{
std::lock_guard state_lock(m_state_change_lock);
// If another thread is trying to PauseAndLock then we need to remember this
// for later to ignore the unpause_on_unlock.
if (m_state_paused_and_locked)
{
m_state_system_request_stepping = true;
return;
}
// We'll deadlock if we synchronize, the CPU may block waiting for our caller to
// finish resulting in the CPU loop never terminating.
SetStateLocked(State::Stepping);
RunAdjacentSystems(false);
}
void CPUManager::Continue()
{
EnableStepping(false);
Core::CallOnStateChangedCallbacks(Core::State::Running);
}
bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
{
// NOTE: This is protected by m_stepping_lock.
static bool s_have_fake_cpu_thread = false;
bool was_unpaused = false;
if (do_lock)
{
m_stepping_lock.lock();
std::unique_lock state_lock(m_state_change_lock);
m_state_paused_and_locked = true;
was_unpaused = m_state == State::Running;
SetStateLocked(State::Stepping);
while (m_state_cpu_thread_active)
{
m_state_cpu_idle_cvar.wait(state_lock);
}
if (control_adjacent)
RunAdjacentSystems(false);
state_lock.unlock();
// NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a
// depth counter instead of being a boolean.
if (!Core::IsCPUThread())
{
s_have_fake_cpu_thread = true;
Core::DeclareAsCPUThread();
}
}
else
{
// Only need the stepping lock for this
if (s_have_fake_cpu_thread)
{
s_have_fake_cpu_thread = false;
Core::UndeclareAsCPUThread();
}
{
std::lock_guard state_lock(m_state_change_lock);
if (m_state_system_request_stepping)
{
m_state_system_request_stepping = false;
}
else if (unpause_on_unlock && SetStateLocked(State::Running))
{
was_unpaused = true;
}
m_state_paused_and_locked = false;
m_state_cpu_cvar.notify_one();
if (control_adjacent)
RunAdjacentSystems(m_state == State::Running);
}
m_stepping_lock.unlock();
}
return was_unpaused;
}
void CPUManager::AddCPUThreadJob(std::function<void()> function)
{
std::unique_lock state_lock(m_state_change_lock);
m_pending_jobs.push(std::move(function));
}
} // namespace CPU