use ch32rv_dmi::{DebugModule, DmiError, DtmAccess, FlashProgMode, RegName};
use gdbstub::common::Signal;
use gdbstub::target::ext::base::singlethread::{
SingleThreadBase, SingleThreadResume, SingleThreadResumeOps, SingleThreadSingleStep,
SingleThreadSingleStepOps,
};
use gdbstub::target::ext::breakpoints::{
Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps, SwBreakpoint, SwBreakpointOps,
};
use gdbstub::target::{Target, TargetError, TargetResult};
use crate::arch::{Rv32, Rv32CoreRegs};
struct SwBp {
addr: u32,
original: Vec<u8>,
}
struct HwBp {
slot: u32,
addr: u32,
}
struct FlashBp {
addr: u32,
len: usize,
}
struct FlashPage {
page_addr: u32,
pristine: Vec<u8>,
current: Vec<u8>,
}
pub struct Ch32Target<T: DtmAccess> {
dtm: T,
breakpoints: Vec<SwBp>,
hw_breakpoints: Vec<HwBp>,
hw_trigger_count: u32,
gpr_count: u8,
flash_page_size: Option<u32>,
flash_prog_mode: FlashProgMode,
flash_bps: Vec<FlashBp>,
flash_pages: Vec<FlashPage>,
}
impl<T: DtmAccess> Ch32Target<T> {
fn alloc_hw_slot(&self) -> Option<u32> {
(0..self.hw_trigger_count).find(|s| !self.hw_breakpoints.iter().any(|b| b.slot == *s))
}
pub fn new(dtm: T, flash: Option<(u32, FlashProgMode)>) -> Result<Self, DmiError> {
let mut t = Self {
dtm,
breakpoints: Vec::new(),
hw_breakpoints: Vec::new(),
hw_trigger_count: 0,
gpr_count: 32,
flash_page_size: flash.map(|(p, _)| p),
flash_prog_mode: flash.map(|(_, m)| m).unwrap_or(FlashProgMode::PgStart),
flash_bps: Vec::new(),
flash_pages: Vec::new(),
};
t.dm().halt()?;
let _ = t.dm().enable_ebreak_debug();
t.hw_trigger_count = t.dm().hw_trigger_count();
const MISA: u16 = 0x301;
const MISA_E: u32 = 1 << 4;
if let Ok(misa) = t.dm().read_reg(RegName::Csr(MISA))
&& misa & MISA_E != 0
{
t.gpr_count = 16;
}
Ok(t)
}
pub fn hw_trigger_count(&self) -> u32 {
self.hw_trigger_count
}
fn dm(&mut self) -> DebugModule<'_, T> {
DebugModule::new(&mut self.dtm)
}
pub fn is_halted(&mut self) -> Result<bool, DmiError> {
self.dm().is_halted()
}
pub fn halt(&mut self) -> Result<(), DmiError> {
self.dm().halt()
}
pub fn into_inner(self) -> T {
self.dtm
}
pub fn flash_breakpoints_supported(&self) -> bool {
self.flash_page_size.is_some()
}
pub fn restore_flash_breakpoints(&mut self) {
let Some(page) = self.flash_page_size else {
return;
};
self.flash_bps.clear();
let addrs: Vec<u32> = self.flash_pages.iter().map(|p| p.page_addr).collect();
for page_addr in addrs {
let _ = self.reprogram_flash_page(page, page_addr);
}
self.flash_pages.clear();
}
fn flash_phys(addr: u32) -> u32 {
const FLASH_BASE: u32 = 0x0800_0000;
if addr < FLASH_BASE {
FLASH_BASE + addr
} else {
addr
}
}
fn ebreak_patch(len: usize) -> &'static [u8] {
if len == 2 {
&[0x02, 0x90] } else {
&[0x73, 0x00, 0x10, 0x00] }
}
fn reprogram_flash_page(&mut self, page: u32, page_addr: u32) -> Result<bool, DmiError> {
let Some(idx) = self
.flash_pages
.iter()
.position(|p| p.page_addr == page_addr)
else {
return Ok(false);
};
let mut desired = self.flash_pages[idx].pristine.clone();
for bp in &self.flash_bps {
if bp.addr & !(page - 1) == page_addr {
let off = (bp.addr - page_addr) as usize;
let patch = Self::ebreak_patch(bp.len);
if off + bp.len <= desired.len() {
desired[off..off + bp.len].copy_from_slice(patch);
}
}
}
if self.flash_pages[idx].current == desired {
return Ok(false); }
let phys = Self::flash_phys(page_addr);
let mode = self.flash_prog_mode;
{
let mut dm = self.dm();
dm.flash_page_erase(phys, mode)?;
dm.flash_program_page(phys, &desired, mode)?;
}
self.flash_pages[idx].current = desired;
Ok(true)
}
fn add_flash_breakpoint(&mut self, addr: u32, len: usize) -> TargetResult<bool, Self> {
let Some(page) = self.flash_page_size else {
return Ok(false);
};
let page_addr = addr & !(page - 1);
if !self.flash_pages.iter().any(|p| p.page_addr == page_addr) {
let content = self
.dm()
.read_mem(page_addr, page)
.map_err(TargetError::Fatal)?;
self.flash_pages.push(FlashPage {
page_addr,
pristine: content.clone(),
current: content,
});
}
self.flash_bps.push(FlashBp { addr, len });
self.reprogram_flash_page(page, page_addr)
.map_err(TargetError::Fatal)?;
let back = self
.dm()
.read_mem(addr, len as u32)
.map_err(TargetError::Fatal)?;
if back != Self::ebreak_patch(len) {
self.flash_bps.pop();
let _ = self.reprogram_flash_page(page, page_addr);
return Ok(false);
}
Ok(true)
}
fn remove_flash_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> {
let Some(page) = self.flash_page_size else {
return Ok(false);
};
let Some(pos) = self.flash_bps.iter().position(|b| b.addr == addr) else {
return Ok(false);
};
let page_addr = addr & !(page - 1);
self.flash_bps.remove(pos);
self.reprogram_flash_page(page, page_addr)
.map_err(TargetError::Fatal)?;
if !self
.flash_bps
.iter()
.any(|b| b.addr & !(page - 1) == page_addr)
{
self.flash_pages.retain(|p| p.page_addr != page_addr);
}
Ok(true)
}
}
impl<T: DtmAccess> Target for Ch32Target<T> {
type Arch = Rv32;
type Error = DmiError;
#[inline(always)]
fn base_ops(&mut self) -> gdbstub::target::ext::base::BaseOps<'_, Self::Arch, Self::Error> {
gdbstub::target::ext::base::BaseOps::SingleThread(self)
}
#[inline(always)]
fn support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>> {
Some(self)
}
}
impl<T: DtmAccess> SingleThreadBase for Ch32Target<T> {
fn read_registers(&mut self, regs: &mut Rv32CoreRegs) -> TargetResult<(), Self> {
let gpr_count = self.gpr_count;
let mut dm = self.dm();
regs.x = [0; 32];
for i in 1..gpr_count {
regs.x[i as usize] = dm.read_reg(RegName::Gpr(i)).map_err(TargetError::Fatal)?;
}
regs.pc = dm.read_reg(RegName::Pc).map_err(TargetError::Fatal)?;
Ok(())
}
fn write_registers(&mut self, regs: &Rv32CoreRegs) -> TargetResult<(), Self> {
let gpr_count = self.gpr_count;
let mut dm = self.dm();
for i in 1..gpr_count {
dm.write_reg(RegName::Gpr(i), regs.x[i as usize])
.map_err(TargetError::Fatal)?;
}
dm.write_reg(RegName::Pc, regs.pc)
.map_err(TargetError::Fatal)?;
Ok(())
}
fn read_addrs(&mut self, start: u32, data: &mut [u8]) -> TargetResult<usize, Self> {
let bytes = self
.dm()
.read_mem(start, data.len() as u32)
.map_err(TargetError::Fatal)?;
let n = bytes.len().min(data.len());
data[..n].copy_from_slice(&bytes[..n]);
Ok(n)
}
fn write_addrs(&mut self, start: u32, data: &[u8]) -> TargetResult<(), Self> {
self.dm()
.write_mem(start, data)
.map_err(TargetError::Fatal)?;
Ok(())
}
#[inline(always)]
fn support_resume(&mut self) -> Option<SingleThreadResumeOps<'_, Self>> {
Some(self)
}
}
impl<T: DtmAccess> SingleThreadResume for Ch32Target<T> {
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
self.dm().resume()
}
#[inline(always)]
fn support_single_step(&mut self) -> Option<SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl<T: DtmAccess> SingleThreadSingleStep for Ch32Target<T> {
fn step(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
self.dm().step()
}
}
impl<T: DtmAccess> Breakpoints for Ch32Target<T> {
#[inline(always)]
fn support_sw_breakpoint(&mut self) -> Option<SwBreakpointOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_hw_breakpoint(&mut self) -> Option<HwBreakpointOps<'_, Self>> {
if self.hw_trigger_count > 0 {
Some(self)
} else {
None
}
}
}
impl<T: DtmAccess> HwBreakpoint for Ch32Target<T> {
fn add_hw_breakpoint(&mut self, addr: u32, _kind: usize) -> TargetResult<bool, Self> {
let Some(slot) = self.alloc_hw_slot() else {
return Ok(false); };
self.dm()
.set_hw_breakpoint(slot, addr)
.map_err(TargetError::Fatal)?;
self.hw_breakpoints.push(HwBp { slot, addr });
Ok(true)
}
fn remove_hw_breakpoint(&mut self, addr: u32, _kind: usize) -> TargetResult<bool, Self> {
if let Some(pos) = self.hw_breakpoints.iter().position(|b| b.addr == addr) {
let bp = self.hw_breakpoints.remove(pos);
self.dm()
.clear_hw_breakpoint(bp.slot)
.map_err(TargetError::Fatal)?;
Ok(true)
} else {
Ok(false)
}
}
}
impl<T: DtmAccess> SwBreakpoint for Ch32Target<T> {
fn add_sw_breakpoint(&mut self, addr: u32, kind: usize) -> TargetResult<bool, Self> {
let (patch, len): (&[u8], usize) = if kind == 2 {
(&[0x02, 0x90], 2) } else {
(&[0x73, 0x00, 0x10, 0x00], 4) };
let (stuck, original) = {
let mut dm = self.dm();
let original = dm.read_mem(addr, len as u32).map_err(TargetError::Fatal)?;
dm.write_mem(addr, patch).map_err(TargetError::Fatal)?;
let back = dm.read_mem(addr, len as u32).map_err(TargetError::Fatal)?;
let stuck = back == patch;
if !stuck {
let _ = dm.write_mem(addr, &original); }
(stuck, original)
};
if stuck {
self.breakpoints.push(SwBp { addr, original });
return Ok(true);
}
if let Some(slot) = self.alloc_hw_slot() {
self.dm()
.set_hw_breakpoint(slot, addr)
.map_err(TargetError::Fatal)?;
self.hw_breakpoints.push(HwBp { slot, addr });
return Ok(true);
}
self.add_flash_breakpoint(addr, len)
}
fn remove_sw_breakpoint(&mut self, addr: u32, _kind: usize) -> TargetResult<bool, Self> {
if let Some(pos) = self.breakpoints.iter().position(|b| b.addr == addr) {
let bp = self.breakpoints.remove(pos);
self.dm()
.write_mem(addr, &bp.original)
.map_err(TargetError::Fatal)?;
return Ok(true);
}
if let Some(pos) = self.hw_breakpoints.iter().position(|b| b.addr == addr) {
let bp = self.hw_breakpoints.remove(pos);
self.dm()
.clear_hw_breakpoint(bp.slot)
.map_err(TargetError::Fatal)?;
return Ok(true);
}
self.remove_flash_breakpoint(addr)
}
}