Skip to content

Commit

Permalink
Merge pull request #4 from Grapple-Systems/add-swd-sequence
Browse files Browse the repository at this point in the history
Add support for SWD sequence
  • Loading branch information
korken89 authored Nov 2, 2024
2 parents 48286e4 + a958131 commit 7d36867
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 4 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dap-rs"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = [
"Emil Fresk <[email protected]>",
Expand All @@ -20,6 +20,9 @@ embedded-hal = "1.0.0"
replace_with = { version = "0.1.7", default-features = false, features = ["panic_abort"] }
bitflags = "1.3.2"

[dev-dependencies]
mockall = "0.13.0"

[dependencies.defmt]
optional = true
version = "0.3"
Expand Down
243 changes: 241 additions & 2 deletions src/dap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,46 @@ where
}
}

fn process_swd_sequence(&self, _req: Request, _resp: &mut ResponseWriter) {
// TODO: Needs implementing
fn process_swd_sequence(&mut self, mut req: Request, resp: &mut ResponseWriter) {
self.state.to_swd();
let swd = match &mut self.state {
State::Swd(swd) => swd,
_ => {
resp.write_err();
return;
}
};

resp.write_ok(); // assume ok until we finish
let sequence_count = req.next_u8();
let success = (0..sequence_count).map(|_| {
// parse the seqnence info
let sequence_info = req.next_u8();
let nbits: usize = match sequence_info & 0x3F {
// CMSIS-DAP says 0 means 64 bits
0 => 64,
// Other integers are normal.
n => n as usize,
};
let nbytes = (nbits + 7) / 8;
let output = (sequence_info & 0x80) == 0;

if output {
let output_data = req.data.get(..nbytes).ok_or(())?;
swd.write_sequence(nbits, output_data).or(Err(()))?;
req.consume(nbytes);
Ok(0)
} else {
let input_data = resp.remaining().get_mut(..nbytes).ok_or(())?;
swd.read_sequence(nbits, input_data).or(Err(()))?;
resp.skip(nbytes);
Ok(nbytes)
}
}).all(|r: Result<usize, ()>| r.is_ok());

if !success {
resp.write_u8_at(0, 0xFF);
}
}

fn process_swo_transport(&mut self, mut req: Request, resp: &mut ResponseWriter) {
Expand Down Expand Up @@ -792,3 +830,204 @@ impl<T> CheckResult<T> for swd::Result<T> {
}
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::mock_device::*;
use mockall::predicate::*;

struct FakeLEDs {}
impl DapLeds for FakeLEDs {
fn react_to_host_status(&mut self, _host_status: HostStatus) {}
}

struct StdDelayUs {}
impl DelayNs for StdDelayUs {
fn delay_ns(&mut self, ns: u32) {
std::thread::sleep(std::time::Duration::from_nanos(ns as u64));
}
}

type TestDap<'a> = Dap<
'a,
MockSwdJtagDevice,
FakeLEDs,
StdDelayUs,
MockSwdJtagDevice,
MockSwdJtagDevice,
swo::MockSwo,
>;

#[test]
fn test_swd_output_reset() {
let mut dap = TestDap::new(
MockSwdJtagDevice::new(),
FakeLEDs {},
StdDelayUs {},
None,
"test_dap",
);

let report = [0x1Du8, 1, 52, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0];
let mut rbuf = [0u8; 64];
dap.state.to_swd();
match &mut dap.state {
State::Swd(swd) => {
swd.expect_write_sequence()
.once()
.with(eq(52), eq([0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0]))
.return_const(Ok(()));
}
_ => assert!(false, "can't switch to swd"),
}
let rsize = dap.process_command(&report, &mut rbuf, DapVersion::V2);
assert_eq!(rsize, 2);
assert_eq!(&rbuf[..2], &[0x1Du8, 0x00])
}

#[test]
fn test_swd_output_max_size() {
let mut dap = TestDap::new(
MockSwdJtagDevice::new(),
FakeLEDs {},
StdDelayUs {},
None,
"test_dap",
);

let report = [0x1Du8, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00];
let mut rbuf = [0u8; 64];
dap.state.to_swd();
match &mut dap.state {
State::Swd(swd) => {
swd.expect_write_sequence()
.once()
.with(
eq(64),
eq([0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00]),
)
.return_const(Ok(()));
}
_ => assert!(false, "can't switch to swd"),
}
let rsize = dap.process_command(&report, &mut rbuf, DapVersion::V2);
assert_eq!(rsize, 2);
assert_eq!(&rbuf[..2], &[0x1Du8, 0x00])
}

#[test]
fn test_swd_input() {
let mut dap = TestDap::new(
MockSwdJtagDevice::new(),
FakeLEDs {},
StdDelayUs {},
None,
"test_dap",
);

let report = [0x1Du8, 1, 0x80 | 52];
let mut rbuf = [0u8; 64];
dap.state.to_swd();
match &mut dap.state {
State::Swd(swd) => {
swd.expect_read_sequence()
.once()
.withf(|nbits, buf| buf.len() >= 7 && *nbits == 52)
.returning(|_, buf| {
buf[..7].clone_from_slice(&[0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0]);
Ok(())
});
}
_ => assert!(false, "can't switch to swd"),
}
let rsize = dap.process_command(&report, &mut rbuf, DapVersion::V2);
assert_eq!(rsize, 9);
assert_eq!(
&rbuf[..9],
&[0x1Du8, 0x00, 0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0]
)
}

#[test]
fn test_swd_input_max_size() {
let mut dap = TestDap::new(
MockSwdJtagDevice::new(),
FakeLEDs {},
StdDelayUs {},
None,
"test_dap",
);

let report = [0x1Du8, 1, 0x80];
let mut rbuf = [0u8; 64];
dap.state.to_swd();
match &mut dap.state {
State::Swd(swd) => {
swd.expect_read_sequence()
.once()
.withf(|nbits, buf| buf.len() >= 8 && *nbits == 64)
.returning(|_, buf| {
buf[..8]
.clone_from_slice(&[0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00]);
Ok(())
});
}
_ => assert!(false, "can't switch to swd"),
}
let rsize = dap.process_command(&report, &mut rbuf, DapVersion::V2);
assert_eq!(rsize, 10);
assert_eq!(
&rbuf[..10],
&[0x1Du8, 0x00, 0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00]
)
}

#[test]
fn test_target_select() {
let mut dap = TestDap::new(
MockSwdJtagDevice::new(),
FakeLEDs {},
StdDelayUs {},
None,
"test_dap",
);

// write 8 bits, read 5 bits, write 33 bits
let report = [0x1Du8, 3, 8, 0b10111101, 0x80 | 5, 33, 0x56, 0x83, 0xAB, 0x32, 0x01];
let mut rbuf = [0u8; 64];
dap.state.to_swd();
match &mut dap.state {
State::Swd(swd) => {
swd.expect_write_sequence()
.once()
.with(
eq(8),
eq([0b10111101u8]),
)
.return_const(Ok(()));
swd.expect_read_sequence()
.once()
.withf(|nbits, buf| buf.len() >= 1 && *nbits == 5)
.returning(|_, buf| {
buf[0] = 0x1F;
Ok(())
});
swd.expect_write_sequence()
.once()
.with(
eq(33),
eq([0x56, 0x83, 0xAB, 0x32, 0x01]),
)
.return_const(Ok(()));
}
_ => assert!(false, "can't switch to swd"),
}
let rsize = dap.process_command(&report, &mut rbuf, DapVersion::V2);
assert_eq!(rsize, 3);
assert_eq!(
&rbuf[..3],
&[0x1Du8, 0x00, 0x1F]
)
}
}
4 changes: 4 additions & 0 deletions src/dap/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl<'a> Request<'a> {
value
}

pub fn consume(&mut self, count: usize) {
self.data = &self.data[count..];
}

pub fn rest(self) -> &'a [u8] {
&self.data
}
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! TODO: Crate docs

#![no_std]
#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]

/// TODO: Dap docs
Expand All @@ -11,6 +11,9 @@ pub mod swj;
pub mod swo;
pub mod usb;

#[cfg(test)]
mod mock_device;

// Re-export the usb-device crate, so that crates depending on us can use it without
// having to track it as a separate dependency.
pub use usb_device;
76 changes: 76 additions & 0 deletions src/mock_device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::{jtag, swd, swj};

#[mockall::automock]
pub trait SwdJtagDevice {
// swj
fn high_impedance_mode(&mut self);
fn process_swj_clock(&mut self, max_frequency: u32) -> bool;
fn process_swj_pins(&mut self, output: swj::Pins, mask: swj::Pins, wait_us: u32) -> swj::Pins;
fn process_swj_sequence(&mut self, data: &[u8], nbits: usize);

// swd
fn read_inner(&mut self, apndp: swd::APnDP, a: swd::DPRegister) -> swd::Result<u32>;
fn write_inner(&mut self, apndp: swd::APnDP, a: swd::DPRegister, data: u32) -> swd::Result<()>;
fn read_sequence(&mut self, num_bits: usize, data: &mut [u8]) -> swd::Result<()>;
fn write_sequence(&mut self, num_bits: usize, data: &[u8]) -> swd::Result<()>;

// jtag
fn sequences(&mut self, data: &[u8], rxbuf: &mut [u8]) -> u32;

// swd/jtag
fn set_clock(&mut self, max_frequency: u32) -> bool;
}

impl swj::Dependencies<Self, Self> for MockSwdJtagDevice {
fn high_impedance_mode(&mut self) {
SwdJtagDevice::high_impedance_mode(self)
}

fn process_swj_clock(&mut self, max_frequency: u32) -> bool {
SwdJtagDevice::process_swj_clock(self, max_frequency)
}

fn process_swj_pins(&mut self, output: swj::Pins, mask: swj::Pins, wait_us: u32) -> swj::Pins {
SwdJtagDevice::process_swj_pins(self, output, mask, wait_us)
}

fn process_swj_sequence(&mut self, data: &[u8], nbits: usize) {
SwdJtagDevice::process_swj_sequence(self, data, nbits)
}
}

impl swd::Swd<Self> for MockSwdJtagDevice {
const AVAILABLE: bool = true;

fn set_clock(&mut self, max_frequency: u32) -> bool {
SwdJtagDevice::set_clock(self, max_frequency)
}

fn read_inner(&mut self, apndp: swd::APnDP, a: swd::DPRegister) -> swd::Result<u32> {
SwdJtagDevice::read_inner(self, apndp, a)
}

fn write_inner(&mut self, apndp: swd::APnDP, a: swd::DPRegister, data: u32) -> swd::Result<()> {
SwdJtagDevice::write_inner(self, apndp, a, data)
}

fn read_sequence(&mut self, num_bits: usize, data: &mut [u8]) -> swd::Result<()> {
SwdJtagDevice::read_sequence(self, num_bits, data)
}

fn write_sequence(&mut self, num_bits: usize, data: &[u8]) -> swd::Result<()> {
SwdJtagDevice::write_sequence(self, num_bits, data)
}
}

impl jtag::Jtag<MockSwdJtagDevice> for MockSwdJtagDevice {
const AVAILABLE: bool = true;

fn set_clock(&mut self, max_frequency: u32) -> bool {
SwdJtagDevice::set_clock(self, max_frequency)
}

fn sequences(&mut self, data: &[u8], rxbuf: &mut [u8]) -> u32 {
SwdJtagDevice::sequences(self, data, rxbuf)
}
}
6 changes: 6 additions & 0 deletions src/swd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ pub trait Swd<DEPS>: From<DEPS> {

/// Set the maximum clock frequency, return `true` if it is valid.
fn set_clock(&mut self, max_frequency: u32) -> bool;

/// Write a sequence of bits using SWDIO and the clock line running at the configured freq.
fn write_sequence(&mut self, num_bits: usize, data: &[u8]) -> Result<()>;

/// Read a sequence of bits using SWDIO and the clock line running at the configured freq.
fn read_sequence(&mut self, num_bits: usize, data: &mut [u8]) -> Result<()>;
}

/// Helper used by `Swd::read_inner` and `Swd::write_inner` to make the request byte.
Expand Down
1 change: 1 addition & 0 deletions src/swo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub struct SwoStatus {
pub bytes_available: u32,
}

#[cfg_attr(test, mockall::automock)]
pub trait Swo {
fn set_transport(&mut self, transport: SwoTransport);
fn set_mode(&mut self, mode: SwoMode);
Expand Down

0 comments on commit 7d36867

Please sign in to comment.