Hi, first of all I want to clarify the raison d'etre for this posting,
which I have also explained to Manos. Nothing you see here is code
that will be certainly included in QEMU; it's (mostly) throwaway by
design. I don't have much attachment to any of the code except perhaps
the casting and reference counting stuff (which is in, like, the third
rewrite... I got nerd-sniped there :)). But at the same time I think
it's plausibly not too different (in look and complexity) from what
the actual code will look like.

It's also not an attempt to bypass/leapfrog other people in the
design; it's more a "let's be ready for this to happen" than a design
document. The first step and the more immediate focus remains the
build system integration.

But you have good questions and observations so I'll put something
below about the design as well.

On Fri, Jul 5, 2024 at 8:52 PM Pierrick Bouvier
<pierrick.bouv...@linaro.org> wrote:
> To give an example of questions we could have:
>
> Why should we have distinct structs for a device, the object
> represented, and its state?

If you refer to the conf and state, a bit because of the different
traits required (ConstDefault is needed for properties but not the
rest) and especially because we can enforce immutability of
configuration vs. interior mutability of state.

In particular, from Manos's prototype I noticed that you need to
access the Chardev while the state is *not* borrowed; methods on the
Chardev call back into the device and the callback needs mutable
access to the state. So some kind of separation seems to be necessary,
for better or worse, unless interior mutability is achieved with a
dozen Cells (not great).

> Having to implement ObjectImpl and DeviceImpl looks like an
> implementation detail, that could be derived automatically from a
> device. What's wrong with calling a realize that does nothing in the end?

The problem is not calling a realize that does nothing, it's that you
are forced to override the superclass implementation (which might be
in C) if you don't go through Option<>. class_init methods update a
few superclass methods but not all of them.

The vtable in ObjectImpl/DeviceImpl could be generated via macros; for
now I did it by hand to avoid getting carried away too much with
syntactic sugar.

> Could device state be serialized with something like serde?

Probably not, there's extra complications for versioning. A long time
ago there was an attempt to have some kind of IDL embedded in C code
(not unlike Rust attributes) to automatically generate properties and
vmstate, but it was too ambitious.
(https://www.linux-kvm.org/images/b/b5/2012-forum-qidl-talk.pdf,
slides 1-9).

Generating property and vmstate declarations could be done with
"normal" macros just like in C, or with attribute macros (needs a lot
more code and experience, wouldn't do it right away). However, serde
for QAPI and/or visitors may be a possibility, after all JSON is
serde's bread and butter.

> This is the kind of things that could be discussed, on a reduced
> example, without specially looking at how to implement that concretely,
> in a first time.

There are some things that Rust really hates that you do, and they
aren't always obvious. Therefore in this exercise I tried to let
intuition guide me, and see how much the type system fought that
intuition (not much actually!). I started from the more technical and
less artistic part to see if I was able to get somewhere.

But yes, it's a great exercise to do this experiment from the opposite
end. Then the actual glue code will "meet in the middle", applying
lessons learnt from both experiments, with individual pieces of the
real interface implemented and applied to the PL011 sample at the same
time. The main missing thing in PL011 is DMA, otherwise it's a nice
playground.

> usage of magic macros as syntactic sugar should indeed be thought twice.
> Return a collection of QDevProperty could be better than having a magic
> @properties syntactic sugar.

No hard opinions there, sure. I went for the magic syntax because the
properties cannot be a const and I wanted to hide the ugly "static
mut", but certainly there could be better ways to do it.

> Another thing that could be discussed is: do we want to have the whole
> inheritance mechanism for Rust devices? Is it really needed?

Eh, this time unfortunately I think it is. Stuff like children and
properties, which are methods of Object, need to be available to
subclasses in instance_init and/or realize. Reset is in Device and we
have the whole Device/SysBusDevice/PCIDevice set of classes, so you
need to access superclass methods from subclasses. It's not a lot of
code though.

> Between having a clean and simple Device definition, with a bit more of
> magic glue (hidden internally), and requires devices to do some magic
> initialization to satisfy existing architecture, what would be the best?
> Even though it takes 1000 more lines, I would be in favor to have
> Devices that are as clean and simple as possible. Because this is the
> kind of code what will be the most added/modified, compared to the glue
> part.

That's an opinion I share but I wasn't sure it's universal, which was
another reason to prototype and post some "scary but perhaps
necessary" code.

> Or have multiple traits matching every possible operation, and allow a
> device to implement it or not, like Read/Write traits. And the glue code
> could call qemu_chr_fe_set_handlers.

Possibly, yes. https://lwn.net/Articles/863459/ you see many such
techniques: traits (gpio::Chip etc.), composition (device::Data),
lambdas (only for initialization).

The advantage of the lambda approach is that it scales to multiple
backends or multiple memory regions. We'll see.

> I hope my answer helped to understand more my point that discussing the
> interface is more important than discussing the glue needed.

Sure, and I don't think we are very much in disagreement, if at all.

Paolo


Reply via email to