Skip to main content

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