probe_rs/architecture/xtensa/
mod.rs

1//! All the interface bits for Xtensa.
2
3use std::{sync::Arc, time::Duration};
4
5use probe_rs_target::{Architecture, CoreType, InstructionSet};
6
7use crate::{
8    CoreInformation, CoreInterface, CoreRegister, CoreStatus, Error, HaltReason, MemoryInterface,
9    architecture::xtensa::{
10        arch::{
11            CpuRegister, Register, SpecialRegister,
12            instruction::{Instruction, InstructionEncoding},
13        },
14        communication_interface::{
15            DebugCause, IBreakEn, ProgramStatus, WindowProperties, XtensaCommunicationInterface,
16        },
17        registers::{FP, PC, RA, SP, XTENSA_CORE_REGISTERS},
18        sequences::XtensaDebugSequence,
19        xdm::PowerStatus,
20    },
21    core::{
22        BreakpointCause,
23        registers::{CoreRegisters, RegisterId, RegisterValue},
24    },
25    memory::CoreMemoryInterface,
26    semihosting::{SemihostingCommand, decode_semihosting_syscall},
27};
28
29pub(crate) mod arch;
30pub(crate) mod xdm;
31
32pub mod communication_interface;
33pub(crate) mod register_cache;
34pub mod registers;
35pub(crate) mod sequences;
36
37/// Xtensa core state.
38#[derive(Debug)]
39pub struct XtensaCoreState {
40    /// Whether the core is enabled.
41    enabled: bool,
42
43    /// Whether hardware breakpoints are enabled.
44    breakpoints_enabled: bool,
45
46    /// Whether each hardware breakpoint is set.
47    // 2 is the architectural upper limit. The actual count is stored in
48    // [`communication_interface::XtensaInterfaceState`]
49    breakpoint_set: [bool; 2],
50
51    /// Whether the PC was written since we last halted. Used to avoid incrementing the PC on
52    /// resume.
53    pc_written: bool,
54
55    /// The semihosting command that was decoded at the current program counter
56    semihosting_command: Option<SemihostingCommand>,
57}
58
59impl XtensaCoreState {
60    /// Creates a new [`XtensaCoreState`].
61    pub(crate) fn new() -> Self {
62        Self {
63            enabled: false,
64            breakpoints_enabled: false,
65            breakpoint_set: [false; 2],
66            pc_written: false,
67            semihosting_command: None,
68        }
69    }
70
71    /// Creates a bitmask of the currently set breakpoints.
72    fn breakpoint_mask(&self) -> u32 {
73        self.breakpoint_set
74            .iter()
75            .enumerate()
76            .fold(0, |acc, (i, &set)| if set { acc | (1 << i) } else { acc })
77    }
78}
79
80/// An interface to operate an Xtensa core.
81pub struct Xtensa<'probe> {
82    interface: XtensaCommunicationInterface<'probe>,
83    state: &'probe mut XtensaCoreState,
84    sequence: Arc<dyn XtensaDebugSequence>,
85}
86
87impl<'probe> Xtensa<'probe> {
88    const IBREAKA_REGS: [SpecialRegister; 2] =
89        [SpecialRegister::IBreakA0, SpecialRegister::IBreakA1];
90
91    /// Create a new Xtensa interface for a particular core.
92    pub fn new(
93        interface: XtensaCommunicationInterface<'probe>,
94        state: &'probe mut XtensaCoreState,
95        sequence: Arc<dyn XtensaDebugSequence>,
96    ) -> Result<Self, Error> {
97        let mut this = Self {
98            interface,
99            state,
100            sequence,
101        };
102
103        this.on_attach()?;
104
105        Ok(this)
106    }
107
108    fn clear_cache(&mut self) {
109        self.interface.clear_register_cache();
110    }
111
112    fn on_attach(&mut self) -> Result<(), Error> {
113        // If the core was reset, force a reconnection.
114        let core_reset;
115        if self.state.enabled {
116            let status = self.interface.xdm.power_status({
117                let mut clear_value = PowerStatus(0);
118                clear_value.set_core_was_reset(true);
119                clear_value.set_debug_was_reset(true);
120                clear_value
121            })?;
122            core_reset = status.core_was_reset() || !status.core_domain_on();
123            let debug_reset = status.debug_was_reset() || !status.debug_domain_on();
124
125            if core_reset {
126                tracing::debug!("Core was reset");
127                *self.state = XtensaCoreState::new();
128            }
129            if debug_reset {
130                tracing::debug!("Debug was reset");
131                self.state.enabled = false;
132            }
133        } else {
134            core_reset = true;
135        }
136
137        // (Re)enter debug mode if necessary. This also checks if the core is enabled.
138        if !self.state.enabled {
139            // Enable debug module.
140            self.interface.enter_debug_mode()?;
141            self.state.enabled = true;
142
143            if core_reset {
144                // Run the connection sequence while halted.
145                let was_running = self
146                    .interface
147                    .halt_with_previous(Duration::from_millis(500))?;
148
149                self.sequence.on_connect(&mut self.interface)?;
150
151                if was_running {
152                    self.run()?;
153                }
154            }
155        }
156
157        Ok(())
158    }
159
160    fn core_info(&mut self) -> Result<CoreInformation, Error> {
161        let pc = self.read_core_reg(self.program_counter().id)?;
162
163        Ok(CoreInformation { pc: pc.try_into()? })
164    }
165
166    fn skip_breakpoint(&mut self) -> Result<(), Error> {
167        self.state.semihosting_command = None;
168        if !self.state.pc_written {
169            let debug_cause = self.debug_cause()?;
170
171            let pc_increment = if debug_cause.break_instruction() {
172                3
173            } else if debug_cause.break_n_instruction() {
174                2
175            } else {
176                0
177            };
178
179            if pc_increment > 0 {
180                // Step through the breakpoint
181                let mut pc_value = self.interface.read_register_untyped(Register::CurrentPc)?;
182                pc_value += pc_increment;
183                self.interface
184                    .write_register_untyped(Register::CurrentPc, pc_value)?;
185            } else if debug_cause.ibreak_exception() {
186                let pc_value = self.interface.read_register_untyped(Register::CurrentPc)?;
187                let bps = self.hw_breakpoints()?;
188                if let Some(bp_unit) = bps.iter().position(|bp| *bp == Some(pc_value as u64)) {
189                    // Disable the breakpoint
190                    self.clear_hw_breakpoint(bp_unit)?;
191                    // Single step
192                    let ps = self.current_ps()?;
193                    self.interface.step(1, ps.intlevel())?;
194                    // Re-enable the breakpoint
195                    self.set_hw_breakpoint(bp_unit, pc_value as u64)?;
196                }
197            }
198        }
199
200        Ok(())
201    }
202
203    /// Check if the current breakpoint is a semihosting call
204    // OpenOCD implementation: https://github.com/espressif/openocd-esp32/blob/93dd01511fd13d4a9fb322cd9b600c337becef9e/src/target/espressif/esp_xtensa_semihosting.c#L42-L103
205    fn check_for_semihosting(&mut self) -> Result<Option<SemihostingCommand>, Error> {
206        const SEMI_BREAK: u32 = const {
207            let InstructionEncoding::Narrow(bytes) = Instruction::Break(1, 14).encode();
208            bytes
209        };
210
211        // We only want to decode the semihosting command once, since answering it might change some of the registers
212        if let Some(command) = self.state.semihosting_command {
213            return Ok(Some(command));
214        }
215
216        let pc: u64 = self.read_core_reg(self.program_counter().id)?.try_into()?;
217
218        let mut actual_instruction = [0u8; 3];
219        self.read_8(pc, &mut actual_instruction)?;
220        let actual_instruction = u32::from_le_bytes([
221            actual_instruction[0],
222            actual_instruction[1],
223            actual_instruction[2],
224            0,
225        ]);
226
227        tracing::debug!("Semihosting check pc={pc:#x} instruction={actual_instruction:#010x}");
228
229        let command = if actual_instruction == SEMI_BREAK {
230            let syscall = decode_semihosting_syscall(self)?;
231            if let SemihostingCommand::Unknown(details) = syscall {
232                self.sequence
233                    .clone()
234                    .on_unknown_semihosting_command(self, details)?
235            } else {
236                Some(syscall)
237            }
238        } else {
239            None
240        };
241        self.state.semihosting_command = command;
242
243        Ok(command)
244    }
245
246    fn on_halted(&mut self) -> Result<(), Error> {
247        self.state.pc_written = false;
248        self.clear_cache();
249
250        let status = self.status()?;
251        tracing::debug!("Core halted: {:#?}", status);
252
253        if status.is_halted() {
254            self.sequence.on_halt(&mut self.interface)?;
255        }
256
257        Ok(())
258    }
259
260    fn halt_with_previous(&mut self, timeout: Duration) -> Result<bool, Error> {
261        let was_running = self.interface.halt_with_previous(timeout)?;
262        if was_running {
263            self.on_halted()?;
264        }
265
266        Ok(was_running)
267    }
268
269    fn halted_access<F, T>(&mut self, op: F) -> Result<T, Error>
270    where
271        F: FnOnce(&mut Self) -> Result<T, Error>,
272    {
273        let was_running = self.halt_with_previous(Duration::from_millis(100))?;
274
275        let result = op(self);
276
277        if was_running {
278            self.run()?;
279        }
280
281        result
282    }
283
284    fn current_ps(&mut self) -> Result<ProgramStatus, Error> {
285        // Reading ProgramStatus using `read_register` would return the value
286        // after the debug interrupt has been taken.
287        Ok(self
288            .interface
289            .read_register_untyped(Register::CurrentPs)
290            .map(ProgramStatus)?)
291    }
292
293    fn debug_cause(&mut self) -> Result<DebugCause, Error> {
294        Ok(self.interface.read_register::<DebugCause>()?)
295    }
296
297    fn spill_registers(&mut self) -> Result<(), Error> {
298        if self.current_ps()?.excm() {
299            // We are in an exception, possibly WindowOverflowN or WindowUnderflowN.
300            // We can't spill registers in this state.
301            return Ok(());
302        }
303        if self.current_ps()?.woe() {
304            let register_file = self.read_window_registers()?;
305            // We should only spill registers if PS.WOE is set. According to the debug guide, we
306            // also should not spill if INTLEVEL != 0 but I don't see why.
307            register_file.spill(&mut self.interface)?;
308        }
309
310        Ok(())
311    }
312
313    fn read_window_registers(&mut self) -> Result<RegisterFile, Error> {
314        let register_file = RegisterFile::read(
315            self.interface.core_properties().window_option_properties,
316            &mut self.interface,
317        )?;
318
319        let window_reg_count = register_file.core.window_regs;
320        for reg in 0..window_reg_count {
321            let reg =
322                CpuRegister::try_from(reg).expect("Could not convert register to CpuRegister");
323            let value = register_file.read_register(reg);
324            self.interface
325                .state
326                .register_cache
327                .store(Register::Cpu(reg), value);
328        }
329        Ok(register_file)
330    }
331}
332
333impl CoreMemoryInterface for Xtensa<'_> {
334    type ErrorType = Error;
335
336    fn memory(&self) -> &dyn MemoryInterface<Self::ErrorType> {
337        &self.interface
338    }
339    fn memory_mut(&mut self) -> &mut dyn MemoryInterface<Self::ErrorType> {
340        &mut self.interface
341    }
342}
343
344impl CoreInterface for Xtensa<'_> {
345    fn wait_for_core_halted(&mut self, timeout: Duration) -> Result<(), Error> {
346        self.interface.wait_for_core_halted(timeout)?;
347        self.on_halted()?;
348
349        Ok(())
350    }
351
352    fn core_halted(&mut self) -> Result<bool, Error> {
353        let was_halted = self.interface.state.is_halted;
354        let is_halted = self.interface.core_halted()?;
355
356        if !was_halted && is_halted {
357            self.on_halted()?;
358        }
359
360        Ok(is_halted)
361    }
362
363    fn status(&mut self) -> Result<CoreStatus, Error> {
364        let status = if self.core_halted()? {
365            let debug_cause = self.debug_cause()?;
366
367            let mut reason = debug_cause.halt_reason();
368            if reason == HaltReason::Breakpoint(BreakpointCause::Software) {
369                // The chip initiated this halt, therefore we need to update pc_written state
370                self.state.pc_written = false;
371                // Check if the breakpoint is a semihosting call
372                if let Some(cmd) = self.check_for_semihosting()? {
373                    reason = HaltReason::Breakpoint(BreakpointCause::Semihosting(cmd));
374                }
375            }
376
377            CoreStatus::Halted(reason)
378        } else {
379            CoreStatus::Running
380        };
381
382        Ok(status)
383    }
384
385    fn halt(&mut self, timeout: Duration) -> Result<CoreInformation, Error> {
386        self.halt_with_previous(timeout)?;
387
388        self.core_info()
389    }
390
391    fn run(&mut self) -> Result<(), Error> {
392        self.skip_breakpoint()?;
393        Ok(self.interface.resume_core()?)
394    }
395
396    fn reset(&mut self) -> Result<(), Error> {
397        self.reset_and_halt(Duration::from_millis(500))?;
398
399        self.run()
400    }
401
402    fn reset_and_halt(&mut self, timeout: Duration) -> Result<CoreInformation, Error> {
403        self.state.semihosting_command = None;
404        self.sequence
405            .reset_system_and_halt(&mut self.interface, timeout)?;
406        self.on_halted()?;
407
408        // TODO: this may return that the core has gone away, which is fine but currently unexpected
409        self.on_attach()?;
410
411        self.core_info()
412    }
413
414    fn step(&mut self) -> Result<CoreInformation, Error> {
415        self.skip_breakpoint()?;
416
417        // Only count instructions in the current context.
418        let ps = self.current_ps()?;
419        self.interface.step(1, ps.intlevel())?;
420
421        self.on_halted()?;
422
423        self.core_info()
424    }
425
426    fn read_core_reg(&mut self, address: RegisterId) -> Result<RegisterValue, Error> {
427        self.halted_access(|this| {
428            let register = Register::try_from(address)?;
429            let value = this.interface.read_register_untyped(register)?;
430
431            Ok(RegisterValue::U32(value))
432        })
433    }
434
435    fn write_core_reg(&mut self, address: RegisterId, value: RegisterValue) -> Result<(), Error> {
436        self.halted_access(|this| {
437            let value: u32 = value.try_into()?;
438
439            if address == this.program_counter().id {
440                this.state.pc_written = true;
441            }
442
443            let register = Register::try_from(address)?;
444            this.interface.write_register_untyped(register, value)?;
445
446            Ok(())
447        })
448    }
449
450    fn available_breakpoint_units(&mut self) -> Result<u32, Error> {
451        Ok(self.interface.available_breakpoint_units())
452    }
453
454    fn hw_breakpoints(&mut self) -> Result<Vec<Option<u64>>, Error> {
455        self.halted_access(|this| {
456            let mut breakpoints = Vec::with_capacity(this.available_breakpoint_units()? as usize);
457
458            let enabled_breakpoints = this.interface.read_register::<IBreakEn>()?;
459
460            for i in 0..this.available_breakpoint_units()? as usize {
461                let is_enabled = enabled_breakpoints.0 & (1 << i) != 0;
462                let breakpoint = if is_enabled {
463                    let address = this
464                        .interface
465                        .read_register_untyped(Self::IBREAKA_REGS[i])?;
466
467                    Some(address as u64)
468                } else {
469                    None
470                };
471
472                breakpoints.push(breakpoint);
473            }
474
475            Ok(breakpoints)
476        })
477    }
478
479    fn enable_breakpoints(&mut self, state: bool) -> Result<(), Error> {
480        self.halted_access(|this| {
481            this.state.breakpoints_enabled = state;
482            let mask = if state {
483                this.state.breakpoint_mask()
484            } else {
485                0
486            };
487
488            this.interface.write_register(IBreakEn(mask))?;
489
490            Ok(())
491        })
492    }
493
494    fn set_hw_breakpoint(&mut self, unit_index: usize, addr: u64) -> Result<(), Error> {
495        self.halted_access(|this| {
496            this.state.breakpoint_set[unit_index] = true;
497            this.interface
498                .write_register_untyped(Self::IBREAKA_REGS[unit_index], addr as u32)?;
499
500            if this.state.breakpoints_enabled {
501                let mask = this.state.breakpoint_mask();
502                this.interface.write_register(IBreakEn(mask))?;
503            }
504
505            Ok(())
506        })
507    }
508
509    fn clear_hw_breakpoint(&mut self, unit_index: usize) -> Result<(), Error> {
510        self.halted_access(|this| {
511            this.state.breakpoint_set[unit_index] = false;
512
513            if this.state.breakpoints_enabled {
514                let mask = this.state.breakpoint_mask();
515                this.interface.write_register(IBreakEn(mask))?;
516            }
517
518            Ok(())
519        })
520    }
521
522    fn registers(&self) -> &'static CoreRegisters {
523        &XTENSA_CORE_REGISTERS
524    }
525
526    fn program_counter(&self) -> &'static CoreRegister {
527        &PC
528    }
529
530    fn frame_pointer(&self) -> &'static CoreRegister {
531        &FP
532    }
533
534    fn stack_pointer(&self) -> &'static CoreRegister {
535        &SP
536    }
537
538    fn return_address(&self) -> &'static CoreRegister {
539        &RA
540    }
541
542    fn hw_breakpoints_enabled(&self) -> bool {
543        self.state.breakpoints_enabled
544    }
545
546    fn architecture(&self) -> Architecture {
547        Architecture::Xtensa
548    }
549
550    fn core_type(&self) -> CoreType {
551        CoreType::Xtensa
552    }
553
554    fn instruction_set(&mut self) -> Result<InstructionSet, Error> {
555        // TODO: NX exists, too
556        Ok(InstructionSet::Xtensa)
557    }
558
559    fn fpu_support(&mut self) -> Result<bool, Error> {
560        // TODO: ESP32 and ESP32-S3 have FPU
561        Ok(false)
562    }
563
564    fn floating_point_register_count(&mut self) -> Result<usize, Error> {
565        // TODO: ESP32 and ESP32-S3 have FPU
566        Ok(0)
567    }
568
569    fn reset_catch_set(&mut self) -> Result<(), Error> {
570        Err(Error::NotImplemented("reset_catch_set"))
571    }
572
573    fn reset_catch_clear(&mut self) -> Result<(), Error> {
574        Err(Error::NotImplemented("reset_catch_clear"))
575    }
576
577    fn debug_core_stop(&mut self) -> Result<(), Error> {
578        self.interface.leave_debug_mode()?;
579        Ok(())
580    }
581
582    fn spill_registers(&mut self) -> Result<(), Error> {
583        self.spill_registers()
584    }
585}
586
587struct RegisterFile {
588    core: WindowProperties,
589    registers: Vec<u32>,
590    window_base: u8,
591    window_start: u32,
592}
593
594impl RegisterFile {
595    fn read(
596        xtensa: WindowProperties,
597        interface: &mut XtensaCommunicationInterface<'_>,
598    ) -> Result<Self, Error> {
599        let window_base_result = interface.schedule_read_register(SpecialRegister::Windowbase)?;
600        let window_start_result = interface.schedule_read_register(SpecialRegister::Windowstart)?;
601
602        let mut register_values = Vec::with_capacity(xtensa.num_aregs as usize);
603
604        let ar0 = arch::CpuRegister::A0 as u8;
605
606        // Restore registers before reading them, as reading a special
607        // register overwrote scratch registers.
608        interface.restore_registers()?;
609
610        // The window registers alias each other, so we need to make sure we don't read
611        // the cached values. We'll then use the registers we read here to prime the cache.
612        for ar in ar0..ar0 + xtensa.window_regs {
613            let reg = CpuRegister::try_from(ar)?;
614            interface.state.register_cache.remove(reg.into());
615        }
616
617        let mut regs = Vec::with_capacity(xtensa.window_regs as usize);
618        for _ in 0..xtensa.num_aregs / xtensa.window_regs {
619            // Read registers visible in the current window
620            for ar in ar0..ar0 + xtensa.window_regs {
621                let reg = CpuRegister::try_from(ar)?;
622                let deferred_result = interface.schedule_read_register(reg)?;
623                regs.push(deferred_result);
624            }
625
626            // Rotate window to see the next `window_regs` registers
627            let rotw_arg = xtensa.window_regs / xtensa.rotw_rotates;
628            interface
629                .xdm
630                .schedule_execute_instruction(Instruction::Rotw(rotw_arg));
631
632            // Pull out values from cache before we clear the cache
633            for deferred in regs.drain(..) {
634                let result = interface.read_deferred_result(deferred)?;
635                register_values.push(result);
636            }
637
638            // The window registers alias each other, so we need to make sure we don't read
639            // the cached values. We'll then use the registers we read here to prime the cache.
640            for ar in ar0..ar0 + xtensa.window_regs {
641                let reg = CpuRegister::try_from(ar)?;
642                interface.state.register_cache.remove(reg.into());
643            }
644        }
645
646        // Now do the actual read.
647        interface
648            .xdm
649            .execute()
650            .expect("Failed to execute read. This shouldn't happen.");
651
652        // WindowBase points to the first register of the current window in the register file.
653        // In essence, it selects which 16 registers are visible out of the 64 physical registers.
654        let window_base = interface.read_deferred_result(window_base_result)?;
655
656        // The WindowStart Special Register, which is also added by the option and consists of
657        // NAREG/4 bits. Each call frame, which has not been spilled, is represented by a bit in the
658        // WindowStart register. The call frame's bit is set in the position given by the current
659        // WindowBase register value.
660        // Each register window has a single bit in the WindowStart register. The window size
661        // can be calculated by the difference of bit positions in the WindowStart register.
662        // For example, if WindowBase is 6, and WindowStart is 0b0000000001011001:
663        // - The first window uses a0-a11, and executed a CALL12 or CALLX12 instruction.
664        // - The second window uses a0-a3, and executed a CALL14 instruction.
665        // - The third window uses a0-a7, and executed a CALL8 instruction.
666        // - The fourth window is free to use all 16 registers at this point.
667        // There are no more active windows.
668        // This value is used by the hardware to determine if WindowOverflow or WindowUnderflow
669        // exceptions should be raised. The exception handlers then spill or reload registers
670        // from the stack and set/clear the corresponding bit in the WindowStart register.
671        let window_start = interface.read_deferred_result(window_start_result)?;
672
673        // We have read registers relative to the current windowbase. Let's
674        // rotate the registers back so that AR0 is at index 0.
675        register_values.rotate_right((window_base * xtensa.rotw_rotates as u32) as usize);
676
677        Ok(Self {
678            core: xtensa,
679            registers: register_values,
680            window_base: window_base as u8,
681            window_start,
682        })
683    }
684
685    fn spill(&self, xtensa: &mut XtensaCommunicationInterface<'_>) -> Result<(), Error> {
686        if !self.core.has_windowed_registers {
687            return Ok(());
688        }
689
690        if self.window_start == 0 {
691            // There are no stack frames to spill.
692            return Ok(());
693        }
694
695        // Quoting the debug guide:
696        // The proper thing for the debugger to do when doing a call traceback or whenever looking
697        // at a calling function's address registers, is to examine the WINDOWSTART register bits
698        // (in combination with WINDOWBASE) to determine whether to look in the register file or on
699        // the stack for the relevant address register values.
700        // Quote ends here
701        // The above describes what we should do for minimally intrusive debugging, but I don't
702        // think we have the option to do this. Instead we spill everything that needs to be
703        // spilled, and then we can use the stack to unwind the registers. While forcefully
704        // spilling is not faithful to the true code execution, it is relatively simple to do.
705        // The debug guide states this can be noticeably slow, for example if "step to next line" is
706        // implemented by stepping one instruction at a time until the line number changes.
707        // FIXME: we can improve the above issue by only spilling before reading memory.
708
709        // Process registers. We need to roll the windows back by the window increment, and copy
710        // register values to the stack, if the relevant WindowStart bit is set. The window
711        // increment of the current window is saved in the top 2 bits of A0 (the return address).
712
713        // Find oldest window.
714        let mut window_base = self.next_window_base(self.window_base);
715
716        while let Some(window) = RegisterWindow::at_windowbase(self, window_base) {
717            window.spill(xtensa)?;
718            window_base = self.next_window_base(window_base);
719
720            if window_base == self.window_base {
721                // We are back to the original window, so we can stop.
722
723                // We are not spilling the first window. We don't have a destination for it, and we don't
724                // need to unwind it from the stack.
725                break;
726            }
727        }
728
729        Ok(())
730    }
731
732    fn is_window_start(&self, windowbase: u8) -> bool {
733        self.window_start & 1 << (windowbase % self.core.windowbase_size()) != 0
734    }
735
736    fn next_window_base(&self, window_base: u8) -> u8 {
737        let mut wb = (window_base + 1) % self.core.windowbase_size();
738        while wb != window_base {
739            if self.is_window_start(wb) {
740                break;
741            }
742            wb = (wb + 1) % self.core.windowbase_size();
743        }
744
745        wb
746    }
747
748    fn wb_offset_to_canonical(&self, idx: u8) -> u8 {
749        (idx + self.window_base * self.core.rotw_rotates) % self.core.num_aregs
750    }
751
752    fn read_register(&self, reg: CpuRegister) -> u32 {
753        let index = self.wb_offset_to_canonical(reg as u8);
754        self.registers[index as usize]
755    }
756}
757
758struct RegisterWindow<'a> {
759    window_base: u8,
760
761    /// In units of window_base bits.
762    window_size: u8,
763
764    file: &'a RegisterFile,
765}
766
767impl<'a> RegisterWindow<'a> {
768    fn at_windowbase(file: &'a RegisterFile, window_base: u8) -> Option<Self> {
769        if !file.is_window_start(window_base) {
770            return None;
771        }
772
773        let next_window_base = file.next_window_base(window_base);
774        let window_size = (next_window_base + file.core.windowbase_size() - window_base)
775            % file.core.windowbase_size();
776
777        Some(Self {
778            window_base,
779            file,
780            window_size: window_size.min(3),
781        })
782    }
783
784    /// Register spilling needs access to other frames' stack pointers.
785    fn read_register(&self, reg: CpuRegister) -> u32 {
786        let index = self.wb_offset_to_canonical(reg as u8);
787        self.file.registers[index as usize]
788    }
789
790    fn wb_offset_to_canonical(&self, idx: u8) -> u8 {
791        (idx + self.window_base * self.file.core.rotw_rotates) % self.file.core.num_aregs
792    }
793
794    fn spill(&self, interface: &mut XtensaCommunicationInterface<'_>) -> Result<(), Error> {
795        // a0-a3 goes into our stack, the rest into the stack of the caller.
796        let a0_a3 = [
797            self.read_register(CpuRegister::A0),
798            self.read_register(CpuRegister::A1),
799            self.read_register(CpuRegister::A2),
800            self.read_register(CpuRegister::A3),
801        ];
802
803        match self.window_size {
804            0 => {} // Nowhere to spill to
805
806            1 => interface.write_32(self.read_register(CpuRegister::A5) as u64 - 16, &a0_a3)?,
807
808            // Spill a4-a7
809            2 => {
810                let sp = interface.read_word_32(self.read_register(CpuRegister::A1) as u64 - 12)?;
811
812                interface.write_32(self.read_register(CpuRegister::A9) as u64 - 16, &a0_a3)?;
813
814                // Enable check at INFO level to avoid spamming the logs.
815                if tracing::enabled!(tracing::Level::INFO) {
816                    // In some cases (spilling on each halt),
817                    // this readback comes back as 0 for some reason. This assertion is temporarily
818                    // meant to help me debug this.
819                    let written =
820                        interface.read_word_32(self.read_register(CpuRegister::A9) as u64 - 12)?;
821                    assert!(
822                        written == self.read_register(CpuRegister::A1),
823                        "Failed to spill A1. Expected {:#x}, got {:#x}",
824                        self.read_register(CpuRegister::A1),
825                        written
826                    );
827                }
828
829                let regs = [
830                    self.read_register(CpuRegister::A4),
831                    self.read_register(CpuRegister::A5),
832                    self.read_register(CpuRegister::A6),
833                    self.read_register(CpuRegister::A7),
834                ];
835                interface.write_32(sp as u64 - 32, &regs)?;
836            }
837
838            // Spill a4-a11
839            3 => {
840                let sp = interface.read_word_32(self.read_register(CpuRegister::A1) as u64 - 12)?;
841                interface.write_32(self.read_register(CpuRegister::A13) as u64 - 16, &a0_a3)?;
842
843                let regs = [
844                    self.read_register(CpuRegister::A4),
845                    self.read_register(CpuRegister::A5),
846                    self.read_register(CpuRegister::A6),
847                    self.read_register(CpuRegister::A7),
848                    self.read_register(CpuRegister::A8),
849                    self.read_register(CpuRegister::A9),
850                    self.read_register(CpuRegister::A10),
851                    self.read_register(CpuRegister::A11),
852                ];
853                interface.write_32(sp as u64 - 48, &regs)?;
854            }
855
856            // There is no such thing as spilling a12-a15 - there can be only 12 active registers in
857            // a stack frame that is not the topmost, as there is no CALL16 instruction.
858            _ => unreachable!(),
859        }
860
861        Ok(())
862    }
863}