|
@@ -6,8 +6,14 @@ use proc_macro::TokenStream;
|
|
|
use quote::{quote, ToTokens};
|
|
|
use syn::{parse_macro_input, Data, DeriveInput, Generics, Ident, Member};
|
|
|
|
|
|
+#[derive(Clone)]
|
|
|
+enum ParseMode {
|
|
|
+ Static,
|
|
|
+ Dynamic,
|
|
|
+}
|
|
|
+
|
|
|
#[derive(Clone, Default)]
|
|
|
-struct RustSBIImp {
|
|
|
+struct StaticImpl {
|
|
|
fence: Option<Member>,
|
|
|
hsm: Option<Member>,
|
|
|
ipi: Option<Member>,
|
|
@@ -22,55 +28,99 @@ struct RustSBIImp {
|
|
|
env_info: Option<Member>,
|
|
|
}
|
|
|
|
|
|
+impl StaticImpl {
|
|
|
+ fn replace_sbi_extension_ident(
|
|
|
+ &mut self,
|
|
|
+ extension_name: &str,
|
|
|
+ member: Member,
|
|
|
+ ) -> (bool, Option<Member>) {
|
|
|
+ match extension_name {
|
|
|
+ "rfnc" | "fence" => (true, self.fence.replace(member)),
|
|
|
+ "hsm" => (true, self.hsm.replace(member)),
|
|
|
+ "spi" | "ipi" => (true, self.ipi.replace(member)),
|
|
|
+ "srst" | "reset" => (true, self.reset.replace(member)),
|
|
|
+ "time" | "timer" => (true, self.timer.replace(member)),
|
|
|
+ "pmu" => (true, self.pmu.replace(member)),
|
|
|
+ "dbcn" | "console" => (true, self.console.replace(member)),
|
|
|
+ "susp" => (true, self.susp.replace(member)),
|
|
|
+ "cppc" => (true, self.cppc.replace(member)),
|
|
|
+ "nacl" => (true, self.nacl.replace(member)),
|
|
|
+ "sta" => (true, self.sta.replace(member)),
|
|
|
+ "info" | "env_info" => (true, self.env_info.replace(member)),
|
|
|
+ _ => (false, None),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone, Default)]
|
|
|
+struct DynamicImpl {
|
|
|
+ fence: Vec<Member>,
|
|
|
+ hsm: Vec<Member>,
|
|
|
+ ipi: Vec<Member>,
|
|
|
+ reset: Vec<Member>,
|
|
|
+ timer: Vec<Member>,
|
|
|
+ pmu: Vec<Member>,
|
|
|
+ console: Vec<Member>,
|
|
|
+ susp: Vec<Member>,
|
|
|
+ cppc: Vec<Member>,
|
|
|
+ nacl: Vec<Member>,
|
|
|
+ sta: Vec<Member>,
|
|
|
+ env_info: Option<Member>,
|
|
|
+}
|
|
|
+
|
|
|
+impl DynamicImpl {
|
|
|
+ fn push_sbi_extension_ident(&mut self, extension_name: &str, member: Member) -> bool {
|
|
|
+ match extension_name {
|
|
|
+ "rfnc" | "fence" => self.fence.push(member),
|
|
|
+ "hsm" => self.hsm.push(member),
|
|
|
+ "spi" | "ipi" => self.ipi.push(member),
|
|
|
+ "srst" | "reset" => self.reset.push(member),
|
|
|
+ "time" | "timer" => self.timer.push(member),
|
|
|
+ "pmu" => self.pmu.push(member),
|
|
|
+ "dbcn" | "console" => self.console.push(member),
|
|
|
+ "susp" => self.susp.push(member),
|
|
|
+ "cppc" => self.cppc.push(member),
|
|
|
+ "nacl" => self.nacl.push(member),
|
|
|
+ "sta" => self.sta.push(member),
|
|
|
+ "info" | "env_info" => return self.env_info.replace(member).is_none(),
|
|
|
+ _ => return false,
|
|
|
+ }
|
|
|
+ true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// This macro should be used in `rustsbi` crate as `rustsbi::RustSBI`.
|
|
|
#[proc_macro_derive(RustSBI, attributes(rustsbi))]
|
|
|
pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
|
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
|
|
|
|
- let Data::Struct(strukt) = input.data else {
|
|
|
+ let Data::Struct(strukt) = &input.data else {
|
|
|
panic!("#[derive(RustSBI)] must be used on structs");
|
|
|
};
|
|
|
|
|
|
- let mut imp = RustSBIImp::default();
|
|
|
-
|
|
|
- let mut replace_sbi_extension_ident =
|
|
|
- |extension_name: &str, member: Member| match extension_name {
|
|
|
- "rfnc" | "fence" => (true, imp.fence.replace(member)),
|
|
|
- "hsm" => (true, imp.hsm.replace(member)),
|
|
|
- "spi" | "ipi" => (true, imp.ipi.replace(member)),
|
|
|
- "srst" | "reset" => (true, imp.reset.replace(member)),
|
|
|
- "time" | "timer" => (true, imp.timer.replace(member)),
|
|
|
- "pmu" => (true, imp.pmu.replace(member)),
|
|
|
- "dbcn" | "console" => (true, imp.console.replace(member)),
|
|
|
- "susp" => (true, imp.susp.replace(member)),
|
|
|
- "cppc" => (true, imp.cppc.replace(member)),
|
|
|
- "nacl" => (true, imp.nacl.replace(member)),
|
|
|
- "sta" => (true, imp.sta.replace(member)),
|
|
|
- "info" | "env_info" => (true, imp.env_info.replace(member)),
|
|
|
- _ => (false, None),
|
|
|
- };
|
|
|
let mut ans = TokenStream::new();
|
|
|
- let check_already_exists = |field: &syn::Field,
|
|
|
- extension_name: &str,
|
|
|
- origin: Option<Member>,
|
|
|
- ans: &mut TokenStream| {
|
|
|
- if let Some(_origin) = origin {
|
|
|
- // TODO: provide more detailed proc macro error hinting that previous
|
|
|
- // definition of this extension resides in `origin` once RFC 1566
|
|
|
- // (Procedural Macro Diagnostics) is stabilized.
|
|
|
- // Link: https://github.com/rust-lang/rust/issues/54140
|
|
|
- let error = syn::Error::new_spanned(
|
|
|
- field,
|
|
|
- format!(
|
|
|
- "more than one field defined SBI extension '{}'. \
|
|
|
- At most one fields should define the same SBI extension; consider using \
|
|
|
- #[rustsbi(skip)] to ignore fields that shouldn't be treated as an extension.",
|
|
|
- extension_name
|
|
|
- ),
|
|
|
- );
|
|
|
- ans.extend(TokenStream::from(error.to_compile_error()));
|
|
|
+ let mut parse_mode = ParseMode::Static;
|
|
|
+
|
|
|
+ for attr in &input.attrs {
|
|
|
+ if !attr.path().is_ident("rustsbi") {
|
|
|
+ continue;
|
|
|
}
|
|
|
- };
|
|
|
+ let parsed = attr.parse_nested_meta(|meta| {
|
|
|
+ if meta.path.is_ident("dynamic") {
|
|
|
+ parse_mode = ParseMode::Dynamic;
|
|
|
+ Ok(())
|
|
|
+ } else {
|
|
|
+ let path = meta.path.to_token_stream().to_string().replace(' ', "");
|
|
|
+ Err(meta.error(format_args!("unknown RustSBI struct attribute `{}`", path)))
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if let Err(err) = parsed {
|
|
|
+ ans.extend(TokenStream::from(err.to_compile_error()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut static_impl = StaticImpl::default();
|
|
|
+ let mut dynamic_impl = DynamicImpl::default();
|
|
|
|
|
|
for (i, field) in strukt.fields.iter().enumerate() {
|
|
|
let member = match &field.ident {
|
|
@@ -89,11 +139,22 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
|
|
|
current_meta_accepted = true;
|
|
|
} else if let Some(meta_path_ident) = meta.path.get_ident() {
|
|
|
let extension_name = &meta_path_ident.to_string();
|
|
|
- let (replaced, origin) =
|
|
|
- replace_sbi_extension_ident(extension_name, member.clone());
|
|
|
- if replaced {
|
|
|
- check_already_exists(field, extension_name, origin, &mut ans);
|
|
|
- current_meta_accepted = true;
|
|
|
+ match parse_mode {
|
|
|
+ ParseMode::Static => {
|
|
|
+ let (replaced, origin) = static_impl
|
|
|
+ .replace_sbi_extension_ident(extension_name, member.clone());
|
|
|
+ if replaced {
|
|
|
+ check_already_exists(field, extension_name, origin, &mut ans);
|
|
|
+ current_meta_accepted = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ParseMode::Dynamic => {
|
|
|
+ let replaced = dynamic_impl
|
|
|
+ .push_sbi_extension_ident(extension_name, member.clone());
|
|
|
+ if replaced {
|
|
|
+ current_meta_accepted = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
if current_meta_accepted {
|
|
@@ -114,17 +175,59 @@ pub fn derive_rustsbi(input: TokenStream) -> TokenStream {
|
|
|
continue;
|
|
|
}
|
|
|
if let Some(field_ident) = &field.ident {
|
|
|
- let (_replaced, origin) =
|
|
|
- replace_sbi_extension_ident(field_ident.to_string().as_str(), member);
|
|
|
- check_already_exists(field, &field_ident.to_string(), origin, &mut ans);
|
|
|
+ match parse_mode {
|
|
|
+ ParseMode::Static => {
|
|
|
+ let (_replaced, origin) = static_impl
|
|
|
+ .replace_sbi_extension_ident(field_ident.to_string().as_str(), member);
|
|
|
+ check_already_exists(field, &field_ident.to_string(), origin, &mut ans);
|
|
|
+ }
|
|
|
+ ParseMode::Dynamic => {
|
|
|
+ let _replaced = dynamic_impl
|
|
|
+ .push_sbi_extension_ident(field_ident.to_string().as_str(), member);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- ans.extend(impl_derive_rustsbi(&input.ident, imp, &input.generics));
|
|
|
+ match parse_mode {
|
|
|
+ ParseMode::Static => ans.extend(impl_derive_rustsbi_static(
|
|
|
+ &input.ident,
|
|
|
+ static_impl,
|
|
|
+ &input.generics,
|
|
|
+ )),
|
|
|
+ ParseMode::Dynamic => ans.extend(impl_derive_rustsbi_dynamic(
|
|
|
+ &input.ident,
|
|
|
+ dynamic_impl,
|
|
|
+ &input.generics,
|
|
|
+ )),
|
|
|
+ };
|
|
|
ans
|
|
|
}
|
|
|
|
|
|
-fn impl_derive_rustsbi(name: &Ident, imp: RustSBIImp, generics: &Generics) -> TokenStream {
|
|
|
+fn check_already_exists(
|
|
|
+ field: &syn::Field,
|
|
|
+ extension_name: &str,
|
|
|
+ origin: Option<Member>,
|
|
|
+ ans: &mut TokenStream,
|
|
|
+) {
|
|
|
+ if let Some(_origin) = origin {
|
|
|
+ // TODO: provide more detailed proc macro error hinting that previous
|
|
|
+ // definition of this extension resides in `origin` once RFC 1566
|
|
|
+ // (Procedural Macro Diagnostics) is stabilized.
|
|
|
+ // Link: https://github.com/rust-lang/rust/issues/54140
|
|
|
+ let error = syn::Error::new_spanned(
|
|
|
+ field,
|
|
|
+ format!(
|
|
|
+ "more than one field defined SBI extension '{}'. \
|
|
|
+ At most one fields should define the same SBI extension; consider using \
|
|
|
+ #[rustsbi(skip)] to ignore fields that shouldn't be treated as an extension.",
|
|
|
+ extension_name
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ ans.extend(TokenStream::from(error.to_compile_error()));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn impl_derive_rustsbi_static(name: &Ident, imp: StaticImpl, generics: &Generics) -> TokenStream {
|
|
|
let base_probe: usize = 1;
|
|
|
let fence_probe: usize = if imp.fence.is_some() { 1 } else { 0 };
|
|
|
let hsm_probe: usize = if imp.hsm.is_some() { 1 } else { 0 };
|
|
@@ -244,3 +347,173 @@ fn impl_derive_rustsbi(name: &Ident, imp: RustSBIImp, generics: &Generics) -> To
|
|
|
};
|
|
|
gen.into()
|
|
|
}
|
|
|
+
|
|
|
+fn impl_derive_rustsbi_dynamic(name: &Ident, imp: DynamicImpl, generics: &Generics) -> TokenStream {
|
|
|
+ let mut fence_contents = quote! {};
|
|
|
+ let mut prober_fence = quote! {};
|
|
|
+ for fence in &imp.fence {
|
|
|
+ fence_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_fence_probe(&self.#fence) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_fence(&self.#fence, param, function)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ prober_fence.extend(quote! {
|
|
|
+ let value = ::rustsbi::_rustsbi_fence_probe(&self.0.#fence);
|
|
|
+ if value != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return value
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ let mut timer_contents = quote! {};
|
|
|
+ for timer in &imp.timer {
|
|
|
+ timer_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_timer_probe(&self.#timer) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_timer(&self.#timer, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut ipi_contents = quote! {};
|
|
|
+ for ipi in &imp.ipi {
|
|
|
+ ipi_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_ipi_probe(&self.#ipi) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_ipi(&self.#ipi, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut hsm_contents = quote! {};
|
|
|
+ for hsm in &imp.hsm {
|
|
|
+ hsm_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_hsm_probe(&self.#hsm) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_hsm(&self.#hsm, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut reset_contents = quote! {};
|
|
|
+ for reset in &imp.reset {
|
|
|
+ reset_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_reset_probe(&self.#reset) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_reset(&self.#reset, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut pmu_contents = quote! {};
|
|
|
+ for pmu in &imp.pmu {
|
|
|
+ pmu_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_pmu_probe(&self.#pmu) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_pmu(&self.#pmu, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut console_contents = quote! {};
|
|
|
+ for console in &imp.console {
|
|
|
+ console_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_console_probe(&self.#console) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_console(&self.#console, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut susp_contents = quote! {};
|
|
|
+ for susp in &imp.susp {
|
|
|
+ susp_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_susp_probe(&self.#susp) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_susp(&self.#susp, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut cppc_contents = quote! {};
|
|
|
+ for cppc in &imp.cppc {
|
|
|
+ cppc_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_cppc_probe(&self.#cppc) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_cppc(&self.#cppc, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut nacl_contents = quote! {};
|
|
|
+ for nacl in &imp.nacl {
|
|
|
+ nacl_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_nacl_probe(&self.#nacl) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_nacl(&self.#nacl, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ let mut sta_contents = quote! {};
|
|
|
+ for sta in &imp.sta {
|
|
|
+ sta_contents.extend(quote! {
|
|
|
+ if ::rustsbi::_rustsbi_sta_probe(&self.#sta) != ::rustsbi::spec::base::UNAVAILABLE_EXTENSION {
|
|
|
+ return ::rustsbi::_rustsbi_sta(&self.#sta, param, function)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ let define_prober = quote! {
|
|
|
+ struct _Prober<'a>(&'a #name);
|
|
|
+ impl<'a> ::rustsbi::_ExtensionProbe for _Prober<'a> {
|
|
|
+ #[inline(always)]
|
|
|
+ fn probe_extension(&self, extension: usize) -> usize {
|
|
|
+ match extension {
|
|
|
+ // ::rustsbi::spec::base::EID_BASE => self.base,
|
|
|
+ // ::rustsbi::spec::time::EID_TIME => self.timer,
|
|
|
+ // ::rustsbi::spec::spi::EID_SPI => self.ipi,
|
|
|
+ ::rustsbi::spec::rfnc::EID_RFNC => { #prober_fence ::rustsbi::spec::base::UNAVAILABLE_EXTENSION },
|
|
|
+ // ::rustsbi::spec::srst::EID_SRST => self.reset,
|
|
|
+ // ::rustsbi::spec::hsm::EID_HSM => self.hsm,
|
|
|
+ // ::rustsbi::spec::pmu::EID_PMU => self.pmu,
|
|
|
+ // ::rustsbi::spec::dbcn::EID_DBCN => self.console,
|
|
|
+ // ::rustsbi::spec::susp::EID_SUSP => self.susp,
|
|
|
+ // ::rustsbi::spec::cppc::EID_CPPC => self.cppc,
|
|
|
+ // ::rustsbi::spec::nacl::EID_NACL => self.nacl,
|
|
|
+ // ::rustsbi::spec::sta::EID_STA => self.sta,
|
|
|
+ _ => ::rustsbi::spec::base::UNAVAILABLE_EXTENSION,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let base_result = if let Some(env_info) = imp.env_info {
|
|
|
+ quote! {
|
|
|
+ ::rustsbi::_rustsbi_base_env_info(param, function, &self.#env_info, prober)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ match () {
|
|
|
+ #[cfg(not(feature = "machine"))]
|
|
|
+ () => quote! {
|
|
|
+ compile_error!(
|
|
|
+ "can't derive RustSBI: #[cfg(feature = \"machine\")] is needed to derive RustSBI with no extra `EnvInfo` provided; \
|
|
|
+ consider adding an `info` parameter to provide machine environment information implementing `rustsbi::EnvInfo`\
|
|
|
+ if RustSBI is not run on machine mode."
|
|
|
+ )
|
|
|
+ },
|
|
|
+ #[cfg(feature = "machine")]
|
|
|
+ () => quote! {
|
|
|
+ ::rustsbi::_rustsbi_base_bare(param, function, prober)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
|
+ let gen = quote! {
|
|
|
+ impl #impl_generics ::rustsbi::RustSBI for #name #ty_generics #where_clause {
|
|
|
+ #[inline]
|
|
|
+ fn handle_ecall(&self, extension: usize, function: usize, param: [usize; 6]) -> ::rustsbi::SbiRet {
|
|
|
+ match extension {
|
|
|
+ ::rustsbi::spec::rfnc::EID_RFNC => { #fence_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::time::EID_TIME => { #timer_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::spi::EID_SPI => { #ipi_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::hsm::EID_HSM => { #hsm_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::srst::EID_SRST => { #reset_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::pmu::EID_PMU => { #pmu_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::dbcn::EID_DBCN => { #console_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::susp::EID_SUSP => { #susp_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::cppc::EID_CPPC => { #cppc_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::nacl::EID_NACL => { #nacl_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::sta::EID_STA => { #sta_contents ::rustsbi::SbiRet::not_supported() },
|
|
|
+ ::rustsbi::spec::base::EID_BASE => {
|
|
|
+ #define_prober
|
|
|
+ let prober = _Prober(&self);
|
|
|
+ #base_result
|
|
|
+ }
|
|
|
+ _ => ::rustsbi::SbiRet::not_supported(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ gen.into()
|
|
|
+}
|