1 // 2 // Copyright (c) 2010-2025 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using System.Collections.Concurrent; 10 using System.Collections.Generic; 11 using System.Diagnostics; 12 using System.IO; 13 using System.Linq; 14 using System.Reflection; 15 using System.Runtime.ExceptionServices; 16 using System.Runtime.InteropServices; 17 using System.Text; 18 using System.Threading; 19 using Antmicro.Migrant; 20 using Antmicro.Migrant.Hooks; 21 using Antmicro.Renode.Core; 22 using Antmicro.Renode.Debugging; 23 using Antmicro.Renode.Exceptions; 24 using Antmicro.Renode.Logging; 25 using Antmicro.Renode.Logging.Profiling; 26 using Antmicro.Renode.Peripherals.Bus; 27 using Antmicro.Renode.Peripherals.CPU.Disassembler; 28 using Antmicro.Renode.Peripherals.CPU.Registers; 29 using Antmicro.Renode.Time; 30 using Antmicro.Renode.Utilities; 31 using Antmicro.Renode.Utilities.Binding; 32 using ELFSharp.ELF; 33 using ELFSharp.UImage; 34 using Machine = Antmicro.Renode.Core.Machine; 35 36 namespace Antmicro.Renode.Peripherals.CPU 37 { 38 /// <summary> 39 /// <see cref="BaseCPU"/> implements <see cref="ICluster{T}"/> interface 40 /// to seamlessly handle either cluster or CPU as a parameter to different methods. 41 /// </summary> 42 public abstract class BaseCPU : CPUCore, ICluster<BaseCPU>, ICPU, IDisposable, ITimeSink, IInitableCPU 43 { BaseCPU(uint id, string cpuType, IMachine machine, Endianess endianness, CpuBitness bitness = CpuBitness.Bits32)44 protected BaseCPU(uint id, string cpuType, IMachine machine, Endianess endianness, CpuBitness bitness = CpuBitness.Bits32) 45 : base(id) 46 { 47 if(cpuType == null) 48 { 49 throw new ConstructionException("cpuType was null"); 50 } 51 52 Endianness = endianness; 53 PerformanceInMips = 100; 54 this.Model = cpuType; 55 this.machine = machine; 56 this.bitness = bitness; 57 isPaused = true; 58 59 singleStepSynchronizer = new Synchronizer(); 60 EmulationManager.Instance.CurrentEmulation.SingleStepBlockingChanged += () => UpdateHaltedState(); 61 62 Clustered = new BaseCPU[] { this }; 63 } 64 InitFromElf(IELF elf)65 public virtual void InitFromElf(IELF elf) 66 { 67 if(elf.GetBitness() > (int)bitness) 68 { 69 throw new RecoverableException($"Unsupported ELF format - trying to load a {elf.GetBitness()}-bit ELF on a {(int)bitness}-bit machine"); 70 } 71 72 this.Log(LogLevel.Info, "Setting PC value to 0x{0:X}.", elf.GetEntryPoint()); 73 SetPCFromEntryPoint(elf.GetEntryPoint()); 74 } 75 InitFromUImage(UImage uImage)76 public virtual void InitFromUImage(UImage uImage) 77 { 78 this.Log(LogLevel.Info, "Setting PC value to 0x{0:X}.", uImage.EntryPoint); 79 SetPCFromEntryPoint(uImage.EntryPoint); 80 } 81 Step(int count = 1)82 public ulong Step(int count = 1) 83 { 84 if(IsHalted) 85 { 86 this.Log(LogLevel.Warning, "Ignoring stepping on a halted CPU"); 87 return PC; 88 } 89 90 lock(singleStepSynchronizer.Guard) 91 { 92 ExecutionMode = ExecutionMode.SingleStep; 93 94 // Starting emulation has to be done after changing ExecutionMode. Otherwise, continuous CPUs won't be waiting for step command. 95 var emulation = EmulationManager.Instance.CurrentEmulation; 96 if(!emulation.IsStarted) 97 { 98 var emulationCPUs = emulation.Machines.SelectMany(x => x.SystemBus.GetCPUs()); 99 var continuousCPUs = emulationCPUs.Where(x => x.ExecutionMode == ExecutionMode.Continuous); 100 if(continuousCPUs.Any()) 101 { 102 this.Log(LogLevel.Info, "Automatically starting all CPUs due to '{0}' including CPUs with {1} {2}: {3}", nameof(Step), 103 ExecutionMode.Continuous, nameof(ExecutionMode), Misc.PrettyPrintCollection(continuousCPUs, x => x.GetName()) 104 ); 105 } 106 emulation.StartAll(); 107 } 108 else 109 { 110 Resume(); 111 } 112 113 this.Log(LogLevel.Noisy, "Stepping {0} step(s)", count); 114 115 var th = TimeHandle; 116 if(th != null) 117 { 118 th.DeferredEnabled = true; 119 } 120 121 // Invoking this to allow virtual time to be granted, without setting currentHaltedState to false 122 if(!IsSingleStepBlocking) 123 { 124 UpdateHaltedState(ignoreExecutionMode: true); 125 } 126 127 singleStepSynchronizer.CommandStep(count); 128 singleStepSynchronizer.WaitForStepFinished(); 129 130 UpdateHaltedState(); 131 132 return PC; 133 } 134 } 135 SkipTime(TimeInterval amountOfTime)136 public void SkipTime(TimeInterval amountOfTime) 137 { 138 var instructions = amountOfTime.ToCPUCycles(PerformanceInMips, out var residuum); 139 if(residuum > 0) 140 { 141 // We want to execute instructions for at least required amount of time, so we should add 142 // instructions += ceil(residuum / TicksPerMicrosecond) * PerformanceInMips 143 // As residuum < TicksPerMicrosecond by definition, ceiling of it will be always 1 144 instructions += PerformanceInMips; 145 var newInterval = TimeInterval.FromCPUCycles(instructions, PerformanceInMips, out var _); 146 this.Log(LogLevel.Warning, "Conversion from time to instructions is not exact, real time skipped: {0}", newInterval); 147 } 148 SkipInstructions += instructions; 149 } 150 Dispose()151 public virtual void Dispose() 152 { 153 DisposeInner(); 154 } 155 Reset()156 public virtual void Reset() 157 { 158 isAborted = false; 159 Pause(); 160 EmulationState = EmulationCPUState.InReset; 161 } 162 SyncTime()163 public virtual void SyncTime() 164 { 165 // by default do nothing 166 } 167 ExecuteInstructions(ulong numberOfInstructionsToExecute, out ulong numberOfExecutedInstructions)168 public abstract ExecutionResult ExecuteInstructions(ulong numberOfInstructionsToExecute, out ulong numberOfExecutedInstructions); 169 170 public abstract string Architecture { get; } 171 172 public Endianess Endianness { get; } 173 174 public IBusController Bus => machine.SystemBus; 175 176 public IEnumerable<ICluster<BaseCPU>> Clusters { get; } = new List<ICluster<BaseCPU>>(0); 177 178 public IEnumerable<BaseCPU> Clustered { get; } 179 180 public string Model { get; } 181 182 public uint PerformanceInMips 183 { 184 get => performanceInMips.Value; 185 set => performanceInMips.Value = value; 186 } 187 188 //The debug mode disables interrupt handling in the emulated CPU 189 //Additionally, some instructions, suspending execution, until an interrupt arrives (e.g. HLT on x86 or WFI on ARM) are treated as NOP 190 public virtual bool ShouldEnterDebugMode 191 { 192 get => shouldEnterDebugMode; 193 set 194 { 195 if(value == true && !(DebuggerConnected && IsSingleStepMode)) 196 { 197 this.Log(LogLevel.Warning, "The debug mode now has no effect - connect a debugger, and switch to stepping mode."); 198 } 199 shouldEnterDebugMode = value; 200 } 201 } 202 203 public bool OnPossessedThread 204 { 205 get 206 { 207 var cpuThreadLocal = cpuThread; 208 return cpuThreadLocal != null && Thread.CurrentThread.ManagedThreadId == cpuThreadLocal.ManagedThreadId; 209 } 210 } 211 212 public bool DebuggerConnected { get; set; } 213 214 public override bool IsHalted 215 { 216 get 217 { 218 return isHaltedRequested; 219 } 220 set 221 { 222 this.Trace(); 223 if(value == isHaltedRequested) 224 { 225 return; 226 } 227 228 lock(pauseLock) 229 { 230 this.Trace(); 231 isHaltedRequested = value; 232 UpdateHaltedState(); 233 234 if(value) 235 { 236 if(started && !isPaused) 237 { 238 wasRunningWhenHalted = true; 239 Pause(new HaltArguments(HaltReason.Pause, this), checkPauseGuard: false); 240 } 241 } 242 else 243 { 244 if(EmulationState == EmulationCPUState.InReset) 245 { 246 EmulationState = EmulationCPUState.Running; 247 } 248 249 if(wasRunningWhenHalted) 250 { 251 Resume(); 252 } 253 } 254 } 255 } 256 } 257 258 /// <remarks><c>StateChanged</c> is invoked when the value gets changed.</remarks> 259 public EmulationCPUState EmulationState 260 { 261 get => state; 262 263 private set 264 { 265 var oldState = state; 266 if(oldState == value) 267 { 268 return; 269 } 270 state = value; 271 if(oldState == EmulationCPUState.InReset) 272 { 273 OnLeavingResetState(); 274 } 275 StateChanged?.Invoke(this, oldState, value); 276 } 277 } 278 279 public TimeHandle TimeHandle 280 { 281 get 282 { 283 return timeHandle; 284 } 285 set 286 { 287 this.Trace("Setting a new time handle"); 288 timeHandle?.Dispose(); 289 lock(haltedLock) 290 { 291 timeHandle = value; 292 timeHandle.Enabled = !currentHaltedState; 293 timeHandle.PauseRequested += RequestPause; 294 timeHandle.StartRequested += StartCPUThreadTimeHandle; 295 } 296 } 297 } 298 299 public ulong SkippedInstructions { get; private set; } 300 301 public virtual ExecutionMode ExecutionMode 302 { 303 get 304 { 305 return executionMode; 306 } 307 308 set 309 { 310 lock(singleStepSynchronizer.Guard) 311 { 312 if(executionMode == value) 313 { 314 return; 315 } 316 317 executionMode = value; 318 319 singleStepSynchronizer.Enabled = IsSingleStepMode; 320 UpdateHaltedState(); 321 } 322 } 323 } 324 325 public event Action<HaltArguments> Halted; 326 327 /// <remarks>The arguments passed are: <c>StateChanged(cpu, oldState, newState)</c>.</remarks> 328 public event Action<ICPU, EmulationCPUState, EmulationCPUState> StateChanged; 329 330 public abstract ulong ExecutedInstructions { get; } 331 public abstract RegisterValue PC { get; set; } 332 333 public bool IsPaused => isPaused; 334 InnerPause(bool onCpuThread, bool checkPauseGuard)335 protected virtual void InnerPause(bool onCpuThread, bool checkPauseGuard) 336 { 337 RequestPause(); 338 339 if(!onCpuThread) 340 { 341 bool success = false; 342 do 343 { 344 const int startPauseDeadlockResolveTimeoutMs = 100; 345 TimeHandle.Interrupt(ref success, startPauseDeadlockResolveTimeoutMs); 346 if(!success) 347 { 348 /// We weren't able to interrupt the <see cref="TimeHandle"/>. 349 /// Check marker to distinguish a usual timeout from a deadlock. 350 if(pauseLockTimeHandleMarker) 351 { 352 /// We have a deadlock with other thread in <see cref="StartCPUThreadTimeHandle"/>. 353 /// Release <see cref="pauseLock"/> and try <see cref="TimeHandle.Interrupt"/> again later after the deadlock is resolved. 354 /// In rare cases we expect a deadlock as a result of starting and pausing cpu from different threads. 355 /// We use <see cref="Monitor.Pulse"/> on <see cref="pauseLock"/> only in <see cref="StartCPUThreadTimeHandle"/> 356 /// which is a verified case of the race between time source and Monitor thread. 357 /// Clear marker before releasing lock to select short path in <see cref="StartCPUThreadTimeHandle"/>. 358 pauseLockTimeHandleMarker = false; 359 Monitor.Wait(pauseLock); 360 /// We should get control back very soon due to short path in <see cref="StartCPUThreadTimeHandle"/>. 361 DebugHelper.Assert(pauseLockTimeHandleMarker); 362 pauseLockTimeHandleMarker = false; 363 } 364 else 365 { 366 /// We have a usual timeout. Try <see cref="TimeHandle.Interrupt"/> again. 367 /// Leave a warning here, as this may require special attention performance-wise. 368 this.Log(LogLevel.Warning, "Trying to stop CPU execution, but it is taking longer than expected..."); 369 } 370 } 371 } 372 while(!success); 373 } 374 } 375 Pause(HaltArguments haltArgs, bool checkPauseGuard)376 protected virtual void Pause(HaltArguments haltArgs, bool checkPauseGuard) 377 { 378 if(isAborted || isPaused) 379 { 380 // cpu is already paused or aborted 381 return; 382 } 383 384 lock(pauseLock) 385 { 386 // cpuThread can get null as a result of `InnerPause` call 387 var cpuThreadCopy = cpuThread; 388 var onCpuThread = (cpuThreadCopy != null && Thread.CurrentThread.ManagedThreadId == cpuThreadCopy.ManagedThreadId); 389 390 InnerPause(onCpuThread, checkPauseGuard); 391 392 if(!onCpuThread) 393 { 394 singleStepSynchronizer.Enabled = false; 395 this.NoisyLog("Waiting for thread to pause."); 396 cpuThreadCopy?.Join(); 397 this.NoisyLog("Paused."); 398 } 399 400 isPaused = true; 401 } 402 403 InvokeHalted(haltArgs); 404 } 405 ReportProgress(ulong instructions)406 protected void ReportProgress(ulong instructions) 407 { 408 if(instructions > 0) 409 { 410 instructionsLeftThisRound -= instructions; 411 instructionsExecutedThisRound += instructions; 412 // CPU is `executedResiduum` instructions ahead of the reported time and this value is smaller than the smallest positive possible amount to report, 413 // so we report sum of currently executed/skipped instructions and residuum from previously reported progress. 414 var intervalToReport = TimeInterval.FromCPUCycles(instructions + executedResiduum, PerformanceInMips, out executedResiduum); 415 TimeHandle.ReportProgress(intervalToReport); 416 } 417 } 418 OnLeavingResetState()419 protected virtual void OnLeavingResetState() 420 { 421 // Intentionally left blank. 422 } 423 OnResume()424 protected override void OnResume() 425 { 426 if(EmulationState == EmulationCPUState.InReset && !currentHaltedState) 427 { 428 EmulationState = EmulationCPUState.Running; 429 } 430 singleStepSynchronizer.Enabled = IsSingleStepMode; 431 StartCPUThread(); 432 } 433 OnPause()434 protected override void OnPause() 435 { 436 Pause(new HaltArguments(HaltReason.Pause, this), checkPauseGuard: true); 437 } 438 RequestPause()439 protected virtual void RequestPause() 440 { 441 lock(pauseLock) 442 { 443 isPaused = true; 444 this.Trace("Requesting pause"); 445 sleeper.Interrupt(); 446 } 447 } 448 DisposeInner(bool silent = false)449 protected virtual void DisposeInner(bool silent = false) 450 { 451 // Take a copy of the CPU thread because it will be cleared at the end of its body 452 var cpuThreadCopy = cpuThread; 453 disposing = true; 454 if(!silent) 455 { 456 this.NoisyLog("About to dispose CPU."); 457 } 458 started = false; 459 Pause(new HaltArguments(HaltReason.Abort, this), checkPauseGuard: false); 460 singleStepSynchronizer.Enabled = false; 461 cpuThreadCopy?.Join(); 462 } 463 InvokeHalted(HaltArguments arguments)464 protected void InvokeHalted(HaltArguments arguments) 465 { 466 var halted = Halted; 467 if(halted != null) 468 { 469 halted(arguments); 470 } 471 } 472 CpuThreadBody()473 protected virtual void CpuThreadBody() 474 { 475 var isLocked = false; 476 try 477 { 478 #if DEBUG 479 using(this.TraceRegion("CPU loop")) 480 #endif 481 using(var activityTracker = (DisposableWrapper)this.ObtainSinkActiveState()) 482 using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(TimeHandle.TotalElapsedTime, TimeHandle.TimeSource.Domain))) 483 { 484 try 485 { 486 restart: 487 while(!isPaused && !isAborted) 488 { 489 var singleStep = false; 490 // locking here is to ensure that execution mode does not change 491 // before calling `WaitForStepCommand` method 492 lock(singleStepSynchronizer.Guard) 493 { 494 singleStep = IsSingleStepMode; 495 if(singleStep) 496 { 497 // we become incactive as we wait for step command 498 using(this.ObtainSinkInactiveState()) 499 { 500 this.Log(LogLevel.Noisy, "Waiting for a step instruction (PC=0x{0:X8}).", PC.RawValue); 501 InvokeHalted(new HaltArguments(HaltReason.Step, this)); 502 if(!singleStepSynchronizer.WaitForStepCommand()) 503 { 504 this.Trace(); 505 continue; 506 } 507 this.Trace(); 508 } 509 } 510 } 511 512 var cpuResult = CpuThreadBodyInner(singleStep); 513 514 if(singleStep) 515 { 516 switch(cpuResult) 517 { 518 case CpuResult.NothingExecuted: 519 break; 520 case CpuResult.MmuFault: 521 this.Trace("Interrupting stepping due to the external MMU fault"); 522 singleStepSynchronizer.StepInterrupted(); 523 break; 524 default: 525 this.Trace(); 526 singleStepSynchronizer.StepFinished(); 527 break; 528 } 529 } 530 } 531 532 this.Trace(); 533 lock(cpuThreadBodyLock) 534 { 535 if(dispatcherRestartRequested) 536 { 537 dispatcherRestartRequested = false; 538 this.Trace(); 539 goto restart; 540 } 541 542 this.Trace(); 543 // the `locker` is re-acquired here to 544 // make sure that dispose-related code of all usings 545 // is executed before setting `dispatcherThread` to 546 // null (what allows to start new dispatcher thread); 547 // otherwise there could be a race condition when 548 // new thread enters usings (e.g., activates sink side) 549 // and then the old one exits them (deactivating sink 550 // side as a result) 551 Monitor.Enter(cpuThreadBodyLock, ref isLocked); 552 } 553 } 554 catch(Exception) 555 { 556 // being here means we are in trouble anyway, 557 // so we don't have to care about the time framework 558 // protocol that much; 559 // without disabling activity tracker 560 // it will try to disable the time handle 561 // which might in turn crash with it's own 562 // exception (hiding the original one) 563 activityTracker.Disable(); 564 throw; 565 } 566 } 567 } 568 finally 569 { 570 cpuThread = null; 571 if(isLocked) 572 { 573 this.Trace(); 574 Monitor.Exit(cpuThreadBodyLock); 575 } 576 this.Trace(); 577 } 578 } 579 ExecutionFinished(ExecutionResult result)580 protected virtual bool ExecutionFinished(ExecutionResult result) 581 { 582 return false; 583 } 584 CpuThreadBodyInner(bool singleStep)585 protected CpuResult CpuThreadBodyInner(bool singleStep) 586 { 587 if(!TimeHandle.RequestTimeInterval(out var interval)) 588 { 589 this.Trace(); 590 return CpuResult.NothingExecuted; 591 } 592 593 using(performanceInMips.Seal()) 594 { 595 this.Trace($"CPU thread body running... granted {interval.Ticks} ticks"); 596 var mmuFaultThrown = false; 597 var initialExecutedResiduum = executedResiduum; 598 var initialTotalElapsedTime = TimeHandle.TotalElapsedTime; 599 TimeInterval virtualTimeAhead; 600 601 var instructionsToExecuteThisRound = interval.ToCPUCycles(PerformanceInMips, out ulong ticksResiduum); 602 if(instructionsToExecuteThisRound <= executedResiduum) 603 { 604 this.Trace("not enough time granted, reporting continue"); 605 TimeHandle.ReportBackAndContinue(interval); 606 return CpuResult.NothingExecuted; 607 } 608 instructionsLeftThisRound = Math.Min(instructionsToExecuteThisRound - executedResiduum, singleStep ? 1 : ulong.MaxValue); 609 instructionsExecutedThisRound = executedResiduum; 610 611 while(!isPaused && !currentHaltedState && instructionsLeftThisRound > 0) 612 { 613 this.Trace($"CPU thread body in progress; {instructionsLeftThisRound} instructions left..."); 614 615 // this puts a limit on instructions to execute in one round 616 // and makes timers update independent of the current quantum 617 var toExecute = Math.Min(InstructionsToNearestLimit(), instructionsLeftThisRound); 618 619 if(skipInstructions > 0) 620 { 621 var amountOfInstructions = Math.Min(skipInstructions, toExecute); 622 this.Trace($"Skipping {amountOfInstructions} instructions"); 623 624 toExecute -= amountOfInstructions; 625 skipInstructions -= amountOfInstructions; 626 SkippedInstructions += amountOfInstructions; 627 ReportProgress(amountOfInstructions); 628 // We have to update progress immidietely, as we could potentially 629 // call SyncTime during ExecuteInstructions 630 } 631 632 // set upper limit on instructions to execute to `int.MaxValue` 633 // as otherwise it would overflow further down in ExecuteInstructions 634 toExecute = Math.Min(toExecute, int.MaxValue); 635 var result = ExecutionResult.Ok; 636 if(toExecute > 0) 637 { 638 this.Trace($"Asking CPU to execute {toExecute} instructions"); 639 640 result = ExecuteInstructions(toExecute, out var executed); 641 this.Trace($"CPU executed {executed} instructions and returned {result}"); 642 machine.Profiler?.Log(new InstructionEntry(machine.SystemBus.GetCPUSlot(this), ExecutedInstructions)); 643 ReportProgress(executed); 644 } 645 if(ExecutionFinished(result)) 646 { 647 break; 648 } 649 650 if(result == ExecutionResult.WaitingForInterrupt) 651 { 652 if(!InDebugMode && !neverWaitForInterrupt) 653 { 654 this.Trace(); 655 var instructionsToSkip = Math.Min(InstructionsToNearestLimit(), instructionsLeftThisRound); 656 657 virtualTimeAhead = machine.LocalTimeSource.ElapsedVirtualHostTimeDifference; 658 if(!machine.LocalTimeSource.AdvanceImmediately && virtualTimeAhead.Ticks > 0 && instructionsToSkip > 0) 659 { 660 // Don't fall behind realtime by sleeping 661 var intervalToSleep = TimeInterval.FromCPUCycles(instructionsToSkip, PerformanceInMips, out var cyclesResiduum).WithTicksMin(virtualTimeAhead.Ticks); 662 sleeper.Sleep(intervalToSleep.ToTimeSpan(out var nsResiduum), out var intervalSlept); 663 // If we have a CPU of less than 10 MIPS, it might be the case that the interval slept (which is in units of 100 ns) 664 // is less than 1 instruction. Just round up it in this case. 665 instructionsToSkip = Math.Max(TimeInterval.FromTimeSpan(intervalSlept, nsResiduum).ToCPUCycles(PerformanceInMips, out var _) + cyclesResiduum, 1); 666 } 667 668 ReportProgress(instructionsToSkip); 669 } 670 } 671 else if(result == ExecutionResult.ExternalMmuFault) 672 { 673 this.Trace(result.ToString()); 674 mmuFaultThrown = true; 675 break; 676 } 677 else if(result == ExecutionResult.Aborted) 678 { 679 this.Trace(result.ToString()); 680 isAborted = true; 681 break; 682 } 683 else if(result == ExecutionResult.Interrupted || result == ExecutionResult.StoppedAtWatchpoint) 684 { 685 this.Trace(result.ToString()); 686 break; 687 } 688 } 689 690 // If AdvanceImmediately is not enabled, and virtual time has surpassed host time, 691 // sleep to make up the difference. 692 // However, if pause was requested, we want to exit as soon as possible without sleeping, 693 // because it was requested from interactive context (e.g. "pause" monitor command). 694 virtualTimeAhead = machine.LocalTimeSource.ElapsedVirtualHostTimeDifference; 695 if(!machine.LocalTimeSource.AdvanceImmediately && virtualTimeAhead.Ticks > 0 && !isPaused) 696 { 697 // Ignore the return value, if the sleep is interrupted we'll make up any extra 698 // remaining difference next time. Preserve the interrupt request so that if this 699 // extra sleep is interrupted due to a CPU pause, it will be picked up by the WFI 700 // handling above. 701 sleeper.Sleep(virtualTimeAhead.ToTimeSpan(), out var _, preserveInterruptRequest: true); 702 } 703 704 this.Trace("CPU thread body finished"); 705 706 if(isAborted) 707 { 708 this.Trace("aborted, reporting continue"); 709 TimeHandle.ReportBackAndContinue(TimeInterval.Empty); 710 executedResiduum = 0; 711 EmulationState = EmulationCPUState.Aborted; 712 return CpuResult.Aborted; 713 } 714 else if(currentHaltedState) 715 { 716 this.Trace("halted, reporting continue"); 717 TimeHandle.ReportBackAndContinue(TimeInterval.Empty); 718 executedResiduum = 0; 719 } 720 else 721 { 722 var instructionsLeft = instructionsToExecuteThisRound - instructionsExecutedThisRound; 723 // instructionsExecutedThisRound = reportedInstructions + executedResiduum 724 // reportedInstructions + executedResiduum + instructionsLeft = instructionsToExecuteThisRound 725 // reportedInstructions is divisible by instructionsPerTick and instructionsToExecuteThisRound is divisible by instructionsPerTick 726 // so instructionsLeft + executedResiduum is divisible by instructionsPerTick and residuum is 0 727 var timeLeft = TimeInterval.FromCPUCycles(instructionsLeft + executedResiduum, PerformanceInMips, out var residuum) + TimeInterval.FromMicroseconds(ticksResiduum); 728 DebugHelper.Assert(residuum == 0); 729 if(instructionsLeft > 0) 730 { 731 this.Trace("reporting break"); 732 TimeHandle.ReportBackAndBreak(timeLeft); 733 } 734 else 735 { 736 DebugHelper.Assert(executedResiduum == 0); 737 // executedResiduum < instructionsPerTick so timeLeft is 0 + ticksResiduum 738 this.Trace("finished, reporting continue"); 739 TimeHandle.ReportBackAndContinue(timeLeft); 740 } 741 } 742 743 if(mmuFaultThrown) 744 { 745 return CpuResult.MmuFault; 746 } 747 else if(executedResiduum == initialExecutedResiduum && TimeHandle.TotalElapsedTime == initialTotalElapsedTime) 748 { 749 return CpuResult.NothingExecuted; 750 } 751 return CpuResult.ExecutedInstructions; 752 } 753 } 754 StartCPUThreadTimeHandle()755 private void StartCPUThreadTimeHandle() 756 { 757 this.Trace(); 758 pauseLockTimeHandleMarker = true; 759 lock(pauseLock) 760 { 761 if(!pauseLockTimeHandleMarker) 762 { 763 /// Marker value got changed by other thread in <see cref="InnerPause"/>. 764 /// We escaped from a deadlock and we are going to be interrupted by <see cref="TimeHandle.Interrupt"/>. 765 /// It means there is a race between starting and pausing cpu from different threads. 766 /// We allow pausing to win in such case because pausing cpu is more "explicit" operation 767 /// than starting cpu as part of <see cref="TimeHandle.StartRequested"/>. 768 /// We were allowed to enter this critical region guarded by <see cref="pauseLock"/> due to special circumstances 769 /// despite <see cref="pauseLock"/> having previously been held by <see cref="Pause"/> + <see cref="InnerPause"/>. 770 /// Signal that we escaped deadlock condition and return control to the other thread waiting in <see cref="InnerPause"/>. 771 pauseLockTimeHandleMarker = true; 772 Monitor.Pulse(pauseLock); 773 return; 774 } 775 pauseLockTimeHandleMarker = false; // clear marker 776 StartCPUThreadInner(); 777 } 778 } 779 StartCPUThread()780 protected void StartCPUThread() 781 { 782 this.Trace(); 783 lock(pauseLock) 784 { 785 StartCPUThreadInner(); 786 } 787 } 788 StartCPUThreadInner()789 private void StartCPUThreadInner() 790 { 791 lock(cpuThreadBodyLock) 792 { 793 if(isAborted) 794 { 795 return; 796 } 797 if(cpuThread == null) 798 { 799 this.Trace(); 800 cpuThread = new Thread(CpuThreadBody) 801 { 802 IsBackground = true, 803 Name = this.GetCPUThreadName(machine) 804 }; 805 cpuThread.Start(); 806 } 807 else 808 { 809 this.Trace(); 810 dispatcherRestartRequested = true; 811 } 812 } 813 } 814 CheckIfOnSynchronizedThread()815 protected void CheckIfOnSynchronizedThread() 816 { 817 if(Thread.CurrentThread.ManagedThreadId != cpuThread.ManagedThreadId) 818 { 819 this.Log(LogLevel.Warning, "An interrupt from the unsynchronized thread."); 820 } 821 } 822 823 [Conditional("DEBUG")] CheckCpuThreadId()824 protected void CheckCpuThreadId() 825 { 826 if(Thread.CurrentThread != cpuThread) 827 { 828 throw new ArgumentException( 829 string.Format("Method called from a wrong thread. Expected {0}, but got {1}", 830 cpuThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId)); 831 } 832 } 833 UpdateHaltedState(bool ignoreExecutionMode = false)834 protected virtual bool UpdateHaltedState(bool ignoreExecutionMode = false) 835 { 836 var shouldBeHalted = isHaltedRequested || (IsSingleStepMode && !IsSingleStepBlocking && !ignoreExecutionMode); 837 838 if(shouldBeHalted == currentHaltedState) 839 { 840 return false; 841 } 842 843 lock(pauseLock) 844 { 845 this.Trace(); 846 currentHaltedState = shouldBeHalted; 847 if(TimeHandle != null) 848 { 849 this.Trace(); 850 TimeHandle.DeferredEnabled = !shouldBeHalted; 851 } 852 } 853 854 return true; 855 } 856 857 public virtual ulong SkipInstructions 858 { 859 get => skipInstructions; 860 protected set => skipInstructions = value; 861 } 862 863 protected static bool IsSingleStepBlocking => EmulationManager.Instance.CurrentEmulation.SingleStepBlocking; 864 865 protected bool InDebugMode => DebuggerConnected && ShouldEnterDebugMode && IsSingleStepMode; 866 protected bool IsSingleStepMode => executionMode == ExecutionMode.SingleStep; 867 868 protected bool shouldEnterDebugMode; 869 protected bool neverWaitForInterrupt; 870 protected bool dispatcherRestartRequested; 871 protected bool isHaltedRequested; 872 protected bool currentHaltedState; 873 874 [Transient] 875 protected ExecutionMode executionMode; 876 877 [Transient] 878 protected bool disposing; 879 880 [Constructor] 881 protected readonly Synchronizer singleStepSynchronizer; 882 883 protected readonly Sleeper sleeper = new Sleeper(); 884 protected readonly CpuBitness bitness; 885 protected readonly IMachine machine; 886 887 protected enum CpuResult 888 { 889 ExecutedInstructions = 0, 890 NothingExecuted = 1, 891 MmuFault = 2, 892 Aborted = 3, 893 } 894 895 protected class RegisterAttribute : Attribute 896 { 897 } 898 InstructionsToNearestLimit()899 private ulong InstructionsToNearestLimit() 900 { 901 var nearestLimitIn = ((BaseClockSource)machine.ClockSource).NearestLimitIn; 902 var instructionsToNearestLimit = nearestLimitIn.ToCPUCycles(PerformanceInMips, out var unused); 903 // the limit must be reached or surpassed for limit's owner to execute 904 if(instructionsToNearestLimit <= executedResiduum) 905 { 906 return 1; 907 } 908 instructionsToNearestLimit -= executedResiduum; 909 if(instructionsToNearestLimit != ulong.MaxValue && (nearestLimitIn.Ticks == 0 || unused > 0)) 910 { 911 // we must check for `ulong.MaxValue` as otherwise it would overflow 912 instructionsToNearestLimit++; 913 } 914 return instructionsToNearestLimit; 915 } 916 SetPCFromEntryPoint(ulong entryPoint)917 private void SetPCFromEntryPoint(ulong entryPoint) 918 { 919 var what = machine.SystemBus.WhatIsAt(entryPoint, this); 920 if(what != null) 921 { 922 if(((what.Peripheral as IMemory) == null) && ((what.Peripheral as Redirector) != null)) 923 { 924 var redirector = what.Peripheral as Redirector; 925 var newValue = redirector.TranslateAbsolute(entryPoint); 926 this.Log(LogLevel.Info, "Fixing PC address from 0x{0:X} to 0x{1:X}", entryPoint, newValue); 927 entryPoint = newValue; 928 } 929 } 930 PC = entryPoint; 931 } 932 933 [Transient] 934 private Thread cpuThread; 935 936 private EmulationCPUState state = EmulationCPUState.InReset; 937 private TimeHandle timeHandle; 938 939 private bool wasRunningWhenHalted; 940 private ulong executedResiduum; 941 private ulong instructionsLeftThisRound; 942 private ulong instructionsExecutedThisRound; 943 private ulong skipInstructions; 944 945 [Transient] 946 private volatile bool pauseLockTimeHandleMarker; 947 948 private readonly object cpuThreadBodyLock = new object(); 949 private readonly SealableValue<uint> performanceInMips = new SealableValue<uint>(); 950 } 951 } 952