Skip to content
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

Add save/restore for Serial device #73

Merged
merged 6 commits into from
Nov 26, 2021
Merged

Conversation

sabin-rapan
Copy link

Add support for saving and restoring the state (registers) of the serial device. This follows the same pattern in RTC (#65) by adding SerialState and SerialStateSer structs, where the former is a plain container for the data and the later mirrors it and derives Serialize, Deserialize and Versionize.
Both structures gives the caller full access to their fields. Apart from handling input buffers larger than FIFO_SIZE and retriggering interrupts that haven't been consumed by the driver, the current proposal makes no sanity checks on the fields in the state struct. This means, for example, one could set the upper four bits in IER register which is invalid.
If such sanity checks are desired, we can adjust the PR with a follow-up commit.

}

#[test]
fn test_state_ser_constructor() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test seems superfluous.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but without it kcov doesn't consider the public fields of the struct as covered by the test_state_ser_default() test above

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right. kcov.. :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm looks like this test doesn't even check some setters or some new() constructor, so it is indeed superfluous. I don't see how it could ever fail, so I would just remove it if it's okay for you too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked and it turns out I was wrong. Removing the superfluous tests doesn't affect coverage. Will drop the commit.

@@ -914,4 +1013,110 @@ mod tests {
// THR empty interrupt was raised nevertheless.
assert_eq!(iir & IIR_THR_EMPTY_BIT, IIR_THR_EMPTY_BIT);
}

#[test]
fn test_serial_state_constructor() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one seems superfluous too.

};

if serial.is_thr_interrupt_enabled() && serial.is_thr_interrupt_set() {
serial.trigger_interrupt().unwrap_or_default();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a small comment why it's safe to unwrap and ignore?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we cannot trigger this interrupt, isn't the guest going to be stuck? I wonder if in this case it's not better to just return the Error so we can fail fast. Same for the following if.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's was my initial instinct as well, but I wasn't sure what's more desirable for Serial::from_state(): to fail fast or cope with programming errors. Thanks for the feedback. Will update the PR so that from_state() returns errors instead of being best effort.

gsserge
gsserge previously approved these changes Nov 19, 2021
scratch: DEFAULT_SCRATCH,
in_buffer: VecDeque::new(),
/// * `state` - A reference to the state from which the `Serial` is constructed.
pub fn from_state(trigger: T, serial_evts: EV, out: W, state: &SerialState) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state should be the first parameter so that it's in line with the RTC device.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK

/// * `state` - A reference to the state from which the `Serial` is constructed.
pub fn from_state(trigger: T, serial_evts: EV, out: W, state: &SerialState) -> Self {
let mut buffer = state.in_buffer.clone();
buffer.truncate(FIFO_SIZE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice way of insuring the buffer length 👍. We should document this behavior somewhere. One option is either in SerialState or in this constructor. If we end up returning errors from this function, we should also consider returning an error instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change to return an error instead so that we're consistent in behaviour with trigger() failures.

};

if serial.is_thr_interrupt_enabled() && serial.is_thr_interrupt_set() {
serial.trigger_interrupt().unwrap_or_default();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we cannot trigger this interrupt, isn't the guest going to be stuck? I wonder if in this case it's not better to just return the Error so we can fail fast. Same for the following if.

modem_control: DEFAULT_MODEM_CONTROL,
modem_status: DEFAULT_MODEM_STATUS,
scratch: DEFAULT_SCRATCH,
in_buffer: Vec::new(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can unify this test with test_from_state_with_too_many_bytes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK


#[test]
fn test_serial_state_default() {
let state = SerialState::default();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more useful if we instead create the serial from the default state (using with_events), then call save_state, and check that the state is the default_state using assert_eq (because SerialState derives PartialEq). I don't think we need to check the actual values because this is a duplication of the code.

@@ -304,7 +304,7 @@ impl<T: Trigger, W: Write> Serial<T, NoEvents, W> {
///
/// You can see an example of how to use this function in the
/// [`Example` section from `Serial`](struct.Serial.html#example).
pub fn new(trigger: T, out: W) -> Serial<T, NoEvents, W> {
pub fn new(trigger: T, out: W) -> Result<Serial<T, NoEvents, W>, Error<T::E>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should use unwrap and comment why the unwrap is safe so we don't need to change the existing API.

Copy link
Member

@andreeaflorescu andreeaflorescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Can you fix the style as well?

crates/vm-superio/src/serial.rs Outdated Show resolved Hide resolved
Sabin Rapan added 6 commits November 26, 2021 12:54
The SerialState struct will help aid us in instantiating Serial objects
from a predefined state. It will be used in following commits for
adding save/restore support.

At the moment, we do not check if the state we are restoring from has an
obscenely large buffer or if the interrupt should be asserted. These
issues will be addressed in subsequent commits.

Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
This struct implements both Serialize/Deserialize and Versionize to aid
saving/restoring the serial state in multiple formats. The data is then
converted to SerialState which ultimately is consumed when creating
another Serial object via its from_state() method.

Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
... by returning an Error if the input buffer is larger than FIFO_SIZE.

Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
To trigger a specific interrupt one needs first to enable said interrupt
(in the Interrupt Enable Register), then the logic that triggers the
interrupt will set the corresponding bit in Interrupt Identification
Register, so that the driver can figure out what interrupt to handle.
When it reads IIR, this causes the respective bit to be reset.

We can leverage this same logic to retrigger interrupts when restoring
from a given state by checking that both IER and IIR have been set for
each supported interrupt kind. This way we avoid adding unnecessary
fields in SerialState to do the bookkeeping of these pending interrupts.

Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
Just some basic tests, nothing fancy, to make sure our dependencies work
with our structs like we would expect (and improve our code coverage).

Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
Signed-off-by: Sabin Rapan <sabrapan@amazon.com>
Copy link
Collaborator

@lauralt lauralt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@lauralt lauralt merged commit c0d3f74 into rust-vmm:main Nov 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants