-
Notifications
You must be signed in to change notification settings - Fork 747
Allow custom implementations of DebugWriter #4382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to do something like this, but I get the sense that what is in debug.rs right now is more based on what worked rather than something meant to be extensible.
I think we should create the trait, but having extract() as a member function doesn't make much sense. Probably flush() should be there instead.
The existing struct should be called DebugWriterRingBuffer
or something.
kernel/src/debug.rs
Outdated
@@ -303,22 +303,36 @@ pub struct DebugWriter { | |||
count: Cell<usize>, | |||
} | |||
|
|||
pub trait DebugWriterTrait: Write + IoWrite { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub trait DebugWriterTrait: Write + IoWrite { | |
pub trait DebugWriter: Write + IoWrite { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the draft implementation is a somewhat dirty WA intended to keep all the existing implementation as is while also allowing custom one. Ideally the empty trait functions should not be there as they are only required by specific implementations. If we are in agreement we want this, I can look into cleaner implementation when I have some free cycles...
597f65a
to
5d3ad86
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically looks good to me! Seems like a good candidate for SeggerRTT, no @bradjc ?
/// Available length of the internal buffer | ||
fn available_len(&self) -> usize { | ||
// Default implementation is to return maximum (for synchronous/unbuffered implementations) | ||
usize::MAX | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this default method implementation. It is very easy to miss overriding default methods, and as such we should only use them when they're always a sensible, correct implementation w.r.t. the abstract interface's guarantees.
In this case, many implementations may not be synchronous and unbuffered, and they should be forced to implement this method themselves faithfully to their implementation.
/// Publish bytes from the internal buffer to the output | ||
fn publish_bytes(&self) -> usize { | ||
// Default implementation is to do nothing | ||
0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the return value indicate? It is legal for an implementation to "publish" bytes before this method is called? Should this really be called "flush"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should create the trait, but having extract() as a member function doesn't make much sense. Probably flush() should be there instead.
I only now saw @bradjc's comment, which I'm echoing above.
/// Extract the internal buffer | ||
fn extract(&self) -> Option<&mut RingBuffer<'static, u8>> { | ||
// Default implementation has no internal buffer | ||
None | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the wrong place for this method. It concerns implementation details of the underlying debug writer implementation (such as it, perhaps, internally using a RingBuffer
). Such a method should instead be implemented on the proper type, not this generic trait.
It doesn't seem to be used, so I'd ask to remove this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should create the trait, but having extract() as a member function doesn't make much sense. Probably flush() should be there instead.
Same here, agree with @bradjc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This extract()
is somewhat problematic. Adding flush()
instead needs it to take another writer as a parameter to flush into, which is in turn has to be Write + IoWrite
. But then it make DebugWriter "not dyn compatible" and I am not sure how to fix it.
How about splitting the trait into two like this instead:
pub trait DebugWriter {
/// Write bytes to output
/// The `overflow` slice is used as a message to be appended to the end of the available
/// buffer if it becomes full.
fn write(&self, bytes: &[u8], overflow: &[u8]) -> usize;
/// Return a reference to self if it implements [`BufferedDebugWriter`].
fn as_buffered(&self) -> Option<&dyn BufferedDebugWriter>;
}
pub trait BufferedDebugWriter: DebugWriter {
/// Available length of the internal buffer
fn available_len(&self) -> usize;
/// Publish bytes from the internal buffer to the output
fn publish_bytes(&self) -> usize;
/// Extract the internal buffer
fn extract(&self) -> Option<&RingBuffer<'static, u8>>;
}
Then buffered writers can implement both traits, while unbuffered can only implement DebugWriter
. The wrapper will take care if handling the as_buffered()
as in
impl DebugWriterWrapper {
// .....
fn extract(&self) -> Option<&RingBuffer<'static, u8>> {
self.dw
.and_then(|dw| dw.as_buffered())
.and_then(|dw| dw.extract())
}
// ...
}
} | ||
|
||
impl DebugWriter { | ||
impl UartDebugWriter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impl UartDebugWriter { | |
impl RingBufferDebugWriter { |
Pull Request Overview
Fixes #4302
Add a trait for debug writer, so the downstream code can implement it's own, not depending on UART backend.
Testing Strategy
Tested on QEMU and local simulated RiscV platform with both UART based and non-UART implentations.
Tested debug buffer overflow on buffered implementation
TODO or Help Wanted
Would be nice to test with some implementation using
console_ordered
as it has some "intrusive" calls to the debug apiDocumentation Updated
Inline documentation added in
debug.rs
Formatting
make prepush
.