diff --git a/qemu/qom-rust.txt b/qemu/qom-rust.txt
new file mode 100644
index 0000000..1588445
--- /dev/null
+++ b/qemu/qom-rust.txt
@@ -0,0 +1,82 @@
+Rust QOM interoperability design
+--------------------------------
+
+Passing objects around
+----------------------
+
+ObjectRef:
+-> trait for performing casts on objects
+-> upcasts safe at compile time, downcasts safe at runtime
+-> implemented by &T and qom::Owned<T>
+-> casting &T produces &U, casting qom::Owned<T> produces qom::Owned<U>
+
+qom::Owned<T>
+-> T is a struct for a QOM object
+-> cloning qom::Owned calls object_ref, dropping qom::Owned calls object_unref
+
+
+Calling methods
+---------------
+
+- all methods &self (interior mutability)
+  - Rust implementation needs to wrap state with Cell<>, RefCell<> or Mutex<>
+
+one struct per class; one trait per non-final class; one trait per interface
+struct: Object, Device, ...
+- defines constructors
+     example: PL011::new()   (technically defined on ObjectType)
+
+- defines methods of final classes
+
+trait: ObjectMethods, DeviceMethods, UserCreatableMethods, ...
+- defines methods of non-final classes and interfaces
+     example: obj.typename()
+
+- automatically implemented by &T where T is a subclass
+
+
+all methods expect interior mutability
+- structs not Send/Sync by default since they contain C pointers
+  - hence &T and Owned<T> also not thread-safe
+- good: QOM tree (e.g. object_unparent) not thread-safe
+- what if objects _are_ thread-safe?
+  - possibly another trait ObjectSyncMethods?
+
+Bindings for C classes
+----------------------
+
+struct must implement ObjectType
+
+    unsafe impl ObjectType for Object {
+        const TYPE: &'static CStr = c"object";
+    }
+
+struct must implement IsA<T> for all superclasses T
+
+    unsafe impl IsA<Object> for Object {}
+
+
+Defining QOM classes in Rust
+----------------------------
+
+struct must be #[repr(C)]
+
+one traits per class + one more if it has virtual functions
+
+trait #1: ObjectTypeImpl, DeviceTypeImpl, ...
+- metadata
+    type Super: ObjectType;
+- functions:
+    unsafe fn instance_init(obj: *mut Self);
+    ...
+
+trait #2: ObjectImpl, DeviceImpl, ...
+- functions:
+    fn unrealize(&self)
+
+Rust implementation split in configuration (Default + ConstDefault) and
+state (Default)
+
+instance_init implemented automatically via Default/ConstDefault trait
+   maybe: pre_init hook that replaces memset(obj, 0, type->instance_size)?
+instance_finalize implemented automatically via Drop trait
diff --git a/qemu/src/lib.rs b/qemu/src/lib.rs
index 5f926b8..0d91623 100644
--- a/qemu/src/lib.rs
+++ b/qemu/src/lib.rs
@@ -2,6 +2,12 @@
 #![allow(dead_code)]
 
 pub mod bindings;
+pub use bindings::Object;
+
+pub mod qom;
+pub use qom::object::ObjectType;
+pub use qom::refs::ObjectCast;
+pub use qom::refs::Owned;
 
 pub mod util;
 pub use util::error::Error;
diff --git a/qemu/src/qom/mod.rs b/qemu/src/qom/mod.rs
new file mode 100644
index 0000000..95489c5
--- /dev/null
+++ b/qemu/src/qom/mod.rs
@@ -0,0 +1,2 @@
+pub mod object;
+pub mod refs;
diff --git a/qemu/src/qom/object.rs b/qemu/src/qom/object.rs
new file mode 100644
index 0000000..bd6b957
--- /dev/null
+++ b/qemu/src/qom/object.rs
@@ -0,0 +1,34 @@
+//! Bindings for the QOM Object class
+//!
+//! @author Paolo Bonzini
+
+use std::ffi::CStr;
+
+use crate::bindings::Object;
+
+use crate::qom_isa;
+
+/// Trait exposed by all structs corresponding to QOM objects.
+/// Defines "class methods" for the class.  Usually these can be
+/// implemented on the class itself; here, using a trait allows
+/// each class to define `TYPE`, and it also lets `new()` return the
+/// right type.
+///
+/// # Safety
+///
+/// - the first field of the struct must be of `Object` type,
+///   or derived from it
+///
+/// - `TYPE` must match the type name used in the `TypeInfo` (no matter
+///   if it is defined in C or Rust).
+///
+/// - the struct must be `#[repr(C)]`
+pub unsafe trait ObjectType: Sized {
+    const TYPE: &'static CStr;
+}
+
+unsafe impl ObjectType for Object {
+    const TYPE: &'static CStr = c"object";
+}
+
+qom_isa!(Object);
diff --git a/qemu/src/qom/refs.rs b/qemu/src/qom/refs.rs
new file mode 100644
index 0000000..a319bde
--- /dev/null
+++ b/qemu/src/qom/refs.rs
@@ -0,0 +1,274 @@
+//! Casting and reference counting traits for QOM objects
+//!
+//! @author Paolo Bonzini
+
+use crate::bindings::object_dynamic_cast;
+use crate::bindings::Object;
+use crate::bindings::{object_ref, object_unref};
+
+use crate::qom::object::ObjectType;
+
+use std::borrow::Borrow;
+use std::mem::ManuallyDrop;
+use std::ops::Deref;
+use std::ptr::NonNull;
+
+/// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct
+/// or indirect parent of `Self`).
+///
+/// # Safety
+///
+/// The struct `Self` must begin, directly or indirectly, with a field of type
+/// `P`.  This ensures that invalid casts, which rely on `IsA<>` for static
+/// checking, are rejected at compile time.
+pub unsafe trait IsA<P: ObjectType>: ObjectType {}
+
+// SAFETY: it is always safe to cast to your own type
+unsafe impl<T: ObjectType> IsA<T> for T {}
+
+#[macro_export]
+macro_rules! qom_isa {
+    ($struct:ty $(,$parent:ty)* ) => {
+        $(
+            impl AsRef<$parent> for $struct {
+                fn as_ref(&self) -> &$parent {
+                    use $crate::ObjectCast;
+                    self.upcast::<$parent>()
+                }
+            }
+
+            // SAFETY: it is the caller responsibility to have $parent as the
+            // first field
+            unsafe impl $crate::qom::refs::IsA<$parent> for $struct {}
+        )*
+    };
+}
+
+/// Trait for a reference to a QOM object.  Allows conversion to/from
+/// C objects in generic code.
+pub trait ObjectCast: Copy + Deref
+where
+    Self::Target: ObjectType,
+{
+    /// Convert this (possibly smart) reference to a basic Rust reference.
+    fn as_ref(&self) -> &Self::Target {
+        self.deref()
+    }
+
+    /// Convert to a const Rust pointer, to be used for example for FFI.
+    fn as_ptr(&self) -> *const Self::Target {
+        self.as_ref()
+    }
+
+    /// Convert to a mutable Rust pointer, to be used for example for FFI.
+    /// Used to implement interior mutability for objects.
+    ///
+    /// # Safety
+    ///
+    /// This method is unsafe because it overrides const-ness of `&self`.
+    /// Bindings to C APIs will use it a lot, but otherwise it should not
+    /// be necessary.
+    unsafe fn as_mut_ptr(&self) -> *mut Self::Target {
+        #[allow(clippy::as_ptr_cast_mut)]
+        {
+            self.as_ptr().cast_mut()
+        }
+    }
+
+    /// Perform a cast to a superclass
+    fn upcast<'a, U: ObjectType>(self) -> &'a U
+    where
+        Self::Target: IsA<U>,
+        Self: 'a,
+    {
+        // SAFETY: soundness is declared via IsA<U>, which is an unsafe trait
+        unsafe { self.unsafe_cast::<U>() }
+    }
+
+    /// Perform a cast to a subclass.  Checks at compile time that the
+    /// cast can succeed, but the final verification will happen at
+    /// runtime only.
+    fn downcast<'a, U: IsA<Self::Target>>(self) -> Option<&'a U>
+    where
+        Self: 'a,
+    {
+        self.dynamic_cast::<U>()
+    }
+
+    /// Perform a cast between QOM types.  The check that U is indeed
+    /// the dynamic type of `self` happens at runtime.
+    fn dynamic_cast<'a, U: ObjectType>(self) -> Option<&'a U>
+    where
+        Self: 'a,
+    {
+        unsafe {
+            // SAFETY: upcasting to Object is always valid, and the
+            // return type is either NULL or the argument itself
+            let result: *const U =
+                object_dynamic_cast(self.unsafe_cast::<Object>().as_mut_ptr(), U::TYPE.as_ptr())
+                    .cast();
+
+            result.as_ref()
+        }
+    }
+
+    /// Unconditional cast to an arbitrary QOM type.
+    ///
+    /// # Safety
+    ///
+    /// What safety? You need to know yourself that the cast is correct; only use
+    /// when performance is paramount.  It is still better than a raw pointer
+    /// `cast()`, which does not even check that you remain in the realm of
+    /// QOM `ObjectType`s.
+    ///
+    /// `unsafe_cast::<Object>()` can also be used, and is always safe, if all
+    /// you have is an `ObjectType` (as opposed to an `IsA<Object>`).
+    unsafe fn unsafe_cast<'a, U: ObjectType>(self) -> &'a U
+    where
+        Self: 'a,
+    {
+        &*(self.as_ptr().cast::<U>())
+    }
+}
+
+impl<T: ObjectType> ObjectCast for &T {}
+
+/// An owned reference to a QOM object.
+///
+/// Like [`std::sync::Arc`], references are added with [`Clone::clone`] and removed
+/// by dropping the `Owned`.
+#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Owned<T: ObjectType>(NonNull<T>);
+
+// QOM knows how to handle reference counting across threads, but sending
+// the Owned to another thread requires the implementation itself to be
+// thread-safe (aka Sync).  But I'm not entirely sure that this is enough
+// (see for example ARef in rust/kernel/types.rs, which is very similar
+// to this type).
+//
+//unsafe impl<T: Sync + ObjectType> Send for Owned<T> {}
+//unsafe impl<T: ObjectType> Sync for Owned<T> {}
+
+impl<T: ObjectType> Owned<T> {
+    /// Obtain a reference from a raw C pointer
+    ///
+    /// # Safety
+    ///
+    /// Typically this function will only be used by low level bindings
+    /// to C APIs.
+    pub unsafe fn from_raw(ptr: *const T) -> Self {
+        // SAFETY NOTE: while NonNull requires a mutable pointer,
+        // only Deref is implemented so the pointer passed to from_raw
+        // remains const
+        Owned(NonNull::new_unchecked(ptr.cast_mut()))
+    }
+
+    /// Obtain a raw C pointer from a reference.  `src` is consumed
+    /// and the reference is leaked.
+    pub fn into_raw(src: Owned<T>) -> *const T {
+        let src = ManuallyDrop::new(src);
+        src.0.as_ptr()
+    }
+
+
+    /// Increase the reference count of a QOM object and return
+    ///
+    /// # Safety
+    ///
+    /// Unsafe because the object could be embedded in another.  To
+    /// obtain an `Owned` safely, use `ObjectType::new()`.
+    pub unsafe fn from(obj: &T) -> Self {
+        object_ref(obj.unsafe_cast::<Object>().as_mut_ptr());
+
+        // SAFETY NOTE: while NonNull requires a mutable pointer,
+        // only Deref is implemented so the pointer passed to from_raw
+        // remains const
+        Owned(NonNull::new_unchecked(obj.as_mut_ptr()))
+    }
+
+    /// Perform a cast to a superclass
+    pub fn upcast<U: ObjectType>(src: Owned<T>) -> Owned<U>
+    where
+        T: IsA<U>,
+    {
+        // SAFETY: soundness is declared via IsA<U>, which is an unsafe trait
+        unsafe { Owned::unsafe_cast::<U>(src) }
+    }
+
+    /// Perform a cast to a subclass.  Checks at compile time that the
+    /// cast can succeed, but the final verification will happen at
+    /// runtime only.
+    pub fn downcast<U: IsA<T>>(src: Owned<T>) -> Result<Owned<U>, Owned<T>> {
+        Owned::dynamic_cast::<U>(src)
+    }
+
+    /// Perform a cast between QOM types.  The check that U is indeed
+    /// the dynamic type of `self` happens at runtime.
+    pub fn dynamic_cast<U: ObjectType>(src: Owned<T>) -> Result<Owned<U>, Owned<T>> {
+        // override automatic drop to skip the unref/ref
+        let src = ManuallyDrop::new(src);
+        match src.dynamic_cast::<U>() {
+            // get the ownership back from the ManuallyDrop<>
+            None => Err(ManuallyDrop::into_inner(src)),
+
+            // SAFETY: the ref is moved (thanks to ManuallyDrop) from
+            // self to casted_ref
+            Some(casted_ref) => Ok(unsafe { Owned::<U>::from_raw(casted_ref) }),
+        }
+    }
+
+    /// Unconditional cast to an arbitrary QOM type.
+    ///
+    /// # Safety
+    ///
+    /// What safety? You need to know yourself that the cast is correct.  Only use
+    /// when performance is paramount
+    pub unsafe fn unsafe_cast<U: ObjectType>(src: Owned<T>) -> Owned<U> {
+        // override automatic drop to skip the unref/ref
+        let src = ManuallyDrop::new(src);
+        let casted_ref = src.unsafe_cast::<U>();
+        Owned::<U>::from_raw(casted_ref)
+    }
+}
+
+impl<T: ObjectType> AsRef<T> for Owned<T> {
+    fn as_ref(&self) -> &T {
+        self.deref()
+    }
+}
+
+impl<T: ObjectType> Borrow<T> for Owned<T> {
+    fn borrow(&self) -> &T {
+        self.deref()
+    }
+}
+
+impl<T: ObjectType> Clone for Owned<T> {
+    fn clone(&self) -> Self {
+        // SAFETY: creation method is unsafe, and whoever calls it
+        // has responsibility that the pointer is valid
+        unsafe { Owned::from(self.deref()) }
+    }
+}
+
+impl<T: ObjectType> Deref for Owned<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: creation method is unsafe, and whoever calls it
+        // has responsibility that the pointer has a static lifetime.
+        // Once that is guaranteed, reference counting ensures that
+        // the object remains alive.
+        unsafe { &*self.0.as_ptr() }
+    }
+}
+
+impl<T: ObjectType> Drop for Owned<T> {
+    fn drop(&mut self) {
+        // SAFETY: creation method is unsafe, and whoever calls it
+        // has responsibility that the pointer is valid
+        unsafe {
+            object_unref(self.unsafe_cast::<Object>().as_mut_ptr());
+        }
+    }
+}
