From nobody Sat Nov 23 19:34:26 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linaro.org ARC-Seal: i=1; a=rsa-sha256; t=1729778717; cv=none; d=zohomail.com; s=zohoarc; b=i5spGlYFUYHQ1UjnKay6vJigpH0kfw5pxeIvIu6hgXvOkWGJMaK9TmCbkNCL6RtBr5JUazKeDRXeIfN7jMD4SjTJnB40ltYXFSQBQ5M5AVtVdE2ILcdp+MtnyjbF7cQ1DiT1ahyVNwype+lrowemTREzWiD3SthE1cHib8FPnHo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1729778717; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=2iGhVX3FthgCu9tCSB5ib+BmYJD5e1RgHUcKSHCgOOI=; b=bOPEE42Yo9an6soTMyXqs+IpVGpx7ScWpT4UeFkF4VkginJk67O9u3czO7mx/TexW7cygXq1ZauyJMnrSQ63qKZcfzpIx05/s4z1rPe+liu5G0QzOeYohsvX3zOQvo97US9ZRMVXeahzxbLXNTNusabAfkq0TLEWxDdJt4J6ftw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1729778717262335.9086770749336; Thu, 24 Oct 2024 07:05:17 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1t3yRW-0007Bf-RB; Thu, 24 Oct 2024 10:04:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1t3yRC-00078q-EU for qemu-devel@nongnu.org; Thu, 24 Oct 2024 10:03:50 -0400 Received: from mail-lj1-x235.google.com ([2a00:1450:4864:20::235]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1t3yR8-0003wK-OK for qemu-devel@nongnu.org; Thu, 24 Oct 2024 10:03:42 -0400 Received: by mail-lj1-x235.google.com with SMTP id 38308e7fff4ca-2fb51e00c05so14154581fa.0 for ; Thu, 24 Oct 2024 07:03:37 -0700 (PDT) Received: from [127.0.1.1] (adsl-113.37.6.2.tellas.gr. [37.6.2.113]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-a9a912f6878sm621407566b.86.2024.10.24.07.03.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 07:03:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1729778616; x=1730383416; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=2iGhVX3FthgCu9tCSB5ib+BmYJD5e1RgHUcKSHCgOOI=; b=rUGCQrLWPDkD2kiGqZSdoWxuu2ET2LjSjxyJPDSao/0rESoR17GvIP4KufdEdTeCx8 w3ZN3i+DgRIILLAHSBBI25LBRRRrQLWjfHfk3U7wRrggD15+xpyLqo6dfapcnR2F2wQJ X6iphCWjoTN7eNGQtQUgpoZgd7YdL7UFB+dKOMzjmNsT/wiWIJDtAsVmzJ+khkMm0Jzj ANxC3rQhVpK5owDxDyd5lpMCgFYxRwIMf5jOSvm6yCcUz3mrqtg9jqRhq2I0I+Z4mrNb FgSvxrpfBwTHSkV9warB9nyVBIF7q40o5RM7fhVwuTY/Xw7yDCHoo+/nu0nL7PqFuo4r DCAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729778616; x=1730383416; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=2iGhVX3FthgCu9tCSB5ib+BmYJD5e1RgHUcKSHCgOOI=; b=pmguOuAmTQedk+MOWQD3hoZnHpREKzG0Yo+VctXWWmhpArieOFvA5JBmTGdF9ZNsBA CtPL5ggadf39fv2JtKNgLwtX51QF5nL6JVFjWLCfiASpSA1SKHxmr7YjTEEKdAFIvqqN SvEL5wQi7Z0JBpln5iHLtV6XQKIlVcX6zll24eZhWzLbQ21JbtZmbg3az+P323iixAUQ DvhF1TrhpYWcHhE3/mDiLGgCxifPKnNEbYcgbTWA7OlnL6eYudjvSWLLTQsAnUP4VDOB CREEe6n7fPDNgPpdnxdnebA9hJMQY81xoFbthgOuOtzDmdMaNMhviPFYFc2Zuy1xMc2P C3aw== X-Gm-Message-State: AOJu0YyuJFm+muB3qd6jYt2Bx2g3oC1xogK6SDKZMBASPjrXC+DXzko6 lmUofYwTnb+40wbe4tW4tago8Ke5ZTJu+Zed9s5HKpF9ZR6ZiZV7VzzGIn5E/k8= X-Google-Smtp-Source: AGHT+IGhCJhXR6hGf4u3tLOUo/0A2sEJdUiGwOYPNA3umLPkGF/soXznrg3r49In9R5KsO2cYOw3oA== X-Received: by 2002:a05:6512:220d:b0:535:6baa:8c5d with SMTP id 2adb3069b0e04-53b1a33cc87mr5521258e87.20.1729778615109; Thu, 24 Oct 2024 07:03:35 -0700 (PDT) From: Manos Pitsidianakis Date: Thu, 24 Oct 2024 17:03:02 +0300 Subject: [PATCH 04/11] rust: add support for migration in device models MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20241024-rust-round-2-v1-4-051e7a25b978@linaro.org> References: <20241024-rust-round-2-v1-0-051e7a25b978@linaro.org> In-Reply-To: <20241024-rust-round-2-v1-0-051e7a25b978@linaro.org> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , =?utf-8?q?Alex_Benn=C3=A9e?= , Thomas Huth , Junjie Mao , Junjie Mao , Zhao Liu , Kevin Wolf , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , Richard Henderson , Gustavo Romero , Pierrick Bouvier X-Mailer: b4 0.15-dev-12fc5 X-Developer-Signature: v=1; a=openpgp-sha256; l=37037; i=manos.pitsidianakis@linaro.org; h=from:subject:message-id; bh=kB5qjmgamQwXPRu8ReZyXP7rlHpISzFf2JMFS5i2g8I=; b=LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpvd0VCYlFLUy9aQU5Bd0FLQVhjcHgzQi9mZ 25RQWNzbVlnQm5HbE9pWnc3Q3VJbkM0NUJpRGVnVXhqWTViMTRpCndBd1cxQU04RTNCTVU2cVMv SWlKQWpNRUFBRUtBQjBXSVFUTVhCdE9SS0JXODRkd0hSQjNLY2R3ZjM0SjBBVUMKWnhwVG9nQUt DUkIzS2Nkd2YzNEowQzJRRC85YVU0WGJnYWtIQVdMSnp2NGM0NHdEQW9mSnVzTWYreHY2RUxLZA p5N01aU2FTcm1yNk9uNi9VV216VUJEVlEwd2pLWFZrR3BWK1hJTml4UlhYY1o3Y0JOeTMzR1J1Y 3dZTkJjdGdWCkZkMEdWQVNIWkJadXlyU0FqL0RrbFdoVXhBSE5KOWVGa0RoSWl3WXhXc2dYMENr VmtuRDh2YTdRS054U0J0Z2kKdmpNOStjUU1qL28rNjJOWGlaWUN0WHYzbHluS0hEMDc1UXNLOGF LUkkySUJkcXVTc0NlQmpPQktKMHRQamtJUgptMFBTd3ZtN3pmVlBlY0llSm85bk5LdG9aUGY0dE l6WHN0TyszNnl6WjJkWlNBWEhQRHRQOUlvMENvazcxZkxsClhmWERlTWFGelJhRUxNREdUd2RtR EJjR0ZsWnA2MittbENqSVlzVU9ndjhCbGJ3ajdFUXd2ZmdjRE5sbm5OaTkKbE9HY3NkdWdmOFZI bHFDVy9Mc21KY2JOeHQ0VmFGcUNWcmVYaDJITkxHWG9XM0cxZGJUWXpNVjZQMjY3K2tpawoxR00 3R0hOdWtLcWp0VVl4ZjRJVlpTY1BWU3U4b2x1SWZKdTR3MDR5RmRhaFhXbjRvVUlad0EwVDdySV BENFdwCk44aVhpMkdtUXZMZjNRK0dnRnpRcXFmZHNIYnkrRFpWb1ZaRnVTYnAyNnRCZ0loQ3NmS EdVZTcwNVBYdXVsVjUKczdBYytUTkhjd0UwbFB6SVR2Yy92T1I3dXFBWTFFUjdWM3pjT0RFSmNy RUxEeFc5b29GODYvTUVYcFlXNlF5WApnSlB6SU1WSlhNWjVGWWJvbGNjOVF4OGcwdGFpOGlVbzh FM3dNZ25nR085ZU9ZZkp4T2ZUT01QdEJidnhBbFZXCkJKTHBGQT09Cj05SFZ0Ci0tLS0tRU5EIF BHUCBNRVNTQUdFLS0tLS0K X-Developer-Key: i=manos.pitsidianakis@linaro.org; a=openpgp; fpr=7C721DF9DB3CC7182311C0BF68BC211D47B421E1 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::235; envelope-from=manos.pitsidianakis@linaro.org; helo=mail-lj1-x235.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @linaro.org) X-ZM-MESSAGEID: 1729778718289116600 This commit adds support for declaring migration state to device models in Rust. This is done through different but related parts: - The Device derive macro gains new attributes `vmstate_fields` and `vmstate_subsections`. This allows the device declaration to include the vmstate fields directly at the struct definition. - a new qemu_api module, `vmstate` was added. There a bunch of Rust macros declared there that are equivalent in spirit to the C macros declared in include/migration/vmstate.h. For example the Rust of equivalent of the C macro: VMSTATE_UINT32(field_name, struct_name) is: vmstate_uint32!(field_name, StructName) This breathtaking development now allows us to not have to define VMStateDescription ourselves but split the notion of migration to two parts: - A Migrateable trait that allows a type to define version_ids, name, priority, override methods like pre_load, post_load, pre_save etc. - Define the actual vmstate fields and subsections through the Device derive macro right there with the struct definition: ------------------------ >8 ------------------------ #[repr(C)] #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)] +#[device( + class_name_override =3D PL011Class, + vmstate_fields =3D vmstate_fields!{ + vmstate_unused!(u32::BITS as u64), + vmstate_uint32!(flags, PL011State), + vmstate_uint32!(line_control, PL011State), + vmstate_uint32!(receive_status_error_clear, PL011State), + vmstate_uint32!(control, PL011State), + vmstate_uint32!(dmacr, PL011State), + vmstate_uint32!(int_enabled, PL011State), + vmstate_uint32!(int_level, PL011State), + vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH), + vmstate_uint32!(ilpr, PL011State), + vmstate_uint32!(ibrd, PL011State), + vmstate_uint32!(fbrd, PL011State), + vmstate_uint32!(ifl, PL011State), + vmstate_int32!(read_pos, PL011State), + vmstate_int32!(read_count, PL011State), + vmstate_int32!(read_trigger, PL011State), + }, + vmstate_subsections =3D vmstate_subsections!{ + VMSTATE_PL011_CLOCK + } +)] /// PL011 Device Model in QEMU pub struct PL011State { pub parent_obj: SysBusDevice, ------------------------ >8 ------------------------ Signed-off-by: Manos Pitsidianakis --- rust/hw/char/pl011/src/device.rs | 92 +++++++- rust/qemu-api-macros/src/device.rs | 111 +++------- rust/qemu-api-macros/src/lib.rs | 1 + rust/qemu-api-macros/src/symbols.rs | 2 + rust/qemu-api-macros/src/vmstate.rs | 113 ++++++++++ rust/qemu-api/meson.build | 1 + rust/qemu-api/src/lib.rs | 3 + rust/qemu-api/src/vmstate.rs | 403 ++++++++++++++++++++++++++++++++= ++++ 8 files changed, 637 insertions(+), 89 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/devi= ce.rs index c469877b1ca70dd1a02e3a2449c65ad3e57c93ae..57dc37dadef631fbccfa3049a3d= 8701b4e62b5b3 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -10,6 +10,8 @@ use qemu_api::{ bindings::{self, *}, objects::*, + vmstate_clock, vmstate_fields, vmstate_int32, vmstate_subsections, vms= tate_uint32, + vmstate_uint32_array, vmstate_unused, }; =20 use crate::{ @@ -20,14 +22,74 @@ =20 static PL011_ID_ARM: [c_uchar; 8] =3D [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0,= 0x05, 0xb1]; =20 +/// Integer Baud Rate Divider, `UARTIBRD` +const IBRD_MASK: u32 =3D 0x3f; + +/// Fractional Baud Rate Divider, `UARTFBRD` +const FBRD_MASK: u32 =3D 0xffff; + const DATA_BREAK: u32 =3D 1 << 10; =20 /// QEMU sourced constant. pub const PL011_FIFO_DEPTH: usize =3D 16_usize; =20 +#[no_mangle] +extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool { + unsafe { + debug_assert!(!opaque.is_null()); + let state =3D NonNull::new_unchecked(opaque.cast::()); + state.as_ref().migrate_clock + } +} + +qemu_api::vmstate_description! { + /// Migration subsection for [`PL011State`] clock. + pub static VMSTATE_PL011_CLOCK: VMStateDescription =3D VMStateDescript= ion { + name: c"pl011/clock", + unmigratable: false, + early_setup: false, + version_id: 1, + minimum_version_id: 1, + priority: MigrationPriority::MIG_PRI_DEFAULT, + pre_load: None, + post_load: None, + pre_save: None, + post_save: None, + needed: Some(pl011_clock_needed), + dev_unplug_pending: None, + fields: vmstate_fields!{ + vmstate_clock!(clock, PL011State), + }, + subsections: ::core::ptr::null(), + }; +} + #[repr(C)] #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)] -#[device(class_name_override =3D PL011Class)] +#[device( + class_name_override =3D PL011Class, + vmstate_fields =3D vmstate_fields!{ + vmstate_unused!(u32::BITS as u64), + vmstate_uint32!(flags, PL011State), + vmstate_uint32!(line_control, PL011State), + vmstate_uint32!(receive_status_error_clear, PL011State), + vmstate_uint32!(control, PL011State), + vmstate_uint32!(dmacr, PL011State), + vmstate_uint32!(int_enabled, PL011State), + vmstate_uint32!(int_level, PL011State), + vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH), + vmstate_uint32!(ilpr, PL011State), + vmstate_uint32!(ibrd, PL011State), + vmstate_uint32!(fbrd, PL011State), + vmstate_uint32!(ifl, PL011State), + vmstate_int32!(read_pos, PL011State), + vmstate_int32!(read_count, PL011State), + vmstate_int32!(read_trigger, PL011State), + }, + vmstate_subsections =3D vmstate_subsections!{ + VMSTATE_PL011_CLOCK + } +)] /// PL011 Device Model in QEMU pub struct PL011State { pub parent_obj: SysBusDevice, @@ -165,7 +227,33 @@ fn reset(&mut self) { } } =20 -impl qemu_api::objects::Migrateable for PL011State {} +impl qemu_api::objects::Migrateable for PL011State { + const NAME: Option<&'static CStr> =3D Some(c"pl011"); + const UNMIGRATABLE: bool =3D false; + const VERSION_ID: c_int =3D 2; + const MINIMUM_VERSION_ID: c_int =3D 2; + + unsafe fn post_load(&mut self, _version_id: c_int) -> c_int { + /* Sanity-check input state */ + if self.read_pos >=3D self.read_fifo.len() || self.read_count > se= lf.read_fifo.len() { + return -1; + } + + if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > = 0 { + // Older versions of PL011 didn't ensure that the single + // character in the FIFO in FIFO-disabled mode is in + // element 0 of the array; convert to follow the current + // code's assumptions. + self.read_fifo[0] =3D self.read_fifo[self.read_pos]; + self.read_pos =3D 0; + } + + self.ibrd &=3D IBRD_MASK; + self.fbrd &=3D FBRD_MASK; + + 0 + } +} =20 #[used] pub static CLK_NAME: &CStr =3D c"clk"; diff --git a/rust/qemu-api-macros/src/device.rs b/rust/qemu-api-macros/src/= device.rs index 3b965576a065601cd5c97d5ab6a2501f96d16a61..a666c64087715b9dc0d9ebe33f2= b22d965381c64 100644 --- a/rust/qemu-api-macros/src/device.rs +++ b/rust/qemu-api-macros/src/device.rs @@ -10,11 +10,13 @@ }; use syn::{parse_macro_input, DeriveInput}; =20 -use crate::{symbols::*, utilities::*}; +use crate::{symbols::*, utilities::*, vmstate}; =20 #[derive(Debug, Default)] struct DeriveContainer { category: Option, + vmstate_fields: Option, + vmstate_subsections: Option, class_name: Option, class_name_override: Option, } @@ -27,6 +29,8 @@ fn parse(input: ParseStream) -> Result { assert_eq!(DEVICE, bracketed.parse::()?); let mut retval =3D Self { category: None, + vmstate_fields: None, + vmstate_subsections: None, class_name: None, class_name_override: None, }; @@ -54,6 +58,20 @@ fn parse(input: ParseStream) -> Result { let lit: syn::LitStr =3D content.parse()?; let path: syn::Path =3D lit.parse()?; retval.category =3D Some(path); + } else if value =3D=3D VMSTATE_FIELDS { + let _: syn::Token![=3D] =3D content.parse()?; + if retval.vmstate_fields.is_some() { + panic!("{} can only be used at most once", VMSTATE_FIE= LDS); + } + let expr: syn::Expr =3D content.parse()?; + retval.vmstate_fields =3D Some(expr); + } else if value =3D=3D VMSTATE_SUBSECTIONS { + let _: syn::Token![=3D] =3D content.parse()?; + if retval.vmstate_subsections.is_some() { + panic!("{} can only be used at most once", VMSTATE_SUB= SECTIONS); + } + let expr: syn::Expr =3D content.parse()?; + retval.vmstate_subsections =3D Some(expr); } else { panic!("unrecognized token `{}`", value); } @@ -272,7 +290,11 @@ pub struct #class_name { let class_base_init_fn =3D format_ident!("__{}_class_base_init_generat= ed", class_name); =20 let (vmsd, vmsd_impl) =3D { - let (i, vmsd) =3D make_vmstate(name); + let (i, vmsd) =3D vmstate::make_vmstate( + name, + derive_container.vmstate_fields, + derive_container.vmstate_subsections, + ); (quote! { &#i }, vmsd) }; let category =3D if let Some(category) =3D derive_container.category { @@ -346,88 +368,3 @@ unsafe impl ::qemu_api::objects::ClassImplUnsafe for #= class_name { #vmsd_impl } } - -fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStrea= m) { - let vmstate_description_ident =3D format_ident!("__VMSTATE_{}", name); - - let pre_load =3D format_ident!("__{}_pre_load_generated", name); - let post_load =3D format_ident!("__{}_post_load_generated", name); - let pre_save =3D format_ident!("__{}_pre_save_generated", name); - let post_save =3D format_ident!("__{}_post_save_generated", name); - let needed =3D format_ident!("__{}_needed_generated", name); - let dev_unplug_pending =3D format_ident!("__{}_dev_unplug_pending_gene= rated", name); - - let migrateable_fish =3D quote! {<#name as ::qemu_api::objects::Migrat= eable>}; - let vmstate_description =3D quote! { - #[used] - #[allow(non_upper_case_globals)] - pub static #vmstate_description_ident: ::qemu_api::bindings::VMSta= teDescription =3D ::qemu_api::bindings::VMStateDescription { - name: if let Some(name) =3D #migrateable_fish::NAME { - name.as_ptr() - } else { - <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INF= O.name - }, - unmigratable: #migrateable_fish::UNMIGRATABLE, - early_setup: #migrateable_fish::EARLY_SETUP, - version_id: #migrateable_fish::VERSION_ID, - minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID, - priority: #migrateable_fish::PRIORITY, - pre_load: Some(#pre_load), - post_load: Some(#post_load), - pre_save: Some(#pre_save), - post_save: Some(#post_save), - needed: Some(#needed), - dev_unplug_pending: Some(#dev_unplug_pending), - fields: ::core::ptr::null(), - subsections: ::core::ptr::null(), - }; - - #[no_mangle] - pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_voi= d) -> ::core::ffi::c_int { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::pre_load(instance.as_mut= ()) - } - } - #[no_mangle] - pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_vo= id, version_id: core::ffi::c_int) -> ::core::ffi::c_int { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::post_load(instance.as_mu= t(), version_id) - } - } - #[no_mangle] - pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_voi= d) -> ::core::ffi::c_int { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::pre_save(instance.as_mut= ()) - } - } - #[no_mangle] - pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_vo= id) -> ::core::ffi::c_int { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::post_save(instance.as_mu= t()) - } - } - #[no_mangle] - pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void)= -> bool { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::needed(instance.as_mut()) - } - } - #[no_mangle] - pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::= ffi::c_void) -> bool { - let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); - unsafe { - ::qemu_api::objects::Migrateable::dev_unplug_pending(insta= nce.as_mut()) - } - } - }; - - let expanded =3D quote! { - #vmstate_description - }; - (vmstate_description_ident, expanded) -} diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib= .rs index 7753a853fae72fc87e6dc642cf076c6d0c736345..7b5c0c044da879241b05bba75ed= cb17b498e5d5a 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -10,6 +10,7 @@ mod object; mod symbols; mod utilities; +mod vmstate; =20 #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { diff --git a/rust/qemu-api-macros/src/symbols.rs b/rust/qemu-api-macros/src= /symbols.rs index f73768d228ed2b4d478c18336db56cb11e70f012..79c242cf069d5de1dd0cd61b2a5= c7814564af47e 100644 --- a/rust/qemu-api-macros/src/symbols.rs +++ b/rust/qemu-api-macros/src/symbols.rs @@ -15,6 +15,8 @@ pub const CLASS_NAME_OVERRIDE: Symbol =3D Symbol("class_name_override"); pub const QDEV_PROP: Symbol =3D Symbol("qdev_prop"); pub const MIGRATEABLE: Symbol =3D Symbol("migrateable"); +pub const VMSTATE_FIELDS: Symbol =3D Symbol("vmstate_fields"); +pub const VMSTATE_SUBSECTIONS: Symbol =3D Symbol("vmstate_subsections"); pub const PROPERTIES: Symbol =3D Symbol("properties"); pub const PROPERTY: Symbol =3D Symbol("property"); =20 diff --git a/rust/qemu-api-macros/src/vmstate.rs b/rust/qemu-api-macros/src= /vmstate.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d72bf13b5acc861fac0814d749= 762ddb76824d5 --- /dev/null +++ b/rust/qemu-api-macros/src/vmstate.rs @@ -0,0 +1,113 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +use quote::{format_ident, quote}; + +pub fn make_vmstate( + name: &syn::Ident, + vmstate_fields: Option, + vmstate_subsections: Option, +) -> (syn::Ident, proc_macro2::TokenStream) { + let vmstate_description_ident =3D format_ident!("__VMSTATE_{}", name); + + let pre_load =3D format_ident!("__{}_pre_load_generated", name); + let post_load =3D format_ident!("__{}_post_load_generated", name); + let pre_save =3D format_ident!("__{}_pre_save_generated", name); + let post_save =3D format_ident!("__{}_post_save_generated", name); + let needed =3D format_ident!("__{}_needed_generated", name); + let dev_unplug_pending =3D format_ident!("__{}_dev_unplug_pending_gene= rated", name); + + let migrateable_fish =3D quote! {<#name as ::qemu_api::objects::Migrat= eable>}; + let vmstate_fields =3D if let Some(fields) =3D vmstate_fields { + quote! { + #fields + } + } else { + quote! { + ::core::ptr::null() + } + }; + let vmstate_subsections =3D if let Some(subsections) =3D vmstate_subse= ctions { + quote! { + #subsections + } + } else { + quote! { + ::core::ptr::null() + } + }; + + let vmstate_description =3D quote! { + #[used] + #[allow(non_upper_case_globals)] + pub static #vmstate_description_ident: ::qemu_api::bindings::VMSta= teDescription =3D ::qemu_api::bindings::VMStateDescription { + name: if let Some(name) =3D #migrateable_fish::NAME { + name.as_ptr() + } else { + <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INF= O.name + }, + unmigratable: #migrateable_fish::UNMIGRATABLE, + early_setup: #migrateable_fish::EARLY_SETUP, + version_id: #migrateable_fish::VERSION_ID, + minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID, + priority: #migrateable_fish::PRIORITY, + pre_load: Some(#pre_load), + post_load: Some(#post_load), + pre_save: Some(#pre_save), + post_save: Some(#post_save), + needed: Some(#needed), + dev_unplug_pending: Some(#dev_unplug_pending), + fields: #vmstate_fields, + subsections: #vmstate_subsections, + }; + + #[no_mangle] + pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_voi= d) -> ::core::ffi::c_int { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::pre_load(instance.as_mut= ()) + } + } + #[no_mangle] + pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_vo= id, version_id: core::ffi::c_int) -> ::core::ffi::c_int { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::post_load(instance.as_mu= t(), version_id) + } + } + #[no_mangle] + pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_voi= d) -> ::core::ffi::c_int { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::pre_save(instance.as_mut= ()) + } + } + #[no_mangle] + pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_vo= id) -> ::core::ffi::c_int { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::post_save(instance.as_mu= t()) + } + } + #[no_mangle] + pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void)= -> bool { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::needed(instance.as_mut()) + } + } + #[no_mangle] + pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::= ffi::c_void) -> bool { + let mut instance =3D NonNull::new(opaque.cast::<#name>()).expe= ct(concat!("Expected opaque to be a non-null pointer of type ", stringify!(= #name), "::Object")); + unsafe { + ::qemu_api::objects::Migrateable::dev_unplug_pending(insta= nce.as_mut()) + } + } + }; + + let expanded =3D quote! { + #vmstate_description + }; + (vmstate_description_ident, expanded) +} diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 0bd70b59afcc005251135802897954789b068e6e..11984abb878bef18be3c819f61d= a24ce1405ea59 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -4,6 +4,7 @@ _qemu_api_rs =3D static_library( [ 'src/lib.rs', 'src/objects.rs', + 'src/vmstate.rs', ], {'.' : bindings_rs}, ), diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index b94adc15288cdc62de7679988f549ebd80f895d7..d276adfb6622eee6e42494e089e= 1f20b0b5cdf08 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -26,8 +26,11 @@ unsafe impl Send for bindings::Property {} unsafe impl Sync for bindings::Property {} unsafe impl Sync for bindings::TypeInfo {} unsafe impl Sync for bindings::VMStateDescription {} +unsafe impl Sync for bindings::VMStateField {} +unsafe impl Sync for bindings::VMStateInfo {} =20 pub mod objects; +pub mod vmstate; =20 use std::alloc::{GlobalAlloc, Layout}; =20 diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs new file mode 100644 index 0000000000000000000000000000000000000000..4478febc9ac2768cca3e638ebae= 27b042edb1bf2 --- /dev/null +++ b/rust/qemu-api/src/vmstate.rs @@ -0,0 +1,403 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Helper macros to declare migration state for device models. +//! +//! Some macros are direct equivalents to the C macros declared in `includ= e/migration/vmstate.h` +//! while [`vmstate_description`], [`vmstate_subsections`] and [`vmstate_f= ields`] are meant to be +//! used when declaring a device model state struct with the [`Device`](qe= mu_api_macros::Device) +//! `Derive` macro. + +#[doc(alias =3D "VMSTATE_UNUSED_BUFFER")] +#[macro_export] +macro_rules! vmstate_unused_buffer { + ($field_exists_fn:expr, $version_id:expr, $size:expr) =3D> {{ + $crate::bindings::VMStateField { + name: c"unused".as_ptr(), + err_hint: ::core::ptr::null(), + offset: 0, + size: $size, + start: 0, + num: 0, + num_offset: 0, + size_offset: 0, + info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate= _info_unused_buffer) }, + flags: VMStateFlags::VMS_BUFFER, + vmsd: ::core::ptr::null(), + version_id: $version_id, + struct_version_id: 0, + field_exists: $field_exists_fn, + } + }}; +} + +#[doc(alias =3D "VMSTATE_UNUSED_V")] +#[macro_export] +macro_rules! vmstate_unused_v { + ($version_id:expr, $size:expr) =3D> {{ + $crate::vmstate_unused_buffer!(None, $version_id, $size) + }}; +} + +#[doc(alias =3D "VMSTATE_UNUSED")] +#[macro_export] +macro_rules! vmstate_unused { + ($size:expr) =3D> {{ + $crate::vmstate_unused_v!(0, $size) + }}; +} + +#[doc(alias =3D "VMSTATE_SINGLE_TEST")] +#[macro_export] +macro_rules! vmstate_single_test { + ($field_name:ident, $struct_name:ty, $field_exists_fn:expr, $version_i= d:expr, $info:block, $size:expr) =3D> {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), 0) + .as_bytes() + .as_ptr() as *const ::core::ffi::c_char, + err_hint: ::core::ptr::null(), + offset: ::core::mem::offset_of!($struct_name, $field_name) as = _, + size: $size, + start: 0, + num: 0, + num_offset: 0, + size_offset: 0, + info: $info, + flags: VMStateFlags::VMS_SINGLE, + vmsd: ::core::ptr::null(), + version_id: $version_id, + struct_version_id: 0, + field_exists: $field_exists_fn, + } + }}; +} + +#[doc(alias =3D "VMSTATE_SINGLE")] +#[macro_export] +macro_rules! vmstate_single { + ($field_name:ident, $struct_name:ty, $version_id:expr, $info:block, $s= ize:expr) =3D> {{ + $crate::vmstate_single_test!($field_name, $struct_name, None, $ver= sion_id, $info, $size) + }}; +} + +#[doc(alias =3D "VMSTATE_UINT32_V")] +#[macro_export] +macro_rules! vmstate_uint32_v { + ($field_name:ident, $struct_name:ty, $version_id:expr) =3D> {{ + $crate::vmstate_single!( + $field_name, + $struct_name, + $version_id, + { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_inf= o_uint32) } }, + u32::BITS as u64 + ) + }}; +} + +#[doc(alias =3D "VMSTATE_UINT32")] +#[macro_export] +macro_rules! vmstate_uint32 { + ($field_name:ident, $struct_name:ty) =3D> {{ + $crate::vmstate_uint32_v!($field_name, $struct_name, 0) + }}; +} + +#[doc(alias =3D "VMSTATE_INT32_V")] +#[macro_export] +macro_rules! vmstate_int32_v { + ($field_name:ident, $struct_name:ty, $version_id:expr) =3D> {{ + $crate::vmstate_single!( + $field_name, + $struct_name, + $version_id, + { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_inf= o_int32) } }, + i32::BITS as u64 + ) + }}; +} + +#[doc(alias =3D "VMSTATE_INT32")] +#[macro_export] +macro_rules! vmstate_int32 { + ($field_name:ident, $struct_name:ty) =3D> {{ + $crate::vmstate_int32_v!($field_name, $struct_name, 0) + }}; +} + +#[doc(alias =3D "VMSTATE_ARRAY")] +#[macro_export] +macro_rules! vmstate_array { + ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr, $= info:block, $size:expr) =3D> {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), 0) + .as_bytes() + .as_ptr() as *const ::core::ffi::c_char, + err_hint: ::core::ptr::null(), + offset: ::core::mem::offset_of!($struct_name, $field_name) as = _, + size: $size, + start: 0, + num: $length as _, + num_offset: 0, + size_offset: 0, + info: $info, + flags: VMStateFlags::VMS_ARRAY, + vmsd: ::core::ptr::null(), + version_id: $version_id, + struct_version_id: 0, + field_exists: None, + } + }}; +} + +#[doc(alias =3D "VMSTATE_UINT32_ARRAY_V")] +#[macro_export] +macro_rules! vmstate_uint32_array_v { + ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr) = =3D> {{ + $crate::vmstate_array!( + $field_name, + $struct_name, + $length, + $version_id, + { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_inf= o_uint32) } }, + u32::BITS as u64 + ) + }}; +} + +#[doc(alias =3D "VMSTATE_UINT32_ARRAY")] +#[macro_export] +macro_rules! vmstate_uint32_array { + ($field_name:ident, $struct_name:ty, $length:expr) =3D> {{ + $crate::vmstate_uint32_array_v!($field_name, $struct_name, $length= , 0) + }}; +} + +#[doc(alias =3D "VMSTATE_STRUCT_POINTER_V")] +#[macro_export] +macro_rules! vmstate_struct_pointer_v { + ($field_name:ident, $struct_name:ty, $version_id:expr, $vmsd:expr, $ty= pe:ty) =3D> {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), 0) + .as_bytes() + .as_ptr() as *const ::core::ffi::c_char, + err_hint: ::core::ptr::null(), + offset: ::core::mem::offset_of!($struct_name, $field_name) as = _, + size: ::core::mem::size_of::<*const $type>() as _, + start: 0, + num: 0, + num_offset: 0, + size_offset: 0, + info: ::core::ptr::null(), + flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags:= :VMS_POINTER.0), + vmsd: $vmsd, + version_id: $version_id, + struct_version_id: 0, + field_exists: None, + } + }}; +} + +#[doc(alias =3D "VMSTATE_ARRAY_OF_POINTER")] +#[macro_export] +macro_rules! vmstate_array_of_pointer { + ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $inf= o:expr, $type:ty) =3D> {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), 0) + .as_bytes() + .as_ptr() as *const ::core::ffi::c_char, + version_id: $version_id, + num: $num, + info: $info, + size: ::core::mem::size_of::<*const $type>() as _, + flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::= VMS_ARRAY_OF_POINTER.0), + offset: ::core::mem::offset_of!($struct_name, $field_name) as = _, + err_hint: ::core::ptr::null(), + start: 0, + num_offset: 0, + size_offset: 0, + vmsd: ::core::ptr::null(), + struct_version_id: 0, + field_exists: None, + } + }}; +} + +#[doc(alias =3D "VMSTATE_ARRAY_OF_POINTER_TO_STRUCT")] +#[macro_export] +macro_rules! vmstate_array_of_pointer_to_struct { + ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $vms= d:expr, $type:ty) =3D> {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), 0) + .as_bytes() + .as_ptr() as *const ::core::ffi::c_char, + version_id: $version_id, + num: $num, + vmsd: $vmsd, + size: ::core::mem::size_of::<*const $type>() as _, + flags: VMStateFlags( + VMStateFlags::VMS_ARRAY.0 + | VMStateFlags::VMS_STRUCT.0 + | VMStateFlags::VMS_ARRAY_OF_POINTER.0, + ), + offset: ::core::mem::offset_of!($struct_name, $field_name) as = _, + err_hint: ::core::ptr::null(), + start: 0, + num_offset: 0, + size_offset: 0, + vmsd: ::core::ptr::null(), + struct_version_id: 0, + field_exists: None, + } + }}; +} + +#[doc(alias =3D "VMSTATE_CLOCK_V")] +#[macro_export] +macro_rules! vmstate_clock_v { + ($field_name:ident, $struct_name:ty, $version_id:expr) =3D> {{ + $crate::vmstate_struct_pointer_v!( + $field_name, + $struct_name, + $version_id, + { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clo= ck) } }, + $crate::bindings::Clock + ) + }}; +} + +#[doc(alias =3D "VMSTATE_CLOCK")] +#[macro_export] +macro_rules! vmstate_clock { + ($field_name:ident, $struct_name:ty) =3D> {{ + $crate::vmstate_clock_v!($field_name, $struct_name, 0) + }}; +} + +#[doc(alias =3D "VMSTATE_ARRAY_CLOCK_V")] +#[macro_export] +macro_rules! vmstate_array_clock_v { + ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr) =3D>= {{ + $crate::vmstate_array_of_pointer_to_struct!( + $field_name, + $struct_name, + $num, + $version_id, + { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clo= ck) } }, + $crate::bindings::Clock + ) + }}; +} + +#[doc(alias =3D "VMSTATE_ARRAY_CLOCK")] +#[macro_export] +macro_rules! vmstate_array_clock { + ($field_name:ident, $struct_name:ty, $num:expr) =3D> {{ + $crate::vmstate_array_clock_v!($field_name, $struct_name, $name, 0) + }}; +} + +/// Helper macro to declare a list of +/// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and = return a +/// pointer to the array of values it created. +#[macro_export] +macro_rules! vmstate_fields { + ($($field:expr),*$(,)*) =3D> {{ + #[used] + static _FIELDS: &[$crate::bindings::VMStateField] =3D &[ + $($field),*, + $crate::bindings::VMStateField { + name: ::core::ptr::null(), + err_hint: ::core::ptr::null(), + offset: 0, + size: 0, + start: 0, + num: 0, + num_offset: 0, + size_offset: 0, + info: ::core::ptr::null(), + flags: VMStateFlags::VMS_END, + vmsd: ::core::ptr::null(), + version_id: 0, + struct_version_id: 0, + field_exists: None, + } + ]; + _FIELDS.as_ptr() + }} +} + +/// A transparent wrapper type for the `subsections` field of +/// [`VMStateDescription`](crate::bindings::VMStateDescription). +/// +/// This is necessary to be able to declare subsection descriptions as sta= tics, because the only +/// way to implement `Sync` for a foreign type (and `*const` pointers are = foreign types in Rust) is +/// to create a wrapper struct and `unsafe impl Sync` for it. +/// +/// This struct is used in the [`vmstate_subsections`] macro implementatio= n. +#[repr(transparent)] +pub struct VMStateSubsectionsWrapper(pub &'static [*const crate::bindings:= :VMStateDescription]); + +unsafe impl Sync for VMStateSubsectionsWrapper {} + +/// Helper macro to declare a list of subsections +/// ([`VMStateDescription`](`crate::bindings::VMStateDescription`)) into a= static and return a +/// pointer to the array of pointers it created. +#[macro_export] +macro_rules! vmstate_subsections { + ($($subsection:expr),*$(,)*) =3D> {{ + #[used] + static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = =3D $crate::vmstate::VMStateSubsectionsWrapper(&[ + $({ + #[used] + static _SUBSECTION: $crate::bindings::VMStateDescription = =3D $subsection; + ::core::ptr::addr_of!(_SUBSECTION) + }),*, + ::core::ptr::null() + ]); + _SUBSECTIONS.0.as_ptr() + }} +} + +/// Thin macro to declare a valid [`VMStateDescription`](`crate::bindings:= :VMStateDescription`) +/// static. +#[macro_export] +macro_rules! vmstate_description { + ($(#[$outer:meta])* + pub static $name:ident: VMStateDescription =3D VMStateDescription { + name: $vname:expr, + unmigratable: $um_val:expr, + early_setup: $early_setup:expr, + version_id: $version_id:expr, + minimum_version_id: $minimum_version_id:expr, + priority: $priority:expr, + pre_load: $pre_load_fn:expr, + post_load: $post_load_fn:expr, + pre_save: $pre_save_fn:expr, + post_save: $post_save_fn:expr, + needed: $needed_fn:expr, + dev_unplug_pending: $dev_unplug_pending_fn:expr, + fields: $fields:expr, + subsections: $subsections:expr$(,)* + }; + ) =3D> { + #[used] + $(#[$outer])* + pub static $name: $crate::bindings::VMStateDescription =3D $cr= ate::bindings::VMStateDescription { + name: ::core::ffi::CStr::as_ptr($vname), + unmigratable: $um_val, + early_setup: $early_setup, + version_id: $version_id, + minimum_version_id: $minimum_version_id, + priority: $priority, + pre_load: None, + post_load: None, + pre_save: None, + post_save: None, + needed: None, + dev_unplug_pending: None, + fields: $fields, + subsections: $subsections, + }; + } +} --=20 2.45.2