From bffbb430ee045c616dfe13ad4fd44823f18e6b21 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 23 Jun 2025 15:34:36 +0800 Subject: [PATCH 01/77] rust/qemu-api: Fix binding path in source directory The build.rs had supported placing bindings.inc.rs in rust/qemu-api/src, but this "not encouraged" feature is broken. Considering that manually copying bindings.inc.rs to the development directory is also useful, fix the bindings.inc.rs path generation to give this feature another chance. Fixes: commit 1ae4ca0463d7 ("rust: move rust.bindgen to qemu-api crate") Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250623073436.1833357-1-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/qemu-api/build.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs index 7849486c1b..29d0945625 100644 --- a/rust/qemu-api/build.rs +++ b/rust/qemu-api/build.rs @@ -9,12 +9,14 @@ use std::os::windows::fs::symlink_file; use std::{env, fs::remove_file, io::Result, path::Path}; fn main() -> Result<()> { - // Placing bindings.inc.rs in the source directory is supported - // but not documented or encouraged. - let path = env::var("MESON_BUILD_ROOT") - .unwrap_or_else(|_| format!("{}/src", env!("CARGO_MANIFEST_DIR"))); + let file = if let Ok(root) = env::var("MESON_BUILD_ROOT") { + format!("{root}/rust/qemu-api/bindings.inc.rs") + } else { + // Placing bindings.inc.rs in the source directory is supported + // but not documented or encouraged. + format!("{}/src/bindings.inc.rs", env!("CARGO_MANIFEST_DIR")) + }; - let file = format!("{path}/rust/qemu-api/bindings.inc.rs"); let file = Path::new(&file); if !Path::new(&file).exists() { panic!(concat!( From caa08d30f240c07f2b6fd08c6ffb9ae28d187f09 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 3 Jul 2025 16:33:43 +0300 Subject: [PATCH 02/77] rust/qemu-api-macros: use syn::Error directly Our MacroError type wraps syn::Error as a variant, and uses another variant for custom errors. Fortunately syn::Error can be used directly, avoiding extra code on our side, so change the proc macro crate to use it. Signed-off-by: Manos Pitsidianakis Link: https://lore.kernel.org/r/20250703-rust_macros-v1-1-b99f82febbbf@linaro.org Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 11 ++-- rust/qemu-api-macros/src/bits.rs | 58 ++++++++------------- rust/qemu-api-macros/src/lib.rs | 86 +++++++++++++++---------------- rust/qemu-api-macros/src/utils.rs | 26 ---------- 4 files changed, 70 insertions(+), 111 deletions(-) delete mode 100644 rust/qemu-api-macros/src/utils.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index dc8c44109e..b6737536c6 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -351,7 +351,7 @@ Writing procedural macros ''''''''''''''''''''''''' By conventions, procedural macros are split in two functions, one -returning ``Result`` with the body of +returning ``Result`` with the body of the procedural macro, and the second returning ``proc_macro::TokenStream`` which is the actual procedural macro. The former's name is the same as the latter with the ``_or_error`` suffix. The code for the latter is more @@ -361,18 +361,19 @@ from the type after ``as`` in the invocation of ``parse_macro_input!``:: #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_object_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } The ``qemu_api_macros`` crate has utility functions to examine a ``DeriveInput`` and perform common checks (e.g. looking for a struct -with named fields). These functions return ``Result<..., MacroError>`` +with named fields). These functions return ``Result<..., syn::Error>`` and can be used easily in the procedural macro function:: fn derive_object_or_error(input: DeriveInput) -> - Result + Result { is_c_repr(&input, "#[derive(Object)]")?; diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-api-macros/src/bits.rs index 5ba84757ee..a80a3b9fee 100644 --- a/rust/qemu-api-macros/src/bits.rs +++ b/rust/qemu-api-macros/src/bits.rs @@ -6,8 +6,7 @@ use proc_macro2::{ Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, }; - -use crate::utils::MacroError; +use syn::Error; pub struct BitsConstInternal { typ: TokenTree, @@ -36,27 +35,21 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator, out: &mut TokenStream, - ) -> Result, MacroError> { + ) -> Result, Error> { let next = match tok { TT::Group(ref g) => { if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { - return Err(MacroError::Message("expected parenthesis".into(), g.span())); + return Err(Error::new(g.span(), "expected parenthesis")); } let mut stream = g.stream().into_iter(); let Some(first_tok) = stream.next() else { - return Err(MacroError::Message( - "expected operand, found ')'".into(), - g.span(), - )); + return Err(Error::new(g.span(), "expected operand, found ')'")); }; let mut output = TokenStream::new(); // start from the lowest precedence let next = self.parse_or(first_tok, &mut stream, &mut output)?; if let Some(tok) = next { - return Err(MacroError::Message( - format!("unexpected token {tok}"), - tok.span(), - )); + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); } out.extend(Some(paren(output))); it.next() @@ -74,20 +67,17 @@ impl BitsConstInternal { } TT::Punct(ref p) => { if p.as_char() != '!' { - return Err(MacroError::Message("expected operand".into(), p.span())); + return Err(Error::new(p.span(), "expected operand")); } let Some(rhs_tok) = it.next() else { - return Err(MacroError::Message( - "expected operand at end of input".into(), - p.span(), - )); + return Err(Error::new(p.span(), "expected operand at end of input")); }; let next = self.parse_primary(rhs_tok, it, out)?; out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]); next } _ => { - return Err(MacroError::Message("unexpected literal".into(), tok.span())); + return Err(Error::new(tok.span(), "unexpected literal")); } }; Ok(next) @@ -99,7 +89,7 @@ impl BitsConstInternal { TokenTree, &mut dyn Iterator, &mut TokenStream, - ) -> Result, MacroError>, + ) -> Result, Error>, >( &self, tok: TokenTree, @@ -108,7 +98,7 @@ impl BitsConstInternal { ch: char, f: F, method: &'static str, - ) -> Result, MacroError> { + ) -> Result, Error> { let mut next = f(self, tok, it, out)?; while next.is_some() { let op = next.as_ref().unwrap(); @@ -118,10 +108,7 @@ impl BitsConstInternal { } let Some(rhs_tok) = it.next() else { - return Err(MacroError::Message( - "expected operand at end of input".into(), - p.span(), - )); + return Err(Error::new(p.span(), "expected operand at end of input")); }; let mut rhs = TokenStream::new(); next = f(self, rhs_tok, it, &mut rhs)?; @@ -136,7 +123,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator, out: &mut TokenStream, - ) -> Result, MacroError> { + ) -> Result, Error> { self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") } @@ -146,7 +133,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator, out: &mut TokenStream, - ) -> Result, MacroError> { + ) -> Result, Error> { self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") } @@ -156,7 +143,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator, out: &mut TokenStream, - ) -> Result, MacroError> { + ) -> Result, Error> { self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") } @@ -166,13 +153,13 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator, out: &mut TokenStream, - ) -> Result, MacroError> { + ) -> Result, Error> { self.parse_binop(tok, it, out, '|', Self::parse_xor, "union") } pub fn parse( it: &mut dyn Iterator, - ) -> Result { + ) -> Result { let mut pos = Span::call_site(); let mut typ = proc_macro2::TokenStream::new(); @@ -198,15 +185,15 @@ impl BitsConstInternal { }; let Some(tok) = next else { - return Err(MacroError::Message( - "expected expression, do not call this macro directly".into(), + return Err(Error::new( pos, + "expected expression, do not call this macro directly", )); }; let TT::Group(ref _group) = tok else { - return Err(MacroError::Message( - "expected parenthesis, do not call this macro directly".into(), + return Err(Error::new( tok.span(), + "expected parenthesis, do not call this macro directly", )); }; let mut out = TokenStream::new(); @@ -219,10 +206,7 @@ impl BitsConstInternal { // A parenthesized expression is a single production of the grammar, // so the input must have reached the last token. if let Some(tok) = next { - return Err(MacroError::Message( - format!("unexpected token {tok}"), - tok.span(), - )); + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); } Ok(out) } diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index c18bb4e036..2cb79c799a 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -6,83 +6,79 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, + DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, }; - -mod utils; -use utils::MacroError; - mod bits; use bits::BitsConstInternal; fn get_fields<'a>( input: &'a DeriveInput, msg: &str, -) -> Result<&'a Punctuated, MacroError> { +) -> Result<&'a Punctuated, Error> { let Data::Struct(ref s) = &input.data else { - return Err(MacroError::Message( - format!("Struct required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Struct required for {msg}"), )); }; let Fields::Named(ref fs) = &s.fields else { - return Err(MacroError::Message( - format!("Named fields required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Named fields required for {msg}"), )); }; Ok(&fs.named) } -fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { +fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, Error> { let Data::Struct(ref s) = &input.data else { - return Err(MacroError::Message( - format!("Struct required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Struct required for {msg}"), )); }; let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { - return Err(MacroError::Message( - format!("Tuple struct required for {msg}"), + return Err(Error::new( s.fields.span(), + format!("Tuple struct required for {msg}"), )); }; if unnamed.len() != 1 { - return Err(MacroError::Message( - format!("A single field is required for {msg}"), + return Err(Error::new( s.fields.span(), + format!("A single field is required for {msg}"), )); } Ok(&unnamed[0]) } -fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { +fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { let expected = parse_quote! { #[repr(C)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(MacroError::Message( - format!("#[repr(C)] required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(C)] required for {msg}"), )) } } -fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { +fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { let expected = parse_quote! { #[repr(transparent)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(MacroError::Message( - format!("#[repr(transparent)] required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(transparent)] required for {msg}"), )) } } -fn derive_object_or_error(input: DeriveInput) -> Result { +fn derive_object_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(Object)]")?; let name = &input.ident; @@ -103,12 +99,13 @@ fn derive_object_or_error(input: DeriveInput) -> Result TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_object_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } -fn derive_opaque_or_error(input: DeriveInput) -> Result { +fn derive_opaque_or_error(input: DeriveInput) -> Result { is_transparent_repr(&input, "#[derive(Wrapper)]")?; let name = &input.ident; @@ -149,13 +146,14 @@ fn derive_opaque_or_error(input: DeriveInput) -> Result TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_opaque_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_opaque_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[allow(non_snake_case)] -fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { +fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); if let Some(repr) = repr { let nested = repr.parse_args_with(Punctuated::::parse_terminated)?; @@ -170,23 +168,23 @@ fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { } } - Err(MacroError::Message( - format!("#[repr(u8/u16/u32/u64) required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(u8/u16/u32/u64) required for {msg}"), )) } -fn get_variants(input: &DeriveInput) -> Result<&Punctuated, MacroError> { +fn get_variants(input: &DeriveInput) -> Result<&Punctuated, Error> { let Data::Enum(ref e) = &input.data else { - return Err(MacroError::Message( - "Cannot derive TryInto for union or struct.".to_string(), + return Err(Error::new( input.ident.span(), + "Cannot derive TryInto for union or struct.", )); }; if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { - return Err(MacroError::Message( - "Cannot derive TryInto for enum with non-unit variants.".to_string(), + return Err(Error::new( v.fields.span(), + "Cannot derive TryInto for enum with non-unit variants.", )); } Ok(&e.variants) @@ -197,7 +195,7 @@ fn derive_tryinto_body( name: &Ident, variants: &Punctuated, repr: &Path, -) -> Result { +) -> Result { let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); Ok(quote! { @@ -210,7 +208,7 @@ fn derive_tryinto_body( } #[rustfmt::skip::macros(quote)] -fn derive_tryinto_or_error(input: DeriveInput) -> Result { +fn derive_tryinto_or_error(input: DeriveInput) -> Result { let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; let name = &input.ident; let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?; @@ -247,9 +245,10 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_tryinto_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_tryinto_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[proc_macro] @@ -257,6 +256,7 @@ pub fn bits_const_internal(ts: TokenStream) -> TokenStream { let ts = proc_macro2::TokenStream::from(ts); let mut it = ts.into_iter(); - let expanded = BitsConstInternal::parse(&mut it).unwrap_or_else(Into::into); - TokenStream::from(expanded) + BitsConstInternal::parse(&mut it) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } diff --git a/rust/qemu-api-macros/src/utils.rs b/rust/qemu-api-macros/src/utils.rs deleted file mode 100644 index 02c91aed7f..0000000000 --- a/rust/qemu-api-macros/src/utils.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Procedural macro utilities. -// Author(s): Paolo Bonzini -// SPDX-License-Identifier: GPL-2.0-or-later - -use proc_macro2::Span; -use quote::quote_spanned; - -pub enum MacroError { - Message(String, Span), - ParseError(syn::Error), -} - -impl From for MacroError { - fn from(err: syn::Error) -> Self { - MacroError::ParseError(err) - } -} - -impl From for proc_macro2::TokenStream { - fn from(err: MacroError) -> Self { - match err { - MacroError::Message(msg, span) => quote_spanned! { span => compile_error!(#msg); }, - MacroError::ParseError(err) => err.into_compile_error(), - } - } -} From dab63b8a710c2c489276d60d51e21e82b91a92f5 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 3 Jul 2025 17:20:22 +0300 Subject: [PATCH 03/77] rust/bindings: allow unnecessary_transmutes (1.88) This is a new lint introduced in Rust 1.88. It does not affect compilation when using a previous version or our MSRV, 1.77. But with 1.88 compilation fails because we deny all warnings: error: unnecessary transmute --> rust/qemu-api/libqemu_api.rlib.p/structured/bindings.inc.rs:729:18 | 729 | unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace this with: `u32::cast_signed(self._bitfield_1.get(0usize, 24u8) as u32)` | = note: `-D unnecessary-transmutes` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unnecessary_transmutes)]` Allow this lint, which even though it does not exist in previous versions, it works because we allow for `unknown_lints` in rust/Cargo.toml. Signed-off-by: Manos Pitsidianakis Link: https://lore.kernel.org/r/20250703-rust_bindings_allow_unnecessary_transmutes-v1-1-692ca210d331@linaro.org Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 057de4b646..3cdad0f0ec 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -6,6 +6,7 @@ non_camel_case_types, non_snake_case, non_upper_case_globals, + unnecessary_transmutes, unsafe_op_in_unsafe_fn, clippy::pedantic, clippy::restriction, From c3a08c8dcbe568d9e7f8a66d300a668bcb8673c0 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 4 Jul 2025 13:26:57 +0300 Subject: [PATCH 04/77] rust/qemu-api-macros: normalize TryInto output Remove extraneous `;` and add missing trailing comma to TryInto derive macro to match rustfmt style. We will add a test in the followup commit and we would like the inlined output in the test body to be properly formatted as well. No functional changes intended. Signed-off-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250704-rust_add_derive_macro_unit_tests-v1-1-ebd47fa7f78f@linaro.org Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 2cb79c799a..5bbf8c6127 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -199,7 +199,7 @@ fn derive_tryinto_body( let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); Ok(quote! { - #(const #discriminants: #repr = #name::#discriminants as #repr;)*; + #(const #discriminants: #repr = #name::#discriminants as #repr;)* match value { #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* _ => core::result::Result::Err(value), @@ -227,7 +227,7 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result x, - Err(_) => panic!(#errmsg) + Err(_) => panic!(#errmsg), } } } From a721d9a9f3dd7bb9d6ed81ea1a11a1157755741c Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 4 Jul 2025 13:26:58 +0300 Subject: [PATCH 05/77] rust/qemu-api-macros: add unit tests Add unit tests to check Derive macro output for expected error messages, or for expected correct codegen output. Signed-off-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250704-rust_add_derive_macro_unit_tests-v1-2-ebd47fa7f78f@linaro.org [Remove usage of MacroError. - Paolo] Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/meson.build | 3 + rust/qemu-api-macros/src/lib.rs | 3 + rust/qemu-api-macros/src/tests.rs | 137 ++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 rust/qemu-api-macros/src/tests.rs diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-api-macros/meson.build index 8610ce1c84..2152bcb99b 100644 --- a/rust/qemu-api-macros/meson.build +++ b/rust/qemu-api-macros/meson.build @@ -17,3 +17,6 @@ _qemu_api_macros_rs = rust.proc_macro( qemu_api_macros = declare_dependency( link_with: _qemu_api_macros_rs, ) + +rust.test('rust-qemu-api-macros-tests', _qemu_api_macros_rs, + suite: ['unit', 'rust']) diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 5bbf8c6127..b525d89c09 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -11,6 +11,9 @@ use syn::{ mod bits; use bits::BitsConstInternal; +#[cfg(test)] +mod tests; + fn get_fields<'a>( input: &'a DeriveInput, msg: &str, diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-api-macros/src/tests.rs new file mode 100644 index 0000000000..d6dcd62fcf --- /dev/null +++ b/rust/qemu-api-macros/src/tests.rs @@ -0,0 +1,137 @@ +// Copyright 2025, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +use quote::quote; + +use super::*; + +macro_rules! derive_compile_fail { + ($derive_fn:ident, $input:expr, $error_msg:expr) => {{ + let input: proc_macro2::TokenStream = $input; + let error_msg: &str = $error_msg; + let derive_fn: fn(input: syn::DeriveInput) -> Result = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input); + let err = result.unwrap_err().into_compile_error(); + assert_eq!( + err.to_string(), + quote! { ::core::compile_error! { #error_msg } }.to_string() + ); + }}; +} + +macro_rules! derive_compile { + ($derive_fn:ident, $input:expr, $($expected:tt)*) => {{ + let input: proc_macro2::TokenStream = $input; + let expected: proc_macro2::TokenStream = $($expected)*; + let derive_fn: fn(input: syn::DeriveInput) -> Result = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input).unwrap(); + assert_eq!(result.to_string(), expected.to_string()); + }}; +} + +#[test] +fn test_derive_object() { + derive_compile_fail!( + derive_object_or_error, + quote! { + #[derive(Object)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Object)]" + ); + derive_compile!( + derive_object_or_error, + quote! { + #[derive(Object)] + #[repr(C)] + struct Foo { + _unused: [u8; 0], + } + }, + quote! { + ::qemu_api::assert_field_type!( + Foo, + _unused, + ::qemu_api::qom::ParentField<::ParentType> + ); + ::qemu_api::module_init! { + MODULE_INIT_QOM => unsafe { + ::qemu_api::bindings::type_register_static(&::TYPE_INFO); + } + } + } + ); +} + +#[test] +fn test_derive_tryinto() { + derive_compile_fail!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]" + ); + derive_compile!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + #[repr(u8)] + enum Foo { + First = 0, + Second, + } + }, + quote! { + impl Foo { + #[allow(dead_code)] + pub const fn into_bits(self) -> u8 { + self as u8 + } + + #[allow(dead_code)] + pub const fn from_bits(value: u8) -> Self { + match ({ + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + }) { + Ok(x) => x, + Err(_) => panic!("invalid value for Foo"), + } + } + } + + impl core::convert::TryFrom for Foo { + type Error = u8; + + #[allow(ambiguous_associated_items)] + fn try_from(value: u8) -> Result { + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + } + } + } + ); +} From ed7f6da282e263403badb507b2532f51f8ed31cf Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Jun 2025 10:16:10 +0200 Subject: [PATCH 06/77] rust/qemu-api: log: implement io::Write This makes it possible to lock the log file; it also makes log_mask_ln! not allocate memory when logging a constant string. Reviewed-by: Zhao Liu Reviewed-by: Manos Pitsidianakis Signed-off-by: Paolo Bonzini --- include/qemu/log.h | 2 + rust/qemu-api/src/log.rs | 92 ++++++++++++++++++++++++++++++++++++---- util/log.c | 12 ++++++ 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/include/qemu/log.h b/include/qemu/log.h index 60da703e67..aae72985f0 100644 --- a/include/qemu/log.h +++ b/include/qemu/log.h @@ -84,6 +84,8 @@ typedef struct QEMULogItem { extern const QEMULogItem qemu_log_items[]; +ssize_t rust_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); + bool qemu_set_log(int log_flags, Error **errp); bool qemu_set_log_filename(const char *filename, Error **errp); bool qemu_set_log_filename_flags(const char *name, int flags, Error **errp); diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs index d6c3d6c1b6..a441b8c1f2 100644 --- a/rust/qemu-api/src/log.rs +++ b/rust/qemu-api/src/log.rs @@ -3,6 +3,13 @@ //! Bindings for QEMU's logging infrastructure +use std::{ + io::{self, Write}, + ptr::NonNull, +}; + +use crate::{bindings, errno}; + #[repr(u32)] /// Represents specific error categories within QEMU's logging system. /// @@ -11,11 +18,82 @@ pub enum Log { /// Log invalid access caused by the guest. /// Corresponds to `LOG_GUEST_ERROR` in the C implementation. - GuestError = crate::bindings::LOG_GUEST_ERROR, + GuestError = bindings::LOG_GUEST_ERROR, /// Log guest access of unimplemented functionality. /// Corresponds to `LOG_UNIMP` in the C implementation. - Unimp = crate::bindings::LOG_UNIMP, + Unimp = bindings::LOG_UNIMP, +} + +/// A RAII guard for QEMU's logging infrastructure. Creating the guard +/// locks the log file, and dropping it (letting it go out of scope) unlocks +/// the file. +/// +/// As long as the guard lives, it can be written to using [`std::io::Write`]. +/// +/// The locking is recursive, therefore owning a guard does not prevent +/// using [`log_mask_ln!()`](crate::log_mask_ln). +pub struct LogGuard(NonNull); + +impl LogGuard { + /// Return a RAII guard that writes to QEMU's logging infrastructure. + /// The log file is locked while the guard exists, ensuring that there + /// is no tearing of the messages. + /// + /// Return `None` if the log file is closed and could not be opened. + /// Do *not* use `unwrap()` on the result; failure can be handled simply + /// by not logging anything. + /// + /// # Examples + /// + /// ``` + /// # use qemu_api::log::LogGuard; + /// # use std::io::Write; + /// if let Some(mut log) = LogGuard::new() { + /// writeln!(log, "test"); + /// } + /// ``` + pub fn new() -> Option { + let f = unsafe { bindings::qemu_log_trylock() }.cast(); + NonNull::new(f).map(Self) + } + + /// Writes a formatted string into the log, returning any error encountered. + /// + /// This method is primarily used by the + /// [`log_mask_ln!()`](crate::log_mask_ln) macro, and it is rare for it + /// to be called explicitly. It is public because it is the only way to + /// examine the error, which `log_mask_ln!()` ignores + /// + /// Unlike `log_mask_ln!()`, it does *not* append a newline at the end. + pub fn log_fmt(args: std::fmt::Arguments) -> io::Result<()> { + if let Some(mut log) = Self::new() { + log.write_fmt(args)?; + } + Ok(()) + } +} + +impl Write for LogGuard { + fn write(&mut self, bytes: &[u8]) -> io::Result { + let ret = unsafe { + bindings::rust_fwrite(bytes.as_ptr().cast(), 1, bytes.len(), self.0.as_ptr()) + }; + errno::into_io_result(ret) + } + + fn flush(&mut self) -> io::Result<()> { + // Do nothing, dropping the guard takes care of flushing + Ok(()) + } +} + +impl Drop for LogGuard { + fn drop(&mut self) { + unsafe { + bindings::qemu_log_unlock(self.0.as_ptr()); + } + } } /// A macro to log messages conditionally based on a provided mask. @@ -24,6 +102,8 @@ pub enum Log { /// log level and, if so, formats and logs the message. It is the Rust /// counterpart of the `qemu_log_mask()` macro in the C implementation. /// +/// Errors from writing to the log are ignored. +/// /// # Parameters /// /// - `$mask`: A log level mask. This should be a variant of the `Log` enum. @@ -62,12 +142,8 @@ macro_rules! log_mask_ln { if unsafe { (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 } { - let formatted_string = format!("{}\n", format_args!($fmt $($args)*)); - let c_string = std::ffi::CString::new(formatted_string).unwrap(); - - unsafe { - ::qemu_api::bindings::qemu_log(c_string.as_ptr()); - } + _ = ::qemu_api::log::LogGuard::log_fmt( + format_args!("{}\n", format_args!($fmt $($args)*))); } }}; } diff --git a/util/log.c b/util/log.c index b87d399e4c..58d24de48a 100644 --- a/util/log.c +++ b/util/log.c @@ -558,3 +558,15 @@ void qemu_print_log_usage(FILE *f) fprintf(f, "\nUse \"-d trace:help\" to get a list of trace events.\n\n"); #endif } + +#ifdef CONFIG_HAVE_RUST +ssize_t rust_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + /* + * Same as fwrite, but return -errno because Rust libc does not provide + * portable access to errno. :( + */ + int ret = fwrite(ptr, size, nmemb, stream); + return ret < 0 ? -errno : 0; +} +#endif From cb2273edf5df57cb270bc19d7e92d8be5870c17a Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 5 Jul 2025 14:05:42 +0200 Subject: [PATCH 07/77] target/i386: move max_features to class max_features is always set to true for instances created by -cpu max or -cpu host; it's always false for other classes. Therefore it can be turned into a field in the X86CPUClass. Reviewed-by: Xiaoyao Li Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 7 ++++--- target/i386/cpu.h | 2 +- target/i386/hvf/hvf-cpu.c | 3 ++- target/i386/kvm/kvm-cpu.c | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 0d35e95430..4d4e523424 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6187,6 +6187,7 @@ static void max_x86_cpu_class_init(ObjectClass *oc, const void *data) xcc->ordering = 9; + xcc->max_features = true; xcc->model_description = "Enables all features supported by the accelerator in the current host"; @@ -6201,7 +6202,6 @@ static void max_x86_cpu_initfn(Object *obj) /* We can't fill the features array here because we don't know yet if * "migratable" is true or false. */ - cpu->max_features = true; object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); /* @@ -8282,6 +8282,7 @@ static void x86_cpu_enable_xsave_components(X86CPU *cpu) */ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) { + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); CPUX86State *env = &cpu->env; FeatureWord w; int i; @@ -8301,12 +8302,12 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) } } - /*TODO: Now cpu->max_features doesn't overwrite features + /* TODO: Now xcc->max_features doesn't overwrite features * set using QOM properties, and we can convert * plus_features & minus_features to global properties * inside x86_cpu_parse_featurestr() too. */ - if (cpu->max_features) { + if (xcc->max_features) { for (w = 0; w < FEATURE_WORDS; w++) { /* Override only features that weren't set explicitly * by the user. diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 51e10139df..be3ae6d546 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2196,7 +2196,6 @@ struct ArchCPU { bool expose_tcg; bool migratable; bool migrate_smi_count; - bool max_features; /* Enable all supported features automatically */ uint32_t apic_id; /* Enables publishing of TSC increment and Local APIC bus frequencies to @@ -2349,6 +2348,7 @@ struct X86CPUClass { */ const X86CPUModel *model; + bool max_features; /* Enable all supported features automatically */ bool host_cpuid_required; int ordering; bool migration_safe; diff --git a/target/i386/hvf/hvf-cpu.c b/target/i386/hvf/hvf-cpu.c index dfdda70126..2b991f2fc8 100644 --- a/target/i386/hvf/hvf-cpu.c +++ b/target/i386/hvf/hvf-cpu.c @@ -61,13 +61,14 @@ static void hvf_cpu_xsave_init(void) static void hvf_cpu_instance_init(CPUState *cs) { X86CPU *cpu = X86_CPU(cs); + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); host_cpu_instance_init(cpu); /* Special cases not set in the X86CPUDefinition structs: */ /* TODO: in-kernel irqchip for hvf */ - if (cpu->max_features) { + if (xcc->max_features) { hvf_cpu_max_instance_init(cpu); } diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 16bde4de01..0fb88a239d 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -41,6 +41,7 @@ static void kvm_set_guest_phys_bits(CPUState *cs) static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) { X86CPU *cpu = X86_CPU(cs); + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); CPUX86State *env = &cpu->env; bool ret; @@ -63,7 +64,7 @@ static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) * check/update ucode_rev, phys_bits, guest_phys_bits, mwait * cpu_common_realizefn() (via xcc->parent_realize) */ - if (cpu->max_features) { + if (xcc->max_features) { if (enable_cpu_pm) { if (kvm_has_waitpkg()) { env->features[FEAT_7_0_ECX] |= CPUID_7_0_ECX_WAITPKG; @@ -216,7 +217,7 @@ static void kvm_cpu_instance_init(CPUState *cs) x86_cpu_apply_props(cpu, kvm_default_props); } - if (cpu->max_features) { + if (xcc->max_features) { kvm_cpu_max_instance_init(cpu); } From d93972d88b0984ed0a2090493f8d62cc188976d2 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 5 Jul 2025 14:17:30 +0200 Subject: [PATCH 08/77] target/i386: nvmm, whpx: add accel/CPU class that sets host vendor NVMM and WHPX are virtualizers, and therefore they need to use (at least by default) the host vendor for the guest CPUID. Add a cpu_instance_init implementation to these accelerators. Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 3 ++- target/i386/meson.build | 2 ++ target/i386/nvmm/nvmm-all.c | 25 +++++++++++++++++++++++++ target/i386/whpx/whpx-all.c | 25 +++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 4d4e523424..8fb74b56dd 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -44,6 +44,7 @@ #include "hw/boards.h" #include "hw/i386/sgx-epc.h" #endif +#include "system/qtest.h" #include "tcg/tcg-cpu.h" #include "disas/capstone.h" @@ -1943,7 +1944,7 @@ uint32_t xsave_area_size(uint64_t mask, bool compacted) static inline bool accel_uses_host_cpuid(void) { - return kvm_enabled() || hvf_enabled(); + return !tcg_enabled() && !qtest_enabled(); } static inline uint64_t x86_cpu_xsave_xcr0_components(X86CPU *cpu) diff --git a/target/i386/meson.build b/target/i386/meson.build index c1aacea613..092af34e2d 100644 --- a/target/i386/meson.build +++ b/target/i386/meson.build @@ -11,6 +11,8 @@ i386_ss.add(when: 'CONFIG_SEV', if_true: files('host-cpu.c', 'confidential-guest # x86 cpu type i386_ss.add(when: 'CONFIG_KVM', if_true: files('host-cpu.c')) i386_ss.add(when: 'CONFIG_HVF', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_WHPX', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c')) i386_system_ss = ss.source_set() i386_system_ss.add(files( diff --git a/target/i386/nvmm/nvmm-all.c b/target/i386/nvmm/nvmm-all.c index b4a4d50e86..11c263004d 100644 --- a/target/i386/nvmm/nvmm-all.c +++ b/target/i386/nvmm/nvmm-all.c @@ -19,6 +19,8 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "qemu/queue.h" +#include "accel/accel-cpu-target.h" +#include "host-cpu.h" #include "migration/blocker.h" #include "strings.h" @@ -1207,10 +1209,33 @@ static const TypeInfo nvmm_accel_type = { .class_init = nvmm_accel_class_init, }; +static void nvmm_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); +} + +static void nvmm_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = nvmm_cpu_instance_init; +} + +static const TypeInfo nvmm_cpu_accel_type = { + .name = ACCEL_CPU_NAME("nvmm"), + + .parent = TYPE_ACCEL_CPU, + .class_init = nvmm_cpu_accel_class_init, + .abstract = true, +}; + static void nvmm_type_init(void) { type_register_static(&nvmm_accel_type); + type_register_static(&nvmm_cpu_accel_type); } type_init(nvmm_type_init); diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index faf56e1972..22ac609070 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -26,6 +26,8 @@ #include "qapi/qapi-types-common.h" #include "qapi/qapi-visit-common.h" #include "migration/blocker.h" +#include "host-cpu.h" +#include "accel/accel-cpu-target.h" #include #include "whpx-internal.h" @@ -2500,6 +2502,28 @@ static void whpx_set_kernel_irqchip(Object *obj, Visitor *v, } } +static void whpx_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); +} + +static void whpx_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = whpx_cpu_instance_init; +} + +static const TypeInfo whpx_cpu_accel_type = { + .name = ACCEL_CPU_NAME("whpx"), + + .parent = TYPE_ACCEL_CPU, + .class_init = whpx_cpu_accel_class_init, + .abstract = true, +}; + /* * Partition support */ @@ -2726,6 +2750,7 @@ static const TypeInfo whpx_accel_type = { static void whpx_type_init(void) { type_register_static(&whpx_accel_type); + type_register_static(&whpx_cpu_accel_type); } bool init_whp_dispatch(void) From 810fcc41fc572d90b3c05af3f06f451626ee6b10 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 10 Jul 2025 18:49:17 +0200 Subject: [PATCH 09/77] target/i386: allow reordering max_x86_cpu_initfn vs accel CPU init The PMU feature is only supported by KVM, so move it there. And since all accelerators other than TCG overwrite the vendor, set it in max_x86_cpu_initfn only if it has not been initialized by the superclass. This makes it possible to run max_x86_cpu_initfn after accelerator init. Reviewed-by: Xiaoyao Li Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 24 ++++++++++++------------ target/i386/kvm/kvm-cpu.c | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 8fb74b56dd..9c5cef2c7c 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6199,21 +6199,21 @@ static void max_x86_cpu_class_init(ObjectClass *oc, const void *data) static void max_x86_cpu_initfn(Object *obj) { X86CPU *cpu = X86_CPU(obj); - - /* We can't fill the features array here because we don't know yet if - * "migratable" is true or false. - */ - object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); + CPUX86State *env = &cpu->env; /* - * these defaults are used for TCG and all other accelerators - * besides KVM and HVF, which overwrite these values + * these defaults are used for TCG, other accelerators overwrite these + * values */ - object_property_set_str(OBJECT(cpu), "vendor", CPUID_VENDOR_AMD, - &error_abort); - object_property_set_str(OBJECT(cpu), "model-id", - "QEMU TCG CPU version " QEMU_HW_VERSION, - &error_abort); + if (!env->cpuid_vendor1) { + object_property_set_str(OBJECT(cpu), "vendor", CPUID_VENDOR_AMD, + &error_abort); + } + if (!env->cpuid_model[0]) { + object_property_set_str(OBJECT(cpu), "model-id", + "QEMU TCG CPU version " QEMU_HW_VERSION, + &error_abort); + } } static const TypeInfo max_x86_cpu_type_info = { diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 0fb88a239d..6fed353548 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -111,6 +111,8 @@ static void kvm_cpu_max_instance_init(X86CPU *cpu) host_cpu_max_instance_init(cpu); + object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); + if (lmce_supported()) { object_property_set_bool(OBJECT(cpu), "lmce", true, &error_abort); } From 5f158abef44c7e0945fc5f76715ef135a9bf9bd2 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 5 Jul 2025 14:30:52 +0200 Subject: [PATCH 10/77] target/i386: move accel_cpu_instance_init to .instance_init With the reordering of instance_post_init callbacks that is new in 10.1 accel_cpu_instance_init must execute in .instance_init as is already the case for RISC-V. Otherwise, for example, setting the vendor property is broken when using KVM or Hypervisor.framework, because KVM sets it *after* the user's value is set by DeviceState's intance_post_init callback. Reported-by: Like Xu Reported-by: Dongli Zhang Reviewed-by: Xiaoyao Li Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 9c5cef2c7c..44178bc523 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6202,8 +6202,8 @@ static void max_x86_cpu_initfn(Object *obj) CPUX86State *env = &cpu->env; /* - * these defaults are used for TCG, other accelerators overwrite these - * values + * these defaults are used for TCG, other accelerators have overwritten + * these values */ if (!env->cpuid_vendor1) { object_property_set_str(OBJECT(cpu), "vendor", CPUID_VENDOR_AMD, @@ -9038,8 +9038,6 @@ static void x86_cpu_post_initfn(Object *obj) } } - accel_cpu_instance_init(CPU(obj)); - #ifndef CONFIG_USER_ONLY if (current_machine && current_machine->cgs) { x86_confidential_guest_cpu_instance_init( @@ -9114,6 +9112,8 @@ static void x86_cpu_initfn(Object *obj) if (xcc->model) { x86_cpu_load_model(cpu, xcc->model); } + + accel_cpu_instance_init(CPU(obj)); } static int64_t x86_cpu_get_arch_id(CPUState *cs) From 29f1ba338baf60a9e455b6fdc37489ca1efe25aa Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 11 Jul 2025 09:43:52 +0200 Subject: [PATCH 11/77] target/i386: merge host_cpu_instance_init() and host_cpu_max_instance_init() Simplify the accelerators' cpu_instance_init callbacks by doing all host-cpu setup in a single function. Based-on: <20250711000603.438312-1-pbonzini@redhat.com> Cc: Xiaoyao Li Signed-off-by: Paolo Bonzini --- target/i386/host-cpu.c | 28 ++++++++++++++-------------- target/i386/hvf/hvf-cpu.c | 2 -- target/i386/kvm/kvm-cpu.c | 2 -- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/target/i386/host-cpu.c b/target/i386/host-cpu.c index 7512567298..3399edc1ad 100644 --- a/target/i386/host-cpu.c +++ b/target/i386/host-cpu.c @@ -132,27 +132,27 @@ void host_cpu_instance_init(X86CPU *cpu) { X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); - if (xcc->model) { - char vendor[CPUID_VENDOR_SZ + 1]; - - host_cpu_vendor_fms(vendor, NULL, NULL, NULL); - object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); - } -} - -void host_cpu_max_instance_init(X86CPU *cpu) -{ char vendor[CPUID_VENDOR_SZ + 1] = { 0 }; char model_id[CPUID_MODEL_ID_SZ + 1] = { 0 }; int family, model, stepping; + /* + * setting vendor applies to both max/host and builtin_x86_defs CPU. + * FIXME: this probably should warn or should be skipped if vendors do + * not match, because family numbers are incompatible between Intel and AMD. + */ + host_cpu_vendor_fms(vendor, &family, &model, &stepping); + object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); + + if (!xcc->max_features) { + return; + } + + host_cpu_fill_model_id(model_id); + /* Use max host physical address bits if -cpu max option is applied */ object_property_set_bool(OBJECT(cpu), "host-phys-bits", true, &error_abort); - host_cpu_vendor_fms(vendor, &family, &model, &stepping); - host_cpu_fill_model_id(model_id); - - object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); object_property_set_int(OBJECT(cpu), "family", family, &error_abort); object_property_set_int(OBJECT(cpu), "model", model, &error_abort); object_property_set_int(OBJECT(cpu), "stepping", stepping, diff --git a/target/i386/hvf/hvf-cpu.c b/target/i386/hvf/hvf-cpu.c index 2b991f2fc8..94ee096ecf 100644 --- a/target/i386/hvf/hvf-cpu.c +++ b/target/i386/hvf/hvf-cpu.c @@ -21,8 +21,6 @@ static void hvf_cpu_max_instance_init(X86CPU *cpu) { CPUX86State *env = &cpu->env; - host_cpu_max_instance_init(cpu); - env->cpuid_min_level = hvf_get_supported_cpuid(0x0, 0, R_EAX); env->cpuid_min_xlevel = diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 6fed353548..a99b876464 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -109,8 +109,6 @@ static void kvm_cpu_max_instance_init(X86CPU *cpu) CPUX86State *env = &cpu->env; KVMState *s = kvm_state; - host_cpu_max_instance_init(cpu); - object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); if (lmce_supported()) { From b57999bb258349fabe497c8ff70277b5e5b281e2 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Thu, 3 Jul 2025 10:40:17 +0800 Subject: [PATCH 12/77] i386/tdx: Remove enumeration of GetQuote in tdx_handle_get_tdvmcall_info() GHCI is finalized with the being one of the base VMCALLs, and not enuemrated via . Adjust tdx_handle_get_tdvmcall_info() to match with GHCI. Opportunistically fix the wrong indentation and explicitly set the ret to TDG_VP_VMCALL_SUCCESS (in case KVM leaves unexpected value). Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250703024021.3559286-2-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/tdx.c | 6 ++++-- target/i386/kvm/tdx.h | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index e809e4b2df..8c661c3ecf 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -1259,13 +1259,15 @@ out_free: void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) { if (run->tdx.get_tdvmcall_info.leaf != 1) { - return; + return; } - run->tdx.get_tdvmcall_info.r11 = TDG_VP_VMCALL_SUBFUNC_GET_QUOTE; + run->tdx.get_tdvmcall_info.r11 = 0; run->tdx.get_tdvmcall_info.r12 = 0; run->tdx.get_tdvmcall_info.r13 = 0; run->tdx.get_tdvmcall_info.r14 = 0; + + run->tdx.get_tdvmcall_info.ret = TDG_VP_VMCALL_SUCCESS; } static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, diff --git a/target/i386/kvm/tdx.h b/target/i386/kvm/tdx.h index 35a09c19c5..d439078a87 100644 --- a/target/i386/kvm/tdx.h +++ b/target/i386/kvm/tdx.h @@ -32,8 +32,6 @@ typedef struct TdxGuestClass { #define TDG_VP_VMCALL_GPA_INUSE 0x8000000000000001ULL #define TDG_VP_VMCALL_ALIGN_ERROR 0x8000000000000002ULL -#define TDG_VP_VMCALL_SUBFUNC_GET_QUOTE 0x0000000000000001ULL - enum TdxRamType { TDX_RAM_UNACCEPTED, TDX_RAM_ADDED, From 25c98a135001559be905a0399669e5cdb3b0a613 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Thu, 3 Jul 2025 10:40:18 +0800 Subject: [PATCH 13/77] update Linux headers to KVM tree master To fetch the update of TDX Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250703024021.3559286-3-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- linux-headers/asm-x86/kvm.h | 8 +++++++- linux-headers/linux/kvm.h | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/linux-headers/asm-x86/kvm.h b/linux-headers/asm-x86/kvm.h index cd275ae76d..f0c1a730d9 100644 --- a/linux-headers/asm-x86/kvm.h +++ b/linux-headers/asm-x86/kvm.h @@ -963,7 +963,13 @@ struct kvm_tdx_cmd { struct kvm_tdx_capabilities { __u64 supported_attrs; __u64 supported_xfam; - __u64 reserved[254]; + + __u64 kernel_tdvmcallinfo_1_r11; + __u64 user_tdvmcallinfo_1_r11; + __u64 kernel_tdvmcallinfo_1_r12; + __u64 user_tdvmcallinfo_1_r12; + + __u64 reserved[250]; /* Configurable CPUID bits for userspace */ struct kvm_cpuid2 cpuid; diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 0690743944..32c5885a3c 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -459,6 +459,10 @@ struct kvm_run { __u64 leaf; __u64 r11, r12, r13, r14; } get_tdvmcall_info; + struct { + __u64 ret; + __u64 vector; + } setup_event_notify; }; } tdx; /* Fix the size of the union. */ From 55be385b10658a2372f944fa41aaba016e1e8433 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Thu, 3 Jul 2025 10:40:19 +0800 Subject: [PATCH 14/77] i386/tdx: Set value of based on capabilities of both KVM and QEMU KVM reports the supported TDVMCALL sub leafs in TDX capabilities. one for kernel-supported TDVMCALLs (userspace can set those blindly) and one for user-supported TDVMCALLs (userspace can set those if it knows how to handle them) Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250703024021.3559286-4-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/tdx.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index 8c661c3ecf..10dfb80d22 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -1256,14 +1256,21 @@ out_free: g_free(task); } +#define SUPPORTED_TDVMCALLINFO_1_R11 (0) +#define SUPPORTED_TDVMCALLINFO_1_R12 (0) + void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) { if (run->tdx.get_tdvmcall_info.leaf != 1) { return; } - run->tdx.get_tdvmcall_info.r11 = 0; - run->tdx.get_tdvmcall_info.r12 = 0; + run->tdx.get_tdvmcall_info.r11 = (tdx_caps->user_tdvmcallinfo_1_r11 & + SUPPORTED_TDVMCALLINFO_1_R11) | + tdx_caps->kernel_tdvmcallinfo_1_r11; + run->tdx.get_tdvmcall_info.r12 = (tdx_caps->user_tdvmcallinfo_1_r12 & + SUPPORTED_TDVMCALLINFO_1_R12) | + tdx_caps->kernel_tdvmcallinfo_1_r12; run->tdx.get_tdvmcall_info.r13 = 0; run->tdx.get_tdvmcall_info.r14 = 0; From efa742b23eff2a799c196d756bd506fe74e96fdc Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Thu, 3 Jul 2025 10:40:20 +0800 Subject: [PATCH 15/77] i386/tdx: handle TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT Record the interrupt vector and the apic id of the vcpu that calls TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT. Inject the interrupt to TD guest to notify the completion of when notify interrupt vector is valid. Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250703024021.3559286-5-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/kvm.c | 3 +++ target/i386/kvm/tdx-stub.c | 4 ++++ target/i386/kvm/tdx.c | 48 +++++++++++++++++++++++++++++++++++++- target/i386/kvm/tdx.h | 7 ++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 234878c613..fc58a23b30 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -6182,6 +6182,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) case TDVMCALL_GET_TD_VM_CALL_INFO: tdx_handle_get_tdvmcall_info(cpu, run); break; + case TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT: + tdx_handle_setup_event_notify_interrupt(cpu, run); + break; } ret = 0; break; diff --git a/target/i386/kvm/tdx-stub.c b/target/i386/kvm/tdx-stub.c index 76fee49eff..1f0e108a69 100644 --- a/target/i386/kvm/tdx-stub.c +++ b/target/i386/kvm/tdx-stub.c @@ -26,3 +26,7 @@ void tdx_handle_get_quote(X86CPU *cpu, struct kvm_run *run) void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) { } + +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run) +{ +} diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index 10dfb80d22..fb31071dd8 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -28,10 +28,13 @@ #include "cpu.h" #include "cpu-internal.h" #include "host-cpu.h" +#include "hw/i386/apic_internal.h" +#include "hw/i386/apic-msidef.h" #include "hw/i386/e820_memory_layout.h" #include "hw/i386/tdvf.h" #include "hw/i386/x86.h" #include "hw/i386/tdvf-hob.h" +#include "hw/pci/msi.h" #include "kvm_i386.h" #include "tdx.h" #include "tdx-quote-generator.h" @@ -1123,6 +1126,28 @@ int tdx_parse_tdvf(void *flash_ptr, int size) return tdvf_parse_metadata(&tdx_guest->tdvf, flash_ptr, size); } +static void tdx_inject_interrupt(uint32_t apicid, uint32_t vector) +{ + int ret; + + if (vector < 32 || vector > 255) { + return; + } + + MSIMessage msg = { + .address = ((apicid & 0xff) << MSI_ADDR_DEST_ID_SHIFT) | + (((uint64_t)apicid & 0xffffff00) << 32), + .data = vector | (APIC_DM_FIXED << MSI_DATA_DELIVERY_MODE_SHIFT), + }; + + ret = kvm_irqchip_send_msi(kvm_state, msg); + if (ret < 0) { + /* In this case, no better way to tell it to guest. Log it. */ + error_report("TDX: injection interrupt %d failed, interrupt lost (%s).", + vector, strerror(-ret)); + } +} + static void tdx_get_quote_completion(TdxGenerateQuoteTask *task) { TdxGuest *tdx = task->opaque; @@ -1154,6 +1179,9 @@ static void tdx_get_quote_completion(TdxGenerateQuoteTask *task) error_report("TDX: get-quote: failed to update GetQuote header."); } + tdx_inject_interrupt(tdx_guest->event_notify_apicid, + tdx_guest->event_notify_vector); + g_free(task->send_data); g_free(task->receive_buf); g_free(task); @@ -1256,7 +1284,7 @@ out_free: g_free(task); } -#define SUPPORTED_TDVMCALLINFO_1_R11 (0) +#define SUPPORTED_TDVMCALLINFO_1_R11 (TDG_VP_VMCALL_SUBFUNC_SET_EVENT_NOTIFY_INTERRUPT) #define SUPPORTED_TDVMCALLINFO_1_R12 (0) void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) @@ -1277,6 +1305,21 @@ void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) run->tdx.get_tdvmcall_info.ret = TDG_VP_VMCALL_SUCCESS; } +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run) +{ + uint64_t vector = run->tdx.setup_event_notify.vector; + + if (vector >= 32 && vector < 256) { + qemu_mutex_lock(&tdx_guest->lock); + tdx_guest->event_notify_vector = vector; + tdx_guest->event_notify_apicid = cpu->apic_id; + qemu_mutex_unlock(&tdx_guest->lock); + run->tdx.setup_event_notify.ret = TDG_VP_VMCALL_SUCCESS; + } else { + run->tdx.setup_event_notify.ret = TDG_VP_VMCALL_INVALID_OPERAND; + } +} + static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, char *message, uint64_t gpa) { @@ -1477,6 +1520,9 @@ static void tdx_guest_init(Object *obj) NULL, NULL); qemu_mutex_init(&tdx->lock); + + tdx->event_notify_vector = -1; + tdx->event_notify_apicid = -1; } static void tdx_guest_finalize(Object *obj) diff --git a/target/i386/kvm/tdx.h b/target/i386/kvm/tdx.h index d439078a87..1c38faf983 100644 --- a/target/i386/kvm/tdx.h +++ b/target/i386/kvm/tdx.h @@ -25,6 +25,7 @@ typedef struct TdxGuestClass { #define TDVMCALL_GET_TD_VM_CALL_INFO 0x10000 #define TDVMCALL_GET_QUOTE 0x10002 +#define TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT 0x10004 #define TDG_VP_VMCALL_SUCCESS 0x0000000000000000ULL #define TDG_VP_VMCALL_RETRY 0x0000000000000001ULL @@ -32,6 +33,8 @@ typedef struct TdxGuestClass { #define TDG_VP_VMCALL_GPA_INUSE 0x8000000000000001ULL #define TDG_VP_VMCALL_ALIGN_ERROR 0x8000000000000002ULL +#define TDG_VP_VMCALL_SUBFUNC_SET_EVENT_NOTIFY_INTERRUPT BIT_ULL(1) + enum TdxRamType { TDX_RAM_UNACCEPTED, TDX_RAM_ADDED, @@ -64,6 +67,9 @@ typedef struct TdxGuest { /* GetQuote */ SocketAddress *qg_sock_addr; int num; + + uint32_t event_notify_vector; + uint32_t event_notify_apicid; } TdxGuest; #ifdef CONFIG_TDX @@ -78,5 +84,6 @@ int tdx_parse_tdvf(void *flash_ptr, int size); int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run); void tdx_handle_get_quote(X86CPU *cpu, struct kvm_run *run); void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run); +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run); #endif /* QEMU_I386_TDX_H */ From b28f6d5c16f19f8c56926c10929db29f913895ad Mon Sep 17 00:00:00 2001 From: Zhenzhong Duan Date: Wed, 9 Jul 2025 23:55:38 -0400 Subject: [PATCH 16/77] i386/tdx: Fix the report of gpa in QAPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gpa is defined in QAPI but never reported to monitor because has_gpa is never set to ture. Fix it by setting has_gpa to ture when TDX_REPORT_FATAL_ERROR_GPA_VALID is set in error_code. Fixes: 6e250463b08b ("i386/tdx: Wire TDX_REPORT_FATAL_ERROR with GuestPanic facility") Signed-off-by: Zhenzhong Duan Reviewed-by: Daniel P. BerrangĂ© Link: https://lore.kernel.org/r/20250710035538.303136-1-zhenzhong.duan@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/tdx.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index fb31071dd8..7d69d6d7b0 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -1321,7 +1321,8 @@ void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run) } static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, - char *message, uint64_t gpa) + char *message, bool has_gpa, + uint64_t gpa) { GuestPanicInformation *panic_info; @@ -1330,6 +1331,7 @@ static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, panic_info->u.tdx.error_code = (uint32_t) error_code; panic_info->u.tdx.message = message; panic_info->u.tdx.gpa = gpa; + panic_info->u.tdx.has_gpa = has_gpa; qemu_system_guest_panicked(panic_info); } @@ -1349,6 +1351,7 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) char *message = NULL; uint64_t *tmp; uint64_t gpa = -1ull; + bool has_gpa = false; if (error_code & 0xffff) { error_report("TDX: REPORT_FATAL_ERROR: invalid error code: 0x%"PRIx64, @@ -1381,9 +1384,10 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) if (error_code & TDX_REPORT_FATAL_ERROR_GPA_VALID) { gpa = run->system_event.data[R_R13]; + has_gpa = true; } - tdx_panicked_on_fatal_error(cpu, error_code, message, gpa); + tdx_panicked_on_fatal_error(cpu, error_code, message, has_gpa, gpa); return -1; } From 84fe49d94ac72d7fd226a65d2250c6294885561d Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:00:36 +0100 Subject: [PATCH 17/77] meson: Add optional dependency on IGVM library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IGVM library allows Independent Guest Virtual Machine files to be parsed and processed. IGVM files are used to configure guest memory layout, initial processor state and other configuration pertaining to secure virtual machines. This adds the --enable-igvm configure option, enabled by default, which attempts to locate and link against the IGVM library via pkgconfig and sets CONFIG_IGVM if found. The library is added to the system_ss target in backends/meson.build where the IGVM parsing will be performed by the ConfidentialGuestSupport object. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Daniel P. BerrangĂ© Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/45945a83a638c3f08e68c025f378e7b7f4f6d593.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/meson.build | 3 +++ meson.build | 8 ++++++++ meson_options.txt | 2 ++ scripts/meson-buildoptions.sh | 3 +++ 4 files changed, 16 insertions(+) diff --git a/backends/meson.build b/backends/meson.build index 9b88d22685..ac0fac7845 100644 --- a/backends/meson.build +++ b/backends/meson.build @@ -34,6 +34,9 @@ if have_vhost_user_crypto endif system_ss.add(when: gio, if_true: files('dbus-vmstate.c')) system_ss.add(when: 'CONFIG_SGX', if_true: files('hostmem-epc.c')) +if igvm.found() + system_ss.add(igvm) +endif system_ss.add(when: 'CONFIG_SPDM_SOCKET', if_true: files('spdm-socket.c')) diff --git a/meson.build b/meson.build index b5f74aa37a..200352c244 100644 --- a/meson.build +++ b/meson.build @@ -1424,6 +1424,12 @@ if host_os == 'linux' and (have_system or have_tools) method: 'pkg-config', required: get_option('libudev')) endif +igvm = not_found +if not get_option('igvm').auto() or have_system + igvm = dependency('igvm', version: '>= 0.3.0', + method: 'pkg-config', + required: get_option('igvm')) +endif mpathlibs = [libudev] mpathpersist = not_found @@ -2601,6 +2607,7 @@ config_host_data.set('CONFIG_CFI', get_option('cfi')) config_host_data.set('CONFIG_SELINUX', selinux.found()) config_host_data.set('CONFIG_XEN_BACKEND', xen.found()) config_host_data.set('CONFIG_LIBDW', libdw.found()) +config_host_data.set('CONFIG_IGVM', igvm.found()) if xen.found() # protect from xen.version() having less than three components xen_version = xen.version().split('.') + ['0', '0'] @@ -4965,6 +4972,7 @@ summary_info += {'seccomp support': seccomp} summary_info += {'GlusterFS support': glusterfs} summary_info += {'hv-balloon support': hv_balloon} summary_info += {'TPM support': have_tpm} +summary_info += {'IGVM support': igvm} summary_info += {'libssh support': libssh} summary_info += {'lzo support': lzo} summary_info += {'snappy support': snappy} diff --git a/meson_options.txt b/meson_options.txt index a442be2995..1e429311a2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -117,6 +117,8 @@ option('tpm', type : 'feature', value : 'auto', description: 'TPM support') option('valgrind', type : 'feature', value: 'auto', description: 'valgrind debug support for coroutine stacks') +option('igvm', type: 'feature', value: 'auto', + description: 'Independent Guest Virtual Machine (IGVM) file support') # Do not enable it by default even for Mingw32, because it doesn't # work on Wine. diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 73e0770f42..7851540445 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -130,6 +130,7 @@ meson_options_help() { printf "%s\n" ' hv-balloon hv-balloon driver (requires Glib 2.68+ GTree API)' printf "%s\n" ' hvf HVF acceleration support' printf "%s\n" ' iconv Font glyph conversion support' + printf "%s\n" ' igvm IGVM file support' printf "%s\n" ' jack JACK sound support' printf "%s\n" ' keyring Linux keyring support' printf "%s\n" ' kvm KVM acceleration support' @@ -346,6 +347,8 @@ _meson_option_parse() { --iasl=*) quote_sh "-Diasl=$2" ;; --enable-iconv) printf "%s" -Diconv=enabled ;; --disable-iconv) printf "%s" -Diconv=disabled ;; + --enable-igvm) printf "%s" -Digvm=enabled ;; + --disable-igvm) printf "%s" -Digvm=disabled ;; --includedir=*) quote_sh "-Dincludedir=$2" ;; --enable-install-blobs) printf "%s" -Dinstall_blobs=true ;; --disable-install-blobs) printf "%s" -Dinstall_blobs=false ;; From e7ed19507bc4f63162c578037db7d98178fbf54d Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:03:10 +0100 Subject: [PATCH 18/77] backends/confidential-guest-support: Add functions to support IGVM In preparation for supporting the processing of IGVM files to configure guests, this adds a set of functions to ConfidentialGuestSupport allowing configuration of secure virtual machines that can be implemented for each supported isolation platform type such as Intel TDX or AMD SEV-SNP. These functions will be called by IGVM processing code in subsequent patches. This commit provides a default implementation of the functions that either perform no action or generate an error when they are called. Targets that support ConfidentalGuestSupport should override these implementations. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/23e34a106da87427899f93178102e4a6ef50c966.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/confidential-guest-support.c | 31 ++++++++++ include/system/confidential-guest-support.h | 67 +++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/backends/confidential-guest-support.c b/backends/confidential-guest-support.c index 8ff7bfa857..c5bef1fbfa 100644 --- a/backends/confidential-guest-support.c +++ b/backends/confidential-guest-support.c @@ -14,15 +14,46 @@ #include "qemu/osdep.h" #include "system/confidential-guest-support.h" +#include "qapi/error.h" OBJECT_DEFINE_ABSTRACT_TYPE(ConfidentialGuestSupport, confidential_guest_support, CONFIDENTIAL_GUEST_SUPPORT, OBJECT) +static bool check_support(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary) +{ + /* Default: no support. */ + return false; +} + +static int set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp) +{ + error_setg(errp, + "Setting confidential guest state is not supported for this platform"); + return -1; +} + +static int get_mem_map_entry(int index, ConfidentialGuestMemoryMapEntry *entry, + Error **errp) +{ + error_setg( + errp, + "Obtaining the confidential guest memory map is not supported for this platform"); + return -1; +} + static void confidential_guest_support_class_init(ObjectClass *oc, const void *data) { + ConfidentialGuestSupportClass *cgsc = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); + cgsc->check_support = check_support; + cgsc->set_guest_state = set_guest_state; + cgsc->get_mem_map_entry = get_mem_map_entry; } static void confidential_guest_support_init(Object *obj) diff --git a/include/system/confidential-guest-support.h b/include/system/confidential-guest-support.h index ea46b50c56..79ecd21f42 100644 --- a/include/system/confidential-guest-support.h +++ b/include/system/confidential-guest-support.h @@ -19,6 +19,7 @@ #define QEMU_CONFIDENTIAL_GUEST_SUPPORT_H #include "qom/object.h" +#include "exec/hwaddr.h" #define TYPE_CONFIDENTIAL_GUEST_SUPPORT "confidential-guest-support" OBJECT_DECLARE_TYPE(ConfidentialGuestSupport, @@ -26,6 +27,36 @@ OBJECT_DECLARE_TYPE(ConfidentialGuestSupport, CONFIDENTIAL_GUEST_SUPPORT) +typedef enum ConfidentialGuestPlatformType { + CGS_PLATFORM_SEV, + CGS_PLATFORM_SEV_ES, + CGS_PLATFORM_SEV_SNP, +} ConfidentialGuestPlatformType; + +typedef enum ConfidentialGuestMemoryType { + CGS_MEM_RAM, + CGS_MEM_RESERVED, + CGS_MEM_ACPI, + CGS_MEM_NVS, + CGS_MEM_UNUSABLE, +} ConfidentialGuestMemoryType; + +typedef struct ConfidentialGuestMemoryMapEntry { + uint64_t gpa; + uint64_t size; + ConfidentialGuestMemoryType type; +} ConfidentialGuestMemoryMapEntry; + +typedef enum ConfidentialGuestPageType { + CGS_PAGE_TYPE_NORMAL, + CGS_PAGE_TYPE_VMSA, + CGS_PAGE_TYPE_ZERO, + CGS_PAGE_TYPE_UNMEASURED, + CGS_PAGE_TYPE_SECRETS, + CGS_PAGE_TYPE_CPUID, + CGS_PAGE_TYPE_REQUIRED_MEMORY, +} ConfidentialGuestPageType; + struct ConfidentialGuestSupport { Object parent; @@ -64,6 +95,42 @@ typedef struct ConfidentialGuestSupportClass { int (*kvm_init)(ConfidentialGuestSupport *cgs, Error **errp); int (*kvm_reset)(ConfidentialGuestSupport *cgs, Error **errp); + + /* + * Check to see if this confidential guest supports a particular + * platform or configuration. + * + * Return true if supported or false if not supported. + */ + bool (*check_support)(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary); + + /* + * Configure part of the state of a guest for a particular set of data, page + * type and gpa. This can be used for example to pre-populate and measure + * guest memory contents, define private ranges or set the initial CPU state + * for one or more CPUs. + * + * If memory_type is CGS_PAGE_TYPE_VMSA then ptr points to the initial CPU + * context for a virtual CPU. The format of the data depends on the type of + * confidential virtual machine. For example, for SEV-ES ptr will point to a + * vmcb_save_area structure that should be copied into guest memory at the + * address specified in gpa. The cpu_index parameter contains the index of + * the CPU the VMSA applies to. + */ + int (*set_guest_state)(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp); + + /* + * Iterate the system memory map, getting the entry with the given index + * that can be populated into guest memory. + * + * Returns 0 for ok, 1 if the index is out of range and -1 on error. + */ + int (*get_mem_map_entry)(int index, ConfidentialGuestMemoryMapEntry *entry, + Error **errp); } ConfidentialGuestSupportClass; static inline int confidential_guest_kvm_init(ConfidentialGuestSupport *cgs, From c1d466d267cf40093c9489075906b385459a195f Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:10:22 +0100 Subject: [PATCH 19/77] backends/igvm: Add IGVM loader and configuration Adds an IGVM loader to QEMU which processes a given IGVM file and applies the directives within the file to the current guest configuration. The IGVM loader can be used to configure both confidential and non-confidential guests. For confidential guests, the ConfidentialGuestSupport object for the system is used to encrypt memory, apply the initial CPU state and perform other confidential guest operations. The loader is configured via a new IgvmCfg QOM object which allows the user to provide a path to the IGVM file to process. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Stefano Garzarella Link: https://lore.kernel.org/r/ae3a07d8f514d93845a9c16bb155c847cb567b0d.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/igvm-cfg.c | 51 +++ backends/igvm.c | 807 ++++++++++++++++++++++++++++++++++++++ backends/igvm.h | 22 ++ backends/meson.build | 2 + include/system/igvm-cfg.h | 46 +++ qapi/qom.json | 17 + 6 files changed, 945 insertions(+) create mode 100644 backends/igvm-cfg.c create mode 100644 backends/igvm.c create mode 100644 backends/igvm.h create mode 100644 include/system/igvm-cfg.h diff --git a/backends/igvm-cfg.c b/backends/igvm-cfg.c new file mode 100644 index 0000000000..45df63e06c --- /dev/null +++ b/backends/igvm-cfg.c @@ -0,0 +1,51 @@ +/* + * QEMU IGVM interface + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "system/igvm-cfg.h" +#include "igvm.h" +#include "qom/object_interfaces.h" + +static char *get_igvm(Object *obj, Error **errp) +{ + IgvmCfg *igvm = IGVM_CFG(obj); + return g_strdup(igvm->filename); +} + +static void set_igvm(Object *obj, const char *value, Error **errp) +{ + IgvmCfg *igvm = IGVM_CFG(obj); + g_free(igvm->filename); + igvm->filename = g_strdup(value); +} + +OBJECT_DEFINE_TYPE_WITH_INTERFACES(IgvmCfg, igvm_cfg, IGVM_CFG, OBJECT, + { TYPE_USER_CREATABLE }, { NULL }) + +static void igvm_cfg_class_init(ObjectClass *oc, const void *data) +{ + IgvmCfgClass *igvmc = IGVM_CFG_CLASS(oc); + + object_class_property_add_str(oc, "file", get_igvm, set_igvm); + object_class_property_set_description(oc, "file", + "Set the IGVM filename to use"); + + igvmc->process = qigvm_process_file; +} + +static void igvm_cfg_init(Object *obj) +{ +} + +static void igvm_cfg_finalize(Object *obj) +{ +} diff --git a/backends/igvm.c b/backends/igvm.c new file mode 100644 index 0000000000..2a31021d44 --- /dev/null +++ b/backends/igvm.c @@ -0,0 +1,807 @@ +/* + * QEMU IGVM configuration backend for guests + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "igvm.h" +#include "qapi/error.h" +#include "system/memory.h" +#include "system/address-spaces.h" +#include "hw/core/cpu.h" + +#include +#include + +typedef struct QIgvmParameterData { + QTAILQ_ENTRY(QIgvmParameterData) next; + uint8_t *data; + uint32_t size; + uint32_t index; +} QIgvmParameterData; + +/* + * QIgvm contains the information required during processing + * of a single IGVM file. + */ +typedef struct QIgvm { + IgvmHandle file; + ConfidentialGuestSupport *cgs; + ConfidentialGuestSupportClass *cgsc; + uint32_t compatibility_mask; + unsigned current_header_index; + QTAILQ_HEAD(, QIgvmParameterData) parameter_data; + + /* These variables keep track of contiguous page regions */ + IGVM_VHS_PAGE_DATA region_prev_page_data; + uint64_t region_start; + unsigned region_start_index; + unsigned region_last_index; + unsigned region_page_count; +} QIgvm; + +static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_parameter_area(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_parameter_insert(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_environment_info(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_required_memory(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); + +struct QIGVMHandler { + uint32_t type; + uint32_t section; + int (*handler)(QIgvm *ctx, const uint8_t *header_data, Error **errp); +}; + +static struct QIGVMHandler handlers[] = { + { IGVM_VHT_PAGE_DATA, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_page_data }, + { IGVM_VHT_VP_CONTEXT, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_vp_context }, + { IGVM_VHT_PARAMETER_AREA, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_parameter_area }, + { IGVM_VHT_PARAMETER_INSERT, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_parameter_insert }, + { IGVM_VHT_MEMORY_MAP, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_memory_map }, + { IGVM_VHT_VP_COUNT_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_vp_count }, + { IGVM_VHT_ENVIRONMENT_INFO_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_environment_info }, + { IGVM_VHT_REQUIRED_MEMORY, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_required_memory }, +}; + +static int qigvm_handler(QIgvm *ctx, uint32_t type, Error **errp) +{ + size_t handler; + IgvmHandle header_handle; + const uint8_t *header_data; + int result; + + for (handler = 0; handler < G_N_ELEMENTS(handlers); handler++) { + if (handlers[handler].type != type) { + continue; + } + header_handle = igvm_get_header(ctx->file, handlers[handler].section, + ctx->current_header_index); + if (header_handle < 0) { + error_setg( + errp, + "IGVM file is invalid: Failed to read directive header (code: %d)", + (int)header_handle); + return -1; + } + header_data = igvm_get_buffer(ctx->file, header_handle) + + sizeof(IGVM_VHS_VARIABLE_HEADER); + result = handlers[handler].handler(ctx, header_data, errp); + igvm_free_buffer(ctx->file, header_handle); + return result; + } + error_setg(errp, + "IGVM: Unknown header type encountered when processing file: " + "(type 0x%X)", + type); + return -1; +} + +static void *qigvm_prepare_memory(QIgvm *ctx, uint64_t addr, uint64_t size, + int region_identifier, Error **errp) +{ + ERRP_GUARD(); + MemoryRegion *igvm_pages = NULL; + Int128 gpa_region_size; + MemoryRegionSection mrs = + memory_region_find(get_system_memory(), addr, size); + if (mrs.mr) { + if (!memory_region_is_ram(mrs.mr)) { + memory_region_unref(mrs.mr); + error_setg( + errp, + "Processing of IGVM file failed: Could not prepare memory " + "at address 0x%lX due to existing non-RAM region", + addr); + return NULL; + } + + gpa_region_size = int128_make64(size); + if (int128_lt(mrs.size, gpa_region_size)) { + memory_region_unref(mrs.mr); + error_setg( + errp, + "Processing of IGVM file failed: Could not prepare memory " + "at address 0x%lX: region size exceeded", + addr); + return NULL; + } + return qemu_map_ram_ptr(mrs.mr->ram_block, mrs.offset_within_region); + } else { + /* + * The region_identifier is the is the index of the IGVM directive that + * contains the page with the lowest GPA in the region. This will + * generate a unique region name. + */ + g_autofree char *region_name = + g_strdup_printf("igvm.%X", region_identifier); + igvm_pages = g_new0(MemoryRegion, 1); + if (ctx->cgs && ctx->cgs->require_guest_memfd) { + if (!memory_region_init_ram_guest_memfd(igvm_pages, NULL, + region_name, size, errp)) { + return NULL; + } + } else { + if (!memory_region_init_ram(igvm_pages, NULL, region_name, size, + errp)) { + return NULL; + } + } + memory_region_add_subregion(get_system_memory(), addr, igvm_pages); + return memory_region_get_ram_ptr(igvm_pages); + } +} + +static int qigvm_type_to_cgs_type(IgvmPageDataType memory_type, bool unmeasured, + bool zero) +{ + switch (memory_type) { + case IGVM_PAGE_DATA_TYPE_NORMAL: { + if (unmeasured) { + return CGS_PAGE_TYPE_UNMEASURED; + } else { + return zero ? CGS_PAGE_TYPE_ZERO : CGS_PAGE_TYPE_NORMAL; + } + } + case IGVM_PAGE_DATA_TYPE_SECRETS: + return CGS_PAGE_TYPE_SECRETS; + case IGVM_PAGE_DATA_TYPE_CPUID_DATA: + return CGS_PAGE_TYPE_CPUID; + case IGVM_PAGE_DATA_TYPE_CPUID_XF: + return CGS_PAGE_TYPE_CPUID; + default: + return -1; + } +} + +static bool qigvm_page_attrs_equal(IgvmHandle igvm, unsigned header_index, + const IGVM_VHS_PAGE_DATA *page_1, + const IGVM_VHS_PAGE_DATA *page_2) +{ + IgvmHandle data_handle1, data_handle2; + + /* + * If one page has data and the other doesn't then this results in different + * page types: NORMAL vs ZERO. + */ + data_handle1 = igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, + header_index - 1); + data_handle2 = + igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, header_index); + if ((data_handle1 == IGVMAPI_NO_DATA || + data_handle2 == IGVMAPI_NO_DATA) && + data_handle1 != data_handle2) { + return false; + } + return ((*(const uint32_t *)&page_1->flags == + *(const uint32_t *)&page_2->flags) && + (page_1->data_type == page_2->data_type) && + (page_1->compatibility_mask == page_2->compatibility_mask)); +} + +static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index, + uint64_t gpa_start, unsigned page_count, + const IgvmPageDataFlags *flags, + const IgvmPageDataType page_type, + Error **errp) +{ + uint8_t *region; + IgvmHandle data_handle; + const void *data; + uint32_t data_size; + unsigned page_index; + bool zero = true; + const uint64_t page_size = flags->is_2mb_page ? 0x200000 : 0x1000; + int result; + int cgs_page_type; + + region = qigvm_prepare_memory(ctx, gpa_start, page_count * page_size, + start_index, errp); + if (!region) { + return -1; + } + + for (page_index = 0; page_index < page_count; page_index++) { + data_handle = igvm_get_header_data( + ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, page_index + start_index); + if (data_handle == IGVMAPI_NO_DATA) { + /* No data indicates a zero page */ + memset(®ion[page_index * page_size], 0, page_size); + } else if (data_handle < 0) { + error_setg( + errp, + "IGVM file contains invalid page data for directive with " + "index %d", + page_index + start_index); + return -1; + } else { + zero = false; + data_size = igvm_get_buffer_size(ctx->file, data_handle); + if (data_size < page_size) { + memset(®ion[page_index * page_size], 0, page_size); + } else if (data_size > page_size) { + error_setg(errp, + "IGVM file contains page data with invalid size for " + "directive with index %d", + page_index + start_index); + return -1; + } + data = igvm_get_buffer(ctx->file, data_handle); + memcpy(®ion[page_index * page_size], data, data_size); + igvm_free_buffer(ctx->file, data_handle); + } + } + + /* + * If a confidential guest support object is provided then use it to set the + * guest state. + */ + if (ctx->cgs) { + cgs_page_type = + qigvm_type_to_cgs_type(page_type, flags->unmeasured, zero); + if (cgs_page_type < 0) { + error_setg(errp, + "Invalid page type in IGVM file. Directives: %d to %d, " + "page type: %d", + start_index, start_index + page_count, page_type); + return -1; + } + + result = ctx->cgsc->set_guest_state( + gpa_start, region, page_size * page_count, cgs_page_type, 0, errp); + if (result < 0) { + return result; + } + } + return 0; +} + +static int qigvm_process_mem_page(QIgvm *ctx, + const IGVM_VHS_PAGE_DATA *page_data, + Error **errp) +{ + if (page_data) { + if (ctx->region_page_count == 0) { + ctx->region_start = page_data->gpa; + ctx->region_start_index = ctx->current_header_index; + } else { + if (!qigvm_page_attrs_equal(ctx->file, ctx->current_header_index, + page_data, + &ctx->region_prev_page_data) || + ((ctx->region_prev_page_data.gpa + + (ctx->region_prev_page_data.flags.is_2mb_page ? 0x200000 : + 0x1000)) != + page_data->gpa) || + (ctx->region_last_index != (ctx->current_header_index - 1))) { + /* End of current region */ + if (qigvm_process_mem_region( + ctx, ctx->region_start_index, ctx->region_start, + ctx->region_page_count, + &ctx->region_prev_page_data.flags, + ctx->region_prev_page_data.data_type, errp) < 0) { + return -1; + } + ctx->region_page_count = 0; + ctx->region_start = page_data->gpa; + ctx->region_start_index = ctx->current_header_index; + } + } + memcpy(&ctx->region_prev_page_data, page_data, + sizeof(ctx->region_prev_page_data)); + ctx->region_last_index = ctx->current_header_index; + ctx->region_page_count++; + } else { + if (ctx->region_page_count > 0) { + if (qigvm_process_mem_region( + ctx, ctx->region_start_index, ctx->region_start, + ctx->region_page_count, &ctx->region_prev_page_data.flags, + ctx->region_prev_page_data.data_type, errp) < 0) { + return -1; + } + ctx->region_page_count = 0; + } + } + return 0; +} + +static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PAGE_DATA *page_data = + (const IGVM_VHS_PAGE_DATA *)header_data; + if (page_data->compatibility_mask & ctx->compatibility_mask) { + return qigvm_process_mem_page(ctx, page_data, errp); + } + return 0; +} + +static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_VP_CONTEXT *vp_context = + (const IGVM_VHS_VP_CONTEXT *)header_data; + IgvmHandle data_handle; + uint8_t *data; + int result; + + if (!(vp_context->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + /* + * A confidential guest support object must be provided for setting + * a VP context. + */ + if (!ctx->cgs) { + error_setg( + errp, + "A VP context is present in the IGVM file but is not supported " + "by the current system."); + return -1; + } + + data_handle = igvm_get_header_data(ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, + ctx->current_header_index); + if (data_handle < 0) { + error_setg(errp, "Invalid VP context in IGVM file. Error code: %X", + data_handle); + return -1; + } + + data = (uint8_t *)igvm_get_buffer(ctx->file, data_handle); + result = ctx->cgsc->set_guest_state( + vp_context->gpa, data, igvm_get_buffer_size(ctx->file, data_handle), + CGS_PAGE_TYPE_VMSA, vp_context->vp_index, errp); + igvm_free_buffer(ctx->file, data_handle); + if (result < 0) { + return result; + } + return 0; +} + +static int qigvm_directive_parameter_area(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER_AREA *param_area = + (const IGVM_VHS_PARAMETER_AREA *)header_data; + QIgvmParameterData *param_entry; + + param_entry = g_new0(QIgvmParameterData, 1); + param_entry->size = param_area->number_of_bytes; + param_entry->index = param_area->parameter_area_index; + param_entry->data = g_malloc0(param_entry->size); + + QTAILQ_INSERT_TAIL(&ctx->parameter_data, param_entry, next); + return 0; +} + +static int qigvm_directive_parameter_insert(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER_INSERT *param = + (const IGVM_VHS_PARAMETER_INSERT *)header_data; + QIgvmParameterData *param_entry; + int result; + void *region; + + if (!(param->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + region = qigvm_prepare_memory(ctx, param->gpa, param_entry->size, + ctx->current_header_index, errp); + if (!region) { + return -1; + } + memcpy(region, param_entry->data, param_entry->size); + g_free(param_entry->data); + param_entry->data = NULL; + + /* + * If a confidential guest support object is provided then use it to + * set the guest state. + */ + if (ctx->cgs) { + result = ctx->cgsc->set_guest_state(param->gpa, region, + param_entry->size, + CGS_PAGE_TYPE_UNMEASURED, 0, + errp); + if (result < 0) { + return -1; + } + } + } + } + return 0; +} + +static int qigvm_cmp_mm_entry(const void *a, const void *b) +{ + const IGVM_VHS_MEMORY_MAP_ENTRY *entry_a = + (const IGVM_VHS_MEMORY_MAP_ENTRY *)a; + const IGVM_VHS_MEMORY_MAP_ENTRY *entry_b = + (const IGVM_VHS_MEMORY_MAP_ENTRY *)b; + if (entry_a->starting_gpa_page_number < entry_b->starting_gpa_page_number) { + return -1; + } else if (entry_a->starting_gpa_page_number > + entry_b->starting_gpa_page_number) { + return 1; + } else { + return 0; + } +} + +static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + int max_entry_count; + int entry = 0; + IGVM_VHS_MEMORY_MAP_ENTRY *mm_entry; + ConfidentialGuestMemoryMapEntry cgmm_entry; + int retval = 0; + + if (!ctx->cgs) { + error_setg(errp, + "IGVM file contains a memory map but this is not supported " + "by the current system."); + return -1; + } + + /* Find the parameter area that should hold the memory map */ + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + max_entry_count = + param_entry->size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY); + mm_entry = (IGVM_VHS_MEMORY_MAP_ENTRY *)param_entry->data; + + retval = ctx->cgsc->get_mem_map_entry(entry, &cgmm_entry, errp); + while (retval == 0) { + if (entry > max_entry_count) { + error_setg( + errp, + "IGVM: guest memory map size exceeds parameter area defined in IGVM file"); + return -1; + } + mm_entry[entry].starting_gpa_page_number = cgmm_entry.gpa >> 12; + mm_entry[entry].number_of_pages = cgmm_entry.size >> 12; + + switch (cgmm_entry.type) { + case CGS_MEM_RAM: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_MEMORY; + break; + case CGS_MEM_RESERVED: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + case CGS_MEM_ACPI: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + case CGS_MEM_NVS: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PERSISTENT; + break; + case CGS_MEM_UNUSABLE: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + } + retval = + ctx->cgsc->get_mem_map_entry(++entry, &cgmm_entry, errp); + } + if (retval < 0) { + return retval; + } + /* The entries need to be sorted */ + qsort(mm_entry, entry, sizeof(IGVM_VHS_MEMORY_MAP_ENTRY), + qigvm_cmp_mm_entry); + + break; + } + } + return 0; +} + +static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + uint32_t *vp_count; + CPUState *cpu; + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + vp_count = (uint32_t *)(param_entry->data + param->byte_offset); + *vp_count = 0; + CPU_FOREACH(cpu) + { + (*vp_count)++; + } + break; + } + } + return 0; +} + +static int qigvm_directive_environment_info(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + IgvmEnvironmentInfo *environmental_state; + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + environmental_state = + (IgvmEnvironmentInfo *)(param_entry->data + param->byte_offset); + environmental_state->memory_is_shared = 1; + break; + } + } + return 0; +} + +static int qigvm_directive_required_memory(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_REQUIRED_MEMORY *mem = + (const IGVM_VHS_REQUIRED_MEMORY *)header_data; + uint8_t *region; + int result; + + if (!(mem->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + region = qigvm_prepare_memory(ctx, mem->gpa, mem->number_of_bytes, + ctx->current_header_index, errp); + if (!region) { + return -1; + } + if (ctx->cgs) { + result = + ctx->cgsc->set_guest_state(mem->gpa, region, mem->number_of_bytes, + CGS_PAGE_TYPE_REQUIRED_MEMORY, 0, errp); + if (result < 0) { + return result; + } + } + return 0; +} + +static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) +{ + int32_t header_count; + unsigned header_index; + IgvmHandle header_handle; + IGVM_VHS_SUPPORTED_PLATFORM *platform; + uint32_t compatibility_mask_sev = 0; + uint32_t compatibility_mask_sev_es = 0; + uint32_t compatibility_mask_sev_snp = 0; + uint32_t compatibility_mask = 0; + + header_count = igvm_header_count(ctx->file, IGVM_HEADER_SECTION_PLATFORM); + if (header_count < 0) { + error_setg(errp, + "Invalid platform header count in IGVM file. Error code: %X", + header_count); + return -1; + } + + for (header_index = 0; header_index < (unsigned)header_count; + header_index++) { + IgvmVariableHeaderType typ = igvm_get_header_type( + ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); + if (typ == IGVM_VHT_SUPPORTED_PLATFORM) { + header_handle = igvm_get_header( + ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); + if (header_handle < 0) { + error_setg(errp, + "Invalid platform header in IGVM file. " + "Index: %d, Error code: %X", + header_index, header_handle); + return -1; + } + platform = + (IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(ctx->file, + header_handle) + + sizeof( + IGVM_VHS_VARIABLE_HEADER)); + if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV_ES) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV_ES, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev_es = platform->compatibility_mask; + } + } else if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev = platform->compatibility_mask; + } + } else if ((platform->platform_type == + IGVM_PLATFORM_TYPE_SEV_SNP) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV_SNP, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev_snp = platform->compatibility_mask; + } + } else if (platform->platform_type == IGVM_PLATFORM_TYPE_NATIVE) { + compatibility_mask = platform->compatibility_mask; + } + igvm_free_buffer(ctx->file, header_handle); + } + } + /* Choose the strongest supported isolation technology */ + if (compatibility_mask_sev_snp != 0) { + ctx->compatibility_mask = compatibility_mask_sev_snp; + } else if (compatibility_mask_sev_es != 0) { + ctx->compatibility_mask = compatibility_mask_sev_es; + } else if (compatibility_mask_sev != 0) { + ctx->compatibility_mask = compatibility_mask_sev; + } else if (compatibility_mask != 0) { + ctx->compatibility_mask = compatibility_mask; + } else { + error_setg( + errp, + "IGVM file does not describe a compatible supported platform"); + return -1; + } + return 0; +} + +static IgvmHandle qigvm_file_init(char *filename, Error **errp) +{ + IgvmHandle igvm; + g_autofree uint8_t *buf = NULL; + unsigned long len; + g_autoptr(GError) gerr = NULL; + + if (!g_file_get_contents(filename, (gchar **)&buf, &len, &gerr)) { + error_setg(errp, "Unable to load %s: %s", filename, gerr->message); + return -1; + } + + igvm = igvm_new_from_binary(buf, len); + if (igvm < 0) { + error_setg(errp, "Unable to parse IGVM file %s: %d", filename, igvm); + return -1; + } + return igvm; +} + +int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, + Error **errp) +{ + int32_t header_count; + QIgvmParameterData *parameter; + int retval = -1; + QIgvm ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.file = qigvm_file_init(cfg->filename, errp); + if (ctx.file < 0) { + return -1; + } + + /* + * The ConfidentialGuestSupport object is optional and allows a confidential + * guest platform to perform extra processing, such as page measurement, on + * IGVM directives. + */ + ctx.cgs = cgs; + ctx.cgsc = cgs ? CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(cgs) : NULL; + + /* + * Check that the IGVM file provides configuration for the current + * platform + */ + if (qigvm_supported_platform_compat_mask(&ctx, errp) < 0) { + goto cleanup; + } + + header_count = igvm_header_count(ctx.file, IGVM_HEADER_SECTION_DIRECTIVE); + if (header_count <= 0) { + error_setg( + errp, "Invalid directive header count in IGVM file. Error code: %X", + header_count); + goto cleanup; + } + + QTAILQ_INIT(&ctx.parameter_data); + + for (ctx.current_header_index = 0; + ctx.current_header_index < (unsigned)header_count; + ctx.current_header_index++) { + IgvmVariableHeaderType type = igvm_get_header_type( + ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index); + if (qigvm_handler(&ctx, type, errp) < 0) { + goto cleanup_parameters; + } + } + + /* + * Contiguous pages of data with compatible flags are grouped together in + * order to reduce the number of memory regions we create. Make sure the + * last group is processed with this call. + */ + retval = qigvm_process_mem_page(&ctx, NULL, errp); + +cleanup_parameters: + QTAILQ_FOREACH(parameter, &ctx.parameter_data, next) + { + g_free(parameter->data); + parameter->data = NULL; + } + +cleanup: + igvm_free(ctx.file); + + return retval; +} diff --git a/backends/igvm.h b/backends/igvm.h new file mode 100644 index 0000000000..db02ea9165 --- /dev/null +++ b/backends/igvm.h @@ -0,0 +1,22 @@ +/* + * QEMU IGVM configuration backend for Confidential Guests + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef BACKENDS_IGVM_H +#define BACKENDS_IGVM_H + +#include "system/confidential-guest-support.h" +#include "system/igvm-cfg.h" +#include "qapi/error.h" + +int qigvm_process_file(IgvmCfg *igvm, ConfidentialGuestSupport *cgs, + Error **errp); + +#endif diff --git a/backends/meson.build b/backends/meson.build index ac0fac7845..60021f45d1 100644 --- a/backends/meson.build +++ b/backends/meson.build @@ -36,6 +36,8 @@ system_ss.add(when: gio, if_true: files('dbus-vmstate.c')) system_ss.add(when: 'CONFIG_SGX', if_true: files('hostmem-epc.c')) if igvm.found() system_ss.add(igvm) + system_ss.add(files('igvm-cfg.c'), igvm) + system_ss.add(files('igvm.c'), igvm) endif system_ss.add(when: 'CONFIG_SPDM_SOCKET', if_true: files('spdm-socket.c')) diff --git a/include/system/igvm-cfg.h b/include/system/igvm-cfg.h new file mode 100644 index 0000000000..321b3196f0 --- /dev/null +++ b/include/system/igvm-cfg.h @@ -0,0 +1,46 @@ +/* + * QEMU IGVM interface + * + * Copyright (C) 2024 SUSE + * + * Authors: + * Roy Hopkins + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_IGVM_CFG_H +#define QEMU_IGVM_CFG_H + +#include "qom/object.h" + +typedef struct IgvmCfg { + ObjectClass parent_class; + + /* + * filename: Filename that specifies a file that contains the configuration + * of the guest in Independent Guest Virtual Machine (IGVM) + * format. + */ + char *filename; +} IgvmCfg; + +typedef struct IgvmCfgClass { + ObjectClass parent_class; + + /* + * If an IGVM filename has been specified then process the IGVM file. + * Performs a no-op if no filename has been specified. + * + * Returns 0 for ok and -1 on error. + */ + int (*process)(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, + Error **errp); + +} IgvmCfgClass; + +#define TYPE_IGVM_CFG "igvm-cfg" + +OBJECT_DECLARE_TYPE(IgvmCfg, IgvmCfgClass, IGVM_CFG) + +#endif diff --git a/qapi/qom.json b/qapi/qom.json index b133b06447..bbdb56dced 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -932,6 +932,19 @@ 'data': { '*filename': 'str' }, 'if': 'CONFIG_POSIX' } +## +# @IgvmCfgProperties: +# +# Properties common to objects that handle IGVM files. +# +# @file: IGVM file to use to configure guest +# +# Since: 10.1 +## +{ 'struct': 'IgvmCfgProperties', + 'if': 'CONFIG_IGVM', + 'data': { 'file': 'str' } } + ## # @SevCommonProperties: # @@ -1142,6 +1155,8 @@ 'filter-redirector', 'filter-replay', 'filter-rewriter', + { 'name': 'igvm-cfg', + 'if': 'CONFIG_IGVM' }, 'input-barrier', { 'name': 'input-linux', 'if': 'CONFIG_LINUX' }, @@ -1218,6 +1233,8 @@ 'filter-redirector': 'FilterRedirectorProperties', 'filter-replay': 'NetfilterProperties', 'filter-rewriter': 'FilterRewriterProperties', + 'igvm-cfg': { 'type': 'IgvmCfgProperties', + 'if': 'CONFIG_IGVM' }, 'input-barrier': 'InputBarrierProperties', 'input-linux': { 'type': 'InputLinuxProperties', 'if': 'CONFIG_LINUX' }, From 28e5ef4a6574cbaf6a5706d582b5b463b7f8d162 Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:11:02 +0100 Subject: [PATCH 20/77] hw/i386: Add igvm-cfg object and processing for IGVM files An IGVM file contains configuration of guest state that should be applied during configuration of the guest, before the guest is started. This patch allows the user to add an igvm-cfg object to an X86 machine configuration that allows an IGVM file to be configured that will be applied to the guest before it is started. If an IGVM configuration is provided then the IGVM file is processed at the end of the board initialization, before the state transition to PHASE_MACHINE_INITIALIZED. Signed-off-by: Roy Hopkins Acked-by: Gerd Hoffman Reviewed-by: Michael S. Tsirkin Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/23bc66ae4504ba5cf2134826e055b25df3fc9cd9.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- hw/i386/pc.c | 12 ++++++++++++ hw/i386/pc_piix.c | 10 ++++++++++ hw/i386/pc_q35.c | 10 ++++++++++ include/hw/i386/x86.h | 3 +++ qemu-options.hx | 28 ++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index b211633575..432ab288a8 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1833,6 +1833,18 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) object_class_property_add_bool(oc, "fd-bootchk", pc_machine_get_fd_bootchk, pc_machine_set_fd_bootchk); + +#if defined(CONFIG_IGVM) + object_class_property_add_link(oc, "igvm-cfg", + TYPE_IGVM_CFG, + offsetof(X86MachineState, igvm), + object_property_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_class_property_set_description(oc, "igvm-cfg", + "Set IGVM configuration"); +#endif + + } static const TypeInfo pc_machine_info = { diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index ea7572e783..3184ea1b37 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -366,6 +366,16 @@ static void pc_init1(MachineState *machine, const char *pci_type) x86_nvdimm_acpi_dsmio, x86ms->fw_cfg, OBJECT(pcms)); } + +#if defined(CONFIG_IGVM) + /* Apply guest state from IGVM if supplied */ + if (x86ms->igvm) { + if (IGVM_CFG_GET_CLASS(x86ms->igvm) + ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) { + g_assert_not_reached(); + } + } +#endif } typedef enum PCSouthBridgeOption { diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 33211b1876..6990e1c669 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -325,6 +325,16 @@ static void pc_q35_init(MachineState *machine) x86_nvdimm_acpi_dsmio, x86ms->fw_cfg, OBJECT(pcms)); } + +#if defined(CONFIG_IGVM) + /* Apply guest state from IGVM if supplied */ + if (x86ms->igvm) { + if (IGVM_CFG_GET_CLASS(x86ms->igvm) + ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) { + g_assert_not_reached(); + } + } +#endif } #define DEFINE_Q35_MACHINE(major, minor) \ diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h index fc460b82f8..8755cad50a 100644 --- a/include/hw/i386/x86.h +++ b/include/hw/i386/x86.h @@ -25,6 +25,7 @@ #include "hw/intc/ioapic.h" #include "hw/isa/isa.h" #include "qom/object.h" +#include "system/igvm-cfg.h" struct X86MachineClass { MachineClass parent; @@ -92,6 +93,8 @@ struct X86MachineState { * which means no limitation on the guest's bus locks. */ uint64_t bus_lock_ratelimit; + + IgvmCfg *igvm; }; #define X86_MACHINE_SMM "smm" diff --git a/qemu-options.hx b/qemu-options.hx index 1f862b19a6..f4c05b388b 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -5992,6 +5992,34 @@ SRST -machine ...,memory-encryption=sev0 \\ ..... + ``-object igvm-cfg,file=file`` + Create an IGVM configuration object that defines the initial state + of the guest using a file in that conforms to the Independent Guest + Virtual Machine (IGVM) file format. + + This is currently only supported by ``-machine q35`` and + ``-machine pc``. + + The ``file`` parameter is used to specify the IGVM file to load. + When provided, the IGVM file is used to populate the initial + memory of the virtual machine and, depending on the platform, can + define the initial processor state, memory map and parameters. + + The IGVM file is expected to contain the firmware for the virtual + machine, therefore an ``igvm-cfg`` object cannot be provided along + with other ways of specifying firmware, such as the ``-bios`` + parameter on x86 machines. + + e.g to launch a machine providing the firmware in an IGVM file + + .. parsed-literal:: + + # |qemu_system_x86| \\ + ...... \\ + -object igvm-cfg,id=igvm0,file=bios.igvm \\ + -machine ...,igvm-cfg=igvm0 \\ + ..... + ``-object authz-simple,id=id,identity=string`` Create an authorization object that will control access to network services. From 170beda3cf4381e0b824ac96e06367391a669dca Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:15:34 +0100 Subject: [PATCH 21/77] i386/pc_sysfw: Ensure sysfw flash configuration does not conflict with IGVM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using an IGVM file the configuration of the system firmware is defined by IGVM directives contained in the file. In this case the user should not configure any pflash devices. This commit skips initialization of the ROM mode when pflash0 is not set then checks to ensure no pflash devices have been configured when using IGVM, exiting with an error message if this is not the case. Signed-off-by: Roy Hopkins Acked-by: Gerd Hoffman Reviewed-by: Daniel P. BerrangĂ© Reviewed-by: Michael S. Tsirkin Reviewed-by: Stefano Garzarella Reviewed-by: Pankaj Gupta Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/c6166cfe128933b04003a9288566b7affe170dfe.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- hw/i386/pc_sysfw.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/hw/i386/pc_sysfw.c b/hw/i386/pc_sysfw.c index 821396c16e..1a12b635ad 100644 --- a/hw/i386/pc_sysfw.c +++ b/hw/i386/pc_sysfw.c @@ -220,7 +220,13 @@ void pc_system_firmware_init(PCMachineState *pcms, BlockBackend *pflash_blk[ARRAY_SIZE(pcms->flash)]; if (!pcmc->pci_enabled) { - x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, true); + /* + * If an IGVM file is specified then the firmware must be provided + * in the IGVM file. + */ + if (!X86_MACHINE(pcms)->igvm) { + x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, true); + } return; } @@ -240,8 +246,13 @@ void pc_system_firmware_init(PCMachineState *pcms, } if (!pflash_blk[0]) { - /* Machine property pflash0 not set, use ROM mode */ - x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, false); + /* + * Machine property pflash0 not set, use ROM mode unless using IGVM, + * in which case the firmware must be provided by the IGVM file. + */ + if (!X86_MACHINE(pcms)->igvm) { + x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, false); + } } else { if (kvm_enabled() && !kvm_readonly_mem_enabled()) { /* @@ -257,6 +268,20 @@ void pc_system_firmware_init(PCMachineState *pcms, } pc_system_flash_cleanup_unused(pcms); + + /* + * The user should not have specified any pflash devices when using IGVM + * to configure the guest. + */ + if (X86_MACHINE(pcms)->igvm) { + for (i = 0; i < ARRAY_SIZE(pcms->flash); i++) { + if (pcms->flash[i]) { + error_report("pflash devices cannot be configured when " + "using IGVM"); + exit(1); + } + } + } } void x86_firmware_configure(hwaddr gpa, void *ptr, int size) From 224e807f90a008dac1e4ba52e1bebc93b00472dd Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:21:32 +0100 Subject: [PATCH 22/77] sev: Update launch_update_data functions to use Error handling The class function and implementations for updating launch data return a code in case of error. In some cases an error message is generated and in other cases, just the error return value is used. This small refactor adds an 'Error **errp' parameter to all functions which consistently set an error condition if a non-zero value is returned. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Pankaj Gupta Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/d59721f7b99cfc87aab71f8f551937e98e983615.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- target/i386/sev.c | 68 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index 1a12f0671c..a84f5f5d28 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -122,7 +122,8 @@ struct SevCommonStateClass { Error **errp); int (*launch_start)(SevCommonState *sev_common); void (*launch_finish)(SevCommonState *sev_common); - int (*launch_update_data)(SevCommonState *sev_common, hwaddr gpa, uint8_t *ptr, size_t len); + int (*launch_update_data)(SevCommonState *sev_common, hwaddr gpa, + uint8_t *ptr, size_t len, Error **errp); int (*kvm_init)(ConfidentialGuestSupport *cgs, Error **errp); }; @@ -970,9 +971,8 @@ sev_snp_adjust_cpuid_features(X86ConfidentialGuest *cg, uint32_t feature, uint32 return value; } -static int -sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, - uint8_t *addr, size_t len) +static int sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, + uint8_t *addr, size_t len, Error **errp) { int ret, fw_error; struct kvm_sev_launch_update_data update; @@ -987,8 +987,8 @@ sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, ret = sev_ioctl(sev_common->sev_fd, KVM_SEV_LAUNCH_UPDATE_DATA, &update, &fw_error); if (ret) { - error_report("%s: LAUNCH_UPDATE ret=%d fw_error=%d '%s'", - __func__, ret, fw_error, fw_error_to_str(fw_error)); + error_setg(errp, "%s: LAUNCH_UPDATE ret=%d fw_error=%d '%s'", __func__, + ret, fw_error, fw_error_to_str(fw_error)); } return ret; @@ -1116,8 +1116,8 @@ sev_launch_finish(SevCommonState *sev_common) migrate_add_blocker(&sev_mig_blocker, &error_fatal); } -static int -snp_launch_update_data(uint64_t gpa, void *hva, size_t len, int type) +static int snp_launch_update_data(uint64_t gpa, void *hva, size_t len, + int type, Error **errp) { SevLaunchUpdateData *data; @@ -1132,23 +1132,21 @@ snp_launch_update_data(uint64_t gpa, void *hva, size_t len, int type) return 0; } -static int -sev_snp_launch_update_data(SevCommonState *sev_common, hwaddr gpa, - uint8_t *ptr, size_t len) +static int sev_snp_launch_update_data(SevCommonState *sev_common, hwaddr gpa, + uint8_t *ptr, size_t len, Error **errp) { - int ret = snp_launch_update_data(gpa, ptr, len, - KVM_SEV_SNP_PAGE_TYPE_NORMAL); - return ret; + return snp_launch_update_data(gpa, ptr, len, + KVM_SEV_SNP_PAGE_TYPE_NORMAL, errp); } static int sev_snp_cpuid_info_fill(SnpCpuidInfo *snp_cpuid_info, - const KvmCpuidInfo *kvm_cpuid_info) + const KvmCpuidInfo *kvm_cpuid_info, Error **errp) { size_t i; if (kvm_cpuid_info->cpuid.nent > SNP_CPUID_FUNCTION_MAXCOUNT) { - error_report("SEV-SNP: CPUID entry count (%d) exceeds max (%d)", + error_setg(errp, "SEV-SNP: CPUID entry count (%d) exceeds max (%d)", kvm_cpuid_info->cpuid.nent, SNP_CPUID_FUNCTION_MAXCOUNT); return -1; } @@ -1190,8 +1188,8 @@ sev_snp_cpuid_info_fill(SnpCpuidInfo *snp_cpuid_info, return 0; } -static int -snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, size_t cpuid_len) +static int snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, + size_t cpuid_len, Error **errp) { KvmCpuidInfo kvm_cpuid_info = {0}; SnpCpuidInfo snp_cpuid_info; @@ -1208,26 +1206,25 @@ snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, size_t cpuid_len) } while (ret == -E2BIG); if (ret) { - error_report("SEV-SNP: unable to query CPUID values for CPU: '%s'", - strerror(-ret)); - return 1; + error_setg(errp, "SEV-SNP: unable to query CPUID values for CPU: '%s'", + strerror(-ret)); + return -1; } - ret = sev_snp_cpuid_info_fill(&snp_cpuid_info, &kvm_cpuid_info); - if (ret) { - error_report("SEV-SNP: failed to generate CPUID table information"); - return 1; + ret = sev_snp_cpuid_info_fill(&snp_cpuid_info, &kvm_cpuid_info, errp); + if (ret < 0) { + return -1; } memcpy(hva, &snp_cpuid_info, sizeof(snp_cpuid_info)); return snp_launch_update_data(cpuid_addr, hva, cpuid_len, - KVM_SEV_SNP_PAGE_TYPE_CPUID); + KVM_SEV_SNP_PAGE_TYPE_CPUID, errp); } -static int -snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, uint32_t addr, - void *hva, uint32_t len) +static int snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, + uint32_t addr, void *hva, + uint32_t len, Error **errp) { int type = KVM_SEV_SNP_PAGE_TYPE_ZERO; if (sev_snp->parent_obj.kernel_hashes) { @@ -1239,7 +1236,7 @@ snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, uint32_t addr, sizeof(*sev_snp->kernel_hashes_data)); type = KVM_SEV_SNP_PAGE_TYPE_NORMAL; } - return snp_launch_update_data(addr, hva, len, type); + return snp_launch_update_data(addr, hva, len, type, errp); } static int @@ -1277,12 +1274,14 @@ snp_populate_metadata_pages(SevSnpGuestState *sev_snp, } if (type == KVM_SEV_SNP_PAGE_TYPE_CPUID) { - ret = snp_launch_update_cpuid(desc->base, hva, desc->len); + ret = snp_launch_update_cpuid(desc->base, hva, desc->len, + &error_fatal); } else if (desc->type == SEV_DESC_TYPE_SNP_KERNEL_HASHES) { ret = snp_launch_update_kernel_hashes(sev_snp, desc->base, hva, - desc->len); + desc->len, &error_fatal); } else { - ret = snp_launch_update_data(desc->base, hva, desc->len, type); + ret = snp_launch_update_data(desc->base, hva, desc->len, type, + &error_fatal); } if (ret) { @@ -1615,9 +1614,8 @@ sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp) if (sev_check_state(sev_common, SEV_STATE_LAUNCH_UPDATE)) { int ret; - ret = klass->launch_update_data(sev_common, gpa, ptr, len); + ret = klass->launch_update_data(sev_common, gpa, ptr, len, errp); if (ret < 0) { - error_setg(errp, "SEV: Failed to encrypt pflash rom"); return ret; } } From b0e8986668426dbd2bb3eee4c6e14fe6262ca34e Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:31:58 +0100 Subject: [PATCH 23/77] target/i386: Allow setting of R_LDTR and R_TR with cpu_x86_load_seg_cache() The x86 segment registers are identified by the X86Seg enumeration which includes LDTR and TR as well as the normal segment registers. The function 'cpu_x86_load_seg_cache()' uses the enum to determine which segment to set. However, specifying R_LDTR or R_TR results in an out-of-bounds access of the segment array. Possibly by coincidence, the function does correctly set LDTR or TR in this case as the structures for these registers immediately follow the array which is accessed out of bounds. This patch adds correct handling for R_LDTR and R_TR in the function. Signed-off-by: Roy Hopkins Acked-by: Gerd Hoffman Reviewed-by: Michael S. Tsirkin Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/95c69253ea4f91107625872d5e3f0c586376771d.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- target/i386/cpu.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index be3ae6d546..9829824ac8 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2417,7 +2417,14 @@ static inline void cpu_x86_load_seg_cache(CPUX86State *env, SegmentCache *sc; unsigned int new_hflags; - sc = &env->segs[seg_reg]; + if (seg_reg == R_LDTR) { + sc = &env->ldt; + } else if (seg_reg == R_TR) { + sc = &env->tr; + } else { + sc = &env->segs[seg_reg]; + } + sc->selector = selector; sc->base = base; sc->limit = limit; From 4c7f0976b0d2afc892c10f6cc503e8b4607115e2 Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:31:59 +0100 Subject: [PATCH 24/77] i386/sev: Refactor setting of reset vector and initial CPU state When an SEV guest is started, the reset vector and state are extracted from metadata that is contained in the firmware volume. In preparation for using IGVM to setup the initial CPU state, the code has been refactored to populate vmcb_save_area for each CPU which is then applied during guest startup and CPU reset. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Stefano Garzarella Acked-by: Gerd Hoffman Reviewed-by: Pankaj Gupta Link: https://lore.kernel.org/r/d3c2debca496c4366a278b135f951908f3b9c341.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- target/i386/sev.c | 326 +++++++++++++++++++++++++++++++++++++++++----- target/i386/sev.h | 110 ++++++++++++++++ 2 files changed, 401 insertions(+), 35 deletions(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index a84f5f5d28..a13f91e615 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -50,6 +50,12 @@ OBJECT_DECLARE_TYPE(SevSnpGuestState, SevCommonStateClass, SEV_SNP_GUEST) /* hard code sha256 digest size */ #define HASH_SIZE 32 +/* Convert between SEV-ES VMSA and SegmentCache flags/attributes */ +#define FLAGS_VMSA_TO_SEGCACHE(flags) \ + ((((flags) & 0xff00) << 12) | (((flags) & 0xff) << 8)) +#define FLAGS_SEGCACHE_TO_VMSA(flags) \ + ((((flags) & 0xff00) >> 8) | (((flags) & 0xf00000) >> 12)) + typedef struct QEMU_PACKED SevHashTableEntry { QemuUUID guid; uint16_t len; @@ -89,6 +95,14 @@ typedef struct QEMU_PACKED SevHashTableDescriptor { uint32_t size; } SevHashTableDescriptor; +typedef struct SevLaunchVmsa { + QTAILQ_ENTRY(SevLaunchVmsa) next; + + uint16_t cpu_index; + uint64_t gpa; + struct sev_es_save_area vmsa; +} SevLaunchVmsa; + struct SevCommonState { X86ConfidentialGuest parent_obj; @@ -107,9 +121,7 @@ struct SevCommonState { int sev_fd; SevState state; - uint32_t reset_cs; - uint32_t reset_ip; - bool reset_data_valid; + QTAILQ_HEAD(, SevLaunchVmsa) launch_vmsa; }; struct SevCommonStateClass { @@ -364,6 +376,172 @@ static struct RAMBlockNotifier sev_ram_notifier = { .ram_block_removed = sev_ram_block_removed, }; +static void sev_apply_cpu_context(CPUState *cpu) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + X86CPU *x86; + CPUX86State *env; + struct SevLaunchVmsa *launch_vmsa; + + /* See if an initial VMSA has been provided for this CPU */ + QTAILQ_FOREACH(launch_vmsa, &sev_common->launch_vmsa, next) + { + if (cpu->cpu_index == launch_vmsa->cpu_index) { + x86 = X86_CPU(cpu); + env = &x86->env; + + /* + * Ideally we would provide the VMSA directly to kvm which would + * ensure that the resulting initial VMSA measurement which is + * calculated during KVM_SEV_LAUNCH_UPDATE_VMSA is calculated from + * exactly what we provide here. Currently this is not possible so + * we need to copy the parts of the VMSA structure that we currently + * support into the CPU state. + */ + cpu_load_efer(env, launch_vmsa->vmsa.efer); + cpu_x86_update_cr4(env, launch_vmsa->vmsa.cr4); + cpu_x86_update_cr0(env, launch_vmsa->vmsa.cr0); + cpu_x86_update_cr3(env, launch_vmsa->vmsa.cr3); + env->xcr0 = launch_vmsa->vmsa.xcr0; + env->pat = launch_vmsa->vmsa.g_pat; + + cpu_x86_load_seg_cache( + env, R_CS, launch_vmsa->vmsa.cs.selector, + launch_vmsa->vmsa.cs.base, launch_vmsa->vmsa.cs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.cs.attrib)); + cpu_x86_load_seg_cache( + env, R_DS, launch_vmsa->vmsa.ds.selector, + launch_vmsa->vmsa.ds.base, launch_vmsa->vmsa.ds.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ds.attrib)); + cpu_x86_load_seg_cache( + env, R_ES, launch_vmsa->vmsa.es.selector, + launch_vmsa->vmsa.es.base, launch_vmsa->vmsa.es.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.es.attrib)); + cpu_x86_load_seg_cache( + env, R_FS, launch_vmsa->vmsa.fs.selector, + launch_vmsa->vmsa.fs.base, launch_vmsa->vmsa.fs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.fs.attrib)); + cpu_x86_load_seg_cache( + env, R_GS, launch_vmsa->vmsa.gs.selector, + launch_vmsa->vmsa.gs.base, launch_vmsa->vmsa.gs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.gs.attrib)); + cpu_x86_load_seg_cache( + env, R_SS, launch_vmsa->vmsa.ss.selector, + launch_vmsa->vmsa.ss.base, launch_vmsa->vmsa.ss.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ss.attrib)); + + env->gdt.base = launch_vmsa->vmsa.gdtr.base; + env->gdt.limit = launch_vmsa->vmsa.gdtr.limit; + env->gdt.flags = + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.gdtr.attrib); + env->idt.base = launch_vmsa->vmsa.idtr.base; + env->idt.limit = launch_vmsa->vmsa.idtr.limit; + env->idt.flags = + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.idtr.attrib); + + cpu_x86_load_seg_cache( + env, R_LDTR, launch_vmsa->vmsa.ldtr.selector, + launch_vmsa->vmsa.ldtr.base, launch_vmsa->vmsa.ldtr.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ldtr.attrib)); + cpu_x86_load_seg_cache( + env, R_TR, launch_vmsa->vmsa.tr.selector, + launch_vmsa->vmsa.ldtr.base, launch_vmsa->vmsa.tr.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.tr.attrib)); + + env->dr[6] = launch_vmsa->vmsa.dr6; + env->dr[7] = launch_vmsa->vmsa.dr7; + + env->regs[R_EAX] = launch_vmsa->vmsa.rax; + env->regs[R_ECX] = launch_vmsa->vmsa.rcx; + env->regs[R_EDX] = launch_vmsa->vmsa.rdx; + env->regs[R_EBX] = launch_vmsa->vmsa.rbx; + env->regs[R_ESP] = launch_vmsa->vmsa.rsp; + env->regs[R_EBP] = launch_vmsa->vmsa.rbp; + env->regs[R_ESI] = launch_vmsa->vmsa.rsi; + env->regs[R_EDI] = launch_vmsa->vmsa.rdi; +#ifdef TARGET_X86_64 + env->regs[R_R8] = launch_vmsa->vmsa.r8; + env->regs[R_R9] = launch_vmsa->vmsa.r9; + env->regs[R_R10] = launch_vmsa->vmsa.r10; + env->regs[R_R11] = launch_vmsa->vmsa.r11; + env->regs[R_R12] = launch_vmsa->vmsa.r12; + env->regs[R_R13] = launch_vmsa->vmsa.r13; + env->regs[R_R14] = launch_vmsa->vmsa.r14; + env->regs[R_R15] = launch_vmsa->vmsa.r15; +#endif + env->eip = launch_vmsa->vmsa.rip; + env->eflags = launch_vmsa->vmsa.rflags; + + cpu_set_fpuc(env, launch_vmsa->vmsa.x87_fcw); + env->mxcsr = launch_vmsa->vmsa.mxcsr; + + break; + } + } +} + +static int sev_set_cpu_context(uint16_t cpu_index, const void *ctx, + uint32_t ctx_len, hwaddr gpa, Error **errp) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + SevLaunchVmsa *launch_vmsa; + CPUState *cpu; + bool exists = false; + + /* + * Setting the CPU context is only supported for SEV-ES and SEV-SNP. The + * context buffer will contain a sev_es_save_area from the Linux kernel + * which is defined by "Table B-4. VMSA Layout, State Save Area for SEV-ES" + * in the AMD64 APM, Volume 2. + */ + + if (!sev_es_enabled()) { + error_setg(errp, "SEV: unable to set CPU context: Not supported"); + return -1; + } + + if (ctx_len < sizeof(struct sev_es_save_area)) { + error_setg(errp, "SEV: unable to set CPU context: " + "Invalid context provided"); + return -1; + } + + cpu = qemu_get_cpu(cpu_index); + if (!cpu) { + error_setg(errp, "SEV: unable to set CPU context for out of bounds " + "CPU index %d", cpu_index); + return -1; + } + + /* + * If the context of this VP has already been set then replace it with the + * new context. + */ + QTAILQ_FOREACH(launch_vmsa, &sev_common->launch_vmsa, next) + { + if (cpu_index == launch_vmsa->cpu_index) { + launch_vmsa->gpa = gpa; + memcpy(&launch_vmsa->vmsa, ctx, sizeof(launch_vmsa->vmsa)); + exists = true; + break; + } + } + + if (!exists) { + /* New VP context */ + launch_vmsa = g_new0(SevLaunchVmsa, 1); + memcpy(&launch_vmsa->vmsa, ctx, sizeof(launch_vmsa->vmsa)); + launch_vmsa->cpu_index = cpu_index; + launch_vmsa->gpa = gpa; + QTAILQ_INSERT_TAIL(&sev_common->launch_vmsa, launch_vmsa, next); + } + + /* Synchronise the VMSA with the current CPU state */ + sev_apply_cpu_context(cpu); + + return 0; +} + bool sev_enabled(void) { @@ -998,6 +1176,16 @@ static int sev_launch_update_vmsa(SevGuestState *sev_guest) { int ret, fw_error; + CPUState *cpu; + + /* + * The initial CPU state is measured as part of KVM_SEV_LAUNCH_UPDATE_VMSA. + * Synchronise the CPU state to any provided launch VMSA structures. + */ + CPU_FOREACH(cpu) { + sev_apply_cpu_context(cpu); + } + ret = sev_ioctl(SEV_COMMON(sev_guest)->sev_fd, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL, &fw_error); @@ -1780,40 +1968,109 @@ sev_es_find_reset_vector(void *flash_ptr, uint64_t flash_size, return sev_es_parse_reset_block(info, addr); } + +static void seg_to_vmsa(const SegmentCache *cpu_seg, struct vmcb_seg *vmsa_seg) +{ + vmsa_seg->selector = cpu_seg->selector; + vmsa_seg->base = cpu_seg->base; + vmsa_seg->limit = cpu_seg->limit; + vmsa_seg->attrib = FLAGS_SEGCACHE_TO_VMSA(cpu_seg->flags); +} + +static void initialize_vmsa(const CPUState *cpu, struct sev_es_save_area *vmsa) +{ + const X86CPU *x86 = X86_CPU(cpu); + const CPUX86State *env = &x86->env; + + /* + * Initialize the SEV-ES save area from the current state of + * the CPU. The entire state does not need to be copied, only the state + * that is copied back to the CPUState in sev_apply_cpu_context. + */ + memset(vmsa, 0, sizeof(struct sev_es_save_area)); + vmsa->efer = env->efer; + vmsa->cr0 = env->cr[0]; + vmsa->cr3 = env->cr[3]; + vmsa->cr4 = env->cr[4]; + vmsa->xcr0 = env->xcr0; + vmsa->g_pat = env->pat; + + seg_to_vmsa(&env->segs[R_CS], &vmsa->cs); + seg_to_vmsa(&env->segs[R_DS], &vmsa->ds); + seg_to_vmsa(&env->segs[R_ES], &vmsa->es); + seg_to_vmsa(&env->segs[R_FS], &vmsa->fs); + seg_to_vmsa(&env->segs[R_GS], &vmsa->gs); + seg_to_vmsa(&env->segs[R_SS], &vmsa->ss); + + seg_to_vmsa(&env->gdt, &vmsa->gdtr); + seg_to_vmsa(&env->idt, &vmsa->idtr); + seg_to_vmsa(&env->ldt, &vmsa->ldtr); + seg_to_vmsa(&env->tr, &vmsa->tr); + + vmsa->dr6 = env->dr[6]; + vmsa->dr7 = env->dr[7]; + + vmsa->rax = env->regs[R_EAX]; + vmsa->rcx = env->regs[R_ECX]; + vmsa->rdx = env->regs[R_EDX]; + vmsa->rbx = env->regs[R_EBX]; + vmsa->rsp = env->regs[R_ESP]; + vmsa->rbp = env->regs[R_EBP]; + vmsa->rsi = env->regs[R_ESI]; + vmsa->rdi = env->regs[R_EDI]; + +#ifdef TARGET_X86_64 + vmsa->r8 = env->regs[R_R8]; + vmsa->r9 = env->regs[R_R9]; + vmsa->r10 = env->regs[R_R10]; + vmsa->r11 = env->regs[R_R11]; + vmsa->r12 = env->regs[R_R12]; + vmsa->r13 = env->regs[R_R13]; + vmsa->r14 = env->regs[R_R14]; + vmsa->r15 = env->regs[R_R15]; +#endif + + vmsa->rip = env->eip; + vmsa->rflags = env->eflags; +} + +static void sev_es_set_ap_context(uint32_t reset_addr) +{ + CPUState *cpu; + struct sev_es_save_area vmsa; + SegmentCache cs; + + cs.selector = 0xf000; + cs.base = reset_addr & 0xffff0000; + cs.limit = 0xffff; + cs.flags = DESC_P_MASK | DESC_S_MASK | DESC_CS_MASK | DESC_R_MASK | + DESC_A_MASK; + + CPU_FOREACH(cpu) { + if (cpu->cpu_index == 0) { + /* Do not update the BSP reset state */ + continue; + } + initialize_vmsa(cpu, &vmsa); + seg_to_vmsa(&cs, &vmsa.cs); + vmsa.rip = reset_addr & 0x0000ffff; + sev_set_cpu_context(cpu->cpu_index, &vmsa, + sizeof(struct sev_es_save_area), + 0, &error_fatal); + } +} + void sev_es_set_reset_vector(CPUState *cpu) { - X86CPU *x86; - CPUX86State *env; - ConfidentialGuestSupport *cgs = MACHINE(qdev_get_machine())->cgs; - SevCommonState *sev_common = SEV_COMMON( - object_dynamic_cast(OBJECT(cgs), TYPE_SEV_COMMON)); - - /* Only update if we have valid reset information */ - if (!sev_common || !sev_common->reset_data_valid) { - return; + if (sev_enabled()) { + sev_apply_cpu_context(cpu); } - - /* Do not update the BSP reset state */ - if (cpu->cpu_index == 0) { - return; - } - - x86 = X86_CPU(cpu); - env = &x86->env; - - cpu_x86_load_seg_cache(env, R_CS, 0xf000, sev_common->reset_cs, 0xffff, - DESC_P_MASK | DESC_S_MASK | DESC_CS_MASK | - DESC_R_MASK | DESC_A_MASK); - - env->eip = sev_common->reset_ip; } int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size) { - CPUState *cpu; uint32_t addr; int ret; - SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); if (!sev_es_enabled()) { return 0; @@ -1826,14 +2083,12 @@ int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size) return ret; } + /* + * The reset vector is saved into a CPU context for each AP but not for + * the BSP. This is applied during guest startup or when the CPU is reset. + */ if (addr) { - sev_common->reset_cs = addr & 0xffff0000; - sev_common->reset_ip = addr & 0x0000ffff; - sev_common->reset_data_valid = true; - - CPU_FOREACH(cpu) { - sev_es_set_reset_vector(cpu); - } + sev_es_set_ap_context(addr); } return 0; @@ -2068,6 +2323,7 @@ sev_common_instance_init(Object *obj) object_property_add_uint32_ptr(obj, "reduced-phys-bits", &sev_common->reduced_phys_bits, OBJ_PROP_FLAG_READWRITE); + QTAILQ_INIT(&sev_common->launch_vmsa); } /* sev guest info common to sev/sev-es/sev-snp */ diff --git a/target/i386/sev.h b/target/i386/sev.h index 373669eaac..38caa849f5 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -55,6 +55,116 @@ typedef struct SevKernelLoaderContext { size_t cmdline_size; } SevKernelLoaderContext; +/* Save area definition for SEV-ES and SEV-SNP guests */ +struct QEMU_PACKED sev_es_save_area { + struct vmcb_seg es; + struct vmcb_seg cs; + struct vmcb_seg ss; + struct vmcb_seg ds; + struct vmcb_seg fs; + struct vmcb_seg gs; + struct vmcb_seg gdtr; + struct vmcb_seg ldtr; + struct vmcb_seg idtr; + struct vmcb_seg tr; + uint64_t vmpl0_ssp; + uint64_t vmpl1_ssp; + uint64_t vmpl2_ssp; + uint64_t vmpl3_ssp; + uint64_t u_cet; + uint8_t reserved_0xc8[2]; + uint8_t vmpl; + uint8_t cpl; + uint8_t reserved_0xcc[4]; + uint64_t efer; + uint8_t reserved_0xd8[104]; + uint64_t xss; + uint64_t cr4; + uint64_t cr3; + uint64_t cr0; + uint64_t dr7; + uint64_t dr6; + uint64_t rflags; + uint64_t rip; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr0_addr_mask; + uint64_t dr1_addr_mask; + uint64_t dr2_addr_mask; + uint64_t dr3_addr_mask; + uint8_t reserved_0x1c0[24]; + uint64_t rsp; + uint64_t s_cet; + uint64_t ssp; + uint64_t isst_addr; + uint64_t rax; + uint64_t star; + uint64_t lstar; + uint64_t cstar; + uint64_t sfmask; + uint64_t kernel_gs_base; + uint64_t sysenter_cs; + uint64_t sysenter_esp; + uint64_t sysenter_eip; + uint64_t cr2; + uint8_t reserved_0x248[32]; + uint64_t g_pat; + uint64_t dbgctl; + uint64_t br_from; + uint64_t br_to; + uint64_t last_excp_from; + uint64_t last_excp_to; + uint8_t reserved_0x298[80]; + uint32_t pkru; + uint32_t tsc_aux; + uint8_t reserved_0x2f0[24]; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + uint64_t reserved_0x320; /* rsp already available at 0x01d8 */ + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint8_t reserved_0x380[16]; + uint64_t guest_exit_info_1; + uint64_t guest_exit_info_2; + uint64_t guest_exit_int_info; + uint64_t guest_nrip; + uint64_t sev_features; + uint64_t vintr_ctrl; + uint64_t guest_exit_code; + uint64_t virtual_tom; + uint64_t tlb_id; + uint64_t pcpu_id; + uint64_t event_inj; + uint64_t xcr0; + uint8_t reserved_0x3f0[16]; + + /* Floating point area */ + uint64_t x87_dp; + uint32_t mxcsr; + uint16_t x87_ftw; + uint16_t x87_fsw; + uint16_t x87_fcw; + uint16_t x87_fop; + uint16_t x87_ds; + uint16_t x87_cs; + uint64_t x87_rip; + uint8_t fpreg_x87[80]; + uint8_t fpreg_xmm[256]; + uint8_t fpreg_ymm[256]; +}; + bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp); int sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp); From 86e85eb1d41d1f2b328be812d3390d47055b620f Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:34:41 +0100 Subject: [PATCH 25/77] i386/sev: Implement ConfidentialGuestSupport functions for SEV The ConfidentialGuestSupport object defines a number of virtual functions that are called during processing of IGVM directives to query or configure initial guest state. In order to support processing of IGVM files, these functions need to be implemented by relevant isolation hardware support code such as SEV. This commit implements the required functions for SEV-ES and adds support for processing IGVM files for configuring the guest. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Stefano Garzarella Acked-by: Gerd Hoffman Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/7145835f729e6195f2fbda308aa90e089a96ae6e.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- target/i386/sev.c | 254 ++++++++++++++++++++++++++++++++++++++++++++-- target/i386/sev.h | 2 + 2 files changed, 246 insertions(+), 10 deletions(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index a13f91e615..1296f4feb6 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -41,7 +41,9 @@ #include "confidential-guest.h" #include "hw/i386/pc.h" #include "system/address-spaces.h" +#include "hw/i386/e820_memory_layout.h" #include "qemu/queue.h" +#include "qemu/cutils.h" OBJECT_DECLARE_TYPE(SevCommonState, SevCommonStateClass, SEV_COMMON) OBJECT_DECLARE_TYPE(SevGuestState, SevCommonStateClass, SEV_GUEST) @@ -50,6 +52,9 @@ OBJECT_DECLARE_TYPE(SevSnpGuestState, SevCommonStateClass, SEV_SNP_GUEST) /* hard code sha256 digest size */ #define HASH_SIZE 32 +/* Hard coded GPA that KVM uses for the VMSA */ +#define KVM_VMSA_GPA 0xFFFFFFFFF000 + /* Convert between SEV-ES VMSA and SegmentCache flags/attributes */ #define FLAGS_VMSA_TO_SEGCACHE(flags) \ ((((flags) & 0xff00) << 12) | (((flags) & 0xff) << 8)) @@ -480,6 +485,103 @@ static void sev_apply_cpu_context(CPUState *cpu) } } +static int check_vmsa_supported(hwaddr gpa, const struct sev_es_save_area *vmsa, + Error **errp) +{ + struct sev_es_save_area vmsa_check; + + /* + * KVM always populates the VMSA at a fixed GPA which cannot be modified + * from userspace. Specifying a different GPA will not prevent the guest + * from starting but will cause the launch measurement to be different + * from expected. Therefore check that the provided GPA matches the KVM + * hardcoded value. + */ + if (gpa != KVM_VMSA_GPA) { + error_setg(errp, + "%s: The VMSA GPA must be %lX but is specified as %lX", + __func__, KVM_VMSA_GPA, gpa); + return -1; + } + + /* + * Clear all supported fields so we can then check the entire structure + * is zero. + */ + memcpy(&vmsa_check, vmsa, sizeof(struct sev_es_save_area)); + memset(&vmsa_check.es, 0, sizeof(vmsa_check.es)); + memset(&vmsa_check.cs, 0, sizeof(vmsa_check.cs)); + memset(&vmsa_check.ss, 0, sizeof(vmsa_check.ss)); + memset(&vmsa_check.ds, 0, sizeof(vmsa_check.ds)); + memset(&vmsa_check.fs, 0, sizeof(vmsa_check.fs)); + memset(&vmsa_check.gs, 0, sizeof(vmsa_check.gs)); + memset(&vmsa_check.gdtr, 0, sizeof(vmsa_check.gdtr)); + memset(&vmsa_check.idtr, 0, sizeof(vmsa_check.idtr)); + memset(&vmsa_check.ldtr, 0, sizeof(vmsa_check.ldtr)); + memset(&vmsa_check.tr, 0, sizeof(vmsa_check.tr)); + vmsa_check.efer = 0; + vmsa_check.cr0 = 0; + vmsa_check.cr3 = 0; + vmsa_check.cr4 = 0; + vmsa_check.xcr0 = 0; + vmsa_check.dr6 = 0; + vmsa_check.dr7 = 0; + vmsa_check.rax = 0; + vmsa_check.rcx = 0; + vmsa_check.rdx = 0; + vmsa_check.rbx = 0; + vmsa_check.rsp = 0; + vmsa_check.rbp = 0; + vmsa_check.rsi = 0; + vmsa_check.rdi = 0; + vmsa_check.r8 = 0; + vmsa_check.r9 = 0; + vmsa_check.r10 = 0; + vmsa_check.r11 = 0; + vmsa_check.r12 = 0; + vmsa_check.r13 = 0; + vmsa_check.r14 = 0; + vmsa_check.r15 = 0; + vmsa_check.rip = 0; + vmsa_check.rflags = 0; + + vmsa_check.g_pat = 0; + vmsa_check.xcr0 = 0; + + vmsa_check.x87_fcw = 0; + vmsa_check.mxcsr = 0; + + if (sev_snp_enabled()) { + if (vmsa_check.sev_features != SVM_SEV_FEAT_SNP_ACTIVE) { + error_setg(errp, + "%s: sev_features in the VMSA contains an unsupported " + "value. For SEV-SNP, sev_features must be set to %x.", + __func__, SVM_SEV_FEAT_SNP_ACTIVE); + return -1; + } + vmsa_check.sev_features = 0; + } else { + if (vmsa_check.sev_features != 0) { + error_setg(errp, + "%s: sev_features in the VMSA contains an unsupported " + "value. For SEV-ES and SEV, sev_features must be " + "set to 0.", __func__); + return -1; + } + } + + if (!buffer_is_zero(&vmsa_check, sizeof(vmsa_check))) { + error_setg(errp, + "%s: The VMSA contains fields that are not " + "synchronized with KVM. Continuing would result in " + "either unpredictable guest behavior, or a " + "mismatched launch measurement.", + __func__); + return -1; + } + return 0; +} + static int sev_set_cpu_context(uint16_t cpu_index, const void *ctx, uint32_t ctx_len, hwaddr gpa, Error **errp) { @@ -1491,18 +1593,26 @@ sev_snp_launch_finish(SevCommonState *sev_common) struct kvm_sev_snp_launch_finish *finish = &sev_snp->kvm_finish_conf; /* - * To boot the SNP guest, the hypervisor is required to populate the CPUID - * and Secrets page before finalizing the launch flow. The location of - * the secrets and CPUID page is available through the OVMF metadata GUID. + * Populate all the metadata pages if not using an IGVM file. In the case + * where an IGVM file is provided it will be used to configure the metadata + * pages directly. */ - metadata = pc_system_get_ovmf_sev_metadata_ptr(); - if (metadata == NULL) { - error_report("%s: Failed to locate SEV metadata header", __func__); - exit(1); - } + if (!X86_MACHINE(qdev_get_machine())->igvm) { + /* + * To boot the SNP guest, the hypervisor is required to populate the + * CPUID and Secrets page before finalizing the launch flow. The + * location of the secrets and CPUID page is available through the + * OVMF metadata GUID. + */ + metadata = pc_system_get_ovmf_sev_metadata_ptr(); + if (metadata == NULL) { + error_report("%s: Failed to locate SEV metadata header", __func__); + exit(1); + } - /* Populate all the metadata pages */ - snp_populate_metadata_pages(sev_snp, metadata); + /* Populate all the metadata pages */ + snp_populate_metadata_pages(sev_snp, metadata); + } QTAILQ_FOREACH(data, &launch_update, next) { ret = sev_snp_launch_update(sev_snp, data); @@ -2290,6 +2400,124 @@ static void sev_common_set_kernel_hashes(Object *obj, bool value, Error **errp) SEV_COMMON(obj)->kernel_hashes = value; } +static bool cgs_check_support(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary) +{ + return (((platform == CGS_PLATFORM_SEV_SNP) && sev_snp_enabled()) || + ((platform == CGS_PLATFORM_SEV_ES) && sev_es_enabled()) || + ((platform == CGS_PLATFORM_SEV) && sev_enabled())); +} + +static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(sev_common); + + if (!sev_enabled()) { + error_setg(errp, "%s: attempt to configure guest memory, but SEV " + "is not enabled", __func__); + return -1; + } + + switch (memory_type) { + case CGS_PAGE_TYPE_NORMAL: + case CGS_PAGE_TYPE_ZERO: + return klass->launch_update_data(sev_common, gpa, ptr, len, errp); + + case CGS_PAGE_TYPE_VMSA: + if (!sev_es_enabled()) { + error_setg(errp, + "%s: attempt to configure initial VMSA, but SEV-ES " + "is not supported", + __func__); + return -1; + } + if (check_vmsa_supported(gpa, (const struct sev_es_save_area *)ptr, + errp) < 0) { + return -1; + } + return sev_set_cpu_context(cpu_index, ptr, len, gpa, errp); + + case CGS_PAGE_TYPE_UNMEASURED: + if (sev_snp_enabled()) { + return snp_launch_update_data( + gpa, ptr, len, KVM_SEV_SNP_PAGE_TYPE_UNMEASURED, errp); + } + /* No action required if not SEV-SNP */ + return 0; + + case CGS_PAGE_TYPE_SECRETS: + if (!sev_snp_enabled()) { + error_setg(errp, + "%s: attempt to configure secrets page, but SEV-SNP " + "is not supported", + __func__); + return -1; + } + return snp_launch_update_data(gpa, ptr, len, + KVM_SEV_SNP_PAGE_TYPE_SECRETS, errp); + + case CGS_PAGE_TYPE_REQUIRED_MEMORY: + if (kvm_convert_memory(gpa, len, true) < 0) { + error_setg( + errp, + "%s: failed to configure required memory. gpa: %lX, type: %d", + __func__, gpa, memory_type); + return -1; + } + return 0; + + case CGS_PAGE_TYPE_CPUID: + if (!sev_snp_enabled()) { + error_setg(errp, + "%s: attempt to configure CPUID page, but SEV-SNP " + "is not supported", + __func__); + return -1; + } + return snp_launch_update_cpuid(gpa, ptr, len, errp); + } + error_setg(errp, "%s: failed to update guest. gpa: %lX, type: %d", __func__, + gpa, memory_type); + return -1; +} + +static int cgs_get_mem_map_entry(int index, + ConfidentialGuestMemoryMapEntry *entry, + Error **errp) +{ + struct e820_entry *table; + int num_entries; + + num_entries = e820_get_table(&table); + if ((index < 0) || (index >= num_entries)) { + return 1; + } + entry->gpa = table[index].address; + entry->size = table[index].length; + switch (table[index].type) { + case E820_RAM: + entry->type = CGS_MEM_RAM; + break; + case E820_RESERVED: + entry->type = CGS_MEM_RESERVED; + break; + case E820_ACPI: + entry->type = CGS_MEM_ACPI; + break; + case E820_NVS: + entry->type = CGS_MEM_NVS; + break; + case E820_UNUSABLE: + entry->type = CGS_MEM_UNUSABLE; + break; + } + return 0; +} + static void sev_common_class_init(ObjectClass *oc, const void *data) { @@ -2313,6 +2541,8 @@ static void sev_common_instance_init(Object *obj) { SevCommonState *sev_common = SEV_COMMON(obj); + ConfidentialGuestSupportClass *cgs = + CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(obj); sev_common->kvm_type = -1; @@ -2323,6 +2553,10 @@ sev_common_instance_init(Object *obj) object_property_add_uint32_ptr(obj, "reduced-phys-bits", &sev_common->reduced_phys_bits, OBJ_PROP_FLAG_READWRITE); + cgs->check_support = cgs_check_support; + cgs->set_guest_state = cgs_set_guest_state; + cgs->get_mem_map_entry = cgs_get_mem_map_entry; + QTAILQ_INIT(&sev_common->launch_vmsa); } diff --git a/target/i386/sev.h b/target/i386/sev.h index 38caa849f5..d2eb06db32 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -44,6 +44,8 @@ bool sev_snp_enabled(void); #define SEV_SNP_POLICY_SMT 0x10000 #define SEV_SNP_POLICY_DBG 0x80000 +#define SVM_SEV_FEAT_SNP_ACTIVE 1 + typedef struct SevKernelLoaderContext { char *setup_data; size_t setup_size; From 565d591f719d05763544a5d929de3a40c903b3ea Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 16:41:51 +0100 Subject: [PATCH 26/77] docs/system: Add documentation on support for IGVM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IGVM support has been implemented for Confidential Guests that support AMD SEV and AMD SEV-ES. Add some documentation that gives some background on the IGVM format and how to use it to configure a confidential guest. Signed-off-by: Roy Hopkins Reviewed-by: Daniel P. BerrangĂ© Reviewed-by: Stefano Garzarella Reviewed-by: Pankaj Gupta Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/b4dc920a30717e19cd79bbbe2cc769f3b9ff3d37.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- docs/system/i386/amd-memory-encryption.rst | 2 + docs/system/igvm.rst | 173 +++++++++++++++++++++ docs/system/index.rst | 1 + 3 files changed, 176 insertions(+) create mode 100644 docs/system/igvm.rst diff --git a/docs/system/i386/amd-memory-encryption.rst b/docs/system/i386/amd-memory-encryption.rst index 748f5094ba..6c23f3535f 100644 --- a/docs/system/i386/amd-memory-encryption.rst +++ b/docs/system/i386/amd-memory-encryption.rst @@ -1,3 +1,5 @@ +.. _amd-sev: + AMD Secure Encrypted Virtualization (SEV) ========================================= diff --git a/docs/system/igvm.rst b/docs/system/igvm.rst new file mode 100644 index 0000000000..79508d9588 --- /dev/null +++ b/docs/system/igvm.rst @@ -0,0 +1,173 @@ +Independent Guest Virtual Machine (IGVM) support +================================================ + +IGVM files are designed to encapsulate all the information required to launch a +virtual machine on any given virtualization stack in a deterministic way. This +allows the cryptographic measurement of initial guest state for Confidential +Guests to be calculated when the IGVM file is built, allowing a relying party to +verify the initial state of a guest via a remote attestation. + +Although IGVM files are designed with Confidential Computing in mind, they can +also be used to configure non-confidential guests. Multiple platforms can be +defined by a single IGVM file, allowing a single IGVM file to configure a +virtual machine that can run on, for example, TDX, SEV and non-confidential +hosts. + +QEMU supports IGVM files through the user-creatable ``igvm-cfg`` object. This +object is used to define the filename of the IGVM file to process. A reference +to the object is added to the ``-machine`` to configure the virtual machine +to use the IGVM file for configuration. + +Confidential platform support is provided through the use of +the ``ConfidentialGuestSupport`` object. If the virtual machine provides an +instance of this object then this is used by the IGVM loader to configure the +isolation properties of the directives within the file. + +Further Information on IGVM +--------------------------- + +Information about the IGVM format, including links to the format specification +and documentation for the Rust and C libraries can be found at the project +repository: + +https://github.com/microsoft/igvm + + +Supported Platforms +------------------- + +Currently, IGVM files can be provided for Confidential Guests on host systems +that support AMD SEV, SEV-ES and SEV-SNP with KVM. IGVM files can also be +provided for non-confidential guests. + + +Limitations when using IGVM with AMD SEV, SEV-ES and SEV-SNP +------------------------------------------------------------ + +IGVM files configure the initial state of the guest using a set of directives. +Not every directive is supported by every Confidential Guest type. For example, +AMD SEV does not support encrypted save state regions, therefore setting the +initial CPU state using IGVM for SEV is not possible. When an IGVM file contains +directives that are not supported for the active platform, an error is generated +and the guest launch is aborted. + +The table below describes the list of directives that are supported for SEV, +SEV-ES, SEV-SNP and non-confidential platforms. + +.. list-table:: SEV, SEV-ES, SEV-SNP & non-confidential Supported Directives + :widths: 35 65 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_PAGE_DATA + - ``NORMAL`` zero, measured and unmeasured page types are supported. Other + page types result in an error. + * - IGVM_VHT_PARAMETER_AREA + - + * - IGVM_VHT_PARAMETER_INSERT + - + * - IGVM_VHT_VP_COUNT_PARAMETER + - The guest parameter page is populated with the CPU count. + * - IGVM_VHT_ENVIRONMENT_INFO_PARAMETER + - The ``memory_is_shared`` parameter is set to 1 in the guest parameter + page. + +.. list-table:: Additional SEV, SEV-ES & SEV_SNP Supported Directives + :widths: 25 75 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_MEMORY_MAP + - The memory map page is populated using entries from the E820 table. + * - IGVM_VHT_REQUIRED_MEMORY + - Ensures memory is available in the guest at the specified range. + +.. list-table:: Additional SEV-ES & SEV-SNP Supported Directives + :widths: 25 75 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_VP_CONTEXT + - Setting of the initial CPU state for the boot CPU and additional CPUs is + supported with limitations on the fields that can be provided in the + VMSA. See below for details on which fields are supported. + +Initial CPU state with VMSA +--------------------------- + +The initial state of guest CPUs can be defined in the IGVM file for AMD SEV-ES +and SEV-SNP. The state data is provided as a VMSA structure as defined in Table +B-4 in the AMD64 Architecture Programmer's Manual, Volume 2 [1]. + +The IGVM VMSA is translated to CPU state in QEMU which is then synchronized +by KVM to the guest VMSA during the launch process where it contributes to the +launch measurement. See :ref:`amd-sev` for details on the launch process and +guest launch measurement. + +It is important that no information is lost or changed when translating the +VMSA provided by the IGVM file into the VSMA that is used to launch the guest. +Therefore, QEMU restricts the VMSA fields that can be provided in the IGVM +VMSA structure to the following registers: + +RAX, RCX, RDX, RBX, RBP, RSI, RDI, R8-R15, RSP, RIP, CS, DS, ES, FS, GS, SS, +CR0, CR3, CR4, XCR0, EFER, PAT, GDT, IDT, LDTR, TR, DR6, DR7, RFLAGS, X87_FCW, +MXCSR. + +When processing the IGVM file, QEMU will check if any fields other than the +above are non-zero and generate an error if this is the case. + +KVM uses a hardcoded GPA of 0xFFFFFFFFF000 for the VMSA. When an IGVM file +defines initial CPU state, the GPA for each VMSA must match this hardcoded +value. + +Firmware Images with IGVM +------------------------- + +When an IGVM filename is specified for a Confidential Guest Support object it +overrides the default handling of system firmware: the firmware image, such as +an OVMF binary should be contained as a payload of the IGVM file and not +provided as a flash drive or via the ``-bios`` parameter. The default QEMU +firmware is not automatically populated into the guest memory space. + +If an IGVM file is provided along with either the ``-bios`` parameter or pflash +devices then an error is displayed and the guest startup is aborted. + +Running a guest configured using IGVM +------------------------------------- + +To run a guest configured with IGVM you firstly need to generate an IGVM file +that contains a guest configuration compatible with the platform you are +targeting. + +The ``buildigvm`` tool [2] is an example of a tool that can be used to generate +IGVM files for non-confidential X86 platforms as well as for SEV, SEV-ES and +SEV-SNP confidential platforms. + +Example using this tool to generate an IGVM file for AMD SEV-SNP:: + + buildigvm --firmware /path/to/OVMF.fd --output sev-snp.igvm \ + --cpucount 4 sev-snp + +To run a guest configured with the generated IGVM you need to add an +``igvm-cfg`` object and refer to it from the ``-machine`` parameter: + +Example (for AMD SEV):: + + qemu-system-x86_64 \ + \ + -machine ...,confidential-guest-support=sev0,igvm-cfg=igvm0 \ + -object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \ + -object igvm-cfg,id=igvm0,file=/path/to/sev-snp.igvm + +References +---------- + +[1] AMD64 Architecture Programmer's Manual, Volume 2: System Programming + Rev 3.41 + https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf + +[2] ``buildigvm`` - A tool to build example IGVM files containing OVMF firmware + https://github.com/roy-hopkins/buildigvm \ No newline at end of file diff --git a/docs/system/index.rst b/docs/system/index.rst index 718e9d3c56..427b020483 100644 --- a/docs/system/index.rst +++ b/docs/system/index.rst @@ -38,5 +38,6 @@ or Hypervisor.Framework. security multi-process confidential-guest-support + igvm vm-templating sriov From 596c330b19cf00384ec14d0bff25758ed204b49d Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:02:19 +0100 Subject: [PATCH 27/77] docs/interop/firmware.json: Add igvm to FirmwareDevice Create an enum entry within FirmwareDevice for 'igvm' to describe that an IGVM file can be used to map firmware into memory as an alternative to pre-existing firmware devices. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/2eca2611d372facbffa65ee8244cf2d321eb9d17.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- docs/interop/firmware.json | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json index 745d21d822..0711b6f323 100644 --- a/docs/interop/firmware.json +++ b/docs/interop/firmware.json @@ -57,10 +57,17 @@ # # @memory: The firmware is to be mapped into memory. # +# @igvm: The firmware is defined by a file conforming to the IGVM +# specification and mapped into memory according to directives +# defined in the file. This is similar to @memory but may +# include additional processing defined by the IGVM file +# including initial CPU state or population of metadata into +# the guest address space. Since: 10.1 +# # Since: 3.0 ## { 'enum' : 'FirmwareDevice', - 'data' : [ 'flash', 'kernel', 'memory' ] } + 'data' : [ 'flash', 'kernel', 'memory', 'igvm' ] } ## # @FirmwareArchitecture: @@ -377,6 +384,24 @@ { 'struct' : 'FirmwareMappingMemory', 'data' : { 'filename' : 'str' } } +## +# @FirmwareMappingIgvm: +# +# Describes loading and mapping properties for the firmware executable, +# when @FirmwareDevice is @igvm. +# +# @filename: Identifies the IGVM file containing the firmware executable +# along with other information used to configure the initial +# state of the guest. The IGVM file may be shared by multiple +# virtual machine definitions. This corresponds to creating +# an object on the command line with "-object igvm-cfg, +# file=@filename". +# +# Since: 10.1 +## +{ 'struct' : 'FirmwareMappingIgvm', + 'data' : { 'filename' : 'str' } } + ## # @FirmwareMapping: # @@ -393,7 +418,8 @@ 'discriminator' : 'device', 'data' : { 'flash' : 'FirmwareMappingFlash', 'kernel' : 'FirmwareMappingKernel', - 'memory' : 'FirmwareMappingMemory' } } + 'memory' : 'FirmwareMappingMemory', + 'igvm' : 'FirmwareMappingIgvm' } } ## # @Firmware: From 96a3088f5ebe6854dc8fb5a547c6b1d1db60f0fa Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:02:20 +0100 Subject: [PATCH 28/77] backends/confidential-guest-support: Add set_guest_policy() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For confidential guests a policy can be provided that defines the security level, debug status, expected launch measurement and other parameters that define the configuration of the confidential platform. This commit adds a new function named set_guest_policy() that can be implemented by each confidential platform, such as AMD SEV to set the policy. This will allow configuration of the policy from a multi-platform resource such as an IGVM file without the IGVM processor requiring specific implementation details for each platform. Signed-off-by: Roy Hopkins Reviewed-by: Daniel P. BerrangĂ© Reviewed-by: Stefano Garzarella Reviewed-by: Ani Sinha Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Link: https://lore.kernel.org/r/d3888a2eb170c8d8c85a1c4b7e99accf3a15589c.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/confidential-guest-support.c | 12 ++++++++++++ include/system/confidential-guest-support.h | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/backends/confidential-guest-support.c b/backends/confidential-guest-support.c index c5bef1fbfa..156dd15e66 100644 --- a/backends/confidential-guest-support.c +++ b/backends/confidential-guest-support.c @@ -38,6 +38,17 @@ static int set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, return -1; } +static int set_guest_policy(ConfidentialGuestPolicyType policy_type, + uint64_t policy, + void *policy_data1, uint32_t policy_data1_size, + void *policy_data2, uint32_t policy_data2_size, + Error **errp) +{ + error_setg(errp, + "Setting confidential guest policy is not supported for this platform"); + return -1; +} + static int get_mem_map_entry(int index, ConfidentialGuestMemoryMapEntry *entry, Error **errp) { @@ -53,6 +64,7 @@ static void confidential_guest_support_class_init(ObjectClass *oc, ConfidentialGuestSupportClass *cgsc = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); cgsc->check_support = check_support; cgsc->set_guest_state = set_guest_state; + cgsc->set_guest_policy = set_guest_policy; cgsc->get_mem_map_entry = get_mem_map_entry; } diff --git a/include/system/confidential-guest-support.h b/include/system/confidential-guest-support.h index 79ecd21f42..0cc8b26e64 100644 --- a/include/system/confidential-guest-support.h +++ b/include/system/confidential-guest-support.h @@ -57,6 +57,10 @@ typedef enum ConfidentialGuestPageType { CGS_PAGE_TYPE_REQUIRED_MEMORY, } ConfidentialGuestPageType; +typedef enum ConfidentialGuestPolicyType { + GUEST_POLICY_SEV, +} ConfidentialGuestPolicyType; + struct ConfidentialGuestSupport { Object parent; @@ -123,6 +127,23 @@ typedef struct ConfidentialGuestSupportClass { ConfidentialGuestPageType memory_type, uint16_t cpu_index, Error **errp); + /* + * Set the guest policy. The policy can be used to configure the + * confidential platform, such as if debug is enabled or not and can contain + * information about expected launch measurements, signed verification of + * guest configuration and other platform data. + * + * The format of the policy data is specific to each platform. For example, + * SEV-SNP uses a policy bitfield in the 'policy' argument and provides an + * ID block and ID authentication in the 'policy_data' parameters. The type + * of policy data is identified by the 'policy_type' argument. + */ + int (*set_guest_policy)(ConfidentialGuestPolicyType policy_type, + uint64_t policy, + void *policy_data1, uint32_t policy_data1_size, + void *policy_data2, uint32_t policy_data2_size, + Error **errp); + /* * Iterate the system memory map, getting the entry with the given index * that can be populated into guest memory. From 9de40d7df3266625bf6e6c2712604ab306d12e65 Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:18:59 +0100 Subject: [PATCH 29/77] backends/igvm: Process initialization sections in IGVM file The initialization sections in IGVM files contain configuration that should be applied to the guest platform before it is started. This includes guest policy and other information that can affect the security level and the startup measurement of a guest. This commit introduces handling of the initialization sections during processing of the IGVM file. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Gerd Hoffman Reviewed-by: Stefano Garzarella Link: https://lore.kernel.org/r/9de24fb5df402024b40cbe02de0b13faa7cb4d84.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/igvm.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backends/igvm.c b/backends/igvm.c index 2a31021d44..ebdb4594d1 100644 --- a/backends/igvm.c +++ b/backends/igvm.c @@ -786,6 +786,27 @@ int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, } } + header_count = + igvm_header_count(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION); + if (header_count < 0) { + error_setg( + errp, + "Invalid initialization header count in IGVM file. Error code: %X", + header_count); + goto cleanup_parameters; + } + + for (ctx.current_header_index = 0; + ctx.current_header_index < (unsigned)header_count; + ctx.current_header_index++) { + IgvmVariableHeaderType type = + igvm_get_header_type(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION, + ctx.current_header_index); + if (qigvm_handler(&ctx, type, errp) < 0) { + goto cleanup_parameters; + } + } + /* * Contiguous pages of data with compatible flags are grouped together in * order to reduce the number of memory regions we create. Make sure the From 915b47078d6b4cffde209aab81ab56f73e4a2632 Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:21:57 +0100 Subject: [PATCH 30/77] backends/igvm: Handle policy for SEV guests Adds a handler for the guest policy initialization IGVM section and builds an SEV policy based on this information and the ID block directive if present. The policy is applied using by calling 'set_guest_policy()' on the ConfidentialGuestSupport object. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Stefano Garzarella Acked-by: Gerd Hoffman Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/57707230bef331b53e9366ce6a23ed25cd6f1293.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/igvm.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/backends/igvm.c b/backends/igvm.c index ebdb4594d1..b568f06c76 100644 --- a/backends/igvm.c +++ b/backends/igvm.c @@ -27,6 +27,40 @@ typedef struct QIgvmParameterData { uint32_t index; } QIgvmParameterData; +/* + * Some directives are specific to particular confidential computing platforms. + * Define required types for each of those platforms here. + */ + +/* SEV/SEV-ES/SEV-SNP */ + +/* + * These structures are defined in "SEV Secure Nested Paging Firmware ABI + * Specification" Rev 1.58, section 8.18. + */ +struct QEMU_PACKED sev_id_block { + uint8_t ld[48]; + uint8_t family_id[16]; + uint8_t image_id[16]; + uint32_t version; + uint32_t guest_svn; + uint64_t policy; +}; + +struct QEMU_PACKED sev_id_authentication { + uint32_t id_key_alg; + uint32_t auth_key_algo; + uint8_t reserved[56]; + uint8_t id_block_sig[512]; + uint8_t id_key[1028]; + uint8_t reserved2[60]; + uint8_t id_key_sig[512]; + uint8_t author_key[1028]; + uint8_t reserved3[892]; +}; + +#define IGVM_SEV_ID_BLOCK_VERSION 1 + /* * QIgvm contains the information required during processing * of a single IGVM file. @@ -38,6 +72,17 @@ typedef struct QIgvm { uint32_t compatibility_mask; unsigned current_header_index; QTAILQ_HEAD(, QIgvmParameterData) parameter_data; + IgvmPlatformType platform_type; + + /* + * SEV-SNP platforms can contain an ID block and authentication + * that should be verified by the guest. + */ + struct sev_id_block *id_block; + struct sev_id_authentication *id_auth; + + /* Define the guest policy for SEV guests */ + uint64_t sev_policy; /* These variables keep track of contiguous page regions */ IGVM_VHS_PAGE_DATA region_prev_page_data; @@ -67,6 +112,11 @@ static int qigvm_directive_environment_info(QIgvm *ctx, static int qigvm_directive_required_memory(QIgvm *ctx, const uint8_t *header_data, Error **errp); +static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_initialization_guest_policy(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); struct QIGVMHandler { uint32_t type; @@ -91,6 +141,10 @@ static struct QIGVMHandler handlers[] = { qigvm_directive_environment_info }, { IGVM_VHT_REQUIRED_MEMORY, IGVM_HEADER_SECTION_DIRECTIVE, qigvm_directive_required_memory }, + { IGVM_VHT_SNP_ID_BLOCK, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_snp_id_block }, + { IGVM_VHT_GUEST_POLICY, IGVM_HEADER_SECTION_INITIALIZATION, + qigvm_initialization_guest_policy }, }; static int qigvm_handler(QIgvm *ctx, uint32_t type, Error **errp) @@ -632,6 +686,74 @@ static int qigvm_directive_required_memory(QIgvm *ctx, return 0; } +static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_SNP_ID_BLOCK *igvm_id = + (const IGVM_VHS_SNP_ID_BLOCK *)header_data; + + if (!(igvm_id->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + if (ctx->id_block) { + error_setg(errp, "IGVM: Multiple ID blocks encountered " + "in IGVM file."); + return -1; + } + ctx->id_block = g_new0(struct sev_id_block, 1); + ctx->id_auth = g_new0(struct sev_id_authentication, 1); + + memcpy(ctx->id_block->family_id, igvm_id->family_id, + sizeof(ctx->id_block->family_id)); + memcpy(ctx->id_block->image_id, igvm_id->image_id, + sizeof(ctx->id_block->image_id)); + ctx->id_block->guest_svn = igvm_id->guest_svn; + ctx->id_block->version = IGVM_SEV_ID_BLOCK_VERSION; + memcpy(ctx->id_block->ld, igvm_id->ld, sizeof(ctx->id_block->ld)); + + ctx->id_auth->id_key_alg = igvm_id->id_key_algorithm; + assert(sizeof(igvm_id->id_key_signature) <= + sizeof(ctx->id_auth->id_block_sig)); + memcpy(ctx->id_auth->id_block_sig, &igvm_id->id_key_signature, + sizeof(igvm_id->id_key_signature)); + + ctx->id_auth->auth_key_algo = igvm_id->author_key_algorithm; + assert(sizeof(igvm_id->author_key_signature) <= + sizeof(ctx->id_auth->id_key_sig)); + memcpy(ctx->id_auth->id_key_sig, &igvm_id->author_key_signature, + sizeof(igvm_id->author_key_signature)); + + /* + * SEV and IGVM public key structure population are slightly different. + * See SEV Secure Nested Paging Firmware ABI Specification, Chapter 10. + */ + *((uint32_t *)ctx->id_auth->id_key) = igvm_id->id_public_key.curve; + memcpy(&ctx->id_auth->id_key[4], &igvm_id->id_public_key.qx, 72); + memcpy(&ctx->id_auth->id_key[76], &igvm_id->id_public_key.qy, 72); + + *((uint32_t *)ctx->id_auth->author_key) = + igvm_id->author_public_key.curve; + memcpy(&ctx->id_auth->author_key[4], &igvm_id->author_public_key.qx, + 72); + memcpy(&ctx->id_auth->author_key[76], &igvm_id->author_public_key.qy, + 72); + + return 0; +} + +static int qigvm_initialization_guest_policy(QIgvm *ctx, + const uint8_t *header_data, Error **errp) +{ + const IGVM_VHS_GUEST_POLICY *guest = + (const IGVM_VHS_GUEST_POLICY *)header_data; + + if (guest->compatibility_mask & ctx->compatibility_mask) { + ctx->sev_policy = guest->policy; + } + return 0; +} + static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) { int32_t header_count; @@ -701,12 +823,16 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) /* Choose the strongest supported isolation technology */ if (compatibility_mask_sev_snp != 0) { ctx->compatibility_mask = compatibility_mask_sev_snp; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_SNP; } else if (compatibility_mask_sev_es != 0) { ctx->compatibility_mask = compatibility_mask_sev_es; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_ES; } else if (compatibility_mask_sev != 0) { ctx->compatibility_mask = compatibility_mask_sev; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV; } else if (compatibility_mask != 0) { ctx->compatibility_mask = compatibility_mask; + ctx->platform_type = IGVM_PLATFORM_TYPE_NATIVE; } else { error_setg( errp, @@ -716,6 +842,23 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) return 0; } +static int qigvm_handle_policy(QIgvm *ctx, Error **errp) +{ + if (ctx->platform_type == IGVM_PLATFORM_TYPE_SEV_SNP) { + int id_block_len = 0; + int id_auth_len = 0; + if (ctx->id_block) { + ctx->id_block->policy = ctx->sev_policy; + id_block_len = sizeof(struct sev_id_block); + id_auth_len = sizeof(struct sev_id_authentication); + } + return ctx->cgsc->set_guest_policy(GUEST_POLICY_SEV, ctx->sev_policy, + ctx->id_block, id_block_len, + ctx->id_auth, id_auth_len, errp); + } + return 0; +} + static IgvmHandle qigvm_file_init(char *filename, Error **errp) { IgvmHandle igvm; @@ -814,12 +957,18 @@ int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, */ retval = qigvm_process_mem_page(&ctx, NULL, errp); + if (retval == 0) { + retval = qigvm_handle_policy(&ctx, errp); + } + cleanup_parameters: QTAILQ_FOREACH(parameter, &ctx.parameter_data, next) { g_free(parameter->data); parameter->data = NULL; } + g_free(ctx.id_block); + g_free(ctx.id_auth); cleanup: igvm_free(ctx.file); From 2ff75825cc5a5d56ea90d79cd15578f6b1893561 Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:21:58 +0100 Subject: [PATCH 31/77] i386/sev: Add implementation of CGS set_guest_policy() The new cgs_set_guest_policy() function is provided to receive the guest policy flags, SNP ID block and SNP ID authentication from guest configuration such as an IGVM file and apply it to the platform prior to launching the guest. The policy is used to populate values for the existing 'policy', 'id_block' and 'id_auth' parameters. When provided, the guest policy is applied and the ID block configuration is used to verify the launch measurement and signatures. The guest is only successfully started if the expected launch measurements match the actual measurements and the signatures are valid. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Stefano Garzarella Acked-by: Gerd Hoffman Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/99e82ddec4ad2970c790db8bea16ea3f57eb0e53.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- target/i386/sev.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++ target/i386/sev.h | 12 +++++++ 2 files changed, 95 insertions(+) diff --git a/target/i386/sev.c b/target/i386/sev.c index 1296f4feb6..3e5722ba65 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -2518,6 +2518,88 @@ static int cgs_get_mem_map_entry(int index, return 0; } +static int cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type, + uint64_t policy, void *policy_data1, + uint32_t policy_data1_size, void *policy_data2, + uint32_t policy_data2_size, Error **errp) +{ + if (policy_type != GUEST_POLICY_SEV) { + error_setg(errp, "%s: Invalid guest policy type provided for SEV: %d", + __func__, policy_type); + return -1; + } + /* + * SEV-SNP handles policy differently. The policy flags are defined in + * kvm_start_conf.policy and an ID block and ID auth can be provided. + */ + if (sev_snp_enabled()) { + SevSnpGuestState *sev_snp_guest = + SEV_SNP_GUEST(MACHINE(qdev_get_machine())->cgs); + struct kvm_sev_snp_launch_finish *finish = + &sev_snp_guest->kvm_finish_conf; + + /* + * The policy consists of flags in 'policy' and optionally an ID block + * and ID auth in policy_data1 and policy_data2 respectively. The ID + * block and auth are optional so clear any previous ID block and auth + * and set them if provided, but always set the policy flags. + */ + g_free(sev_snp_guest->id_block); + g_free((guchar *)finish->id_block_uaddr); + g_free(sev_snp_guest->id_auth); + g_free((guchar *)finish->id_auth_uaddr); + sev_snp_guest->id_block = NULL; + finish->id_block_uaddr = 0; + sev_snp_guest->id_auth = NULL; + finish->id_auth_uaddr = 0; + + if (policy_data1_size > 0) { + struct sev_snp_id_authentication *id_auth = + (struct sev_snp_id_authentication *)policy_data2; + + if (policy_data1_size != KVM_SEV_SNP_ID_BLOCK_SIZE) { + error_setg(errp, "%s: Invalid SEV-SNP ID block: incorrect size", + __func__); + return -1; + } + if (policy_data2_size != KVM_SEV_SNP_ID_AUTH_SIZE) { + error_setg(errp, + "%s: Invalid SEV-SNP ID auth block: incorrect size", + __func__); + return -1; + } + assert(policy_data1 != NULL); + assert(policy_data2 != NULL); + + finish->id_block_uaddr = + (__u64)g_memdup2(policy_data1, KVM_SEV_SNP_ID_BLOCK_SIZE); + finish->id_auth_uaddr = + (__u64)g_memdup2(policy_data2, KVM_SEV_SNP_ID_AUTH_SIZE); + + /* + * Check if an author key has been provided and use that to flag + * whether the author key is enabled. The first of the author key + * must be non-zero to indicate the key type, which will currently + * always be 2. + */ + sev_snp_guest->kvm_finish_conf.auth_key_en = + id_auth->author_key[0] ? 1 : 0; + finish->id_block_en = 1; + } + sev_snp_guest->kvm_start_conf.policy = policy; + } else { + SevGuestState *sev_guest = SEV_GUEST(MACHINE(qdev_get_machine())->cgs); + /* Only the policy flags are supported for SEV and SEV-ES */ + if ((policy_data1_size > 0) || (policy_data2_size > 0) || !sev_guest) { + error_setg(errp, "%s: An ID block/ID auth block has been provided " + "but SEV-SNP is not enabled", __func__); + return -1; + } + sev_guest->policy = policy; + } + return 0; +} + static void sev_common_class_init(ObjectClass *oc, const void *data) { @@ -2556,6 +2638,7 @@ sev_common_instance_init(Object *obj) cgs->check_support = cgs_check_support; cgs->set_guest_state = cgs_set_guest_state; cgs->get_mem_map_entry = cgs_get_mem_map_entry; + cgs->set_guest_policy = cgs_set_guest_policy; QTAILQ_INIT(&sev_common->launch_vmsa); } diff --git a/target/i386/sev.h b/target/i386/sev.h index d2eb06db32..9db1a802f6 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -167,6 +167,18 @@ struct QEMU_PACKED sev_es_save_area { uint8_t fpreg_ymm[256]; }; +struct QEMU_PACKED sev_snp_id_authentication { + uint32_t id_key_alg; + uint32_t auth_key_algo; + uint8_t reserved[56]; + uint8_t id_block_sig[512]; + uint8_t id_key[1028]; + uint8_t reserved2[60]; + uint8_t id_key_sig[512]; + uint8_t author_key[1028]; + uint8_t reserved3[892]; +}; + bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp); int sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp); From d60238b4c1e81235d5efb4a978a6f6b1adabccab Mon Sep 17 00:00:00 2001 From: Roy Hopkins Date: Thu, 3 Jul 2025 17:21:59 +0100 Subject: [PATCH 32/77] sev: Provide sev_features flags from IGVM VMSA to KVM_SEV_INIT2 IGVM files can contain an initial VMSA that should be applied to each vcpu as part of the initial guest state. The sev_features flags are provided as part of the VMSA structure. However, KVM only allows sev_features to be set during initialization and not as the guest is being prepared for launch. This patch queries KVM for the supported set of sev_features flags and processes the VP context entries in the IGVM file during kvm_init to determine any sev_features flags set in the IGVM file. These are then provided in the call to KVM_SEV_INIT2 to ensure the guest state matches that specified in the IGVM file. The igvm process() function is modified to allow a partial processing of the file during initialization, with only the IGVM_VHT_VP_CONTEXT fields being processed. This means the function is called twice, firstly to extract the sev_features then secondly to actually configure the guest. Signed-off-by: Roy Hopkins Acked-by: Michael S. Tsirkin Acked-by: Stefano Garzarella Acked-by: Gerd Hoffman Tested-by: Stefano Garzarella Reviewed-by: Liam Merwick Reviewed-by: Ani Sinha Link: https://lore.kernel.org/r/b2f986aae04e1da2aee530c9be22a54c0c59a560.1751554099.git.roy.hopkins@randomman.co.uk Signed-off-by: Paolo Bonzini --- backends/igvm.c | 17 +++- backends/igvm.h | 2 +- hw/i386/pc_piix.c | 2 +- hw/i386/pc_q35.c | 2 +- include/system/igvm-cfg.h | 5 +- target/i386/sev.c | 161 +++++++++++++++++++++++++++++++++----- 6 files changed, 163 insertions(+), 26 deletions(-) diff --git a/backends/igvm.c b/backends/igvm.c index b568f06c76..9ad41582ee 100644 --- a/backends/igvm.c +++ b/backends/igvm.c @@ -880,7 +880,7 @@ static IgvmHandle qigvm_file_init(char *filename, Error **errp) } int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, - Error **errp) + bool onlyVpContext, Error **errp) { int32_t header_count; QIgvmParameterData *parameter; @@ -924,11 +924,22 @@ int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, ctx.current_header_index++) { IgvmVariableHeaderType type = igvm_get_header_type( ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index); - if (qigvm_handler(&ctx, type, errp) < 0) { - goto cleanup_parameters; + if (!onlyVpContext || (type == IGVM_VHT_VP_CONTEXT)) { + if (qigvm_handler(&ctx, type, errp) < 0) { + goto cleanup_parameters; + } } } + /* + * If only processing the VP context then we don't need to process + * any more of the file. + */ + if (onlyVpContext) { + retval = 0; + goto cleanup_parameters; + } + header_count = igvm_header_count(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION); if (header_count < 0) { diff --git a/backends/igvm.h b/backends/igvm.h index db02ea9165..a4abab043a 100644 --- a/backends/igvm.h +++ b/backends/igvm.h @@ -17,6 +17,6 @@ #include "qapi/error.h" int qigvm_process_file(IgvmCfg *igvm, ConfidentialGuestSupport *cgs, - Error **errp); + bool onlyVpContext, Error **errp); #endif diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 3184ea1b37..a3285fbc64 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -371,7 +371,7 @@ static void pc_init1(MachineState *machine, const char *pci_type) /* Apply guest state from IGVM if supplied */ if (x86ms->igvm) { if (IGVM_CFG_GET_CLASS(x86ms->igvm) - ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) { + ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) { g_assert_not_reached(); } } diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 6990e1c669..cf871cfdad 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -330,7 +330,7 @@ static void pc_q35_init(MachineState *machine) /* Apply guest state from IGVM if supplied */ if (x86ms->igvm) { if (IGVM_CFG_GET_CLASS(x86ms->igvm) - ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) { + ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) { g_assert_not_reached(); } } diff --git a/include/system/igvm-cfg.h b/include/system/igvm-cfg.h index 321b3196f0..944f23a814 100644 --- a/include/system/igvm-cfg.h +++ b/include/system/igvm-cfg.h @@ -31,11 +31,14 @@ typedef struct IgvmCfgClass { /* * If an IGVM filename has been specified then process the IGVM file. * Performs a no-op if no filename has been specified. + * If onlyVpContext is true then only the IGVM_VHT_VP_CONTEXT entries + * in the IGVM file will be processed, allowing information about the + * CPU state to be determined before processing the entire file. * * Returns 0 for ok and -1 on error. */ int (*process)(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, - Error **errp); + bool onlyVpContext, Error **errp); } IgvmCfgClass; diff --git a/target/i386/sev.c b/target/i386/sev.c index 3e5722ba65..1057b8ab2c 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -118,6 +118,8 @@ struct SevCommonState { uint32_t cbitpos; uint32_t reduced_phys_bits; bool kernel_hashes; + uint64_t sev_features; + uint64_t supported_sev_features; /* runtime state */ uint8_t api_major; @@ -485,7 +487,40 @@ static void sev_apply_cpu_context(CPUState *cpu) } } -static int check_vmsa_supported(hwaddr gpa, const struct sev_es_save_area *vmsa, +static int check_sev_features(SevCommonState *sev_common, uint64_t sev_features, + Error **errp) +{ + /* + * Ensure SEV_FEATURES is configured for correct SEV hardware and that + * the requested features are supported. If SEV-SNP is enabled then + * that feature must be enabled, otherwise it must be cleared. + */ + if (sev_snp_enabled() && !(sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) { + error_setg( + errp, + "%s: SEV_SNP is enabled but is not enabled in VMSA sev_features", + __func__); + return -1; + } else if (!sev_snp_enabled() && + (sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) { + error_setg( + errp, + "%s: SEV_SNP is not enabled but is enabled in VMSA sev_features", + __func__); + return -1; + } + if (sev_features & ~sev_common->supported_sev_features) { + error_setg(errp, + "%s: VMSA contains unsupported sev_features: %lX, " + "supported features: %lX", + __func__, sev_features, sev_common->supported_sev_features); + return -1; + } + return 0; +} + +static int check_vmsa_supported(SevCommonState *sev_common, hwaddr gpa, + const struct sev_es_save_area *vmsa, Error **errp) { struct sev_es_save_area vmsa_check; @@ -551,24 +586,10 @@ static int check_vmsa_supported(hwaddr gpa, const struct sev_es_save_area *vmsa, vmsa_check.x87_fcw = 0; vmsa_check.mxcsr = 0; - if (sev_snp_enabled()) { - if (vmsa_check.sev_features != SVM_SEV_FEAT_SNP_ACTIVE) { - error_setg(errp, - "%s: sev_features in the VMSA contains an unsupported " - "value. For SEV-SNP, sev_features must be set to %x.", - __func__, SVM_SEV_FEAT_SNP_ACTIVE); - return -1; - } - vmsa_check.sev_features = 0; - } else { - if (vmsa_check.sev_features != 0) { - error_setg(errp, - "%s: sev_features in the VMSA contains an unsupported " - "value. For SEV-ES and SEV, sev_features must be " - "set to 0.", __func__); - return -1; - } + if (check_sev_features(sev_common, vmsa_check.sev_features, errp) < 0) { + return -1; } + vmsa_check.sev_features = 0; if (!buffer_is_zero(&vmsa_check, sizeof(vmsa_check))) { error_setg(errp, @@ -1722,6 +1743,39 @@ static int sev_snp_kvm_type(X86ConfidentialGuest *cg) return KVM_X86_SNP_VM; } +static int sev_init_supported_features(ConfidentialGuestSupport *cgs, + SevCommonState *sev_common, Error **errp) +{ + X86ConfidentialGuestClass *x86_klass = + X86_CONFIDENTIAL_GUEST_GET_CLASS(cgs); + /* + * Older kernels do not support query or setting of sev_features. In this + * case the set of supported features must be zero to match the settings + * in the kernel. + */ + if (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common)) == + KVM_X86_DEFAULT_VM) { + sev_common->supported_sev_features = 0; + return 0; + } + + /* Query KVM for the supported set of sev_features */ + struct kvm_device_attr attr = { + .group = KVM_X86_GRP_SEV, + .attr = KVM_X86_SEV_VMSA_FEATURES, + .addr = (unsigned long)&sev_common->supported_sev_features, + }; + if (kvm_ioctl(kvm_state, KVM_GET_DEVICE_ATTR, &attr) < 0) { + error_setg(errp, "%s: failed to query supported sev_features", + __func__); + return -1; + } + if (sev_snp_enabled()) { + sev_common->supported_sev_features |= SVM_SEV_FEAT_SNP_ACTIVE; + } + return 0; +} + static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) { char *devname; @@ -1802,6 +1856,10 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) } } + if (sev_init_supported_features(cgs, sev_common, errp) < 0) { + return -1; + } + trace_kvm_sev_init(); switch (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common))) { case KVM_X86_DEFAULT_VM: @@ -1813,6 +1871,40 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) case KVM_X86_SEV_ES_VM: case KVM_X86_SNP_VM: { struct kvm_sev_init args = { 0 }; + MachineState *machine = MACHINE(qdev_get_machine()); + X86MachineState *x86machine = X86_MACHINE(qdev_get_machine()); + + /* + * If configuration is provided via an IGVM file then the IGVM file + * might contain configuration of the initial vcpu context. For SEV + * the vcpu context includes the sev_features which should be applied + * to the vcpu. + * + * KVM does not synchronize sev_features from CPU state. Instead it + * requires sev_features to be provided as part of this initialization + * call which is subsequently automatically applied to the VMSA of + * each vcpu. + * + * The IGVM file is normally processed after initialization. Therefore + * we need to pre-process it here to extract sev_features in order to + * provide it to KVM_SEV_INIT2. Each cgs_* function that is called by + * the IGVM processor detects this pre-process by observing the state + * as SEV_STATE_UNINIT. + */ + if (x86machine->igvm) { + if (IGVM_CFG_GET_CLASS(x86machine->igvm) + ->process(x86machine->igvm, machine->cgs, true, errp) == + -1) { + return -1; + } + /* + * KVM maintains a bitmask of allowed sev_features. This does not + * include SVM_SEV_FEAT_SNP_ACTIVE which is set accordingly by KVM + * itself. Therefore we need to clear this flag. + */ + args.vmsa_features = sev_common->sev_features & + ~SVM_SEV_FEAT_SNP_ACTIVE; + } ret = sev_ioctl(sev_common->sev_fd, KVM_SEV_INIT2, &args, &fw_error); break; @@ -2416,6 +2508,24 @@ static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(sev_common); + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + if ((cpu_index == 0) && (memory_type == CGS_PAGE_TYPE_VMSA)) { + const struct sev_es_save_area *sa = + (const struct sev_es_save_area *)ptr; + if (len < sizeof(*sa)) { + error_setg(errp, "%s: invalid VMSA length encountered", + __func__); + return -1; + } + if (check_sev_features(sev_common, sa->sev_features, errp) < 0) { + return -1; + } + sev_common->sev_features = sa->sev_features; + } + return 0; + } + if (!sev_enabled()) { error_setg(errp, "%s: attempt to configure guest memory, but SEV " "is not enabled", __func__); @@ -2435,7 +2545,8 @@ static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, __func__); return -1; } - if (check_vmsa_supported(gpa, (const struct sev_es_save_area *)ptr, + if (check_vmsa_supported(sev_common, gpa, + (const struct sev_es_save_area *)ptr, errp) < 0) { return -1; } @@ -2492,6 +2603,12 @@ static int cgs_get_mem_map_entry(int index, struct e820_entry *table; int num_entries; + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + return 1; + } + num_entries = e820_get_table(&table); if ((index < 0) || (index >= num_entries)) { return 1; @@ -2523,6 +2640,12 @@ static int cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type, uint32_t policy_data1_size, void *policy_data2, uint32_t policy_data2_size, Error **errp) { + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + return 0; + } + if (policy_type != GUEST_POLICY_SEV) { error_setg(errp, "%s: Invalid guest policy type provided for SEV: %d", __func__, policy_type); From 2393fb93ed45109ecd2a07cf6e6444b1b4c8d7cb Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Tue, 1 Jul 2025 15:57:38 +0800 Subject: [PATCH 33/77] i386/cpu: Move the implementation of is_host_cpu_intel() host-cpu.c It's more proper to put is_host_cpu_intel() in host-cpu.c instead of vmsr_energy.c. Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250701075738.3451873-3-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/host-cpu.c | 9 +++++++++ target/i386/host-cpu.h | 1 + target/i386/kvm/vmsr_energy.c | 9 --------- target/i386/kvm/vmsr_energy.h | 1 - 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/target/i386/host-cpu.c b/target/i386/host-cpu.c index 3399edc1ad..e9a49e628f 100644 --- a/target/i386/host-cpu.c +++ b/target/i386/host-cpu.c @@ -161,6 +161,15 @@ void host_cpu_instance_init(X86CPU *cpu) &error_abort); } +bool is_host_cpu_intel(void) +{ + char vendor[CPUID_VENDOR_SZ + 1]; + + host_cpu_vendor_fms(vendor, NULL, NULL, NULL); + + return g_str_equal(vendor, CPUID_VENDOR_INTEL); +} + static void host_cpu_class_init(ObjectClass *oc, const void *data) { X86CPUClass *xcc = X86_CPU_CLASS(oc); diff --git a/target/i386/host-cpu.h b/target/i386/host-cpu.h index b97ec01c9b..10df4b3a3a 100644 --- a/target/i386/host-cpu.h +++ b/target/i386/host-cpu.h @@ -17,4 +17,5 @@ bool host_cpu_realizefn(CPUState *cs, Error **errp); void host_cpu_vendor_fms(char *vendor, int *family, int *model, int *stepping); +bool is_host_cpu_intel(void); #endif /* HOST_CPU_H */ diff --git a/target/i386/kvm/vmsr_energy.c b/target/i386/kvm/vmsr_energy.c index d6aad5246b..58ce3df53a 100644 --- a/target/i386/kvm/vmsr_energy.c +++ b/target/i386/kvm/vmsr_energy.c @@ -27,15 +27,6 @@ char *vmsr_compute_default_paths(void) return g_build_filename(state, "run", "qemu-vmsr-helper.sock", NULL); } -bool is_host_cpu_intel(void) -{ - char vendor[CPUID_VENDOR_SZ + 1]; - - host_cpu_vendor_fms(vendor, NULL, NULL, NULL); - - return g_str_equal(vendor, CPUID_VENDOR_INTEL); -} - int is_rapl_enabled(void) { const char *path = "/sys/class/powercap/intel-rapl/enabled"; diff --git a/target/i386/kvm/vmsr_energy.h b/target/i386/kvm/vmsr_energy.h index 16cc1f4814..151bcbd642 100644 --- a/target/i386/kvm/vmsr_energy.h +++ b/target/i386/kvm/vmsr_energy.h @@ -94,6 +94,5 @@ double vmsr_get_ratio(uint64_t e_delta, unsigned long long delta_ticks, unsigned int maxticks); void vmsr_init_topo_info(X86CPUTopoInfo *topo_info, const MachineState *ms); -bool is_host_cpu_intel(void); int is_rapl_enabled(void); #endif /* VMSR_ENERGY_H */ From 7e862c3526450fe7cb14e25b5200036c237baa03 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Mon, 30 Jun 2025 16:06:07 +0800 Subject: [PATCH 34/77] i386/cpu: Use CPUID_MODEL_ID_SZ instead of hardcoded 48 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is already the MACRO CPUID_MODEL_ID_SZ defined in QEMU. Use it to replace all the hardcoded 48. Opportunistically fix the indentation of CPUID_VENDOR_SZ. Signed-off-by: Xiaoyao Li Reviewed-by: Philippe Mathieu-DaudĂ© Link: https://lore.kernel.org/r/20250630080610.3151956-2-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 8 ++++---- target/i386/cpu.h | 3 ++- target/i386/host-cpu.c | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 44178bc523..70b742fcde 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6469,11 +6469,11 @@ static char *x86_cpuid_get_model_id(Object *obj, Error **errp) char *value; int i; - value = g_malloc(48 + 1); - for (i = 0; i < 48; i++) { + value = g_malloc(CPUID_MODEL_ID_SZ + 1); + for (i = 0; i < CPUID_MODEL_ID_SZ; i++) { value[i] = env->cpuid_model[i >> 2] >> (8 * (i & 3)); } - value[48] = '\0'; + value[CPUID_MODEL_ID_SZ] = '\0'; return value; } @@ -6488,7 +6488,7 @@ static void x86_cpuid_set_model_id(Object *obj, const char *model_id, model_id = ""; } len = strlen(model_id); - memset(env->cpuid_model, 0, 48); + memset(env->cpuid_model, 0, CPUID_MODEL_ID_SZ); for (i = 0; i < 48; i++) { if (i >= len) { c = '\0'; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 9829824ac8..b3bb988857 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1159,7 +1159,8 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); /* PMM enabled */ #define CPUID_C000_0001_EDX_PMM_EN (1U << 13) -#define CPUID_VENDOR_SZ 12 +#define CPUID_VENDOR_SZ 12 +#define CPUID_MODEL_ID_SZ 48 #define CPUID_VENDOR_INTEL_1 0x756e6547 /* "Genu" */ #define CPUID_VENDOR_INTEL_2 0x49656e69 /* "ineI" */ diff --git a/target/i386/host-cpu.c b/target/i386/host-cpu.c index e9a49e628f..b1fb6d6816 100644 --- a/target/i386/host-cpu.c +++ b/target/i386/host-cpu.c @@ -80,7 +80,6 @@ bool host_cpu_realizefn(CPUState *cs, Error **errp) return true; } -#define CPUID_MODEL_ID_SZ 48 /** * cpu_x86_fill_model_id: * Get CPUID model ID string from host CPU. From 58f17c4a4fe9fc78bcadcdceada4c094c4b1d2c8 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Mon, 30 Jun 2025 16:06:08 +0800 Subject: [PATCH 35/77] i386: Cleanup the usage of CPUID_VENDOR_INTEL_1 There are code using "env->cpuid_vendor1 == CPUID_VENDOR_INTEL_1" to check if it is Intel vcpu. Cleanup them to just use IS_INTEL_CPU() Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250630080610.3151956-3-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 2 +- target/i386/tcg/decode-new.c.inc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 70b742fcde..d3d13b1472 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7761,7 +7761,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = env->features[FEAT_8000_0001_ECX]; *edx = env->features[FEAT_8000_0001_EDX]; - if (tcg_enabled() && env->cpuid_vendor1 == CPUID_VENDOR_INTEL_1 && + if (tcg_enabled() && IS_INTEL_CPU(env) && !(env->hflags & HF_LMA_MASK)) { *edx &= ~CPUID_EXT2_SYSCALL; } diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 55216e0d24..853b1c8bf9 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2722,14 +2722,14 @@ static void disas_insn(DisasContext *s, CPUState *cpu) if (decode.e.check & X86_CHECK_i64) { goto illegal_op; } - if ((decode.e.check & X86_CHECK_i64_amd) && env->cpuid_vendor1 != CPUID_VENDOR_INTEL_1) { + if ((decode.e.check & X86_CHECK_i64_amd) && !IS_INTEL_CPU(env)) { goto illegal_op; } } else { if (decode.e.check & X86_CHECK_o64) { goto illegal_op; } - if ((decode.e.check & X86_CHECK_o64_intel) && env->cpuid_vendor1 == CPUID_VENDOR_INTEL_1) { + if ((decode.e.check & X86_CHECK_o64_intel) && IS_INTEL_CPU(env)) { goto illegal_op; } } From 3359b588f1f51c1ceaa72eb95cdc7e842d00a5b0 Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Mon, 30 Jun 2025 16:06:09 +0800 Subject: [PATCH 36/77] i386/kvm-cpu: Fix the indentation inside kvm_cpu_realizefn() The indentation of one of the } inside kvm_cpu_realizefn() isn'f correct. fix it. Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250630080610.3151956-4-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/kvm-cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index a99b876464..89a7953659 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -73,7 +73,7 @@ static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) if (env->features[FEAT_1_ECX] & CPUID_EXT_MONITOR) { host_cpuid(5, 0, &cpu->mwait.eax, &cpu->mwait.ebx, &cpu->mwait.ecx, &cpu->mwait.edx); - } + } } if (cpu->ucode_rev == 0) { cpu->ucode_rev = From b6c5b41ba28198030ce2f2b19cf333a9129d07dc Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Mon, 30 Jun 2025 16:06:10 +0800 Subject: [PATCH 37/77] i386/cpu: Unify family, model and stepping calculation for x86 CPU There are multiple places where CPUID family/model/stepping info are retrieved from env->cpuid_version. Besides, the calculation of family and model inside host_cpu_vendor_fms() doesn't comply to what Intel and AMD define. For family, both Intel and AMD define that Extended Family ID needs to be counted only when (base) Family is 0xF. For model, Intel counts Extended Model when (base) Family is 0x6 or 0xF, while AMD counts EXtended MOdel when (base) Family is 0xF. Introduce generic helper functions to get family, model and stepping from the EAX value of CPUID leaf 1, with the correct calculation formula. Signed-off-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250630080610.3151956-5-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 12 ++++-------- target/i386/cpu.h | 30 ++++++++++++++++++++++++++++++ target/i386/host-cpu.c | 6 +++--- target/i386/kvm/kvm.c | 2 +- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index d3d13b1472..b768838b10 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6325,10 +6325,7 @@ static void x86_cpuid_version_get_family(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = (env->cpuid_version >> 8) & 0xf; - if (value == 0xf) { - value += (env->cpuid_version >> 20) & 0xff; - } + value = x86_cpu_family(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -6366,8 +6363,7 @@ static void x86_cpuid_version_get_model(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = (env->cpuid_version >> 4) & 0xf; - value |= ((env->cpuid_version >> 16) & 0xf) << 4; + value = x86_cpu_model(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -6401,7 +6397,7 @@ static void x86_cpuid_version_get_stepping(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = env->cpuid_version & 0xf; + value = x86_cpu_stepping(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -8155,7 +8151,7 @@ static void mce_init(X86CPU *cpu) CPUX86State *cenv = &cpu->env; unsigned int bank; - if (((cenv->cpuid_version >> 8) & 0xf) >= 6 + if (x86_cpu_family(cenv->cpuid_version) >= 6 && (cenv->features[FEAT_1_EDX] & (CPUID_MCE | CPUID_MCA)) == (CPUID_MCE | CPUID_MCA)) { cenv->mcg_cap = MCE_CAP_DEF | MCE_BANKS_DEF | diff --git a/target/i386/cpu.h b/target/i386/cpu.h index b3bb988857..a580562b3d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2678,6 +2678,36 @@ static inline int32_t x86_get_a20_mask(CPUX86State *env) } } +static inline uint32_t x86_cpu_family(uint32_t eax) +{ + uint32_t family = (eax >> 8) & 0xf; + + if (family == 0xf) { + family += (eax >> 20) & 0xff; + } + + return family; +} + +static inline uint32_t x86_cpu_model(uint32_t eax) +{ + uint32_t family, model; + + family = x86_cpu_family(eax); + model = (eax >> 4) & 0xf; + + if (family >= 0x6) { + model += ((eax >> 16) & 0xf) << 4; + } + + return model; +} + +static inline uint32_t x86_cpu_stepping(uint32_t eax) +{ + return eax & 0xf; +} + static inline bool cpu_has_vmx(CPUX86State *env) { return env->features[FEAT_1_ECX] & CPUID_EXT_VMX; diff --git a/target/i386/host-cpu.c b/target/i386/host-cpu.c index b1fb6d6816..d5e2bb5e18 100644 --- a/target/i386/host-cpu.c +++ b/target/i386/host-cpu.c @@ -117,13 +117,13 @@ void host_cpu_vendor_fms(char *vendor, int *family, int *model, int *stepping) host_cpuid(0x1, 0, &eax, &ebx, &ecx, &edx); if (family) { - *family = ((eax >> 8) & 0x0F) + ((eax >> 20) & 0xFF); + *family = x86_cpu_family(eax); } if (model) { - *model = ((eax >> 4) & 0x0F) | ((eax & 0xF0000) >> 12); + *model = x86_cpu_model(eax); } if (stepping) { - *stepping = eax & 0x0F; + *stepping = x86_cpu_stepping(eax); } } diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index fc58a23b30..e8c8be09ba 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -2259,7 +2259,7 @@ int kvm_arch_init_vcpu(CPUState *cs) cpuid_i = kvm_x86_build_cpuid(env, cpuid_data.entries, cpuid_i); cpuid_data.cpuid.nent = cpuid_i; - if (((env->cpuid_version >> 8)&0xF) >= 6 + if (x86_cpu_family(env->cpuid_version) >= 6 && (env->features[FEAT_1_EDX] & (CPUID_MCE | CPUID_MCA)) == (CPUID_MCE | CPUID_MCA)) { uint64_t mcg_cap, unsupported_caps; From 50fd57418c3f08f13eb964dcb49f065246f2ecbf Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Wed, 25 Jun 2025 11:55:05 +0800 Subject: [PATCH 38/77] i386/tdx: Remove task->watch only when it's valid In some case (e.g., failed to connect to QGS socket), tdx_generate_quote_cleanup() is called with task->watch invalid. It triggers assertion of qemu-system-x86_64: GLib: g_source_remove: assertion 'tag > 0' failed Fix it by checking task->watch. Fixes: 40da501d8989 ("i386/tdx: handle TDG.VP.VMCALL") Signed-off-by: Xiaoyao Li Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250625035505.2770580-1-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/kvm/tdx-quote-generator.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/i386/kvm/tdx-quote-generator.c b/target/i386/kvm/tdx-quote-generator.c index f59715f617..dee8334b27 100644 --- a/target/i386/kvm/tdx-quote-generator.c +++ b/target/i386/kvm/tdx-quote-generator.c @@ -75,7 +75,9 @@ static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task) { timer_del(&task->timer); - g_source_remove(task->watch); + if (task->watch) { + g_source_remove(task->watch); + } qio_channel_close(QIO_CHANNEL(task->sioc), NULL); object_unref(OBJECT(task->sioc)); From 7ff24fb657d35c014f735f69aef03810fde607ab Mon Sep 17 00:00:00 2001 From: Xiaoyao Li Date: Wed, 25 Jun 2025 11:57:10 +0800 Subject: [PATCH 39/77] i386/tdx: Don't mask off CPUID_EXT_PDCM It gets below warning when booting TDX VMs: warning: TDX forcibly sets the feature: CPUID[eax=01h].ECX.pdcm [bit 15] Because CPUID_EXT_PDCM is fixed1 for TDX, and MSR_IA32_PERF_CAPABILITIES is supported for TDX guest unconditioanlly. Don't mask off CPUID_EXT_PDCM for TDX. Signed-off-by: Xiaoyao Li Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250625035710.2770679-1-xiaoyao.li@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b768838b10..f9e6bc8d0e 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -28,6 +28,7 @@ #include "system/hvf.h" #include "hvf/hvf-i386.h" #include "kvm/kvm_i386.h" +#include "kvm/tdx.h" #include "sev.h" #include "qapi/error.h" #include "qemu/error-report.h" @@ -8336,7 +8337,8 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) } } - if (!cpu->enable_pmu) { + /* PDCM is fixed1 bit for TDX */ + if (!cpu->enable_pmu && !is_tdx_vm()) { mark_unavailable_features(cpu, FEAT_1_ECX, env->user_features[FEAT_1_ECX] & CPUID_EXT_PDCM, "This feature is not available due to PMU being disabled"); From 1034b94fe98da35857f261d4db89ce2ebed8c5c3 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:26 +0800 Subject: [PATCH 40/77] i386/cpu: Refine comment of CPUID2CacheDescriptorInfo Refer to SDM vol.3 table 1-21, add the notes about the missing descriptor, and fix the typo and comment format. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-2-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index f9e6bc8d0e..f107e58673 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -68,6 +68,7 @@ struct CPUID2CacheDescriptorInfo { /* * Known CPUID 2 cache descriptors. + * TLB, prefetch and sectored cache related descriptors are not included. * From Intel SDM Volume 2A, CPUID instruction */ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { @@ -89,18 +90,29 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 2, .line_size = 64, }, [0x21] = { .level = 2, .type = UNIFIED_CACHE, .size = 256 * KiB, .associativity = 8, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x22, 0x23 are not included - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x22, 0x23 are not included + */ [0x24] = { .level = 2, .type = UNIFIED_CACHE, .size = 1 * MiB, .associativity = 16, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x25, 0x20 are not included - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x25, 0x29 are not included + */ [0x2C] = { .level = 1, .type = DATA_CACHE, .size = 32 * KiB, .associativity = 8, .line_size = 64, }, [0x30] = { .level = 1, .type = INSTRUCTION_CACHE, .size = 32 * KiB, .associativity = 8, .line_size = 64, }, + /* + * Newer Intel CPUs (having the cores without L3, e.g., Intel MTL, ARL) + * use CPUID 0x4 leaf to describe cache topology, by encoding CPUID 0x2 + * leaf with 0xFF. For older CPUs (without 0x4 leaf), it's also valid + * to just ignore L3's code if there's no L3. + * + * This already covers all the cases in QEMU, so code 0x40 is not + * included. + */ [0x41] = { .level = 2, .type = UNIFIED_CACHE, .size = 128 * KiB, .associativity = 4, .line_size = 32, }, [0x42] = { .level = 2, .type = UNIFIED_CACHE, .size = 256 * KiB, @@ -138,9 +150,10 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 4, .line_size = 64, }, [0x78] = { .level = 2, .type = UNIFIED_CACHE, .size = 1 * MiB, .associativity = 4, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x79, 0x7A, 0x7B, 0x7C are not included. - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x79, 0x7A, 0x7B, 0x7C are not included. + */ [0x7D] = { .level = 2, .type = UNIFIED_CACHE, .size = 2 * MiB, .associativity = 8, .line_size = 64, }, [0x7F] = { .level = 2, .type = UNIFIED_CACHE, .size = 512 * KiB, From 67d54014afcfdd316a0e9c979a0ff8145d4cf963 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:27 +0800 Subject: [PATCH 41/77] i386/cpu: Add descriptor 0x49 for CPUID 0x2 encoding The legacy_l2_cache (2nd-level cache: 4 MByte, 16-way set associative, 64 byte line size) corresponds to descriptor 0x49, but at present cpuid2_cache_descriptors doesn't support descriptor 0x49 because it has multiple meanings. The 0x49 is necessary when CPUID 0x2 and 0x4 leaves have the consistent cache model, and use legacy_l2_cache as the default L2 cache. Therefore, add descriptor 0x49 to represent general L2 cache. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index f107e58673..4386b60ff6 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -129,7 +129,18 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 8, .line_size = 64, }, [0x48] = { .level = 2, .type = UNIFIED_CACHE, .size = 3 * MiB, .associativity = 12, .line_size = 64, }, - /* Descriptor 0x49 depends on CPU family/model, so it is not included */ + /* + * Descriptor 0x49 has 2 cases: + * - 2nd-level cache: 4 MByte, 16-way set associative, 64 byte line size. + * - 3rd-level cache: 4MB, 16-way set associative, 64-byte line size + * (Intel Xeon processor MP, Family 0FH, Model 06H). + * + * When it represents L3, then it depends on CPU family/model. Fortunately, + * the legacy cache/CPU models don't have such special L3. So, just add it + * to represent the general L2 case. + */ + [0x49] = { .level = 2, .type = UNIFIED_CACHE, .size = 4 * MiB, + .associativity = 16, .line_size = 64, }, [0x4A] = { .level = 3, .type = UNIFIED_CACHE, .size = 6 * MiB, .associativity = 12, .line_size = 64, }, [0x4B] = { .level = 3, .type = UNIFIED_CACHE, .size = 8 * MiB, From 2c70a50c8c3d8620f34884b340778358b6faf5d4 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:28 +0800 Subject: [PATCH 42/77] i386/cpu: Add default cache model for Intel CPUs with level < 4 Old Intel CPUs with CPUID level < 4, use CPUID 0x2 leaf (if available) to encode cache information. Introduce a cache model "legacy_intel_cpuid2_cache_info" for the CPUs with CPUID level < 4, based on legacy_l1d_cache, legacy_l1i_cache, legacy_l2_cache_cpuid2 and legacy_l3_cache. But for L2 cache, this cache model completes self_init, sets, partitions, no_invd_sharing and share_level fields, referring legacy_l2_cache, to avoid someone increases CPUID level manually and meets assert() error. But the cache information present in CPUID 0x2 leaf doesn't change. This new cache model makes it possible to remove legacy_l2_cache_cpuid2 in X86CPUState and help to clarify historical cache inconsistency issue. Furthermore, apply this legacy cache model to all Intel CPUs with CPUID level < 4. This includes not only "pentium2" and "pentium3" (which have 0x2 leaf), but also "486" and "pentium" (which only have 0x1 leaf, and cache model won't be presented, just for simplicity). A legacy_intel_cpuid2_cache_info cache model doesn't change the cache information of the above CPUs, because they just depend on 0x2 leaf. Only when someone adjusts the min-level to >=4 will the cache information in CPUID leaf 4 differ from before: previously, the L2 cache information in CPUID leaf 0x2 and 0x4 was different, but now with legacy_intel_cpuid2_cache_info, the information they present will be consistent. This case almost never happens, emulating a CPUID that is not supported by the "ancient" hardware is itself meaningless behavior. Therefore, even though there's the above difference (for really rare case) and considering these old CPUs ("486", "pentium", "pentium2" and "pentium3") won't be used for migration, there's no need to add new versioned CPU models Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-4-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 4386b60ff6..3278d5de5a 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -712,6 +712,67 @@ static CPUCacheInfo legacy_l3_cache = { .share_level = CPU_TOPOLOGY_LEVEL_DIE, }; +/* + * Only used for the CPU models with CPUID level < 4. + * These CPUs (CPUID level < 4) only use CPUID leaf 2 to present + * cache information. + * + * Note: This cache model is just a default one, and is not + * guaranteed to match real hardwares. + */ +static const CPUCaches legacy_intel_cpuid2_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 2 * MiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 4096, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, +}; + /* TLB definitions: */ #define L1_DTLB_2M_ASSOC 1 @@ -3045,6 +3106,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { I486_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium", @@ -3057,6 +3119,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium2", @@ -3069,6 +3132,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM2_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium3", @@ -3081,6 +3145,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM3_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "athlon", From 6699e5dc864b0793e4fd90ef8038f6c71fcd0d47 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:29 +0800 Subject: [PATCH 43/77] i386/cpu: Present same cache model in CPUID 0x2 & 0x4 For a long time, the default cache models used in CPUID 0x2 and 0x4 were inconsistent and had a FIXME note from Eduardo at commit 5e891bf8fd50 ("target-i386: Use #defines instead of magic numbers for CPUID cache info"): "/*FIXME: CPUID leaf 2 descriptor is inconsistent with CPUID leaf 4 */". This difference is wrong, in principle, both 0x2 and 0x4 are used for Intel's cache description. 0x2 leaf is used for ancient machines while 0x4 leaf is a subsequent addition, and both should be based on the same cache model. Furthermore, on real hardware, 0x4 leaf should be used in preference to 0x2 when it is available. Revisiting the git history, that difference occurred much earlier. Current legacy_l2_cache_cpuid2 (hardcode: "0x2c307d"), which is used for CPUID 0x2 leaf, is introduced in commit d8134d91d9b7 ("Intel cache info, by Filip Navara."). Its commit message didn't said anything, but its patch [1] mentioned the cache model chosen is "closest to the ones reported in the AMD registers". Now it is not possible to check which AMD generation this cache model is based on (unfortunately, AMD does not use 0x2 leaf), but at least it is close to the Pentium 4. In fact, the patch description of commit d8134d91d9b7 is also a bit wrong, the original cache model in leaf 2 is from Pentium Pro, and its cache descriptor had specified the cache line size ad 32 byte by default, while the updated cache model in commit d8134d91d9b7 has 64 byte line size. But after so many years, such judgments are no longer meaningful. On the other hand, for legacy_l2_cache, which is used in CPUID 0x4 leaf, is based on Intel Core Duo (patch [2]) and Core2 Duo (commit e737b32a3688 ("Core 2 Duo specification (Alexander Graf).") The patches of Core Duo and Core 2 Duo add the cache model for CPUID 0x4, but did not update CPUID 0x2 encoding. This is the reason that Intel Guests use two cache models in 0x2 and 0x4 all the time. Of course, while no Core Duo or Core 2 Duo machines have been found for double checking, this still makes no sense to encode different cache models on a single machine. Referring to the SDM and the real hardware available, 0x2 leaf can be directly encoded 0xFF to instruct software to go to 0x4 leaf to get the cache information, when 0x4 is available. Therefore, it's time to clean up Intel's default cache models. As the first step, add "x-consistent-cache" compat option to allow newer machines (v10.1 and newer) to have the consistent cache model in CPUID 0x2 and 0x4 leaves. This doesn't affect the CPU models with CPUID level < 4 ("486", "pentium", "pentium2" and "pentium3"), because they have already had the special default cache model - legacy_intel_cpuid2_cache_info. [1]: https://lore.kernel.org/qemu-devel/5b31733c0709081227w3e5f1036odbc649edfdc8c79b@mail.gmail.com/ [2]: https://lore.kernel.org/qemu-devel/478B65C8.2080602@csgraf.de/ Cc: Alexander Graf Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/i386/pc.c | 4 +++- target/i386/cpu.c | 7 ++++++- target/i386/cpu.h | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 432ab288a8..a6fa792368 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -81,7 +81,9 @@ { "qemu64-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },\ { "athlon-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, }, -GlobalProperty pc_compat_10_0[] = {}; +GlobalProperty pc_compat_10_0[] = { + { TYPE_X86_CPU, "x-consistent-cache", "false" }, +}; const size_t pc_compat_10_0_len = G_N_ELEMENTS(pc_compat_10_0); GlobalProperty pc_compat_9_2[] = {}; diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 3278d5de5a..1f27fcf3ee 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8935,7 +8935,11 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) /* Build legacy cache information */ env->cache_info_cpuid2.l1d_cache = &legacy_l1d_cache; env->cache_info_cpuid2.l1i_cache = &legacy_l1i_cache; - env->cache_info_cpuid2.l2_cache = &legacy_l2_cache_cpuid2; + if (!cpu->consistent_cache) { + env->cache_info_cpuid2.l2_cache = &legacy_l2_cache_cpuid2; + } else { + env->cache_info_cpuid2.l2_cache = &legacy_l2_cache; + } env->cache_info_cpuid2.l3_cache = &legacy_l3_cache; env->cache_info_cpuid4.l1d_cache = &legacy_l1d_cache; @@ -9461,6 +9465,7 @@ static const Property x86_cpu_properties[] = { * own cache information (see x86_cpu_load_def()). */ DEFINE_PROP_BOOL("legacy-cache", X86CPU, legacy_cache, true), + DEFINE_PROP_BOOL("x-consistent-cache", X86CPU, consistent_cache, true), DEFINE_PROP_BOOL("legacy-multi-node", X86CPU, legacy_multi_node, false), DEFINE_PROP_BOOL("xen-vapic", X86CPU, xen_vapic, false), diff --git a/target/i386/cpu.h b/target/i386/cpu.h index a580562b3d..a3ebd3e08c 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2259,6 +2259,13 @@ struct ArchCPU { */ bool legacy_cache; + /* + * Compatibility bits for old machine types. + * If true, use the same cache model in CPUID leaf 0x2 + * and 0x4. + */ + bool consistent_cache; + /* Compatibility bits for old machine types. * If true decode the CPUID Function 0x8000001E_ECX to support multiple * nodes per processor From fe77a78149359485459db26814af436cfc873afe Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:30 +0800 Subject: [PATCH 44/77] i386/cpu: Consolidate CPUID 0x4 leaf Modern Intel CPUs use CPUID 0x4 leaf to describe cache information and leave space in 0x2 for prefetch and TLBs (even TLB has its own leaf CPUID 0x18). And 0x2 leaf provides a descriptor 0xFF to instruct software to check cache information in 0x4 leaf instead. Therefore, follow this behavior to encode 0xFF when Intel CPU has 0x4 leaf with "x-consistent-cache=true" for compatibility. In addition, for older CPUs without 0x4 leaf, still enumerate the cache descriptor in 0x2 leaf, except the case that there's no descriptor matching the cache model, then directly encode 0xFF in 0x2 leaf. This makes sense, as in the 0x2 leaf era, all supported caches should have the corresponding descriptor. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-6-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 48 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 1f27fcf3ee..812e85b952 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -225,7 +225,7 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { * Return a CPUID 2 cache descriptor for a given cache. * If no known descriptor is found, return CACHE_DESCRIPTOR_UNAVAILABLE */ -static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache) +static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache, bool *unmacthed) { int i; @@ -242,9 +242,44 @@ static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache) } } + *unmacthed |= true; return CACHE_DESCRIPTOR_UNAVAILABLE; } +/* Encode cache info for CPUID[2] */ +static void encode_cache_cpuid2(X86CPU *cpu, + uint32_t *eax, uint32_t *ebx, + uint32_t *ecx, uint32_t *edx) +{ + CPUX86State *env = &cpu->env; + CPUCaches *caches = &env->cache_info_cpuid2; + int l1d, l1i, l2, l3; + bool unmatched = false; + + *eax = 1; /* Number of CPUID[EAX=2] calls required */ + *ebx = *ecx = *edx = 0; + + l1d = cpuid2_cache_descriptor(caches->l1d_cache, &unmatched); + l1i = cpuid2_cache_descriptor(caches->l1i_cache, &unmatched); + l2 = cpuid2_cache_descriptor(caches->l2_cache, &unmatched); + l3 = cpuid2_cache_descriptor(caches->l3_cache, &unmatched); + + if (!cpu->consistent_cache || + (env->cpuid_min_level < 0x4 && !unmatched)) { + /* + * Though SDM defines code 0x40 for cases with no L2 or L3. It's + * also valid to just ignore l3's code if there's no l2. + */ + if (cpu->enable_l3_cache) { + *ecx = l3; + } + *edx = (l1d << 16) | (l1i << 8) | l2; + } else { + *ecx = 0; + *edx = CACHE_DESCRIPTOR_UNAVAILABLE; + } +} + /* CPUID Leaf 4 constants: */ /* EAX: */ @@ -7446,16 +7481,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *eax = *ebx = *ecx = *edx = 0; break; } - *eax = 1; /* Number of CPUID[EAX=2] calls required */ - *ebx = 0; - if (!cpu->enable_l3_cache) { - *ecx = 0; - } else { - *ecx = cpuid2_cache_descriptor(env->cache_info_cpuid2.l3_cache); - } - *edx = (cpuid2_cache_descriptor(env->cache_info_cpuid2.l1d_cache) << 16) | - (cpuid2_cache_descriptor(env->cache_info_cpuid2.l1i_cache) << 8) | - (cpuid2_cache_descriptor(env->cache_info_cpuid2.l2_cache)); + encode_cache_cpuid2(cpu, eax, ebx, ecx, edx); break; case 4: /* cache info: needed for Core compatibility */ From c416411c28a2b21e590943f300ce0336f109abb5 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:31 +0800 Subject: [PATCH 45/77] i386/cpu: Drop CPUID 0x2 specific cache info in X86CPUState With the pre-defined cache model legacy_intel_cpuid2_cache_info, for X86CPUState there's no need to cache special cache information for CPUID 0x2 leaf. Drop the cache_info_cpuid2 field of X86CPUState and use the legacy_intel_cpuid2_cache_info directly. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-7-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 31 +++++++++++-------------------- target/i386/cpu.h | 3 ++- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 812e85b952..ac22548f47 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -246,19 +246,27 @@ static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache, bool *unmacthed) return CACHE_DESCRIPTOR_UNAVAILABLE; } +static const CPUCaches legacy_intel_cpuid2_cache_info; + /* Encode cache info for CPUID[2] */ static void encode_cache_cpuid2(X86CPU *cpu, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { CPUX86State *env = &cpu->env; - CPUCaches *caches = &env->cache_info_cpuid2; + const CPUCaches *caches; int l1d, l1i, l2, l3; bool unmatched = false; *eax = 1; /* Number of CPUID[EAX=2] calls required */ *ebx = *ecx = *edx = 0; + if (env->enable_legacy_cpuid2_cache) { + caches = &legacy_intel_cpuid2_cache_info; + } else { + caches = &env->cache_info_cpuid4; + } + l1d = cpuid2_cache_descriptor(caches->l1d_cache, &unmatched); l1i = cpuid2_cache_descriptor(caches->l1i_cache, &unmatched); l2 = cpuid2_cache_descriptor(caches->l2_cache, &unmatched); @@ -707,17 +715,6 @@ static CPUCacheInfo legacy_l2_cache = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/*FIXME: CPUID leaf 2 descriptor is inconsistent with CPUID leaf 4 */ -static CPUCacheInfo legacy_l2_cache_cpuid2 = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 2 * MiB, - .line_size = 64, - .associativity = 8, - .share_level = CPU_TOPOLOGY_LEVEL_INVALID, -}; - - /*FIXME: CPUID leaf 0x80000006 is inconsistent with leaves 2 & 4 */ static CPUCacheInfo legacy_l2_cache_amd = { .type = UNIFIED_CACHE, @@ -8955,18 +8952,12 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) "CPU model '%s' doesn't support legacy-cache=off", name); return; } - env->cache_info_cpuid2 = env->cache_info_cpuid4 = env->cache_info_amd = - *cache_info; + env->cache_info_cpuid4 = env->cache_info_amd = *cache_info; } else { /* Build legacy cache information */ - env->cache_info_cpuid2.l1d_cache = &legacy_l1d_cache; - env->cache_info_cpuid2.l1i_cache = &legacy_l1i_cache; if (!cpu->consistent_cache) { - env->cache_info_cpuid2.l2_cache = &legacy_l2_cache_cpuid2; - } else { - env->cache_info_cpuid2.l2_cache = &legacy_l2_cache; + env->enable_legacy_cpuid2_cache = true; } - env->cache_info_cpuid2.l3_cache = &legacy_l3_cache; env->cache_info_cpuid4.l1d_cache = &legacy_l1d_cache; env->cache_info_cpuid4.l1i_cache = &legacy_l1i_cache; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index a3ebd3e08c..d3f7c53e30 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2077,7 +2077,8 @@ typedef struct CPUArchState { * on each CPUID leaf will be different, because we keep compatibility * with old QEMU versions. */ - CPUCaches cache_info_cpuid2, cache_info_cpuid4, cache_info_amd; + CPUCaches cache_info_cpuid4, cache_info_amd; + bool enable_legacy_cpuid2_cache; /* MTRRs */ uint64_t mtrr_fixed[11]; From 216d9bb6d77162a93a0f09d72fdabfd252d941ce Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:32 +0800 Subject: [PATCH 46/77] i386/cpu: Add x-vendor-cpuid-only-v2 option for compatibility Add a compat property "x-vendor-cpuid-only-v2" (for PC machine v10.0 and older) to keep the original behavior. This property will be used to adjust vendor specific CPUID fields. Make x-vendor-cpuid-only-v2 depend on x-vendor-cpuid-only. Although x-vendor-cpuid-only and v2 should be initernal only, QEMU doesn't support "internal" property. To avoid any other unexpected issues, check the dependency. Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-8-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/i386/pc.c | 1 + target/i386/cpu.c | 10 ++++++++++ target/i386/cpu.h | 11 ++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index a6fa792368..7cfa61c9ee 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -83,6 +83,7 @@ GlobalProperty pc_compat_10_0[] = { { TYPE_X86_CPU, "x-consistent-cache", "false" }, + { TYPE_X86_CPU, "x-vendor-cpuid-only-v2", "false" }, }; const size_t pc_compat_10_0_len = G_N_ELEMENTS(pc_compat_10_0); diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ac22548f47..630a40d72b 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8749,6 +8749,16 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) tcg_cflags_set(cs, CF_PCREL); #endif + /* + * x-vendor-cpuid-only and v2 should be initernal only. But + * QEMU doesn't support "internal" property. + */ + if (!cpu->vendor_cpuid_only && cpu->vendor_cpuid_only_v2) { + error_setg(errp, "x-vendor-cpuid-only-v2 property " + "depends on x-vendor-cpuid-only"); + return; + } + if (cpu->apic_id == UNASSIGNED_APIC_ID) { error_setg(errp, "apic-id property was not initialized properly"); return; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index d3f7c53e30..d88481ba8e 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2282,9 +2282,18 @@ struct ArchCPU { /* Enable auto level-increase for all CPUID leaves */ bool full_cpuid_auto_level; - /* Only advertise CPUID leaves defined by the vendor */ + /* + * Compatibility bits for old machine types (PC machine v6.0 and older). + * Only advertise CPUID leaves defined by the vendor. + */ bool vendor_cpuid_only; + /* + * Compatibility bits for old machine types (PC machine v10.0 and older). + * Only advertise CPUID leaves defined by the vendor. + */ + bool vendor_cpuid_only_v2; + /* Only advertise TOPOEXT features that AMD defines */ bool amd_topoext_features_only; From 225aad5a7b78b3dc017996ab5b5e1b9249c036a0 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:33 +0800 Subject: [PATCH 47/77] i386/cpu: Mark CPUID[0x80000005] as reserved for Intel Per SDM, 0x80000005 leaf is reserved for Intel CPU, and its current "assert" check blocks adding new cache model for non-AMD CPUs. And please note, although Zhaoxin mostly follows Intel behavior, this leaf is an exception [1]. So, with the compat property "x-vendor-cpuid-only-v2", for the machine since v10.1, check the vendor and encode this leaf as all-0 only for Intel CPU. In addition, drop lines_per_tag assertion in encode_cache_cpuid80000005(), since Zhaoxin will use legacy Intel cache model in this leaf - which doesn't have this field. This fix also resolves 2 FIXMEs of legacy_l1d_cache_amd and legacy_l1i_cache_amd: /*FIXME: CPUID leaf 0x80000005 is inconsistent with leaves 2 & 4 */ In addition, per AMD's APM, update the comment of CPUID[0x80000005]. [1]: https://lore.kernel.org/qemu-devel/fa16f7a8-4917-4731-9d9f-7d4c10977168@zhaoxin.com/ Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-9-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 630a40d72b..2abcb5acff 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -499,7 +499,6 @@ static void encode_topo_cpuid1f(CPUX86State *env, uint32_t count, static uint32_t encode_cache_cpuid80000005(CPUCacheInfo *cache) { assert(cache->size % 1024 == 0); - assert(cache->lines_per_tag > 0); assert(cache->associativity > 0); assert(cache->line_size > 0); return ((cache->size / 1024) << 24) | (cache->associativity << 16) | @@ -657,7 +656,6 @@ static CPUCacheInfo legacy_l1d_cache = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/*FIXME: CPUID leaf 0x80000005 is inconsistent with leaves 2 & 4 */ static CPUCacheInfo legacy_l1d_cache_amd = { .type = DATA_CACHE, .level = 1, @@ -686,7 +684,6 @@ static CPUCacheInfo legacy_l1i_cache = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/*FIXME: CPUID leaf 0x80000005 is inconsistent with leaves 2 & 4 */ static CPUCacheInfo legacy_l1i_cache_amd = { .type = INSTRUCTION_CACHE, .level = 1, @@ -7884,11 +7881,17 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = env->cpuid_model[(index - 0x80000002) * 4 + 3]; break; case 0x80000005: - /* cache info (L1 cache) */ + /* cache info (L1 cache/TLB Associativity Field) */ if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; } + + if (cpu->vendor_cpuid_only_v2 && IS_INTEL_CPU(env)) { + *eax = *ebx = *ecx = *edx = 0; + break; + } + *eax = (L1_DTLB_2M_ASSOC << 24) | (L1_DTLB_2M_ENTRIES << 16) | (L1_ITLB_2M_ASSOC << 8) | (L1_ITLB_2M_ENTRIES); *ebx = (L1_DTLB_4K_ASSOC << 24) | (L1_DTLB_4K_ENTRIES << 16) | @@ -9478,6 +9481,7 @@ static const Property x86_cpu_properties[] = { DEFINE_PROP_STRING("hv-vendor-id", X86CPU, hyperv_vendor), DEFINE_PROP_BOOL("cpuid-0xb", X86CPU, enable_cpuid_0xb, true), DEFINE_PROP_BOOL("x-vendor-cpuid-only", X86CPU, vendor_cpuid_only, true), + DEFINE_PROP_BOOL("x-vendor-cpuid-only-v2", X86CPU, vendor_cpuid_only_v2, true), DEFINE_PROP_BOOL("x-amd-topoext-features-only", X86CPU, amd_topoext_features_only, true), DEFINE_PROP_BOOL("lmce", X86CPU, enable_lmce, false), DEFINE_PROP_BOOL("l3-cache", X86CPU, enable_l3_cache, true), From 3275a379c8ff0862550910a97e8c0c9b056643cd Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:34 +0800 Subject: [PATCH 48/77] i386/cpu: Rename AMD_ENC_ASSOC to X86_ENC_ASSOC Rename AMD_ENC_ASSOC to X86_ENC_ASSOC since Intel also uses the same rules. While there are some slight differences between the rules in AMD APM v4.07 no.40332 and Intel. But considerring the needs of current QEMU, generally they are consistent and current AMD_ENC_ASSOC can be applied for Intel CPUs.. Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-10-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 2abcb5acff..8a97272b4a 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -507,8 +507,8 @@ static uint32_t encode_cache_cpuid80000005(CPUCacheInfo *cache) #define ASSOC_FULL 0xFF -/* AMD associativity encoding used on CPUID Leaf 0x80000006: */ -#define AMD_ENC_ASSOC(a) (a <= 1 ? a : \ +/* x86 associativity encoding used on CPUID Leaf 0x80000006: */ +#define X86_ENC_ASSOC(a) (a <= 1 ? a : \ a == 2 ? 0x2 : \ a == 4 ? 0x4 : \ a == 8 ? 0x6 : \ @@ -534,7 +534,7 @@ static void encode_cache_cpuid80000006(CPUCacheInfo *l2, assert(l2->lines_per_tag > 0); assert(l2->line_size > 0); *ecx = ((l2->size / 1024) << 16) | - (AMD_ENC_ASSOC(l2->associativity) << 12) | + (X86_ENC_ASSOC(l2->associativity) << 12) | (l2->lines_per_tag << 8) | (l2->line_size); if (l3) { @@ -543,7 +543,7 @@ static void encode_cache_cpuid80000006(CPUCacheInfo *l2, assert(l3->lines_per_tag > 0); assert(l3->line_size > 0); *edx = ((l3->size / (512 * 1024)) << 18) | - (AMD_ENC_ASSOC(l3->associativity) << 12) | + (X86_ENC_ASSOC(l3->associativity) << 12) | (l3->lines_per_tag << 8) | (l3->line_size); } else { *edx = 0; @@ -7905,13 +7905,13 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; } - *eax = (AMD_ENC_ASSOC(L2_DTLB_2M_ASSOC) << 28) | + *eax = (X86_ENC_ASSOC(L2_DTLB_2M_ASSOC) << 28) | (L2_DTLB_2M_ENTRIES << 16) | - (AMD_ENC_ASSOC(L2_ITLB_2M_ASSOC) << 12) | + (X86_ENC_ASSOC(L2_ITLB_2M_ASSOC) << 12) | (L2_ITLB_2M_ENTRIES); - *ebx = (AMD_ENC_ASSOC(L2_DTLB_4K_ASSOC) << 28) | + *ebx = (X86_ENC_ASSOC(L2_DTLB_4K_ASSOC) << 28) | (L2_DTLB_4K_ENTRIES << 16) | - (AMD_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | + (X86_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | (L2_ITLB_4K_ENTRIES); encode_cache_cpuid80000006(env->cache_info_amd.l2_cache, cpu->enable_l3_cache ? From 63dc9f52f63475f61404a7f5a010aa352ead8763 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:35 +0800 Subject: [PATCH 49/77] i386/cpu: Fix CPUID[0x80000006] for Intel CPU Per SDM, Intel supports CPUID[0x80000006]. But only L2 information is encoded in ECX (note that L2 associativity field encodings rules consistent with AMD are used), all other fields are reserved. Therefore, make the following changes to CPUID[0x80000006]: * Check the vendor in CPUID[0x80000006] and just encode L2 to ECX for Intel. * Drop the lines_per_tag assertion, since AMD supports this field but Intel doesn't. And this field can be easily checked via cpuid tool in Guest. * Apply the encoding change of Intel for Zhaoxin as well [1]. This fix also resolves the FIXME of legacy_l2_cache_amd: /*FIXME: CPUID leaf 0x80000006 is inconsistent with leaves 2 & 4 */ In addition, per AMD's APM, update the comment of CPUID[0x80000006]. [1]: https://lore.kernel.org/qemu-devel/c522ebb5-04d5-49c6-9ad8-d755b8998988@zhaoxin.com/ Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-11-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 8a97272b4a..6a0e8592bb 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -531,16 +531,15 @@ static void encode_cache_cpuid80000006(CPUCacheInfo *l2, { assert(l2->size % 1024 == 0); assert(l2->associativity > 0); - assert(l2->lines_per_tag > 0); assert(l2->line_size > 0); *ecx = ((l2->size / 1024) << 16) | (X86_ENC_ASSOC(l2->associativity) << 12) | (l2->lines_per_tag << 8) | (l2->line_size); + /* For Intel, EDX is reserved. */ if (l3) { assert(l3->size % (512 * 1024) == 0); assert(l3->associativity > 0); - assert(l3->lines_per_tag > 0); assert(l3->line_size > 0); *edx = ((l3->size / (512 * 1024)) << 18) | (X86_ENC_ASSOC(l3->associativity) << 12) | @@ -712,7 +711,6 @@ static CPUCacheInfo legacy_l2_cache = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/*FIXME: CPUID leaf 0x80000006 is inconsistent with leaves 2 & 4 */ static CPUCacheInfo legacy_l2_cache_amd = { .type = UNIFIED_CACHE, .level = 2, @@ -7900,11 +7898,20 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = encode_cache_cpuid80000005(env->cache_info_amd.l1i_cache); break; case 0x80000006: - /* cache info (L2 cache) */ + /* cache info (L2 cache/TLB/L3 cache) */ if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; } + + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *eax = *ebx = 0; + encode_cache_cpuid80000006(env->cache_info_cpuid4.l2_cache, + NULL, ecx, edx); + break; + } + *eax = (X86_ENC_ASSOC(L2_DTLB_2M_ASSOC) << 28) | (L2_DTLB_2M_ENTRIES << 16) | (X86_ENC_ASSOC(L2_ITLB_2M_ASSOC) << 12) | @@ -7913,6 +7920,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, (L2_DTLB_4K_ENTRIES << 16) | (X86_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | (L2_ITLB_4K_ENTRIES); + encode_cache_cpuid80000006(env->cache_info_amd.l2_cache, cpu->enable_l3_cache ? env->cache_info_amd.l3_cache : NULL, From a9b6cca2df62ab3b0ba03edb3229a0eda385f65b Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:36 +0800 Subject: [PATCH 50/77] i386/cpu: Add legacy_intel_cache_info cache model Based on legacy_l1d_cache, legacy_l1i_cache, legacy_l2_cache and legacy_l3_cache, build a complete legacy intel cache model, which can clarify the purpose of these trivial legacy cache models, simplify the initialization of cache info in X86CPUState, and make it easier to handle compatibility later. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-12-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 101 +++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 6a0e8592bb..d265dc8156 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -640,21 +640,6 @@ static void encode_topo_cpuid8000001e(X86CPU *cpu, X86CPUTopoInfo *topo_info, * These are legacy cache values. If there is a need to change any * of these values please use builtin_x86_defs */ - -/* L1 data cache: */ -static CPUCacheInfo legacy_l1d_cache = { - .type = DATA_CACHE, - .level = 1, - .size = 32 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 8, - .sets = 64, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - static CPUCacheInfo legacy_l1d_cache_amd = { .type = DATA_CACHE, .level = 1, @@ -669,20 +654,6 @@ static CPUCacheInfo legacy_l1d_cache_amd = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/* L1 instruction cache: */ -static CPUCacheInfo legacy_l1i_cache = { - .type = INSTRUCTION_CACHE, - .level = 1, - .size = 32 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 8, - .sets = 64, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - static CPUCacheInfo legacy_l1i_cache_amd = { .type = INSTRUCTION_CACHE, .level = 1, @@ -697,20 +668,6 @@ static CPUCacheInfo legacy_l1i_cache_amd = { .share_level = CPU_TOPOLOGY_LEVEL_CORE, }; -/* Level 2 unified cache: */ -static CPUCacheInfo legacy_l2_cache = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 4 * MiB, - .self_init = 1, - .line_size = 64, - .associativity = 16, - .sets = 4096, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - static CPUCacheInfo legacy_l2_cache_amd = { .type = UNIFIED_CACHE, .level = 2, @@ -800,6 +757,59 @@ static const CPUCaches legacy_intel_cpuid2_cache_info = { }, }; +static const CPUCaches legacy_intel_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 4 * MiB, + .self_init = 1, + .line_size = 64, + .associativity = 16, + .sets = 4096, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, +}; + /* TLB definitions: */ #define L1_DTLB_2M_ASSOC 1 @@ -8980,10 +8990,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) env->enable_legacy_cpuid2_cache = true; } - env->cache_info_cpuid4.l1d_cache = &legacy_l1d_cache; - env->cache_info_cpuid4.l1i_cache = &legacy_l1i_cache; - env->cache_info_cpuid4.l2_cache = &legacy_l2_cache; - env->cache_info_cpuid4.l3_cache = &legacy_l3_cache; + env->cache_info_cpuid4 = legacy_intel_cache_info; env->cache_info_amd.l1d_cache = &legacy_l1d_cache_amd; env->cache_info_amd.l1i_cache = &legacy_l1i_cache_amd; From dcec3c18f9daed23bfb38786edfbd2c0c6ee71c6 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:37 +0800 Subject: [PATCH 51/77] i386/cpu: Add legacy_amd_cache_info cache model Based on legacy_l1d_cachei_amd, legacy_l1i_cache_amd, legacy_l2_cache_amd and legacy_l3_cache, build a complete legacy AMD cache model, which can clarify the purpose of these trivial legacy cache models, simplify the initialization of cache info in X86CPUState, and make it easier to handle compatibility later. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-13-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 112 ++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index d265dc8156..92d21ce64c 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -640,60 +640,58 @@ static void encode_topo_cpuid8000001e(X86CPU *cpu, X86CPUTopoInfo *topo_info, * These are legacy cache values. If there is a need to change any * of these values please use builtin_x86_defs */ -static CPUCacheInfo legacy_l1d_cache_amd = { - .type = DATA_CACHE, - .level = 1, - .size = 64 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 2, - .sets = 512, - .partitions = 1, - .lines_per_tag = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -static CPUCacheInfo legacy_l1i_cache_amd = { - .type = INSTRUCTION_CACHE, - .level = 1, - .size = 64 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 2, - .sets = 512, - .partitions = 1, - .lines_per_tag = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -static CPUCacheInfo legacy_l2_cache_amd = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 512 * KiB, - .line_size = 64, - .lines_per_tag = 1, - .associativity = 16, - .sets = 512, - .partitions = 1, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/* Level 3 unified cache: */ -static CPUCacheInfo legacy_l3_cache = { - .type = UNIFIED_CACHE, - .level = 3, - .size = 16 * MiB, - .line_size = 64, - .associativity = 16, - .sets = 16384, - .partitions = 1, - .lines_per_tag = 1, - .self_init = true, - .inclusive = true, - .complex_indexing = true, - .share_level = CPU_TOPOLOGY_LEVEL_DIE, +static const CPUCaches legacy_amd_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 64 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 2, + .sets = 512, + .partitions = 1, + .lines_per_tag = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 64 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 2, + .sets = 512, + .partitions = 1, + .lines_per_tag = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 512 * KiB, + .line_size = 64, + .lines_per_tag = 1, + .associativity = 16, + .sets = 512, + .partitions = 1, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, }; /* @@ -8991,11 +8989,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) } env->cache_info_cpuid4 = legacy_intel_cache_info; - - env->cache_info_amd.l1d_cache = &legacy_l1d_cache_amd; - env->cache_info_amd.l1i_cache = &legacy_l1i_cache_amd; - env->cache_info_amd.l2_cache = &legacy_l2_cache_amd; - env->cache_info_amd.l3_cache = &legacy_l3_cache; + env->cache_info_amd = legacy_amd_cache_info; } #ifndef CONFIG_USER_ONLY From 2f5b76bce5c9aa71b35bbe5fb666343eed7478f1 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:38 +0800 Subject: [PATCH 52/77] i386/cpu: Select legacy cache model based on vendor in CPUID 0x2 As preparation for merging cache_info_cpuid4 and cache_info_amd in X86CPUState, set legacy cache model based on vendor in the CPUID 0x2 leaf. For AMD CPU, select legacy AMD cache model (in cache_info_amd) as the default cache model, otherwise, select legacy Intel cache model (in cache_info_cpuid4) as before. To ensure compatibility is not broken, add an enable_legacy_vendor_cache flag based on x-vendor-only-v2 to indicate cases where the legacy cache model should be used regardless of the vendor. For CPUID 0x2 leaf, enable_legacy_vendor_cache flag indicates to pick legacy Intel cache model, which is for compatibility with the behavior of PC machine v10.0 and older. The following explains how current vendor-based default legacy cache model ensures correctness without breaking compatibility. * For the PC machine v6.0 and older, vendor_cpuid_only=false, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model, and doesn't use legacy cache model (legacy_cache=false), then cache_info_cpuid4 and cache_info_amd are same, so 0x2 leaf uses its own cache model regardless of the vendor. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy Intel cache model just like their previous behavior. * For the PC machine v10.0 and older (to v6.1), vendor_cpuid_only=true, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x2 leaf regardless of the vendor. Only AMD CPUs have all-0 leaf due to vendor_cpuid_only=true, and this is exactly the behavior of these old machines. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy Intel cache model. Similarly, only AMD CPUs have all-0 leaf, and this is exactly the behavior of these old machines. * For the PC machine v10.1 and newer, vendor_cpuid_only=true, and vendor_cpuid_only_v2=true. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x2 leaf regardless of the vendor. And AMD CPUs have all-0 leaf. Nothing will change. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is false, the legacy cache model is selected based on vendor. For AMD CPU, it will use legacy AMD cache but still get all-0 leaf due to vendor_cpuid_only=true. For non-AMD (Intel/Zhaoxin) CPU, it will use legacy Intel cache as expected. Here, selecting the legacy cache model based on the vendor does not change the previous (before the change) behavior. Therefore, the above analysis proves that, with the help of the flag enable_legacy_vendor_cache, it is acceptable to select the default legacy cache model based on the vendor. For the CPUID 0x2 leaf, in X86CPUState, a unified cache_info is enough. It only needs to be initialized and configured with the corresponding legacy cache model based on the vendor. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-14-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 47 +++++++++++++++++++++++++++++++++++++---------- target/i386/cpu.h | 1 + 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 92d21ce64c..ca85603077 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -250,23 +250,17 @@ static const CPUCaches legacy_intel_cpuid2_cache_info; /* Encode cache info for CPUID[2] */ static void encode_cache_cpuid2(X86CPU *cpu, + const CPUCaches *caches, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { CPUX86State *env = &cpu->env; - const CPUCaches *caches; int l1d, l1i, l2, l3; bool unmatched = false; *eax = 1; /* Number of CPUID[EAX=2] calls required */ *ebx = *ecx = *edx = 0; - if (env->enable_legacy_cpuid2_cache) { - caches = &legacy_intel_cpuid2_cache_info; - } else { - caches = &env->cache_info_cpuid4; - } - l1d = cpuid2_cache_descriptor(caches->l1d_cache, &unmatched); l1i = cpuid2_cache_descriptor(caches->l1i_cache, &unmatched); l2 = cpuid2_cache_descriptor(caches->l2_cache, &unmatched); @@ -7472,8 +7466,37 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ebx |= threads_per_pkg << 16; } break; - case 2: - /* cache info: needed for Pentium Pro compatibility */ + case 2: { /* cache info: needed for Pentium Pro compatibility */ + const CPUCaches *caches; + + if (env->enable_legacy_cpuid2_cache) { + caches = &legacy_intel_cpuid2_cache_info; + } else if (env->enable_legacy_vendor_cache) { + caches = &legacy_intel_cache_info; + } else { + /* + * FIXME: Temporarily select cache info model here based on + * vendor, and merge these 2 cache info models later. + * + * This condition covers the following cases (with + * enable_legacy_vendor_cache=false): + * - When CPU model has its own cache model and doesn't use legacy + * cache model (legacy_model=off). Then cache_info_amd and + * cache_info_cpuid4 are the same. + * + * - For v10.1 and newer machines, when CPU model uses legacy cache + * model. Non-AMD CPUs use cache_info_cpuid4 like before and AMD + * CPU will use cache_info_amd. But this doesn't matter for AMD + * CPU, because this leaf encodes all-0 for AMD whatever its cache + * model is. + */ + if (IS_AMD_CPU(env)) { + caches = &env->cache_info_amd; + } else { + caches = &env->cache_info_cpuid4; + } + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; @@ -7481,8 +7504,9 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *eax = *ebx = *ecx = *edx = 0; break; } - encode_cache_cpuid2(cpu, eax, ebx, ecx, edx); + encode_cache_cpuid2(cpu, caches, eax, ebx, ecx, edx); break; + } case 4: /* cache info: needed for Core compatibility */ if (cpu->cache_info_passthrough) { @@ -8988,6 +9012,9 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) env->enable_legacy_cpuid2_cache = true; } + if (!cpu->vendor_cpuid_only_v2) { + env->enable_legacy_vendor_cache = true; + } env->cache_info_cpuid4 = legacy_intel_cache_info; env->cache_info_amd = legacy_amd_cache_info; } diff --git a/target/i386/cpu.h b/target/i386/cpu.h index d88481ba8e..20499a82a5 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2079,6 +2079,7 @@ typedef struct CPUArchState { */ CPUCaches cache_info_cpuid4, cache_info_amd; bool enable_legacy_cpuid2_cache; + bool enable_legacy_vendor_cache; /* MTRRs */ uint64_t mtrr_fixed[11]; From 69fe18ff9ced6e9cac3e4f3f3b94452603f2006e Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:39 +0800 Subject: [PATCH 53/77] i386/cpu: Select legacy cache model based on vendor in CPUID 0x4 As preparation for merging cache_info_cpuid4 and cache_info_amd in X86CPUState, set legacy cache model based on vendor in the CPUID 0x4 leaf. For AMD CPU, select legacy AMD cache model (in cache_info_amd) as the default cache model, otherwise, select legacy Intel cache model (in cache_info_cpuid4) as before. To ensure compatibility is not broken, add an enable_legacy_vendor_cache flag based on x-vendor-only-v2 to indicate cases where the legacy cache model should be used regardless of the vendor. For CPUID 0x4 leaf, enable_legacy_vendor_cache flag indicates to pick legacy Intel cache model, which is for compatibility with the behavior of PC machine v10.0 and older. The following explains how current vendor-based default legacy cache model ensures correctness without breaking compatibility. * For the PC machine v6.0 and older, vendor_cpuid_only=false, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model, and doesn't use legacy cache model (legacy_cache=false), then cache_info_cpuid4 and cache_info_amd are same, so 0x4 leaf uses its own cache model regardless of the vendor. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy Intel cache model just like their previous behavior. * For the PC machine v10.0 and older (to v6.1), vendor_cpuid_only=true, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x4 leaf regardless of the vendor. Only AMD CPUs have all-0 leaf due to vendor_cpuid_only=true, and this is exactly the behavior of these old machines. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy Intel cache model. Similarly, only AMD CPUs have all-0 leaf, and this is exactly the behavior of these old machines. * For the PC machine v10.1 and newer, vendor_cpuid_only=true, and vendor_cpuid_only_v2=true. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x4 leaf regardless of the vendor. And AMD CPUs have all-0 leaf. Nothing will change. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is false, the legacy cache model is selected based on vendor. For AMD CPU, it will use legacy AMD cache but still get all-0 leaf due to vendor_cpuid_only=true. For non-AMD (Intel/Zhaoxin) CPU, it will use legacy Intel cache as expected. Here, selecting the legacy cache model based on the vendor does not change the previous (before the change) behavior. Therefore, the above analysis proves that, with the help of the flag enable_legacy_vendor_cache, it is acceptable to select the default legacy cache model based on the vendor. For the CPUID 0x4 leaf, in X86CPUState, a unified cache_info is enough. It only needs to be initialized and configured with the corresponding legacy cache model based on the vendor. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-15-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ca85603077..565eaf0071 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7507,7 +7507,35 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, encode_cache_cpuid2(cpu, caches, eax, ebx, ecx, edx); break; } - case 4: + case 4: { + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_intel_cache_info; + } else { + /* + * FIXME: Temporarily select cache info model here based on + * vendor, and merge these 2 cache info models later. + * + * This condition covers the following cases (with + * enable_legacy_vendor_cache=false): + * - When CPU model has its own cache model and doesn't use legacy + * cache model (legacy_model=off). Then cache_info_amd and + * cache_info_cpuid4 are the same. + * + * - For v10.1 and newer machines, when CPU model uses legacy cache + * model. Non-AMD CPUs use cache_info_cpuid4 like before and AMD + * CPU will use cache_info_amd. But this doesn't matter for AMD + * CPU, because this leaf encodes all-0 for AMD whatever its cache + * model is. + */ + if (IS_AMD_CPU(env)) { + caches = &env->cache_info_amd; + } else { + caches = &env->cache_info_cpuid4; + } + } + /* cache info: needed for Core compatibility */ if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, count, eax, ebx, ecx, edx); @@ -7535,30 +7563,26 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, switch (count) { case 0: /* L1 dcache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l1d_cache, - topo_info, + encode_cache_cpuid4(caches->l1d_cache, topo_info, eax, ebx, ecx, edx); if (!cpu->l1_cache_per_core) { *eax &= ~MAKE_64BIT_MASK(14, 12); } break; case 1: /* L1 icache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l1i_cache, - topo_info, + encode_cache_cpuid4(caches->l1i_cache, topo_info, eax, ebx, ecx, edx); if (!cpu->l1_cache_per_core) { *eax &= ~MAKE_64BIT_MASK(14, 12); } break; case 2: /* L2 cache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l2_cache, - topo_info, + encode_cache_cpuid4(caches->l2_cache, topo_info, eax, ebx, ecx, edx); break; case 3: /* L3 cache info */ if (cpu->enable_l3_cache) { - encode_cache_cpuid4(env->cache_info_cpuid4.l3_cache, - topo_info, + encode_cache_cpuid4(caches->l3_cache, topo_info, eax, ebx, ecx, edx); break; } @@ -7569,6 +7593,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } } break; + } case 5: /* MONITOR/MWAIT Leaf */ *eax = cpu->mwait.eax; /* Smallest monitor-line size in bytes */ From d4a8c66de8cb1f90ef9c23db8fb0ccf668c3518b Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:40 +0800 Subject: [PATCH 54/77] i386/cpu: Select legacy cache model based on vendor in CPUID 0x80000005 As preparation for merging cache_info_cpuid4 and cache_info_amd in X86CPUState, set legacy cache model based on vendor in the CPUID 0x80000005 leaf. For AMD CPU, select legacy AMD cache model (in cache_info_amd) as the default cache model like before, otherwise, select legacy Intel cache model (in cache_info_cpuid4). To ensure compatibility is not broken, add an enable_legacy_vendor_cache flag based on x-vendor-only-v2 to indicate cases where the legacy cache model should be used regardless of the vendor. For CPUID 0x80000005 leaf, enable_legacy_vendor_cache flag indicates to pick legacy AMD cache model, which is for compatibility with the behavior of PC machine v10.0 and older. The following explains how current vendor-based default legacy cache model ensures correctness without breaking compatibility. * For the PC machine v6.0 and older, vendor_cpuid_only=false, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model, and doesn't use legacy cache model (legacy_cache=false), then cache_info_cpuid4 and cache_info_amd are same, so 0x80000005 leaf uses its own cache model regardless of the vendor. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy AMD cache model just like their previous behavior. * For the PC machine v10.0 and older (to v6.1), vendor_cpuid_only=true, and vendor_cpuid_only_v2=false. - No change, since this leaf doesn't aware vendor_cpuid_only. * For the PC machine v10.1 and newer, vendor_cpuid_only=true, and vendor_cpuid_only_v2=true. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x80000005 leaf regardless of the vendor. Only Intel CPUs have all-0 leaf due to vendor_cpuid_only_2=true, and this is exactly the expected behavior. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is false, the legacy cache model is selected based on vendor. For AMD CPU, it will use legacy AMD cache as expected. For Intel CPU, it will use legacy Intel cache but still get all-0 leaf due to vendor_cpuid_only_2=true as expected. (Note) And for Zhaoxin CPU, it will use legacy Intel cache model instead of AMD's. This is the difference brought by this change! But it's correct since then Zhaoxin could have the consistent cache info in CPUID 0x2, 0x4 and 0x80000005 leaves. Here, except Zhaoxin, selecting the legacy cache model based on the vendor does not change the previous (before the change) behavior. And the change for Zhaoxin is also a good improvement. Therefore, the above analysis proves that, with the help of the flag enable_legacy_vendor_cache, it is acceptable to select the default legacy cache model based on the vendor. For the CPUID 0x80000005 leaf, in X86CPUState, a unified cache_info is enough. It only needs to be initialized and configured with the corresponding legacy cache model based on the vendor. Cc: EwanHai Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-16-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 565eaf0071..e98ffb11c3 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7935,8 +7935,36 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = env->cpuid_model[(index - 0x80000002) * 4 + 2]; *edx = env->cpuid_model[(index - 0x80000002) * 4 + 3]; break; - case 0x80000005: + case 0x80000005: { /* cache info (L1 cache/TLB Associativity Field) */ + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_amd_cache_info; + } else { + /* + * FIXME: Temporarily select cache info model here based on + * vendor, and merge these 2 cache info models later. + * + * This condition covers the following cases (with + * enable_legacy_vendor_cache=false): + * - When CPU model has its own cache model and doesn't uses legacy + * cache model (legacy_model=off). Then cache_info_amd and + * cache_info_cpuid4 are the same. + * + * - For v10.1 and newer machines, when CPU model uses legacy cache + * model. AMD CPUs use cache_info_amd like before and non-AMD + * CPU will use cache_info_cpuid4. But this doesn't matter, + * because for Intel CPU, it will get all-0 leaf, and Zhaoxin CPU + * will get correct cache info. Both are expected. + */ + if (IS_AMD_CPU(env)) { + caches = &env->cache_info_amd; + } else { + caches = &env->cache_info_cpuid4; + } + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; @@ -7951,9 +7979,10 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, (L1_ITLB_2M_ASSOC << 8) | (L1_ITLB_2M_ENTRIES); *ebx = (L1_DTLB_4K_ASSOC << 24) | (L1_DTLB_4K_ENTRIES << 16) | (L1_ITLB_4K_ASSOC << 8) | (L1_ITLB_4K_ENTRIES); - *ecx = encode_cache_cpuid80000005(env->cache_info_amd.l1d_cache); - *edx = encode_cache_cpuid80000005(env->cache_info_amd.l1i_cache); + *ecx = encode_cache_cpuid80000005(caches->l1d_cache); + *edx = encode_cache_cpuid80000005(caches->l1i_cache); break; + } case 0x80000006: /* cache info (L2 cache/TLB/L3 cache) */ if (cpu->cache_info_passthrough) { From 00fa96c96a864e133c22a5f3793b468bb8a121a5 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:41 +0800 Subject: [PATCH 55/77] i386/cpu: Select legacy cache model based on vendor in CPUID 0x80000006 As preparation for merging cache_info_cpuid4 and cache_info_amd in X86CPUState, set legacy cache model based on vendor in the CPUID 0x80000006 leaf. For AMD CPU, select legacy AMD cache model (in cache_info_amd) as the default cache model like before, otherwise, select legacy Intel cache model (in cache_info_cpuid4). To ensure compatibility is not broken, add an enable_legacy_vendor_cache flag based on x-vendor-only-v2 to indicate cases where the legacy cache model should be used regardless of the vendor. For CPUID 0x80000006 leaf, enable_legacy_vendor_cache flag indicates to pick legacy Intel cache model, which is for compatibility with the behavior of PC machine v10.0 and older. The following explains how current vendor-based default legacy cache model ensures correctness without breaking compatibility. * For the PC machine v6.0 and older, vendor_cpuid_only=false, and vendor_cpuid_only_v2=false. - If the named CPU model has its own cache model, and doesn't use legacy cache model (legacy_cache=false), then cache_info_cpuid4 and cache_info_amd are same, so 0x80000006 leaf uses its own cache model regardless of the vendor. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is true, they will use legacy AMD cache model just like their previous behavior. * For the PC machine v10.0 and older (to v6.1), vendor_cpuid_only=true, and vendor_cpuid_only_v2=false. - No change, since this leaf doesn't aware vendor_cpuid_only. * For the PC machine v10.1 and newer, vendor_cpuid_only=true, and vendor_cpuid_only_v2=true. - If the named CPU model has its own cache model (legacy_cache=false), then cache_info_cpuid4 & cache_info_amd both equal to its own cache model, so it uses its own cache model in 0x80000006 leaf regardless of the vendor. Intel and Zhaoxin CPUs have their special encoding based on SDM, which is the expected behavior and no different from before. - For max/host/named CPU (without its own cache model), then the flag enable_legacy_vendor_cache is false, the legacy cache model is selected based on vendor. For AMD CPU, it will use legacy AMD cache as before. For non-AMD (Intel/Zhaoxin) CPU, it will use legacy Intel cache and be encoded based on SDM as expected. Here, selecting the legacy cache model based on the vendor does not change the previous (before the change) behavior. Therefore, the above analysis proves that, with the help of the flag enable_legacy_vendor_cache, it is acceptable to select the default legacy cache model based on the vendor. For the CPUID 0x80000006 leaf, in X86CPUState, a unified cache_info is enough. It only needs to be initialized and configured with the corresponding legacy cache model based on the vendor. Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-17-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index e98ffb11c3..b557fd01c0 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7983,8 +7983,33 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = encode_cache_cpuid80000005(caches->l1i_cache); break; } - case 0x80000006: - /* cache info (L2 cache/TLB/L3 cache) */ + case 0x80000006: { /* cache info (L2 cache/TLB/L3 cache) */ + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_amd_cache_info; + } else { + /* + * FIXME: Temporarily select cache info model here based on + * vendor, and merge these 2 cache info models later. + * + * This condition covers the following cases (with + * enable_legacy_vendor_cache=false): + * - When CPU model has its own cache model and doesn't uses legacy + * cache model (legacy_model=off). Then cache_info_amd and + * cache_info_cpuid4 are the same. + * + * - For v10.1 and newer machines, when CPU model uses legacy cache + * model. AMD CPUs use cache_info_amd like before and non-AMD + * CPU (Intel & Zhaoxin) will use cache_info_cpuid4 as expected. + */ + if (IS_AMD_CPU(env)) { + caches = &env->cache_info_amd; + } else { + caches = &env->cache_info_cpuid4; + } + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; @@ -7993,7 +8018,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, if (cpu->vendor_cpuid_only_v2 && (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { *eax = *ebx = 0; - encode_cache_cpuid80000006(env->cache_info_cpuid4.l2_cache, + encode_cache_cpuid80000006(caches->l2_cache, NULL, ecx, edx); break; } @@ -8007,11 +8032,12 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, (X86_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | (L2_ITLB_4K_ENTRIES); - encode_cache_cpuid80000006(env->cache_info_amd.l2_cache, + encode_cache_cpuid80000006(caches->l2_cache, cpu->enable_l3_cache ? - env->cache_info_amd.l3_cache : NULL, + caches->l3_cache : NULL, ecx, edx); break; + } case 0x80000007: *eax = 0; *ebx = env->features[FEAT_8000_0007_EBX]; From 25acae4c6e7635ff0f0d36945548de4c0dc70608 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:42 +0800 Subject: [PATCH 56/77] i386/cpu: Select legacy cache model based on vendor in CPUID 0x8000001D As preparation for merging cache_info_cpuid4 and cache_info_amd in X86CPUState, set legacy cache model based on vendor in the CPUID 0x8000001D leaf. For AMD CPU, select legacy AMD cache model (in cache_info_amd) as the default cache model like before, otherwise, select legacy Intel cache model (in cache_info_cpuid4). In fact, for Intel (and Zhaoxin) CPU, this change is safe because the extended CPUID level supported by Intel is up to 0x80000008. So Intel Guest doesn't have this 0x8000001D leaf. Although someone could bump "xlevel" up to 0x8000001D for Intel Guest, it's meaningless and this is undefined behavior. This leaf should be considered reserved, but the SDM does not explicitly state this. So, there's no need to specifically use vendor_cpuid_only_v2 to fix anything, as it doesn't even qualify as a fix since nothing is currently broken. Therefore, it is acceptable to select the default legacy cache model based on the vendor. For the CPUID 0x8000001D leaf, in X86CPUState, a unified cache_info is enough. It only needs to be initialized and configured with the corresponding legacy cache model based on the vendor. Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-18-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b557fd01c0..5b969743bc 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8080,7 +8080,22 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = 0; } break; - case 0x8000001D: + case 0x8000001D: { + const CPUCaches *caches; + + /* + * FIXME: Temporarily select cache info model here based on + * vendor, and merge these 2 cache info models later. + * + * Intel doesn't support this leaf so that Intel Guests don't + * have this leaf. This change is harmless to Intel CPUs. + */ + if (IS_AMD_CPU(env)) { + caches = &env->cache_info_amd; + } else { + caches = &env->cache_info_cpuid4; + } + *eax = 0; if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, count, eax, ebx, ecx, edx); @@ -8088,19 +8103,19 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } switch (count) { case 0: /* L1 dcache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l1d_cache, + encode_cache_cpuid8000001d(caches->l1d_cache, topo_info, eax, ebx, ecx, edx); break; case 1: /* L1 icache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l1i_cache, + encode_cache_cpuid8000001d(caches->l1i_cache, topo_info, eax, ebx, ecx, edx); break; case 2: /* L2 cache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l2_cache, + encode_cache_cpuid8000001d(caches->l2_cache, topo_info, eax, ebx, ecx, edx); break; case 3: /* L3 cache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l3_cache, + encode_cache_cpuid8000001d(caches->l3_cache, topo_info, eax, ebx, ecx, edx); break; default: /* end of info */ @@ -8111,6 +8126,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx &= CACHE_NO_INVD_SHARING | CACHE_INCLUSIVE; } break; + } case 0x8000001E: if (cpu->core_id <= 255) { encode_topo_cpuid8000001e(cpu, topo_info, eax, ebx, ecx, edx); From 2141898bb9b7f3d426871eaa6e98b4c77a1f81e8 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:21:43 +0800 Subject: [PATCH 57/77] i386/cpu: Use a unified cache_info in X86CPUState At present, all cases using the cache model (CPUID 0x2, 0x4, 0x80000005, 0x80000006 and 0x8000001D leaves) have been verified to be able to select either cache_info_intel or cache_info_amd based on the vendor. Therefore, further merge cache_info_intel and cache_info_amd into a unified cache_info in X86CPUState, and during its initialization, set different legacy cache models based on the vendor. Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711102143.1622339-19-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 150 ++++++++-------------------------------------- target/i386/cpu.h | 5 +- 2 files changed, 27 insertions(+), 128 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 5b969743bc..ca6e412024 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7474,27 +7474,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } else if (env->enable_legacy_vendor_cache) { caches = &legacy_intel_cache_info; } else { - /* - * FIXME: Temporarily select cache info model here based on - * vendor, and merge these 2 cache info models later. - * - * This condition covers the following cases (with - * enable_legacy_vendor_cache=false): - * - When CPU model has its own cache model and doesn't use legacy - * cache model (legacy_model=off). Then cache_info_amd and - * cache_info_cpuid4 are the same. - * - * - For v10.1 and newer machines, when CPU model uses legacy cache - * model. Non-AMD CPUs use cache_info_cpuid4 like before and AMD - * CPU will use cache_info_amd. But this doesn't matter for AMD - * CPU, because this leaf encodes all-0 for AMD whatever its cache - * model is. - */ - if (IS_AMD_CPU(env)) { - caches = &env->cache_info_amd; - } else { - caches = &env->cache_info_cpuid4; - } + caches = &env->cache_info; } if (cpu->cache_info_passthrough) { @@ -7513,27 +7493,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, if (env->enable_legacy_vendor_cache) { caches = &legacy_intel_cache_info; } else { - /* - * FIXME: Temporarily select cache info model here based on - * vendor, and merge these 2 cache info models later. - * - * This condition covers the following cases (with - * enable_legacy_vendor_cache=false): - * - When CPU model has its own cache model and doesn't use legacy - * cache model (legacy_model=off). Then cache_info_amd and - * cache_info_cpuid4 are the same. - * - * - For v10.1 and newer machines, when CPU model uses legacy cache - * model. Non-AMD CPUs use cache_info_cpuid4 like before and AMD - * CPU will use cache_info_amd. But this doesn't matter for AMD - * CPU, because this leaf encodes all-0 for AMD whatever its cache - * model is. - */ - if (IS_AMD_CPU(env)) { - caches = &env->cache_info_amd; - } else { - caches = &env->cache_info_cpuid4; - } + caches = &env->cache_info; } /* cache info: needed for Core compatibility */ @@ -7942,27 +7902,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, if (env->enable_legacy_vendor_cache) { caches = &legacy_amd_cache_info; } else { - /* - * FIXME: Temporarily select cache info model here based on - * vendor, and merge these 2 cache info models later. - * - * This condition covers the following cases (with - * enable_legacy_vendor_cache=false): - * - When CPU model has its own cache model and doesn't uses legacy - * cache model (legacy_model=off). Then cache_info_amd and - * cache_info_cpuid4 are the same. - * - * - For v10.1 and newer machines, when CPU model uses legacy cache - * model. AMD CPUs use cache_info_amd like before and non-AMD - * CPU will use cache_info_cpuid4. But this doesn't matter, - * because for Intel CPU, it will get all-0 leaf, and Zhaoxin CPU - * will get correct cache info. Both are expected. - */ - if (IS_AMD_CPU(env)) { - caches = &env->cache_info_amd; - } else { - caches = &env->cache_info_cpuid4; - } + caches = &env->cache_info; } if (cpu->cache_info_passthrough) { @@ -7989,25 +7929,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, if (env->enable_legacy_vendor_cache) { caches = &legacy_amd_cache_info; } else { - /* - * FIXME: Temporarily select cache info model here based on - * vendor, and merge these 2 cache info models later. - * - * This condition covers the following cases (with - * enable_legacy_vendor_cache=false): - * - When CPU model has its own cache model and doesn't uses legacy - * cache model (legacy_model=off). Then cache_info_amd and - * cache_info_cpuid4 are the same. - * - * - For v10.1 and newer machines, when CPU model uses legacy cache - * model. AMD CPUs use cache_info_amd like before and non-AMD - * CPU (Intel & Zhaoxin) will use cache_info_cpuid4 as expected. - */ - if (IS_AMD_CPU(env)) { - caches = &env->cache_info_amd; - } else { - caches = &env->cache_info_cpuid4; - } + caches = &env->cache_info; } if (cpu->cache_info_passthrough) { @@ -8080,22 +8002,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = 0; } break; - case 0x8000001D: { - const CPUCaches *caches; - - /* - * FIXME: Temporarily select cache info model here based on - * vendor, and merge these 2 cache info models later. - * - * Intel doesn't support this leaf so that Intel Guests don't - * have this leaf. This change is harmless to Intel CPUs. - */ - if (IS_AMD_CPU(env)) { - caches = &env->cache_info_amd; - } else { - caches = &env->cache_info_cpuid4; - } - + case 0x8000001D: *eax = 0; if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, count, eax, ebx, ecx, edx); @@ -8103,19 +8010,19 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } switch (count) { case 0: /* L1 dcache info */ - encode_cache_cpuid8000001d(caches->l1d_cache, + encode_cache_cpuid8000001d(env->cache_info.l1d_cache, topo_info, eax, ebx, ecx, edx); break; case 1: /* L1 icache info */ - encode_cache_cpuid8000001d(caches->l1i_cache, + encode_cache_cpuid8000001d(env->cache_info.l1i_cache, topo_info, eax, ebx, ecx, edx); break; case 2: /* L2 cache info */ - encode_cache_cpuid8000001d(caches->l2_cache, + encode_cache_cpuid8000001d(env->cache_info.l2_cache, topo_info, eax, ebx, ecx, edx); break; case 3: /* L3 cache info */ - encode_cache_cpuid8000001d(caches->l3_cache, + encode_cache_cpuid8000001d(env->cache_info.l3_cache, topo_info, eax, ebx, ecx, edx); break; default: /* end of info */ @@ -8126,7 +8033,6 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx &= CACHE_NO_INVD_SHARING | CACHE_INCLUSIVE; } break; - } case 0x8000001E: if (cpu->core_id <= 255) { encode_topo_cpuid8000001e(cpu, topo_info, eax, ebx, ecx, edx); @@ -8825,46 +8731,34 @@ static bool x86_cpu_update_smp_cache_topo(MachineState *ms, X86CPU *cpu, level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l1d_cache->share_level = level; - env->cache_info_amd.l1d_cache->share_level = level; + env->cache_info.l1d_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, - env->cache_info_cpuid4.l1d_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, - env->cache_info_amd.l1d_cache->share_level); + env->cache_info.l1d_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l1i_cache->share_level = level; - env->cache_info_amd.l1i_cache->share_level = level; + env->cache_info.l1i_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, - env->cache_info_cpuid4.l1i_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, - env->cache_info_amd.l1i_cache->share_level); + env->cache_info.l1i_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l2_cache->share_level = level; - env->cache_info_amd.l2_cache->share_level = level; + env->cache_info.l2_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, - env->cache_info_cpuid4.l2_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, - env->cache_info_amd.l2_cache->share_level); + env->cache_info.l2_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l3_cache->share_level = level; - env->cache_info_amd.l3_cache->share_level = level; + env->cache_info.l3_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, - env->cache_info_cpuid4.l3_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, - env->cache_info_amd.l3_cache->share_level); + env->cache_info.l3_cache->share_level); } if (!machine_check_smp_cache(ms, errp)) { @@ -9101,7 +8995,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) "CPU model '%s' doesn't support legacy-cache=off", name); return; } - env->cache_info_cpuid4 = env->cache_info_amd = *cache_info; + env->cache_info = *cache_info; } else { /* Build legacy cache information */ if (!cpu->consistent_cache) { @@ -9111,8 +9005,12 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) if (!cpu->vendor_cpuid_only_v2) { env->enable_legacy_vendor_cache = true; } - env->cache_info_cpuid4 = legacy_intel_cache_info; - env->cache_info_amd = legacy_amd_cache_info; + + if (IS_AMD_CPU(env)) { + env->cache_info = legacy_amd_cache_info; + } else { + env->cache_info = legacy_intel_cache_info; + } } #ifndef CONFIG_USER_ONLY diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 20499a82a5..f977fc49a7 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2073,11 +2073,12 @@ typedef struct CPUArchState { /* Features that were explicitly enabled/disabled */ FeatureWordArray user_features; uint32_t cpuid_model[12]; - /* Cache information for CPUID. When legacy-cache=on, the cache data + /* + * Cache information for CPUID. When legacy-cache=on, the cache data * on each CPUID leaf will be different, because we keep compatibility * with old QEMU versions. */ - CPUCaches cache_info_cpuid4, cache_info_amd; + CPUCaches cache_info; bool enable_legacy_cpuid2_cache; bool enable_legacy_vendor_cache; From 1f0a9ce6c0c934507eed076b1b9a2774b7c81de4 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:45:55 +0800 Subject: [PATCH 58/77] i386/cpu: Introduce cache model for SierraForest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the cache model to SierraForest (v3) to better emulate its environment. The cache model is based on SierraForest-SP (Scalable Performance): --- cache 0 --- cache type = data cache (1) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x8 (8) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 32768 (32 KB) --- cache 1 --- cache type = instruction cache (2) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x8 (8) number of sets = 0x80 (128) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 128 (size synth) = 65536 (64 KB) --- cache 2 --- cache type = unified cache (3) cache level = 0x2 (2) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x7 (7) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x1000 (4096) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 4096 (size synth) = 4194304 (4 MB) --- cache 3 --- cache type = unified cache (3) cache level = 0x3 (3) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1ff (511) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0xc (12) number of sets = 0x24000 (147456) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = true number of sets (s) = 147456 (size synth) = 113246208 (108 MB) --- cache 4 --- cache type = no more caches (0) Suggested-by: Tejus GK Suggested-by: Jason Zeng Suggested-by: "Daniel P . BerrangĂ©" Reviewed-by: Dapeng Mi Reviewed-by: Tao Su Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-2-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ca6e412024..3c28e9588a 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -2880,6 +2880,97 @@ static const CPUCaches epyc_turin_cache_info = { .no_invd_sharing = true, .complex_indexing = false, .share_level = CPU_TOPOLOGY_LEVEL_DIE, + } +}; + +static const CPUCaches xeon_srf_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 32 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x1.ECX */ + .sets = 128, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 64 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 4096, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 4 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_MODULE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x3.ECX */ + .sets = 147456, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 108 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, }, }; @@ -5005,6 +5096,11 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with srf-sp cache model", + .cache_info = &xeon_srf_cache_info, + }, { /* end of list */ }, }, }, From 23fb57838ddfa9271f8ebf8b7a74ed7ba3359cd3 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:45:56 +0800 Subject: [PATCH 59/77] i386/cpu: Introduce cache model for GraniteRapids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the cache model to GraniteRapids (v3) to better emulate its environment. The cache model is based on GraniteRapids-SP (Scalable Performance): --- cache 0 --- cache type = data cache (1) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0xc (12) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 49152 (48 KB) --- cache 1 --- cache type = instruction cache (2) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 65536 (64 KB) --- cache 2 --- cache type = unified cache (3) cache level = 0x2 (2) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x800 (2048) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 2048 (size synth) = 2097152 (2 MB) --- cache 3 --- cache type = unified cache (3) cache level = 0x3 (3) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0xff (255) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x48000 (294912) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = true number of sets (s) = 294912 (size synth) = 301989888 (288 MB) --- cache 4 --- cache type = no more caches (0) Suggested-by: Tejus GK Suggested-by: Jason Zeng Suggested-by: "Daniel P . BerrangĂ©" Reviewed-by: Dapeng Mi Reviewed-by: Tao Su Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 3c28e9588a..d1fc74eb0e 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -2883,6 +2883,97 @@ static const CPUCaches epyc_turin_cache_info = { } }; +static const CPUCaches xeon_gnr_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 48 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 64 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 2048, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 2 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x3.ECX */ + .sets = 294912, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 288 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, + }, +}; + static const CPUCaches xeon_srf_cache_info = { .l1d_cache = &(CPUCacheInfo) { /* CPUID 0x4.0x0.EAX */ @@ -4951,6 +5042,11 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with gnr-sp cache model", + .cache_info = &xeon_gnr_cache_info, + }, { /* end of list */ }, }, }, From 8d69fc2158edc83517622bd862beb29ffc6bbf02 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:45:57 +0800 Subject: [PATCH 60/77] i386/cpu: Introduce cache model for SapphireRapids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the cache model to SapphireRapids (v4) to better emulate its environment. The cache model is based on SapphireRapids-SP (Scalable Performance): --- cache 0 --- cache type = data cache (1) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0xc (12) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 49152 (48 KB) --- cache 1 --- cache type = instruction cache (2) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x8 (8) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 32768 (32 KB) --- cache 2 --- cache type = unified cache (3) cache level = 0x2 (2) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x1 (1) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x800 (2048) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 2048 (size synth) = 2097152 (2 MB) --- cache 3 --- cache type = unified cache (3) cache level = 0x3 (3) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x7f (127) maximum IDs for cores in pkg = 0x3f (63) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0xf (15) number of sets = 0x10000 (65536) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = true number of sets (s) = 65536 (size synth) = 62914560 (60 MB) --- cache 4 --- cache type = no more caches (0) Suggested-by: Tejus GK Suggested-by: Jason Zeng Suggested-by: "Daniel P . BerrangĂ©" Reviewed-by: Dapeng Mi Reviewed-by: Tao Su Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-4-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index d1fc74eb0e..b3b29f6966 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -2883,6 +2883,97 @@ static const CPUCaches epyc_turin_cache_info = { } }; +static const CPUCaches xeon_spr_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 48 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 32 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 2048, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 2 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 15, + + /* CPUID 0x4.0x3.ECX */ + .sets = 65536, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 60 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, + }, +}; + static const CPUCaches xeon_gnr_cache_info = { .l1d_cache = &(CPUCacheInfo) { /* CPUID 0x4.0x0.EAX */ @@ -4889,6 +4980,11 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 4, + .note = "with spr-sp cache model", + .cache_info = &xeon_spr_cache_info, + }, { /* end of list */ } } }, From b1a3a090b21a505d7a9da097c2046824f9e38f84 Mon Sep 17 00:00:00 2001 From: Ewan Hai Date: Fri, 11 Jul 2025 18:45:58 +0800 Subject: [PATCH 61/77] i386/cpu: Introduce cache model for YongFeng Add the cache model to YongFeng (v3) to better emulate its environment. Note, although YongFeng v2 was added after v10.0, it was also back ported to v10.0.2. Therefore, the new version (v3) is needed to avoid conflict. The cache model is as follows: --- cache 0 --- cache type = data cache (1) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x0 (0) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x8 (8) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 32768 (32 KB) --- cache 1 --- cache type = instruction cache (2) cache level = 0x1 (1) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x0 (0) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x40 (64) WBINVD/INVD acts on lower caches = false inclusive to lower caches = false complex cache indexing = false number of sets (s) = 64 (size synth) = 65536 (64 KB) --- cache 2 --- cache type = unified cache (3) cache level = 0x2 (2) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x0 (0) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x8 (8) number of sets = 0x200 (512) WBINVD/INVD acts on lower caches = false inclusive to lower caches = true complex cache indexing = false number of sets (s) = 512 (size synth) = 262144 (256 KB) --- cache 3 --- cache type = unified cache (3) cache level = 0x3 (3) self-initializing cache level = true fully associative cache = false maximum IDs for CPUs sharing cache = 0x0 (0) maximum IDs for cores in pkg = 0x0 (0) system coherency line size = 0x40 (64) physical line partitions = 0x1 (1) ways of associativity = 0x10 (16) number of sets = 0x2000 (8192) WBINVD/INVD acts on lower caches = true inclusive to lower caches = true complex cache indexing = false number of sets (s) = 8192 (size synth) = 8388608 (8 MB) --- cache 4 --- cache type = no more caches (0) Tested-by: Yi Lai Signed-off-by: Ewan Hai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b3b29f6966..40f3b5eac8 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -3156,6 +3156,105 @@ static const CPUCaches xeon_srf_cache_info = { }, }; +static const CPUCaches yongfeng_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + /* CPUID 0x80000005.ECX */ + .lines_per_tag = 1, + .size = 32 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + /* CPUID 0x80000005.EDX */ + .lines_per_tag = 1, + .size = 64 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x2.ECX */ + .sets = 512, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = true, + .complex_indexing = false, + + /* CPUID 0x80000006.ECX */ + .size = 256 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x3.ECX */ + .sets = 8192, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = true, + .inclusive = true, + .complex_indexing = false, + + .size = 8 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, +}; + /* The following VMX features are not supported by KVM and are left out in the * CPU definitions: * @@ -6435,6 +6534,11 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with the cache model", + .cache_info = &yongfeng_cache_info, + }, { /* end of list */ } } }, From bf4032561447a518e9cc4af308ef1a15023bc4c6 Mon Sep 17 00:00:00 2001 From: Manish Mishra Date: Fri, 11 Jul 2025 18:45:59 +0800 Subject: [PATCH 62/77] i386/cpu: Add a "x-force-cpuid-0x1f" property Add a "x-force-cpuid-0x1f" property so that CPU models can enable it and have 0x1f CPUID leaf natually as the Host CPU. The advantage is that when the CPU model's cache model is already consistent with the Host CPU, for example, SRF defaults to l2 per module & l3 per package, 0x1f can better help users identify the topology in the VM. Adding 0x1f for specific CPU models should not cause any trouble in principle. This property is only enabled for CPU models that already have 0x1f leaf on the Host, so software that originally runs normally on the Host won't encounter issues in the Guest with corresponding CPU model. Conversely, some software that relies on checking 0x1f might have problems in the Guest due to the lack of 0x1f [*]. In summary, adding 0x1f is also intended to further emulate the Host CPU environment. [*]: https://lore.kernel.org/qemu-devel/PH0PR02MB738410511BF51B12DB09BE6CF6AC2@PH0PR02MB7384.namprd02.prod.outlook.com/ Signed-off-by: Manish Mishra Co-authored-by: Xiaoyao Li Signed-off-by: Xiaoyao Li [Integrated and rebased 2 previous patches (ordered by post time)] Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-6-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 40f3b5eac8..482979a443 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -9940,6 +9940,7 @@ static const Property x86_cpu_properties[] = { DEFINE_PROP_BOOL("x-intel-pt-auto-level", X86CPU, intel_pt_auto_level, true), DEFINE_PROP_BOOL("x-l1-cache-per-thread", X86CPU, l1_cache_per_core, true), + DEFINE_PROP_BOOL("x-force-cpuid-0x1f", X86CPU, force_cpuid_0x1f, false), }; #ifndef CONFIG_USER_ONLY From e468c5e44435f5cbe1dd61099e6991a66c25988f Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:46:00 +0800 Subject: [PATCH 63/77] i386/cpu: Enable 0x1f leaf for SierraForest by default Host SierraForest CPU has 0x1f leaf by default, so that enable it for Guest CPU by default as well. Suggested-by: Igor Mammedov Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-7-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 482979a443..668f3e63b7 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5389,8 +5389,11 @@ static const X86CPUDefinition builtin_x86_defs[] = { }, { .version = 3, - .note = "with srf-sp cache model", + .note = "with srf-sp cache model and 0x1f leaf", .cache_info = &xeon_srf_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + } }, { /* end of list */ }, }, From af62bd3db0fde8aa132dd2a0d7dc620827ba1361 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:46:00 +0800 Subject: [PATCH 64/77] i386/cpu: Enable 0x1f leaf for SierraForest by default Host SierraForest CPU has 0x1f leaf by default, so that enable it for Guest CPU by default as well. Suggested-by: Igor Mammedov Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-7-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 668f3e63b7..c15082e8af 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5393,6 +5393,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { .cache_info = &xeon_srf_cache_info, .props = (PropValue[]) { { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, } }, { /* end of list */ }, From 5dbdcdce06d9421598e84fa77692810ccc5c3a81 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:46:01 +0800 Subject: [PATCH 65/77] i386/cpu: Enable 0x1f leaf for GraniteRapids by default Host GraniteRapids CPU has 0x1f leaf by default, so that enable it for Guest CPU by default as well. Suggested-by: Igor Mammedov Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-8-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index c15082e8af..a11e9bb111 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5239,8 +5239,12 @@ static const X86CPUDefinition builtin_x86_defs[] = { }, { .version = 3, - .note = "with gnr-sp cache model", + .note = "with gnr-sp cache model and 0x1f leaf", .cache_info = &xeon_gnr_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } }, { /* end of list */ }, }, From 1bf465f3e0a786bf3267931461c0c15fe6232bc8 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:46:02 +0800 Subject: [PATCH 66/77] i386/cpu: Enable 0x1f leaf for SapphireRapids by default Host SapphireRapids CPU has 0x1f leaf by default, so that enable it for Guest CPU by default as well. Suggested-by: Igor Mammedov Reviewed-by: Dapeng Mi Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-9-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index a11e9bb111..216e0232df 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5081,8 +5081,12 @@ static const X86CPUDefinition builtin_x86_defs[] = { }, { .version = 4, - .note = "with spr-sp cache model", + .note = "with spr-sp cache model and 0x1f leaf", .cache_info = &xeon_spr_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } }, { /* end of list */ } } From 8113b7f0e662c138a4d0a739807cc3ad33bca8b3 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 11 Jul 2025 18:46:03 +0800 Subject: [PATCH 67/77] i386/cpu: Enable 0x1f leaf for YongFeng by default Host YongFeng CPU has 0x1f leaf by default, so that enable it for Guest CPU by default as well. Suggested-by: Ewan Hai Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250711104603.1634832-10-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 216e0232df..4f0c973446 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6548,8 +6548,12 @@ static const X86CPUDefinition builtin_x86_defs[] = { }, { .version = 3, - .note = "with the cache model", + .note = "with the cache model and 0x1f leaf", .cache_info = &yongfeng_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } }, { /* end of list */ } } From a539cd26145c726fddd19eb0e2c20332960b0245 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 27 Jun 2025 11:51:26 +0800 Subject: [PATCH 68/77] i386/cpu: Mark EBX/ECX/EDX in CPUID 0x80000000 leaf as reserved for Intel Per SDM, 80000000H EAX Maximum Input Value for Extended Function CPUID Information. EBX Reserved. ECX Reserved. EDX Reserved. EBX/ECX/EDX in CPUID 0x80000000 leaf are reserved. Intel is using 0x0 leaf to encode vendor. Signed-off-by: Zhao Liu Reviewed-by: Tao Su Link: https://lore.kernel.org/r/20250627035129.2755537-2-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 4f0c973446..ae508fa962 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8280,9 +8280,15 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, break; case 0x80000000: *eax = env->cpuid_xlevel; - *ebx = env->cpuid_vendor1; - *edx = env->cpuid_vendor2; - *ecx = env->cpuid_vendor3; + + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *ebx = *ecx = *edx = 0; + } else { + *ebx = env->cpuid_vendor1; + *edx = env->cpuid_vendor2; + *ecx = env->cpuid_vendor3; + } break; case 0x80000001: *eax = env->cpuid_version; From 1c52c470baba1a2cc2d96e14c9f845ec3d2ea8c4 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 27 Jun 2025 11:51:27 +0800 Subject: [PATCH 69/77] i386/cpu: Mark CPUID 0x80000007[EBX] as reserved for Intel Per SDM, 80000007H EAX Reserved = 0. EBX Reserved = 0. ECX Reserved = 0. EDX Bits 07-00: Reserved = 0. Bit 08: Invariant TSC available if 1. Bits 31-09: Reserved = 0. EAX/EBX/ECX in CPUID 0x80000007 leaf are reserved for Intel. At present, EAX is reserved for AMD, too. And AMD hasn't used ECX in QEMU. So these 2 registers are both left as 0. Therefore, only fix the EBX and excode it as 0 for Intel. Signed-off-by: Zhao Liu Reviewed-by: Tao Su Link: https://lore.kernel.org/r/20250627035129.2755537-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ae508fa962..533c9d9abc 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8376,7 +8376,11 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } case 0x80000007: *eax = 0; - *ebx = env->features[FEAT_8000_0007_EBX]; + if (cpu->vendor_cpuid_only_v2 && IS_INTEL_CPU(env)) { + *ebx = 0; + } else { + *ebx = env->features[FEAT_8000_0007_EBX]; + } *ecx = 0; *edx = env->features[FEAT_8000_0007_EDX]; break; From da84c011544b808b9ea3dface2292437dd29d053 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 27 Jun 2025 11:51:28 +0800 Subject: [PATCH 70/77] i386/cpu: Mark CPUID 0x80000008 ECX bits[0:7] & [12:15] as reserved for Intel/Zhaoxin Per SDM, 80000008H EAX Linear/Physical Address size. Bits 07-00: #Physical Address Bits*. Bits 15-08: #Linear Address Bits. Bits 31-16: Reserved = 0. EBX Bits 08-00: Reserved = 0. Bit 09: WBNOINVD is available if 1. Bits 31-10: Reserved = 0. ECX Reserved = 0. EDX Reserved = 0. ECX/EDX in CPUID 0x80000008 leaf are reserved. Currently, in QEMU, only ECX bits[0:7] and ECX bits[12:15] are encoded, and both are emulated in QEMU. Considering that Intel and Zhaoxin are already using the 0x1f leaf to describe CPU topology, which includes similar information, Intel and Zhaoxin will not implement ECX bits[0:7] and bits[12:15] of 0x80000008. Therefore, mark these two fields as reserved and clear them for Intel and Zhaoxin guests. Reviewed-by: Tao Su Tested-by: Yi Lai Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250714080859.1960104-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 533c9d9abc..1a2cae6ea1 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8393,6 +8393,17 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *eax |= (cpu->guest_phys_bits << 16); } *ebx = env->features[FEAT_8000_0008_EBX]; + + /* + * Don't emulate Bits [7:0] & Bits [15:12] for Intel/Zhaoxin, since + * they're using 0x1f leaf. + */ + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *ecx = *edx = 0; + break; + } + if (threads_per_pkg > 1) { /* * Bits 15:12 is "The number of bits in the initial From 1b74aa47c33f2aa37ab7339826e30abb22ca0a5f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 12 Jul 2025 09:21:14 +0200 Subject: [PATCH 71/77] tests/functional: test_x86_cpu_model_versions: remove dead tests Tests that require machines older than 4.2 are now unconditionally skipped. Remove them if they test legacy behavior, or use the latest machine if they test current behavior. Signed-off-by: Paolo Bonzini --- .../functional/test_x86_cpu_model_versions.py | 110 ++---------------- 1 file changed, 12 insertions(+), 98 deletions(-) diff --git a/tests/functional/test_x86_cpu_model_versions.py b/tests/functional/test_x86_cpu_model_versions.py index bd18acd44f..36c968f1c0 100755 --- a/tests/functional/test_x86_cpu_model_versions.py +++ b/tests/functional/test_x86_cpu_model_versions.py @@ -72,44 +72,11 @@ class X86CPUModelAliases(QemuSystemTest): self.assertNotIn("EPYC-IBPB-v1", cpus, "EPYC-IBPB shouldn't be versioned") - def test_4_0_alias_compatibility(self): - """ - Check if pc-*-4.0 unversioned CPU model won't be reported as aliases - """ - self.set_machine('pc-i440fx-4.0') - # pc-*-4.0 won't expose non-versioned CPU models as aliases - # We do this to help management software to keep compatibility - # with older QEMU versions that didn't have the versioned CPU model - self.vm.add_args('-S') - self.vm.launch() - cpus = dict((m['name'], m) for m in - self.vm.cmd('query-cpu-definitions')) - - self.assertFalse(cpus['Cascadelake-Server']['static'], - 'unversioned Cascadelake-Server CPU model must not be static') - self.assertNotIn('alias-of', cpus['Cascadelake-Server'], - 'Cascadelake-Server must not be an alias') - self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'], - 'Cascadelake-Server-v1 must not be an alias') - - self.assertFalse(cpus['qemu64']['static'], - 'unversioned qemu64 CPU model must not be static') - self.assertNotIn('alias-of', cpus['qemu64'], - 'qemu64 must not be an alias') - self.assertNotIn('alias-of', cpus['qemu64-v1'], - 'qemu64-v1 must not be an alias') - - self.validate_variant_aliases(cpus) - - # On pc-*-4.0, no CPU model should be reported as an alias: - for name,c in cpus.items(): - self.assertNotIn('alias-of', c, "%s shouldn't be an alias" % (name)) - - def test_4_1_alias(self): + def test_unversioned_alias(self): """ Check if unversioned CPU model is an alias pointing to right version """ - self.set_machine('pc-i440fx-4.1') + self.set_machine('pc') self.vm.add_args('-S') self.vm.launch() @@ -133,7 +100,7 @@ class X86CPUModelAliases(QemuSystemTest): self.validate_variant_aliases(cpus) - # On pc-*-4.1, -noTSX and -IBRS models should be aliases: + # On recent PC machines, -noTSX and -IBRS models should be aliases: self.assertEqual(cpus["Haswell"].get('alias-of'), "Haswell-v1", "Haswell must be an alias") @@ -247,8 +214,8 @@ class CascadelakeArchCapabilities(QemuSystemTest): cpu_path = self.vm.cmd('query-cpus-fast')[0].get('qom-path') return self.vm.cmd('qom-get', path=cpu_path, property=prop) - def test_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test(self): + self.set_machine('pc') # machine-type only: self.vm.add_args('-S') self.set_vm_arg('-cpu', @@ -256,80 +223,27 @@ class CascadelakeArchCapabilities(QemuSystemTest): 'enforce=off') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server should not have arch-capabilities') + 'pc + Cascadelake-Server should not have arch-capabilities') - def test_4_0(self): - self.set_machine('pc-i440fx-4.0') - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server should not have arch-capabilities') - - def test_set_4_0(self): - self.set_machine('pc-i440fx-4.0') - # command line must override machine-type if CPU model is not versioned: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server,x-force-features=on,check=off,' - 'enforce=off,+arch-capabilities') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server,+arch-capabilities should have arch-capabilities') - - def test_unset_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test_unset(self): + self.set_machine('pc') self.vm.add_args('-S') self.set_vm_arg('-cpu', 'Cascadelake-Server,x-force-features=on,check=off,' 'enforce=off,-arch-capabilities') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server,-arch-capabilities should not have arch-capabilities') + 'pc + Cascadelake-Server,-arch-capabilities should not have arch-capabilities') - def test_v1_4_0(self): - self.set_machine('pc-i440fx-4.0') - # versioned CPU model overrides machine-type: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v1,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v1 should not have arch-capabilities') - - def test_v2_4_0(self): - self.set_machine('pc-i440fx-4.0') - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v2,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v2 should have arch-capabilities') - - def test_v1_set_4_0(self): - self.set_machine('pc-i440fx-4.0') - # command line must override machine-type and versioned CPU model: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v1,x-force-features=on,check=off,' - 'enforce=off,+arch-capabilities') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v1,+arch-capabilities should have arch-capabilities') - - def test_v2_unset_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test_v2_unset(self): + self.set_machine('pc') self.vm.add_args('-S') self.set_vm_arg('-cpu', 'Cascadelake-Server-v2,x-force-features=on,check=off,' 'enforce=off,-arch-capabilities') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server-v2,-arch-capabilities should not have arch-capabilities') + 'pc + Cascadelake-Server-v2,-arch-capabilities should not have arch-capabilities') if __name__ == '__main__': QemuSystemTest.main() From c8958b7eb494d06a209b1befb6c34edfbce68867 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 12 Jul 2025 10:02:01 +0200 Subject: [PATCH 72/77] tests/vm: bump FreeBSD image to 14.3 Signed-off-by: Paolo Bonzini --- tests/vm/freebsd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/vm/freebsd b/tests/vm/freebsd index 74b3b1e520..2e96c9eba5 100755 --- a/tests/vm/freebsd +++ b/tests/vm/freebsd @@ -28,8 +28,8 @@ class FreeBSDVM(basevm.BaseVM): name = "freebsd" arch = "x86_64" - link = "https://download.freebsd.org/releases/CI-IMAGES/14.1-RELEASE/amd64/Latest/FreeBSD-14.1-RELEASE-amd64-BASIC-CI.raw.xz" - csum = "202fe27a05427f0a86d3ebb97712745186f2776ccc4f70d95466dd99a0238ba5" + link = "https://download.freebsd.org/releases/CI-IMAGES/14.3-RELEASE/amd64/Latest/FreeBSD-14.3-RELEASE-amd64-BASIC-CI.raw.xz" + csum = "ec0f5a4bbe63aa50a725d9fee0f1931f850e9a21cbebdadb991df00f168d6805" size = "20G" BUILD_SCRIPT = """ From 075e91a4a42dad834d6488cd8141fa29c7b218bd Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 27 Jun 2025 11:51:29 +0800 Subject: [PATCH 73/77] i386/cpu: Reorder CPUID leaves in cpu_x86_cpuid() Sort the CPUID leaves strictly by index to facilitate checking and changing. Signed-off-by: Zhao Liu Reviewed-by: Tao Su Link: https://lore.kernel.org/r/20250627035129.2755537-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 1a2cae6ea1..3b7c22e5d3 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8052,21 +8052,6 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, assert(!(*eax & ~0x1f)); *ebx &= 0xffff; /* The count doesn't need to be reliable. */ break; - case 0x1C: - if (cpu->enable_pmu && (env->features[FEAT_7_0_EDX] & CPUID_7_0_EDX_ARCH_LBR)) { - x86_cpu_get_supported_cpuid(0x1C, 0, eax, ebx, ecx, edx); - *edx = 0; - } - break; - case 0x1F: - /* V2 Extended Topology Enumeration Leaf */ - if (!x86_has_cpuid_0x1f(cpu)) { - *eax = *ebx = *ecx = *edx = 0; - break; - } - - encode_topo_cpuid1f(env, count, topo_info, eax, ebx, ecx, edx); - break; case 0xD: { /* Processor Extended State */ *eax = 0; @@ -8207,6 +8192,12 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } break; } + case 0x1C: + if (cpu->enable_pmu && (env->features[FEAT_7_0_EDX] & CPUID_7_0_EDX_ARCH_LBR)) { + x86_cpu_get_supported_cpuid(0x1C, 0, eax, ebx, ecx, edx); + *edx = 0; + } + break; case 0x1D: { /* AMX TILE, for now hardcoded for Sapphire Rapids*/ *eax = 0; @@ -8244,6 +8235,15 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } break; } + case 0x1F: + /* V2 Extended Topology Enumeration Leaf */ + if (!x86_has_cpuid_0x1f(cpu)) { + *eax = *ebx = *ecx = *edx = 0; + break; + } + + encode_topo_cpuid1f(env, count, topo_info, eax, ebx, ecx, edx); + break; case 0x24: { *eax = 0; *ebx = 0; @@ -8472,6 +8472,21 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = 0; } break; + case 0x8000001F: + *eax = *ebx = *ecx = *edx = 0; + if (sev_enabled()) { + *eax = 0x2; + *eax |= sev_es_enabled() ? 0x8 : 0; + *eax |= sev_snp_enabled() ? 0x10 : 0; + *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */ + *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */ + } + break; + case 0x80000021: + *eax = *ebx = *ecx = *edx = 0; + *eax = env->features[FEAT_8000_0021_EAX]; + *ebx = env->features[FEAT_8000_0021_EBX]; + break; case 0x80000022: *eax = *ebx = *ecx = *edx = 0; /* AMD Extended Performance Monitoring and Debug */ @@ -8504,21 +8519,6 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = 0; *edx = 0; break; - case 0x8000001F: - *eax = *ebx = *ecx = *edx = 0; - if (sev_enabled()) { - *eax = 0x2; - *eax |= sev_es_enabled() ? 0x8 : 0; - *eax |= sev_snp_enabled() ? 0x10 : 0; - *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */ - *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */ - } - break; - case 0x80000021: - *eax = *ebx = *ecx = *edx = 0; - *eax = env->features[FEAT_8000_0021_EAX]; - *ebx = env->features[FEAT_8000_0021_EBX]; - break; default: /* reserved values: zero */ *eax = 0; From f985a1195ba2d9c6f6f33e83fe2e419a7e8acb60 Mon Sep 17 00:00:00 2001 From: Chuang Xu Date: Mon, 14 Jul 2025 16:08:56 +0800 Subject: [PATCH 74/77] i386/cpu: Fix number of addressable IDs field for CPUID.01H.EBX[23:16] When QEMU is started with: -cpu host,migratable=on,host-cache-info=on,l3-cache=off -smp 180,sockets=2,dies=1,cores=45,threads=2 On Intel platform: CPUID.01H.EBX[23:16] is defined as "max number of addressable IDs for logical processors in the physical package". When executing "cpuid -1 -l 1 -r" in the guest, we obtain a value of 90 for CPUID.01H.EBX[23:16], whereas the expected value is 128. Additionally, executing "cpuid -1 -l 4 -r" in the guest yields a value of 63 for CPUID.04H.EAX[31:26], which matches the expected result. As (1+CPUID.04H.EAX[31:26]) rounds up to the nearest power-of-2 integer, it's necessary to round up CPUID.01H.EBX[23:16] to the nearest power-of-2 integer too. Otherwise there would be unexpected results in guest with older kernel. For example, when QEMU is started with CLI above and xtopology is disabled, guest kernel 5.15.120 uses CPUID.01H.EBX[23:16]/(1+CPUID.04H.EAX[31:26]) to calculate threads-per-core in detect_ht(). Then guest will get "90/(1+63)=1" as the result, even though threads-per-core should actually be 2. And on AMD platform: CPUID.01H.EBX[23:16] is defined as "Logical processor count". Current result meets our expectation. So round up CPUID.01H.EBX[23:16] to the nearest power-of-2 integer only for Intel platform to solve the unexpected result. Use the "x-vendor-cpuid-only-v2" compat option to fix this issue. Reviewed-by: Zhao Liu Signed-off-by: Guixiong Wei Signed-off-by: Yipeng Yin Signed-off-by: Chuang Xu Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250714080859.1960104-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 3b7c22e5d3..12e719e995 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7871,7 +7871,17 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } *edx = env->features[FEAT_1_EDX]; if (threads_per_pkg > 1) { - *ebx |= threads_per_pkg << 16; + /* + * For CPUID.01H.EBX[Bits 23-16], AMD requires logical processor + * count, but Intel needs maximum number of addressable IDs for + * logical processors per package. + */ + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *ebx |= 1 << apicid_pkg_offset(topo_info) << 16; + } else { + *ebx |= threads_per_pkg << 16; + } } break; case 2: { /* cache info: needed for Pentium Pro compatibility */ From a62fef58299562aae6667b8d8552247423e886b3 Mon Sep 17 00:00:00 2001 From: Qian Wen Date: Mon, 14 Jul 2025 16:08:57 +0800 Subject: [PATCH 75/77] i386/cpu: Fix cpu number overflow in CPUID.01H.EBX[23:16] The legacy topology enumerated by CPUID.1.EBX[23:16] is defined in SDM Vol2: Bits 23-16: Maximum number of addressable IDs for logical processors in this physical package. When threads_per_socket > 255, it will 1) overwrite bits[31:24] which is apic_id, 2) bits [23:16] get truncated. Specifically, if launching the VM with -smp 256, the value written to EBX[23:16] is 0 because of data overflow. If the guest only supports legacy topology, without V2 Extended Topology enumerated by CPUID.0x1f or Extended Topology enumerated by CPUID.0x0b to support over 255 CPUs, the return of the kernel invoking cpu_smt_allowed() is false and APs (application processors) will fail to bring up. Then only CPU 0 is online, and others are offline. For example, launch VM via: qemu-system-x86_64 -M q35,accel=kvm,kernel-irqchip=split \ -cpu qemu64,cpuid-0xb=off -smp 256 -m 32G \ -drive file=guest.img,if=none,id=virtio-disk0,format=raw \ -device virtio-blk-pci,drive=virtio-disk0,bootindex=1 --nographic The guest shows: CPU(s): 256 On-line CPU(s) list: 0 Off-line CPU(s) list: 1-255 To avoid this issue caused by overflow, limit the max value written to EBX[23:16] to 255 as the HW does. Cc: qemu-stable@nongnu.org Reviewed-by: Xiaoyao Li Signed-off-by: Qian Wen Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250714080859.1960104-6-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 12e719e995..608fdcf757 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7871,6 +7871,8 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } *edx = env->features[FEAT_1_EDX]; if (threads_per_pkg > 1) { + uint32_t num; + /* * For CPUID.01H.EBX[Bits 23-16], AMD requires logical processor * count, but Intel needs maximum number of addressable IDs for @@ -7878,10 +7880,13 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, */ if (cpu->vendor_cpuid_only_v2 && (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { - *ebx |= 1 << apicid_pkg_offset(topo_info) << 16; + num = 1 << apicid_pkg_offset(topo_info); } else { - *ebx |= threads_per_pkg << 16; + num = threads_per_pkg; } + + /* Fixup overflow: max value for bits 23-16 is 255. */ + *ebx |= MIN(num, 255) << 16; } break; case 2: { /* cache info: needed for Pentium Pro compatibility */ From 3e86124e7cb9b66e07fb992667865a308f16fcf2 Mon Sep 17 00:00:00 2001 From: Qian Wen Date: Mon, 14 Jul 2025 16:08:58 +0800 Subject: [PATCH 76/77] i386/cpu: Fix overflow of cache topology fields in CPUID.04H According to SDM, CPUID.0x4:EAX[31:26] indicates the Maximum number of addressable IDs for processor cores in the physical package. If we launch over 64 cores VM, the 6-bit field will overflow, and the wrong core_id number will be reported. Since the HW reports 0x3f when the intel processor has over 64 cores, limit the max value written to EAX[31:26] to 63, so max num_cores should be 64. For EAX[14:25], though at present Q35 supports up to 4096 CPUs, by constructing a specific topology, the width of the APIC ID can be extended beyond 12 bits. For example, using `-smp threads=33,cores=9, modules=9` results in a die level offset of 6 + 4 + 4 = 14 bits, which can also cause overflow. check and honor the maximum value for EAX[14:25] as well. In addition, for host-cache-info case, also apply the same checks and fixes. Reviewed-by: Xiaoyao Li Signed-off-by: Qian Wen Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250714080859.1960104-7-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 608fdcf757..fdc677614d 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -349,11 +349,17 @@ static void encode_cache_cpuid4(CPUCacheInfo *cache, assert(cache->size == cache->line_size * cache->associativity * cache->partitions * cache->sets); + /* + * The following fields have bit-width limitations, so consider the + * maximum values to avoid overflow: + * Bits 25-14: maximum 4095. + * Bits 31-26: maximum 63. + */ *eax = CACHE_TYPE(cache->type) | CACHE_LEVEL(cache->level) | (cache->self_init ? CACHE_SELF_INIT_LEVEL : 0) | - (max_core_ids_in_package(topo_info) << 26) | - (max_thread_ids_for_cache(topo_info, cache->share_level) << 14); + (MIN(max_core_ids_in_package(topo_info), 63) << 26) | + (MIN(max_thread_ids_for_cache(topo_info, cache->share_level), 4095) << 14); assert(cache->line_size > 0); assert(cache->partitions > 0); @@ -7930,13 +7936,13 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, int host_vcpus_per_cache = 1 + ((*eax & 0x3FFC000) >> 14); *eax &= ~0xFC000000; - *eax |= max_core_ids_in_package(topo_info) << 26; + *eax |= MIN(max_core_ids_in_package(topo_info), 63) << 26; if (host_vcpus_per_cache > threads_per_pkg) { *eax &= ~0x3FFC000; /* Share the cache at package level. */ - *eax |= max_thread_ids_for_cache(topo_info, - CPU_TOPOLOGY_LEVEL_SOCKET) << 14; + *eax |= MIN(max_thread_ids_for_cache(topo_info, + CPU_TOPOLOGY_LEVEL_SOCKET), 4095) << 14; } } } else if (cpu->vendor_cpuid_only && IS_AMD_CPU(env)) { From 5d21ee453ad8e3f95f75e542cb3b35c5bb7cf23a Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 14 Jul 2025 16:08:59 +0800 Subject: [PATCH 77/77] i386/cpu: Honor maximum value for CPUID.8000001DH.EAX[25:14] CPUID.8000001DH:EAX[25:14] is "NumSharingCache", and the number of logical processors sharing this cache is the value of this field incremented by 1. Because of its width limitation, the maximum value currently supported is 4095. Though at present Q35 supports up to 4096 CPUs, by constructing a specific topology, the width of the APIC ID can be extended beyond 12 bits. For example, using `-smp threads=33,cores=9,modules=9` results in a die level offset of 6 + 4 + 4 = 14 bits, which can also cause overflow. Check and honor the maximum value as CPUID.04H did. Cc: Babu Moger Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250714080859.1960104-8-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index fdc677614d..da7d8dca63 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -560,7 +560,8 @@ static void encode_cache_cpuid8000001d(CPUCacheInfo *cache, *eax = CACHE_TYPE(cache->type) | CACHE_LEVEL(cache->level) | (cache->self_init ? CACHE_SELF_INIT_LEVEL : 0); - *eax |= max_thread_ids_for_cache(topo_info, cache->share_level) << 14; + /* Bits 25:14 - NumSharingCache: maximum 4095. */ + *eax |= MIN(max_thread_ids_for_cache(topo_info, cache->share_level), 4095) << 14; assert(cache->line_size > 0); assert(cache->partitions > 0);