diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 97fa8c5..14b9313 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,33 +2,36 @@ name: Rust on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: ⚡ Cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo - - name: Set permissions - run: chmod +x d - - name: Load sources - run: ./d download-all - - name: Run build - run: cargo run --release --bin stm32-bindings-gen - - name: Run package build - run: cd build/stm32-bindings && cargo build --target=thumbv8m.main-none-eabihf + - uses: actions/checkout@v4 + - name: ⚡ Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo + - name: Install ARM GCC toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi + - name: Set permissions + run: chmod +x d + - name: Load sources + run: ./d download-all + - name: Run build + run: cargo run --release --bin stm32-bindings-gen + - name: Run package build + run: cd build/stm32-bindings && cargo build --target=thumbv8m.main-none-eabihf diff --git a/stm32-bindings-gen/inc/app_conf.h b/stm32-bindings-gen/inc/app_conf.h new file mode 100644 index 0000000..c8d8e73 --- /dev/null +++ b/stm32-bindings-gen/inc/app_conf.h @@ -0,0 +1,44 @@ +/* Stub configuration header used during bindings generation. */ + +#ifndef STM32_BINDINGS_GEN_APP_CONF_H +#define STM32_BINDINGS_GEN_APP_CONF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Provide minimal definitions so that ST middleware headers compile under bindgen. */ + +#ifndef LOG_LEVEL_INFO +#define LOG_LEVEL_INFO (0) +#endif + +#ifndef LOG_INFO_APP +#define LOG_INFO_APP(...) +#endif + +#ifndef CFG_LOG_SUPPORTED +#define CFG_LOG_SUPPORTED (0U) +#endif + +#ifndef PWR_LDO_SUPPLY +#define PWR_LDO_SUPPLY (0U) +#endif + +#ifndef RADIO_INTR_NUM +#define RADIO_INTR_NUM (0U) +#endif + +#ifndef RADIO_INTR_PRIO_HIGH +#define RADIO_INTR_PRIO_HIGH (0U) +#endif + +#ifndef RADIO_INTR_PRIO_LOW +#define RADIO_INTR_PRIO_LOW (0U) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* STM32_BINDINGS_GEN_APP_CONF_H */ diff --git a/stm32-bindings-gen/inc/ble-wba.h b/stm32-bindings-gen/inc/ble-wba.h new file mode 100644 index 0000000..d393691 --- /dev/null +++ b/stm32-bindings-gen/inc/ble-wba.h @@ -0,0 +1,91 @@ +#ifndef BLE_WBA_BINDINGS_H_ +#define BLE_WBA_BINDINGS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "cmsis_compiler.h" + +/* Toolchain abstraction utilities */ + + +/* Link Layer command interface */ +#include "bsp.h" +#include "common_types.h" +#include "dtm.h" +#include "event_manager.h" +#include "evnt_schdlr_gnrc_if.h" +#include "hci.h" +#include "ll_error.h" +#include "ll_intf.h" +#include "ll_intf_cmn.h" +#include "mac_host_intf.h" +#include "mem_intf.h" +#include "os_wrapper.h" +#include "platform.h" +#include "power_table.h" +#include "pta.h" +#include "ral.h" +#include "rfd_dev_config.h" + + +/* Core BLE stack */ +#include "blestack.h" +#include "ble_bufsize.h" +#include "ble_codec.h" +#include "ble_const.h" +#include "ble_core.h" +#include "ble_defs.h" +#include "ble_legacy.h" +#include "ble_std.h" +#include "bleplat.h" + +/* Auto-generated ACI command definitions */ +#include "auto/ble_events.h" +#include "auto/ble_gap_aci.h" +#include "auto/ble_gatt_aci.h" +#include "auto/ble_hal_aci.h" +#include "auto/ble_hci_le.h" +#include "auto/ble_l2cap_aci.h" +#include "auto/ble_raw_api.h" +#include "auto/ble_types.h" +#include "auto/ble_vs_codes.h" + +/* BLE Audio stack */ +#include "ble_audio_stack.h" +#include "ble_audio_plat.h" +#include "audio_types.h" +#include "bap_bufsize.h" +#include "bap_types.h" +#include "cap.h" +#include "cap_types.h" +#include "ccp.h" +#include "ccp_types.h" +#include "csip.h" +#include "csip_types.h" +#include "ltv_utils.h" +#include "mcp.h" +#include "mcp_types.h" +#include "micp.h" +#include "micp_types.h" +#include "vcp.h" +#include "vcp_types.h" + +/* Codec manager interfaces */ +#include "codec_if.h" +#include "codec_mngr.h" + +/* LC3 codec interfaces */ +#include "LC3.h" +#include "LC3_decoder.h" +#include "LC3_encoder.h" + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BLE_WBA_BINDINGS_H_ */ diff --git a/stm32-bindings-gen/inc/string.h b/stm32-bindings-gen/inc/string.h new file mode 100644 index 0000000..07ba823 --- /dev/null +++ b/stm32-bindings-gen/inc/string.h @@ -0,0 +1,28 @@ +#ifndef STM32_BINDINGS_GEN_STUB_STRING_H +#define STM32_BINDINGS_GEN_STUB_STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void *memcpy(void *dest, const void *src, size_t n); +void *memmove(void *dest, const void *src, size_t n); +void *memset(void *s, int c, size_t n); +int memcmp(const void *s1, const void *s2, size_t n); + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t maxlen); +char *strcpy(char *dest, const char *src); +char *strncpy(char *dest, const char *src, size_t n); +char *strcat(char *dest, const char *src); +char *strncat(char *dest, const char *src, size_t n); +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* STM32_BINDINGS_GEN_STUB_STRING_H */ \ No newline at end of file diff --git a/stm32-bindings-gen/inc/wba_wpan_mac.h b/stm32-bindings-gen/inc/wba_wpan_mac.h index 994fdd4..04d6cac 100644 --- a/stm32-bindings-gen/inc/wba_wpan_mac.h +++ b/stm32-bindings-gen/inc/wba_wpan_mac.h @@ -1,4 +1,18 @@ #include "st_mac_802_15_4_sap.h" +#include "st_mac_802_15_4_types.h" +#include "st_mac_802_15_4_core.h" +#include "st_mac_802_15_4_sys.h" +#include "st_mac_802_15_4_config.h" +#include "st_mac_802_15_4_core_config.h" +#include "st_mac_802_15_4_sys_svc.h" +#include "st_mac_802_15_4_svc.h" +#include "st_mac_802_15_4_ext_svc.h" +#include "st_mac_802_15_4_raw_svc.h" +#include "st_mac_802_15_4_dbg_svc.h" +#include "st_mac_buffer_management.h" +#include "st_mac_queue.h" +#include "stm_queue.h" +#include "utilities_common.h" /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef APP_CONF_H @@ -459,4 +473,4 @@ extern "C" } /* extern "C" */ #endif -#endif /* APP_CERTIFICATION_MAC_802_15_4_PROCESS_H */ \ No newline at end of file +#endif /* APP_CERTIFICATION_MAC_802_15_4_PROCESS_H */ diff --git a/stm32-bindings-gen/res/src/bindings/mod.rs b/stm32-bindings-gen/res/src/bindings/mod.rs index b5570eb..6f24ac3 100644 --- a/stm32-bindings-gen/res/src/bindings/mod.rs +++ b/stm32-bindings-gen/res/src/bindings/mod.rs @@ -1,2 +1,13 @@ #[cfg(feature = "wba_wpan_mac")] -mod wba_wpan_mac; +pub mod wba_wpan_mac; +#[cfg(feature = "wba_wpan_mac")] +pub use self::wba_wpan_mac as mac_802_15_4; +#[cfg(feature = "wba_wpan_mac")] +pub use self::wba_wpan_mac as wpan_wba; + +#[cfg(feature = "wba_wpan_ble")] +pub mod ble_stack; +#[cfg(feature = "wba_wpan_ble")] +pub use self::ble_stack as ble; +#[cfg(feature = "wba_wpan_ble")] +pub use self::ble_stack as ble_wba; diff --git a/stm32-bindings-gen/res/src/lib.rs b/stm32-bindings-gen/res/src/lib.rs index 1f12fe9..7593be9 100644 --- a/stm32-bindings-gen/res/src/lib.rs +++ b/stm32-bindings-gen/res/src/lib.rs @@ -4,4 +4,5 @@ #![allow(non_camel_case_types)] #![doc(html_no_source)] -mod bindings; +pub mod bindings; +pub use bindings::*; diff --git a/stm32-bindings-gen/src/lib.rs b/stm32-bindings-gen/src/lib.rs index f160d63..e18db9b 100644 --- a/stm32-bindings-gen/src/lib.rs +++ b/stm32-bindings-gen/src/lib.rs @@ -1,6 +1,9 @@ use bindgen::callbacks::{ItemInfo, ItemKind, ParseCallbacks}; use std::io::Write; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use tempfile::NamedTempFile; #[derive(Debug)] @@ -19,40 +22,52 @@ impl ParseCallbacks for UppercaseCallbacks { pub struct Options { pub out_dir: PathBuf, pub sources_dir: PathBuf, + pub target_triple: String, } -pub struct Library { - pub sources_dir: PathBuf, - pub target_triple: String, - pub header: &'static [u8], - pub module: &'static str, - pub includes: Vec, - pub library: PathBuf, +fn host_isystem_args() -> Vec { + let mut args = Vec::new(); + if cfg!(target_os = "macos") { + if let Ok(output) = Command::new("xcrun").arg("--show-sdk-path").output() { + if output.status.success() { + if let Ok(path) = String::from_utf8(output.stdout) { + let trimmed = path.trim(); + if !trimmed.is_empty() { + args.push(format!("-isystem{}/usr/include", trimmed)); + } + } + } + } + } + args } pub struct Gen { opts: Options, - libs: Vec, } impl Gen { - pub fn new(opts: Options, libs: Vec) -> Self { - Self { opts, libs } + pub fn new(opts: Options) -> Self { + Self { opts } } pub fn run_gen(&mut self) { - let _ = fs::remove_dir_all(self.opts.out_dir.clone()); - fs::create_dir_all(self.opts.out_dir.join("src/bindings")).unwrap(); - fs::create_dir_all(self.opts.out_dir.join("src/lib")).unwrap(); + println!( + "Generating bindings into {} for target {}", + self.opts.out_dir.display(), + self.opts.target_triple + ); - for lib in &self.libs { - let sources_dir = self.opts.sources_dir.join(&lib.sources_dir); + self.prepare_out_dir(); + self.write_static_files(); - // Create a named temporary file - let mut header = NamedTempFile::with_suffix(".h").unwrap(); + let mut modules = Vec::new(); + let mut aliases = Vec::new(); - // Write some data to the first handle - header.write_all(lib.header).unwrap(); + for spec in BINDING_SPECS { + println!(" -> generating `{}` bindings", spec.module); + self.generate_bindings_for_spec(spec); + self.copy_artifacts_for_spec(spec); // The bindgen::Builder is the main entry point // to bindgen, and lets you build up options for @@ -64,99 +79,216 @@ impl Gen { // Force Clang to use the same layout as the selected target. .clang_arg(&target_flag); + let crate_inc = Path::new(env!("CARGO_MANIFEST_DIR")).join("inc"); + builder = builder.clang_arg(&format!("-iquote{}", crate_inc.display())); + builder = builder.clang_arg(&format!("-I{}", crate_inc.display())); + for include_arg in &lib.includes { builder = builder.clang_arg(&format!( "-I{}", sources_dir.join(include_arg).to_str().unwrap() )); } + } - if lib.target_triple.to_ascii_lowercase().starts_with("thumb") { - builder = builder.clang_arg("-mthumb"); + self.write_bindings_mod(&modules, &aliases); + } + + fn prepare_out_dir(&self) { + let _ = fs::remove_dir_all(&self.opts.out_dir); + self.create_dir(self.opts.out_dir.join("src/bindings")); + self.create_dir(self.opts.out_dir.join("src/lib")); + } + + fn write_static_files(&self) { + self.write_bytes("README.md", include_bytes!("../res/README.md")); + self.write_bytes("Cargo.toml", include_bytes!("../res/Cargo.toml")); + self.write_bytes("build.rs", include_bytes!("../res/build.rs")); + self.write_bytes("src/lib.rs", include_bytes!("../res/src/lib.rs")); + } + + fn write_bindings_mod( + &self, + modules: &[(String, Option)], + aliases: &[(String, String, Option)], + ) { + let mut body = String::new(); + for (module, feature) in modules { + if let Some(feature) = feature { + body.push_str(&format!("#[cfg(feature = \"{feature}\")]\n")); } - let bindings = builder - // The input header we would like to generate - // bindings for. - .header(header.path().to_str().unwrap()) - // Finish the builder and generate the bindings. - .generate() - // Unwrap the Result and panic on failure. - .expect("Unable to generate bindings"); - - let out_path = self - .opts - .out_dir - .join("src") - .join("bindings") - .join(format!("{}.rs", lib.module)); - - bindings - .write_to_file(&out_path) - .expect("Couldn't write bindings!"); - - let mut file_contents = fs::read_to_string(&out_path).unwrap(); - file_contents = file_contents - .replace("::std::mem::", "::core::mem::") - .replace("::std::os::raw::", "::core::ffi::") - .replace("::std::option::", "::core::option::"); - - file_contents = file_contents - .lines() - .map(|line| { - if let Some(rest) = line.strip_prefix("pub const ") { - if let Some((name, tail)) = rest.split_once(':') { - let upper = name.trim().to_ascii_uppercase(); - return format!("pub const {}:{}", upper, tail); - } - } - line.to_owned() - }) - .collect::>() - .join("\n"); + body.push_str("pub mod "); + body.push_str(module); + body.push_str(";\n"); + } + if !aliases.is_empty() { + body.push('\n'); + for (module, alias, feature) in aliases { + if let Some(feature) = feature { + body.push_str(&format!("#[cfg(feature = \"{feature}\")]\n")); + } + body.push_str("pub use self::"); + body.push_str(module); + body.push_str(" as "); + body.push_str(alias); + body.push_str(";\n"); + } + } + self.write_string("src/bindings/mod.rs", body); + } + + fn generate_bindings_for_spec(&self, spec: &BindingSpec) { + let mut builder = bindgen::Builder::default() + .parse_callbacks(Box::new(UppercaseCallbacks)) + .header(spec.header) + .clang_arg(format!("--target={}", self.opts.target_triple)); + + for arg in host_isystem_args() { + builder = builder.clang_arg(arg); + } + + let crate_inc = Path::new(env!("CARGO_MANIFEST_DIR")).join("inc"); + builder = builder.clang_arg(format!("-iquote{}", crate_inc.display())); + + if Self::is_thumb_target(&self.opts.target_triple) { + builder = builder.clang_arg("-mthumb"); + } + + for dir in spec.include_dirs { + let include_path = Path::new(dir); + let resolved = if include_path.is_absolute() { + include_path.to_path_buf() + } else { + self.opts.sources_dir.join(include_path) + }; + builder = builder.clang_arg(format!("-I{}", resolved.display())); + } + + for arg in spec.clang_args { + builder = builder.clang_arg(*arg); + } + + if !spec.allowlist.is_empty() { + for pattern in spec.allowlist { + builder = builder + .allowlist_type(pattern) + .allowlist_var(pattern) + .allowlist_function(pattern); + } + } - if !file_contents.ends_with('\n') { - file_contents.push('\n'); + let bindings = builder + .generate() + .unwrap_or_else(|err| panic!("Unable to generate bindings for {}: {err}", spec.module)); + + let mut file_contents = bindings.to_string(); + file_contents = Self::normalize_bindings(file_contents); + + let out_path = self + .opts + .out_dir + .join("src/bindings") + .join(format!("{}.rs", spec.module)); + + self.write_string_path(&out_path, file_contents); + } + + fn copy_artifacts_for_spec(&self, spec: &BindingSpec) { + for artifact in spec.library_artifacts { + let src = self.opts.sources_dir.join(artifact.source); + let dst = self.opts.out_dir.join(artifact.destination); + + if src.is_file() { + self.copy_file(&src, &dst) + .unwrap_or_else(|err| panic!("Failed to copy file {}: {err}", src.display())); + } else if src.is_dir() { + self.copy_dir(&src, &dst) + .unwrap_or_else(|err| panic!("Failed to copy dir {}: {err}", src.display())); + } else { + panic!( + "Artifact source {} is neither file nor directory", + src.display() + ); } + } + } + + fn write_bytes(&self, relative: &str, bytes: &[u8]) { + let path = self.opts.out_dir.join(relative); + if let Some(parent) = path.parent() { + self.create_dir(parent); + } + fs::write(path, bytes).expect("Unable to write bytes"); + } + + fn write_string(&self, relative: &str, contents: String) { + let path = self.opts.out_dir.join(relative); + self.write_string_path(&path, contents); + } + + fn write_string_path(&self, path: &Path, mut contents: String) { + if !contents.ends_with('\n') { + contents.push('\n'); + } + if let Some(parent) = path.parent() { + self.create_dir(parent); + } + fs::write(path, contents).expect("Unable to write string"); + } + + fn create_dir>(&self, path: P) { + let path_ref = path.as_ref(); + if !path_ref.exists() { + fs::create_dir_all(path_ref).expect("Unable to create directory"); + } + } + + fn copy_file(&self, src: &Path, dst: &Path) -> io::Result<()> { + if let Some(parent) = dst.parent() { + fs::create_dir_all(parent)?; + } + fs::copy(src, dst)?; + Ok(()) + } + + fn copy_dir(&self, src: &Path, dst: &Path) -> io::Result<()> { + if !dst.exists() { + fs::create_dir_all(dst)?; + } + for entry in fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + let target = dst.join(entry.file_name()); + if path.is_dir() { + self.copy_dir(&path, &target)?; + } else { + self.copy_file(&path, &target)?; + } + } + Ok(()) + } + + fn normalize_bindings(mut contents: String) -> String { + for (from, to) in STD_TO_CORE_REPLACEMENTS { + contents = contents.replace(from, to); + } + + contents + .lines() + .map(|line| { + if let Some(rest) = line.strip_prefix("pub const ") { + if let Some((name, tail)) = rest.split_once(':') { + let upper = name.trim().to_ascii_uppercase(); + return format!("pub const {}:{}", upper, tail); + } + } + line.to_owned() + }) + .collect::>() + .join("\n") + } - fs::write(&out_path, file_contents).unwrap(); - - // copy misc files - fs::copy( - sources_dir.join(&lib.library), - self.opts - .out_dir - .join("src") - .join("lib") - .join(format!("{}.a", lib.module)), - ) - .unwrap(); - } - - fs::write( - self.opts.out_dir.join("README.md"), - include_bytes!("../res/README.md"), - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("Cargo.toml"), - include_bytes!("../res/Cargo.toml"), - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("build.rs"), - include_bytes!("../res/build.rs"), - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("src/lib.rs"), - include_bytes!("../res/src/lib.rs"), - ) - .unwrap(); - - fs::write( - self.opts.out_dir.join("src/bindings/mod.rs"), - include_bytes!("../res/src/bindings/mod.rs"), - ) - .unwrap(); + fn is_thumb_target(triple: &str) -> bool { + triple.trim().to_ascii_lowercase().starts_with("thumb") } } diff --git a/stm32-bindings-gen/src/main.rs b/stm32-bindings-gen/src/main.rs index f7c4d68..7e434f7 100644 --- a/stm32-bindings-gen/src/main.rs +++ b/stm32-bindings-gen/src/main.rs @@ -1,26 +1,29 @@ use std::{env, path::PathBuf, process}; -use stm32_bindings_gen::{Gen, Library, Options}; +use stm32_bindings_gen::{Gen, Options}; fn main() { let out_dir = PathBuf::from("build/stm32-bindings"); - let sources_dir = PathBuf::from("sources"); + let sources_dir = resolve_sources_dir(); + let target_triple = resolve_target_triple(); let opts = Options { out_dir, sources_dir, + target_triple, }; - let libs = Vec::from([Library { - target_triple: String::from("thumbv8m.main-none-eabihf"), - sources_dir: "STM32CubeWBA".into(), - header: include_bytes!("../inc/wba_wpan_mac.h"), - module: "wba_wpan_mac", - includes: Vec::from(["Middlewares/ST/STM32_WPAN/mac_802_15_4/core/inc".into()]), - library: "Middlewares/ST/STM32_WPAN/mac_802_15_4/lib/wba_mac_lib.a".into(), - }]); + Gen::new(opts).run_gen(); +} + +fn resolve_sources_dir() -> PathBuf { + let nested = PathBuf::from("sources/STM32CubeWBA"); - Gen::new(opts, libs).run_gen(); + if nested.exists() { + nested + } else { + PathBuf::from("sources") + } } #[allow(dead_code)]