Skip to main content

embedded_debugger_mcp/debugger/
discovery.rs

1//! Debug probe discovery and enumeration
2
3use probe_rs::probe::list::Lister;
4use crate::error::{DebugError, Result};
5use crate::utils::ProbeType;
6use tracing::{debug, info, warn};
7
8/// Information about discovered debug probe
9#[derive(Debug, Clone)]
10pub struct ProbeInfo {
11    pub identifier: String,
12    pub vendor_id: u16,
13    pub product_id: u16,
14    pub serial_number: Option<String>,
15    pub probe_type: String,
16    pub speed_khz: u32,
17    pub version: Option<String>,
18}
19
20/// Debug probe discovery utility
21pub struct ProbeDiscovery;
22
23impl ProbeDiscovery {
24    /// List all available debug probes
25    pub fn list_probes() -> Result<Vec<ProbeInfo>> {
26        debug!("Discovering debug probes");
27        
28        let lister = Lister::new();
29        
30        let probes = lister
31            .list_all()
32            .into_iter()
33            .map(|probe_info| {
34                let probe_type = ProbeType::from_vid_pid(probe_info.vendor_id, probe_info.product_id);
35                
36                ProbeInfo {
37                    identifier: probe_info.identifier.clone(),
38                    vendor_id: probe_info.vendor_id,
39                    product_id: probe_info.product_id,
40                    serial_number: probe_info.serial_number.clone(),
41                    probe_type: probe_type.to_string(),
42                    speed_khz: 4000, // Default speed
43                    version: Some("USB".to_string()),
44                }
45            })
46            .collect::<Vec<_>>();
47
48        info!("Found {} debug probes", probes.len());
49        for probe in &probes {
50            debug!("  {} - {} ({})", 
51                   probe.identifier, 
52                   probe.probe_type, 
53                   probe.serial_number.as_deref().unwrap_or("no serial"));
54        }
55
56        Ok(probes)
57    }
58
59    /// Find a specific probe by selector criteria
60    pub fn find_probe(
61        serial_number: Option<&str>,
62        vendor_id: Option<u16>,
63        product_id: Option<u16>,
64        probe_type: Option<&str>,
65    ) -> Result<ProbeInfo> {
66        debug!("Finding probe with criteria: serial={:?}, vid={:?}, pid={:?}, type={:?}",
67               serial_number, vendor_id, product_id, probe_type);
68        
69        let all_probes = Self::list_probes()?;
70        
71        if all_probes.is_empty() {
72            return Err(DebugError::ProbeNotFound("No debug probes found".to_string()));
73        }
74
75        // If no criteria specified, return the first probe
76        if serial_number.is_none() && vendor_id.is_none() && product_id.is_none() && probe_type.is_none() {
77            return Ok(all_probes[0].clone());
78        }
79
80        // Filter probes based on criteria
81        let matching_probes: Vec<_> = all_probes
82            .into_iter()
83            .filter(|probe| {
84                // Check serial number
85                if let Some(serial) = serial_number {
86                    if probe.serial_number.as_deref() != Some(serial) {
87                        return false;
88                    }
89                }
90
91                // Check vendor ID
92                if let Some(vid) = vendor_id {
93                    if probe.vendor_id != vid {
94                        return false;
95                    }
96                }
97
98                // Check product ID
99                if let Some(pid) = product_id {
100                    if probe.product_id != pid {
101                        return false;
102                    }
103                }
104
105                // Check probe type
106                if let Some(ptype) = probe_type {
107                    if probe.probe_type.to_lowercase() != ptype.to_lowercase() {
108                        return false;
109                    }
110                }
111
112                true
113            })
114            .collect();
115
116        if matching_probes.is_empty() {
117            return Err(DebugError::ProbeNotFound(
118                "No probe found matching the specified criteria".to_string()
119            ));
120        }
121
122        if matching_probes.len() > 1 {
123            warn!("Multiple probes match criteria, using the first one");
124            for (i, probe) in matching_probes.iter().enumerate() {
125                debug!("  {}: {} ({})", i, probe.identifier, 
126                       probe.serial_number.as_deref().unwrap_or("no serial"));
127            }
128        }
129
130        Ok(matching_probes[0].clone())
131    }
132
133    /// Auto-select the best available probe
134    pub fn auto_select_probe() -> Result<ProbeInfo> {
135        debug!("Auto-selecting debug probe");
136        
137        let all_probes = Self::list_probes()?;
138        
139        if all_probes.is_empty() {
140            return Err(DebugError::ProbeNotFound("No debug probes found".to_string()));
141        }
142
143        // Prefer probes in this order: J-Link, ST-Link, DAPLink, others
144        let preferred_order = ["j-link", "st-link", "daplink"];
145        
146        for preferred_type in &preferred_order {
147            for probe in &all_probes {
148                if probe.probe_type.to_lowercase().contains(preferred_type) {
149                    info!("Auto-selected probe: {} ({})", probe.identifier, probe.probe_type);
150                    return Ok(probe.clone());
151                }
152            }
153        }
154
155        // If no preferred probe found, use the first one
156        let selected = &all_probes[0];
157        info!("Auto-selected probe: {} ({})", selected.identifier, selected.probe_type);
158        Ok(selected.clone())
159    }
160
161    /// Get detailed information about a specific probe
162    pub fn get_probe_details(identifier: &str) -> Result<ProbeInfo> {
163        debug!("Getting details for probe: {}", identifier);
164        
165        let all_probes = Self::list_probes()?;
166        
167        all_probes
168            .into_iter()
169            .find(|probe| probe.identifier == identifier)
170            .ok_or_else(|| DebugError::ProbeNotFound(format!("Probe not found: {}", identifier)))
171    }
172
173    /// Check if a probe supports a specific target
174    pub fn check_target_support(probe_type: &ProbeType, target_chip: &str) -> bool {
175        match probe_type {
176            ProbeType::JLink => {
177                // J-Link supports most ARM and RISC-V targets
178                target_chip.to_lowercase().contains("stm32") ||
179                target_chip.to_lowercase().contains("nrf") ||
180                target_chip.to_lowercase().contains("cortex") ||
181                target_chip.to_lowercase().contains("risc")
182            }
183            ProbeType::StLink => {
184                // ST-Link primarily supports STM32
185                target_chip.to_lowercase().contains("stm32")
186            }
187            ProbeType::DapLink => {
188                // DAPLink supports ARM Cortex targets
189                target_chip.to_lowercase().contains("cortex") ||
190                target_chip.to_lowercase().contains("stm32") ||
191                target_chip.to_lowercase().contains("nrf")
192            }
193            ProbeType::Blackmagic => {
194                // Black Magic Probe supports ARM Cortex
195                target_chip.to_lowercase().contains("cortex") ||
196                target_chip.to_lowercase().contains("stm32")
197            }
198            ProbeType::Ftdi => {
199                // FTDI can support various targets
200                true
201            }
202            ProbeType::Unknown => {
203                // Unknown probes might work
204                true
205            }
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_probe_type_support() {
216        assert!(ProbeDiscovery::check_target_support(&ProbeType::JLink, "STM32F407VG"));
217        assert!(ProbeDiscovery::check_target_support(&ProbeType::StLink, "STM32F407VG"));
218        assert!(ProbeDiscovery::check_target_support(&ProbeType::DapLink, "nRF52832"));
219        assert!(!ProbeDiscovery::check_target_support(&ProbeType::StLink, "ESP32"));
220    }
221
222    #[tokio::test]
223    async fn test_list_probes() {
224        // This test will only pass if debug probes are connected
225        // In CI/testing environments, this might be empty
226        let result = ProbeDiscovery::list_probes();
227        assert!(result.is_ok());
228        
229        let probes = result.unwrap();
230        // Just verify the structure is correct
231        for probe in probes {
232            assert!(!probe.identifier.is_empty());
233            assert!(!probe.probe_type.is_empty());
234        }
235    }
236}