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 methods to Transport to read and write config space, rather than returning a pointer. #163

Merged
merged 2 commits into from
Nov 22, 2024

Conversation

qwandor
Copy link
Collaborator

@qwandor qwandor commented Oct 29, 2024

The config space is not necessarily memory mapped, some other transports require the config space to be accessed via other IO operations such as hypercalls.

@osteffenrh
Copy link

Nice, I was looking for something like this! Having Transport offer the config space access is a good idea IMHO.

I had tried stuffing the platform specific IO operations into the Hal and had volread / volwrite require the Hal as input. The Transport then of course also depends on the Hal ( struct MmioTransport<H: Hal>).
Here is what I tried (incomplete): https://github.com/osteffenrh/virtio-drivers/commits/virtio-pr/

returning a pointer.

The config space is not necessarily memory mapped, some other transports
require the config space to be accessed via other IO operations such as
hypercalls.
This also makes some device methods return a Result.
Copy link

@keirf keirf left a comment

Choose a reason for hiding this comment

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

I like the concept, and the code is more readable by me as a result of these changes. Caveat: This is not a line-by-line review!

Copy link
Contributor

@aliciawyy aliciawyy left a comment

Choose a reason for hiding this comment

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

The overall change is more readable than before.

@qwandor qwandor merged commit de98c7a into master Nov 22, 2024
6 checks passed
@qwandor qwandor deleted the configspace branch November 22, 2024 12:35
let capacity = unsafe {
volread!(config, capacity_low) as u64 | (volread!(config, capacity_high) as u64) << 32
};
let capacity = transport.read_config_space::<u32>(offset_of!(BlkConfig, capacity_low))?
Copy link
Contributor

Choose a reason for hiding this comment

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

In the new code, the you have to be careful about keeping the template param and the field types in sync at every call point. It looks easy to mess up. I wonder if we could have a macro like read_config_space!(transport, BlkConfig, capacity_low) that handles the offset and makes sure the types line up properly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had the same thought, but can't find any way to do so as there doesn't seem to be any macro to get the type of a struct field from its name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here is a trick

macro_rules! offset_and_size {
    ($t:ty, $f:ident) => {{
        let v: $t = Default::default();
        (std::mem::offset_of!($t, $f), std::mem::size_of_val(&v.$f))
    }};
}

Seems to optimize out https://godbolt.org/z/hzWGjqxY1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think getting the size helps, the issue is to get the type. But I think I have found a way around that, I'll send another PR in a bit.

};
let jacks = transport.read_config_space(offset_of!(VirtIOSoundConfig, jacks))?;
let streams = transport.read_config_space(offset_of!(VirtIOSoundConfig, streams))?;
let chmaps = transport.read_config_space(offset_of!(VirtIOSoundConfig, chmaps))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

inferring the types seems dangerous here since the field declaration is far away

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The important thing here is that the type of e.g. VirtIOSound::jacks must match the type of VirtIOSoundConfig::jacks. I'm not sure that adding an explicit type parameter here really helps with that.

};
let capacity = transport.read_config_space::<u32>(offset_of!(BlkConfig, capacity_low))?
as u64
| (transport.read_config_space::<u32>(offset_of!(BlkConfig, capacity_high))? as u64)
Copy link
Contributor

Choose a reason for hiding this comment

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

not related to this PR, but technically you should use the config generation field when doing multiple part reads like this: https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-230001

(FWIW, the crosvm VMM currently always has config_generation == 0)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point, I missed that. Done in #169.

align_of::<T>());
assert!(offset % align_of::<T>() == 0);

// SAFETY: The caller of `MmioTransport::new` guaranteed that the header pointer was valid,
Copy link
Contributor

Choose a reason for hiding this comment

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

the device tree should contain an upper bound for the config space (from the reg field i think) and you could check that offset doesn't go beyond it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, will do.

align_of::<T>());
assert!(offset % align_of::<T>() == 0);

// SAFETY: The caller of `MmioTransport::new` guaranteed that the header pointer was valid,
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will do.

Ok(config_space_ptr.cast())
Ok((config_space.as_ptr() as *mut T)
.byte_add(offset)
.read_volatile())
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe too pedantic, but this allows any T and that doesn't satify src must point to a properly initialized value of type T. could add T: zerocopy::FromBytes if that dep is available

Copy link
Collaborator Author

@qwandor qwandor Nov 26, 2024

Choose a reason for hiding this comment

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

Good point. I already had T: FromBytes as a constraint on the Transport trait, but I'll add it here on the implementation too.

// TODO: Use NonNull::as_non_null_ptr once it is stable.
let config_space_ptr = NonNull::new(config_space.as_ptr() as *mut u32).unwrap();
Ok(config_space_ptr.cast())
Ok((config_space.as_ptr() as *mut T)
Copy link
Contributor

Choose a reason for hiding this comment

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

technically only need *const T here i think

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll change this to pointer::cast instead.

@qwandor
Copy link
Collaborator Author

qwandor commented Nov 26, 2024

@fkm3 I've sent #167 to fix the things you suggested.

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.

5 participants