Skip to content

Commit

Permalink
Minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
RainerZ committed Jan 20, 2025
1 parent 760e1af commit 0942a57
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cargo.lock
*.tmp
*.bin

# Unnessesary CANape artefacts
# Unnecessary CANape artefacts
*.hex
*.HEX
*.mf4
Expand Down
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ members = [
"examples/hello_xcp",
"examples/single_thread_demo",
"examples/multi_thread_demo",
"examples/protobuf_demo",
"examples/point_cloud_demo",
"examples/rayon_demo",
"examples/tokio_demo",
"examples/type_description_demo",
"examples/xcp_idl_generator_demo",
"examples/xcp_daemon"
"examples/xcp_idl_generator_demo"
]


Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ Requires CANape 22. Example projects are updated to CANape 23.

XCP is a measurement and calibration protocol commonly used in the automotive industry. It is an ASAM standard.

It provides real time signal acquisition (measurement) and modification of parameter constants (calibrations) in a target microcontroller system (ECU), to help observing and optimizing control algorithms in real time.
It provides real time signal acquisition (measurement) and modification of parameter constants (calibrations) in a target micro controller system (ECU), to help observing and optimizing control algorithms in real time.

Timestamped events, measurement variables and parameter constants are described by an ASAM-A2L description file, another associated ASAM standard.
Data objects are identified by an address. In a microcontroller system programmed in C or C++, these addresses are used to directly access the ECUs memory, like a debugger would do. This concept has minimum impact on the target system in terms of memory consumption and runtime. The A2l is a kind of annotated ELF Linker-Address-Map, with rich semantic information on data instances and data types.
Data objects are identified by an address. In a micro controller system programmed in C or C++, these addresses are used to directly access the ECUs memory, like a debugger would do. This concept has minimum impact on the target system in terms of memory consumption and runtime. The A2l is a kind of annotated ELF Linker-Address-Map, with rich semantic information on data instances and data types.
In a higher abstraction level programming language, XCP can be treated as a serializer/deserializer, where A2L is the schema, which is generated from the target software data types and instances. Measurement signals and calibration parameters must have static lifetime and a defined memory layout, but no predefined memory location. Data acquisition and modification is achieved by appropriate code instrumentation for measurement and wrapper types for calibration parameters and parameter groups.

The ASAM-XCP standard defines a protocol and a transport layer. There are transport layers for all common communication busses used in the automotive industry, such as CAN, CAN-FD, FLEXRAY, SPI and Ethernet.
Expand All @@ -36,7 +36,7 @@ XCPlite (https://github.com/vectorgrp/XCPlite) is a simplified implementation of

In C or C++ software, A2L data objects are usually created with global or static variables, which means they have a constant memory address. XCPlite for C++ introduced an additional code instrumentation concept to measure and calibrate instances of classes located on heap. It is still using direct memory access, but A2L addresses are relative and the lifetime of measurement variables is associated to events.

An implementation of XCP in Rust, with direct memory access, will get into conflict with the memory and concurrency safety concepts of Rust. In Rust, mutating static variables by using pointers is considered Unsafe code, which might create undefined behaviour in parallel access. Thread safety when accessing any data will be strictly enforced.
An implementation of XCP in Rust, with direct memory access, will get into conflict with the memory and concurrency safety concepts of Rust. In Rust, mutating static variables by using pointers is considered Unsafe code, which might create undefined behavior in parallel access. Thread safety when accessing any data will be strictly enforced.

xcp-lite (https://github.com/vectorgrp/xcp-lite) is an implementation of XCP for Rust. It provides a user friendly concept to wrap structs with calibration parameters in a convenient and thread safe type, to make calibration parameters accessible and safely interior mutable by the XCP client tool.
To achieve this, the generation of the A2L description is part of the solution. In XCPlite this was an option.
Expand All @@ -46,7 +46,7 @@ The calibration parameter wrapper type CalSeg enables all advanced calibration f

xcp-lite also implements a concept to measure variables on stack or as thread local instances.

Currently xcp-lite for Rust uses a C library build from XCPlite sources, which contains the XCP server, an ethernet transport layer with its rx/tx server threads, the protocol layer, time stamp generation and time synchronisation. The C implementation is optimized for speed by minimizing copying and locking data. There are no heap allocations. The Rust layer includes the registry and A2L generation, wrapper types for calibration parameters and macros to capture measurement data on events.
Currently xcp-lite for Rust uses a C library build from XCPlite sources, which contains the XCP server, an ethernet transport layer with its rx/tx server threads, the protocol layer, time stamp generation and time synchronization. The C implementation is optimized for speed by minimizing copying and locking data. There are no heap allocations. The Rust layer includes the registry and A2L generation, wrapper types for calibration parameters and macros to capture measurement data on events.

The code should work on Linux, Windows and Mac, Intel and ARM.

Expand Down Expand Up @@ -178,7 +178,7 @@ fn main() -> Result<()> {
let xcp = XcpBuilder::new("my_module_name").set_log_level(2).set_epk("MY_EPK")
.start_server(XcpTransportLayer::Udp, [127, 0, 0, 1], 5555)?;

// Create a calibration parameter set named "calsseg" (struct CalSeg, a MEMORY_SEGMENT in A2L and CANape)
// Create a calibration parameter set named "calseg" (struct CalSeg, a MEMORY_SEGMENT in A2L and CANape)
// Calibration segments have 2 pages, a constant default "FLASH" page (CAL_PAGE) and a mutable "RAM" page
// The RAM page can be loaded from a json file (load_json=true)
let calseg = xcp.create_calseg(
Expand Down Expand Up @@ -210,7 +210,7 @@ fn main() -> Result<()> {
The fundamental functional concept of this XCP implementation is, to mutate the calibration variables in their original binary representation in a thread safe, transparent wrapper type.
The implementation restricts memory accesses to the inner calibration page of a calibration segment, but does not check the correctness of modifications inside the calibration page.
As usual, the invariants to consider this safe, include the correctness of the A2L file and of the XCP client tool. When the A2L file is uploaded by the XCP tool on changes, this is always guaranteed.
The wrapper type is Send, not Sync and implements the Deref trait for convenience. This opens the possibility to get aliases to the inner calibration values, which should be avoided. But this will never cause undefined behaviour, as the values will just not get updated, when the XCP tool does a calibration page switch.
The wrapper type is Send, not Sync and implements the Deref trait for convenience. This opens the possibility to get aliases to the inner calibration values, which should be avoided. But this will never cause undefined behavior, as the values will just not get updated, when the XCP tool does a calibration page switch.

Code in Unsafe blocks exists in the following places:

Expand Down Expand Up @@ -279,8 +279,8 @@ Use --nocapture because the debug output from the XCPlite C library is via norma
All measurement and calibration code instrumentation is non blocking and the trigger event and sync methods is optimized for speed and minimal locking.
There are no heap allocation during runtime, except for the lazy registrations of and for A2L generation.

build.rs automatically builds a minimum static C library from individually preconfigured core XCPlite sources.
On C level, there is a synchronisation mutex for the mpsc transmit queue.
build.rs automatically builds a minimum static C library from individually pre configured core XCPlite sources.
On C level, there is a synchronization mutex for the mpsc transmit queue.
The C code has the option to start the server with 2 normal threads for rx and tx socket handling.

The generated A2L file is finalized on XCP connect and provided for upload via XCP.
Expand All @@ -302,7 +302,7 @@ The EPK version string in the A2L file can be set by the application. It resides

## Possible improvements

- Create a lock free algorithm to acquire an entry in the MPSC event queue
- Create a lock free algorithm to acquire an entry in the mpsc event queue
- Support specialized types of calibration parameters, including types for curves and maps with axis
- Avoid the mutex lock in CalSeg::Sync when there is no pending parameter modification or switch to a mcu algorithm
- Improve the meta data annotations of the A2L serializer
Expand Down
77 changes: 37 additions & 40 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,43 @@ fn main() {
build_info_build::build_script();

// Generate XCPlite C code bindings
// Uncomment this to regenerate the bindings

let bindings = bindgen::Builder::default()
.header("xcplib/wrapper.h")
.clang_arg("-Ixcplib/src")
.clang_arg("-Ixcplib")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
//
.blocklist_type("T_CLOCK_INFO")
.allowlist_type("tXcpDaqLists")
// Protocol layer
.allowlist_function("XcpInit")
//.allowlist_function("XcpStart")
.allowlist_function("XcpDisconnect")
// ETH server mode
.allowlist_function("XcpEthServerInit")
.allowlist_function("XcpEthServerShutdown")
.allowlist_function("XcpEthServerStatus")
.allowlist_function("XcpEthTlGetInfo")
// DAQ
.allowlist_function("XcpTriggerDaqEventAt")
//.allowlist_function("XcpEventAt")
.allowlist_function("XcpEvent")
//.allowlist_function("XcpEventExtAt")
.allowlist_function("XcpEventExt")
// Misc
.allowlist_function("XcpPrint")
.allowlist_function("XcpSendTerminateSessionEvent")
.allowlist_function("ApplXcpSetLogLevel")
.allowlist_function("ApplXcpSetA2lName")
.allowlist_function("ApplXcpSetEpk")
.allowlist_function("ApplXcpGetAddr")
.allowlist_function("ApplXcpRegisterCallbacks")
//
.generate()
.expect("Unable to generate bindings");
bindings.write_to_file("src/xcp/xcplib.rs").expect("Couldn't write bindings!");
/*
let bindings = bindgen::Builder::default()
.header("xcplib/wrapper.h")
.clang_arg("-Ixcplib/src")
.clang_arg("-Ixcplib")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
//
.blocklist_type("T_CLOCK_INFO")
.allowlist_type("tXcpDaqLists")
// Protocol layer
.allowlist_function("XcpInit")
//.allowlist_function("XcpStart")
.allowlist_function("XcpDisconnect")
// ETH server mode
.allowlist_function("XcpEthServerInit")
.allowlist_function("XcpEthServerShutdown")
.allowlist_function("XcpEthServerStatus")
.allowlist_function("XcpEthTlGetInfo")
// DAQ
.allowlist_function("XcpTriggerDaqEventAt")
//.allowlist_function("XcpEventAt")
.allowlist_function("XcpEvent")
//.allowlist_function("XcpEventExtAt")
.allowlist_function("XcpEventExt")
// Misc
.allowlist_function("XcpPrint")
.allowlist_function("XcpSendTerminateSessionEvent")
.allowlist_function("ApplXcpSetLogLevel")
.allowlist_function("ApplXcpSetA2lName")
.allowlist_function("ApplXcpSetEpk")
.allowlist_function("ApplXcpGetAddr")
.allowlist_function("ApplXcpRegisterCallbacks")
//
.generate()
.expect("Unable to generate bindings");
bindings.write_to_file("src/xcp/xcplib.rs").expect("Couldn't write bindings!");
*/

// Build a XCP on ETH version of XCPlite as a library
cc::Build::new()
Expand All @@ -47,7 +47,6 @@ fn main() {
.file("xcplib/xcpAppl.c")
.file("xcplib/src/platform.c")
.file("xcplib/src/xcpLite.c")
//.file("xcplib/src/xcpDaq.c")
.file("xcplib/src/xcpTlQueue.c")
.file("xcplib/src/xcpTl.c")
.file("xcplib/src/xcpEthTl.c")
Expand Down Expand Up @@ -75,6 +74,4 @@ fn main() {
println!("cargo:rerun-if-changed=xcplib/src/xcp.h");
println!("cargo:rerun-if-changed=xcplib/src/xcpLite.h");
println!("cargo:rerun-if-changed=xcplib/src/xcpLite.c");
//println!("cargo:rerun-if-changed=xcplib/src/xcpDaq.h");
//println!("cargo:rerun-if-changed=xcplib/src/xcpDaq.c");
}
14 changes: 7 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ use xcp_type_description::prelude::*;
//-----------------------------------------------------------------------------
// Static measurement variables

// This is the classical address oriented calibration approach for indivual measurement signals
// This is the classical address oriented calibration approach for individual measurement signals

struct StaticVars {
test_u32: u32,
Expand All @@ -85,13 +85,13 @@ static STATIC_VARS: static_cell::StaticCell<StaticVars> = static_cell::StaticCel
//-----------------------------------------------------------------------------
// Static calibration variables

// This is the classical address oriented calibration approach for indivual calibration parameters or structs
// This is the classical address oriented calibration approach for individual calibration parameters or structs
// The calibration parameters are defined as static instances with constant memory address
// Each variable or struct field has to be registered manually in the A2L registry
// A2L addresses are absolute in the application process memory space (which means relative to the module load address)

// This approach uses a OnceCell to initialize a static instance of calibration data, a mutable static instead would need unnsafe, a static might be in write protected memory and a const has no memory address
// The inner UnsafeCell allows interiour mutability, but this could theoretically cause undefined behaviour or inconsistencies depending on the nature of the platform
// This approach uses a OnceCell to initialize a static instance of calibration data, a mutable static instead would need unsafe, a static might be in write protected memory and a const has no memory address
// The inner UnsafeCell allows interior mutability, but this could theoretically cause undefined behavior or inconsistencies depending on the nature of the platform
// Many C,C++ implementations of XCP do not care about this, but this approach is not recommended for rust projects

struct StaticCalPage {
Expand All @@ -106,8 +106,8 @@ static STATIC_CAL_PAGE: once_cell::sync::OnceCell<StaticCalPage> = once_cell::sy

//-----------------------------------------------------------------------------
// Dynamic calibration data example
// This approach uses the segment oriented calibration approach with a calibrastion segment wrapper cell type
// It provides defined behaviour, thread safety and data consistency
// This approach uses the segment oriented calibration approach with a calibration segment wrapper cell type
// It provides defined behavior, thread safety and data consistency
// Fields may be automatically added to the A2L registry by the #[derive(serde::Serialize, serde::Deserialize)] feature and the XcpTypeDescription derive macro
// Each page defines a MEMORY_SEGMENT in A2L and CANape
// A2l addresses are relative to the segment start address, the segment number is coded in the address
Expand Down Expand Up @@ -362,7 +362,7 @@ fn task1(calseg: CalSeg<CalPage>, calseg1: CalSeg<CalPage1>) {
// Create an event with capture capacity of 1024 bytes for point_cloud serialization
let event = daq_create_event!("task1");

// Register signals of bassic types or array to be captured directly from stack
// Register signals of basic types or array to be captured directly from stack
daq_register!(counter, event, "", "", 1.0, 0.0);
daq_register!(counter_i8, event, "wrapping counter: i8", "");
daq_register!(counter_u8, event, "wrapping counter: u8", "");
Expand Down
1 change: 0 additions & 1 deletion src/xcp/cal/cal_seg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,6 @@ mod cal_tests {
}

#[test]
// #[ignore] // @@@@ fails on Windows!!!!!!!!!!!!!!!!
fn test_calibration_segment_basics() {
//
const CAL_PAGE_TEST1: CalPageTest1 = CalPageTest1 {
Expand Down
2 changes: 1 addition & 1 deletion src/xcp/daq/daq_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ macro_rules! daq_create_event_tli {
/// Register the given meta data once for each event instance
/// The events index number will be appended to the variable name
/// Append an index to the variable name to distinguish between different threads
// @@@@ The offset does not need to be stores in thread local storage, sttaic would be sufficient, as it is the same for all instances of a task
// @@@@ The offset does not need to be stored in thread local storage, static would be sufficient, as it is the same for all instances of a task
#[allow(unused_macros)]
#[macro_export]
macro_rules! daq_capture_tli {
Expand Down
4 changes: 2 additions & 2 deletions tests/xcp_test_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ pub async fn xcp_test_executor(_xcp: &Xcp, test_mode_cal: TestModeCal, test_mode
.expect("could not create calibration object CalPage1.test_f64");
let v = xcp_client.get_value_u64(test_u64);
println!("test_u64={:X}", v);
assert_eq!(v, 0x0102030405060708u64); // @@@@ fail !!!!!!!!!!!!!!!!!
assert_eq!(v, 0x0102030405060708u64);

// Test f64
debug!("Create calibration object CalPage1.test_f64");
Expand All @@ -542,7 +542,7 @@ pub async fn xcp_test_executor(_xcp: &Xcp, test_mode_cal: TestModeCal, test_mode
.await
.expect("could not create calibration object CalPage1.test_f64");
let v = xcp_client.get_value_f64(test_f64);
assert_eq!(v, 0.123456789E-100); // @@@@ fail !!!!!!!!!!!!!!!!!
assert_eq!(v, 0.123456789E-100);

// Test static
debug!("Create calibration object static_vars.test_u32");
Expand Down
2 changes: 1 addition & 1 deletion xcp_client/src/xcp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ impl XcpClient {

// Handle DAQ data if DAQ running
if c.running {
let mut m = decode_daq.lock(); // @@@@ Unnessesary mutex ?????
let mut m = decode_daq.lock(); // @@@@ Unnecessary mutex ?????
m.decode(ctr_lost, &buf[i + 4..i + 4 + len]);
ctr_lost = 0;
} // running
Expand Down
Loading

0 comments on commit 0942a57

Please sign in to comment.