Feature/#97 - #98
Conversation
There was a problem hiding this comment.
Summary of Changes
Hello @p14c31355, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request implements a new "glue" function to integrate the existing scanner and explorer modules, specifically for I2C bus operations. This integration streamlines the process of scanning for I2C devices and then applying an initialization sequence, enabling a more cohesive workflow for device setup and command execution.
Highlights
- Enhanced
scan_init_sequence: Thescan_init_sequencefunction insrc/scanner.rshas been updated to accept a mutableinit_sequenceand now returns thedetected_cmdsas aVec<u8, 64>, allowing for further processing of the scan results. - New
run_explorerFunction: A new public functionrun_explorerhas been introduced insrc/scanner.rs. This function orchestrates the initial I2C bus scan usingscan_init_sequenceand then proceeds to apply a driver initialization sequence using theexplorermodule, handling potential errors. PrefixExecutorImplementation: A newPrefixExecutorstruct and its implementation of theCmdExecutortrait have been added. This executor is designed to prepend a specified prefix byte to I2C commands before execution, which is useful for certain I2C devices that require a prefix for their commands.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces a run_explorer function to integrate the I2C scanner and explorer functionalities, along with a PrefixExecutor for command execution. The changes are generally good, but there are several areas for improvement regarding code clarity, correctness, and performance. My review includes suggestions to remove an unused generic parameter, clarify function signatures by removing ignored parameters, improve performance by avoiding repeated memory allocations, and adopt more idiomatic Rust patterns.
| pub fn run_explorer<I2C, S, E>( | ||
| explorer: &crate::explorer::Explorer<'_>, | ||
| i2c: &mut I2C, | ||
| serial: &mut S, | ||
| init_sequence: &mut [u8], | ||
| prefix: u8, | ||
| log_level: LogLevel, | ||
| ) -> Result<(), crate::explorer::ExplorerError> | ||
| where | ||
| I2C: crate::compat::I2cCompat, | ||
| S: core::fmt::Write, | ||
| E: crate::explorer::CmdExecutor<I2C>, | ||
| <I2C as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt, |
There was a problem hiding this comment.
The generic parameter E is declared but not used within the run_explorer function. The function unconditionally creates and uses a PrefixExecutor. The unused generic parameter makes the function signature misleading and should be removed.
pub fn run_explorer<I2C, S>(
explorer: &crate::explorer::Explorer<'_>,
i2c: &mut I2C,
serial: &mut S,
init_sequence: &mut [u8],
prefix: u8,
log_level: LogLevel,
) -> Result<(), crate::explorer::ExplorerError>
where
I2C: crate::compat::I2cCompat,
S: core::fmt::Write,
<<> as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt,| for b in init_sequence.iter_mut() { | ||
| let _ = writeln!(serial, " 0x{b:02X}"); | ||
| } |
There was a problem hiding this comment.
The init_sequence is being iterated over for printing purposes only, but iter_mut() is used. This suggests that the sequence is being mutated, which is not the case and can be misleading. Using iter() would be more appropriate here to signal a read-only iteration.
| for b in init_sequence.iter_mut() { | |
| let _ = writeln!(serial, " 0x{b:02X}"); | |
| } | |
| for b in init_sequence.iter() { | |
| let _ = writeln!(serial, " 0x{b:02X}"); | |
| } |
| match explorer.explore( | ||
| i2c, | ||
| serial, | ||
| &mut PrefixExecutor::new( | ||
| successful_seq.as_mut_slice(), | ||
| prefix, | ||
| ), | ||
| ) { | ||
| Ok(()) => { | ||
| let _ = writeln!(serial, "[driver] init sequence applied"); | ||
| Ok(()) | ||
| } | ||
| Err(e) => { | ||
| let _ = writeln!(serial, "[error] explorer failed: {e:?}"); | ||
| Err(e) | ||
| } | ||
| } |
There was a problem hiding this comment.
This match block can be expressed more idiomatically using Result::map and Result::map_err for handling the Ok and Err cases respectively. This can make the code more concise and easier to read.
explorer.explore(
i2c,
serial,
&mut PrefixExecutor::new(
successful_seq.as_mut_slice(),
prefix,
),
)
.map(|()| {
let _ = writeln!(serial, "[driver] init sequence applied");
})
.map_err(|e| {
let _ = writeln!(serial, "[error] explorer failed: {e:?}");
e
})| } | ||
|
|
||
| impl PrefixExecutor { | ||
| fn new<T>(_: T, prefix: u8) -> Self { |
There was a problem hiding this comment.
The new function for PrefixExecutor takes a generic parameter T which is ignored. This is confusing for callers, who might think the argument is used when it is not. It should be removed to make the function's intent clearer. Remember to update the call site in run_explorer accordingly.
fn new(prefix: u8) -> Self {| fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool { | ||
| use heapless::Vec; | ||
| // This executor is a dummy for the explorer. | ||
| let mut buffer = Vec::<u8, 33>::new(); | ||
|
|
||
| if buffer.push(self.prefix).is_err() || buffer.extend_from_slice(cmd).is_err() { | ||
| return false; | ||
| } | ||
| i2c.write(addr, &buffer).is_ok() | ||
| } |
There was a problem hiding this comment.
A new heapless::Vec buffer is allocated on the stack every time exec is called. Since exec might be called frequently within the explorer's permutation loop, this is inefficient. To improve performance, consider making the buffer a field of PrefixExecutor and reusing it across calls by clearing it at the beginning of exec.
You would need to update PrefixExecutor struct and its new function:
struct PrefixExecutor {
prefix: u8,
buffer: heapless::Vec<>,
}
impl PrefixExecutor {
fn new(prefix: u8) -> Self {
Self { prefix, buffer: heapless::Vec::new() }
}
}Then, exec can be updated as follows:
fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool {
// This executor is a dummy for the explorer.
self.buffer.clear();
if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() {
return false;
}
i2c.write(addr, &self.buffer).is_ok()
}|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a glue function to connect the scanner and explorer modules, along with some logging improvements. The overall direction is good, but there's a significant logical issue in the new run_explorer function where the result from the scanner is calculated but then discarded, which likely indicates a bug. I've also pointed out some opportunities to improve code clarity by using immutable references where appropriate and fixing some minor formatting issues. Addressing the logical bug in run_explorer and PrefixExecutor should be the priority.
| pub fn run_explorer<I2C, S>( | ||
| explorer: &crate::explorer::Explorer<'_>, | ||
| i2c: &mut I2C, | ||
| serial: &mut S, | ||
| init_sequence: &mut [u8], | ||
| prefix: u8, | ||
| log_level: LogLevel, | ||
| ) -> Result<(), crate::explorer::ExplorerError> | ||
| where | ||
| I2C: crate::compat::I2cCompat, | ||
| S: core::fmt::Write, | ||
| <I2C as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt, | ||
| { | ||
| let _ = writeln!(serial, "[log] Scanning I2C bus..."); | ||
| let successful_seq = crate::scanner::scan_init_sequence( | ||
| i2c, | ||
| serial, | ||
| init_sequence, | ||
| log_level, | ||
| ); | ||
| let _ = writeln!(serial, "[scan] initial sequence scan completed"); | ||
|
|
||
| let _ = writeln!(serial, "[log] Start driver safe init"); | ||
|
|
||
| explorer.explore( | ||
| i2c, | ||
| serial, | ||
| &mut PrefixExecutor::new(prefix,successful_seq), | ||
| ) | ||
| .map(|()| { | ||
| let _ = writeln!(serial, "[driver] init sequence applied"); | ||
| }) | ||
| .map_err(|e| { | ||
| let _ = writeln!(serial, "[error] explorer failed: {e:?}"); | ||
| e | ||
| }) | ||
| } | ||
| struct PrefixExecutor { | ||
| prefix: u8, | ||
| buffer: heapless::Vec<u8, 64>, | ||
| } | ||
|
|
||
| impl PrefixExecutor { | ||
| fn new(prefix: u8, buffer: heapless::Vec<u8, 64>) -> Self { | ||
| Self { | ||
| prefix, | ||
| buffer, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<I2C> crate::explorer::CmdExecutor<I2C> for PrefixExecutor | ||
| where | ||
| I2C: crate::compat::I2cCompat, | ||
| <I2C as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt, | ||
| { | ||
| fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool { | ||
| // This executor is a dummy for the explorer. | ||
| self.buffer.clear(); | ||
|
|
||
| if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() { | ||
| return false; | ||
| } | ||
| i2c.write(addr, &self.buffer).is_ok() | ||
| } | ||
| } |
There was a problem hiding this comment.
There are a couple of issues in this new run_explorer function and the PrefixExecutor struct:
-
The
successful_seqvariable is calculated on line 303, but its value is effectively discarded. It's passed toPrefixExecutor::new, butPrefixExecutor::execclears its buffer on every call, ignoring the initial content. This seems like a bug. Thesuccessful_seqshould either be used correctly, or if it's only for logging, thescan_init_sequencefunction shouldn't return it. -
The
PrefixExecutortakes abufferin itsnewfunction, which is misleading for the reason above. It should probably initialize its own empty buffer. -
The comment
// This executor is a dummy for the explorer.on line 346 is confusing. The executor performs real I2C operations and is essential for the explorer's function. It should be rephrased to be more descriptive.
Here is a suggested refactoring to address these points. Note that this leaves successful_seq as an unused variable, which you should address by either using it or removing its calculation.
pub fn run_explorer<I2C, S>(
explorer: &crate::explorer::Explorer<'_>,
i2c: &mut I2C,
serial: &mut S,
init_sequence: &mut [u8],
prefix: u8,
log_level: LogLevel,
) -> Result<(), crate::explorer::ExplorerError>
where
I2C: crate::compat::I2cCompat,
S: core::fmt::Write,
<I2C as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt,
{
let _ = writeln!(serial, "[log] Scanning I2C bus...");
let successful_seq = crate::scanner::scan_init_sequence(
i2c,
serial,
init_sequence,
log_level,
);
// TODO: The 'successful_seq' is currently unused.
let _ = writeln!(serial, "[scan] initial sequence scan completed");
let _ = writeln!(serial, "[log] Start driver safe init");
explorer.explore(
i2c,
serial,
&mut PrefixExecutor::new(prefix),
)
.map(|()| {
let _ = writeln!(serial, "[driver] init sequence applied");
})
.map_err(|e| {
let _ = writeln!(serial, "[error] explorer failed: {e:?}");
e
})
}
struct PrefixExecutor {
prefix: u8,
buffer: heapless::Vec<u8, 64>,
}
impl PrefixExecutor {
fn new(prefix: u8) -> Self {
Self {
prefix,
buffer: heapless::Vec::new(),
}
}
}
impl<I2C> crate::explorer::CmdExecutor<I2C> for PrefixExecutor
where
I2C: crate::compat::I2cCompat,
<I2C as crate::compat::I2cCompat>::Error: crate::compat::HalErrorExt,
{
fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool {
// This executor prepends a prefix to each command.
self.buffer.clear();
if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() {
return false;
}
i2c.write(addr, &self.buffer).is_ok()
}
}Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
@gemini-code-assist |
|
Hi @p14c31355, That's a great question regarding Currently, the Here's why:
This means that the contents of To ensure For example, if Let me know your thoughts on the intended behavior, and I can provide a more specific suggestion! |
|
@gemini-code-assist |
|
Hi @p14c31355, Thanks for raising this important point. You're right to be concerned about how incorrect commands might lead to unintended behavior, especially in a search or exploration function. To help me understand the specific scenario you're envisioning, could you clarify a few things?
Understanding these details will help me provide more targeted feedback and suggestions, especially in the context of how Looking forward to your clarification! |
|
@gemini-code-assist |
|
Hi @p14c31355, Thanks for clarifying the role of You've accurately identified that
To align the code with your intent – where Let me know if this clarifies the situation and if you'd like to discuss potential API changes to achieve this behavior! |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable feature by adding a glue function to connect the I2C scanner and explorer. The implementation is generally well-done. I've identified a couple of areas for improvement in the new code within src/scanner.rs. My suggestions focus on enhancing the API's ergonomics and refactoring a new function for better clarity and maintainability. The changes in src/explorer.rs are positive logging improvements.
| // Init sequence before exploring | ||
| self.buffer.clear(); | ||
| for &c in self.init_sequence.iter() { | ||
| if self.buffer.push(self.prefix).is_err() || self.buffer.push(c).is_err() { | ||
| return false; | ||
| } | ||
| if i2c.write(addr, &self.buffer).is_err() { | ||
| return false; | ||
| } | ||
| self.buffer.clear(); | ||
| } | ||
|
|
||
| // Run the explorer command | ||
| if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() { | ||
| return false; | ||
| } | ||
|
|
||
| i2c.write(addr, &self.buffer).is_ok() |
There was a problem hiding this comment.
The implementation of exec can be simplified for better readability and maintainability. The current approach uses self.buffer for both the initialization sequence and the explorer command, which requires clearing it within the loop and makes the logic for buffer management complex.
A cleaner approach is to handle the fixed-size initialization sequence writes using a temporary stack-allocated array. This reserves self.buffer solely for the variable-length explorer command, making the function's logic easier to follow and maintain.
// Init sequence before exploring
for &c in self.init_sequence.iter() {
let command_with_prefix = [self.prefix, c];
if i2c.write(addr, &command_with_prefix).is_err() {
return false;
}
}
// Run the explorer command
self.buffer.clear();
if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() {
return false;
}
i2c.write(addr, &self.buffer).is_ok()Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new run_explorer function to connect the I2C scanner and command sequence explorer. It refactors scan_init_sequence to return detected commands and adds a PrefixExecutor to manage command execution with prefixes and initial sequences. My review includes suggestions to improve the documentation example for run_explorer for clarity and to refactor the PrefixExecutor::exec method for better readability and efficiency.
| /// ```ignore | ||
| /// # use dvcdbg::prelude::*; | ||
| /// # fn main() -> ! { | ||
| /// let mut i2c = /* your I2C instance */; | ||
| /// let mut serial = /* your serial instance */; | ||
| /// let mut init_sequence = [0u8; 16]; // Example initial sequence | ||
| /// let explorer = Explorer::new(/* ... */); | ||
| /// | ||
| /// run_explorer( | ||
| /// &explorer, | ||
| /// &mut i2c, | ||
| /// &mut serial, | ||
| /// &mut init_sequence, | ||
| /// 0x3C, // Example prefix | ||
| /// LogLevel::Info, | ||
| /// )?; | ||
| /// # Ok(()) | ||
| /// # } | ||
| /// ``` |
There was a problem hiding this comment.
The example in the run_explorer documentation has a few issues that could confuse users:
Explorer::newis used, but this constructor doesn't exist. It should beExplorer { sequence: &[] }or similar, based on other examples in the codebase.&mut init_sequenceis passed. While this coerces to&[u8], it's misleading as the sequence is not mutated. Using&init_sequencewould be clearer.LogLevel::Infois not a valid variant ofLogLevel. It should beLogLevel::VerboseorLogLevel::Quiet.- The function signature of
mainis-> !but the?operator is used, which requires aResultreturn type.
Here is a corrected version of the example.
# use dvcdbg::prelude::*;
# use dvcdbg::explorer::ExplorerError;
# use dvcdbg::scanner::LogLevel;
# fn main() -> Result<(), ExplorerError> {
let mut i2c = /* your I2C instance */;
let mut serial = /* your serial instance */;
let init_sequence = [0u8; 16]; // Example initial sequence
let explorer = Explorer { sequence: &[] }; // Dummy explorer
run_explorer(
&explorer,
&mut i2c,
&mut serial,
&init_sequence,
0x3C, // Example prefix
LogLevel::Verbose,
)?;
# Ok(())
# }| fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool { | ||
| // Init sequence before exploring | ||
| self.buffer.clear(); | ||
| for &c in self.init_sequence.iter() { | ||
| if self.buffer.push(self.prefix).is_err() || self.buffer.push(c).is_err() { | ||
| return false; | ||
| } | ||
| if i2c.write(addr, &self.buffer).is_err() { | ||
| return false; | ||
| } | ||
| self.buffer.clear(); | ||
| } | ||
|
|
||
| // Run the explorer command | ||
| if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() { | ||
| return false; | ||
| } | ||
|
|
||
| i2c.write(addr, &self.buffer).is_ok() | ||
| } |
There was a problem hiding this comment.
The implementation of exec can be simplified for better readability and to avoid unnecessary operations on self.buffer. The loop for sending the init_sequence repeatedly clears and fills self.buffer for each command. This can be made more efficient by creating the two-byte command on the stack within the loop. This avoids modifying self.buffer until it's needed for the main explorer command.
fn exec(&mut self, i2c: &mut I2C, addr: u8, cmd: &[u8]) -> bool {
// Init sequence before exploring
for &c in self.init_sequence.iter() {
let command = [self.prefix, c];
if i2c.write(addr, &command).is_err() {
return false;
}
}
// Run the explorer command
self.buffer.clear();
if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() {
return false;
}
i2c.write(addr, &self.buffer).is_ok()
}|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new run_explorer function that integrates the scanner and explorer modules to find valid I2C device initialization sequences. The changes include adding the PrefixExecutor to manage command execution with a prefix and an initial sequence, updating function signatures to return detected commands, and improving log messages. My review focuses on improving the robustness and clarity of the new functionality. I've pointed out a documentation inaccuracy and a potential for silent failure in the PrefixExecutor that could mislead users. Overall, this is a good step towards automating device analysis.
| if self.buffer.push(self.prefix).is_err() || self.buffer.extend_from_slice(cmd).is_err() { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
A buffer overflow here will be treated as a regular I2C failure by the Explorer, which could be misleading when debugging. The PrefixExecutor's buffer has a fixed capacity of 64 bytes, but commands from the Explorer can be of any length. If 1 + cmd.len() exceeds 64, this check will fail, and exec will return false without indicating the true cause of the failure.
To prevent this silent failure, I recommend adding a check at the beginning of run_explorer to validate command lengths against the buffer capacity and return a distinct error if a command is too long. This will provide clearer feedback to the user. For example:
// In run_explorer, before calling explorer.explore()
const PREFIX_EXECUTOR_BUF_CAP: usize = 64; // Ideally, this constant is shared with PrefixExecutor
if explorer.sequence.iter().any(|node| 1 + node.bytes.len() > PREFIX_EXECUTOR_BUF_CAP) {
let _ = writeln!(serial, "[error] Command too long for executor buffer.");
// You might want to add a new error variant to ExplorerError and return it here.
return Ok(()); // Or Err(...)
}Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
🚀 Pull Request
Overview
Change details
Build / Test Results