Skip to content

Commit

Permalink
Minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
RainerZ committed Oct 15, 2024
1 parent 5fc9add commit 8953230
Show file tree
Hide file tree
Showing 19 changed files with 246 additions and 248 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 stricly 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 behaviour in parallel access. Thread safety when accessing any data will be stricly 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 convienient and thread safe type, to make calibration parameters accessible and safely interiour 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 Down Expand Up @@ -211,15 +211,15 @@ The implementation restricts memory accesses to the inner calibration page of a
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 garantueed.
The wrapper type is Send, not Sync and implements the Deref trait for convinience. 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.

Code in unsafe blocks exists in the following places:
Code in Unsafe blocks exists in the following places:

- The implementation of Sync for CalSeg
- All calls to the C FFI of the XCPlite server (optional), transport layer and protocol layer
- In particular the XCPlite bindings XcpEventExt and ApplXcpRead/WriteMemeory, which transfer a byte pointers to a calibration values.
- The implementation of Sync for CalSeg
- In particular the XCPlite bindings XcpEventExt for measurment and cb_read/cb_write for calibration, which carry byte pointers and memory offsets of measurement and calibration objects
- And formally all calls to the C FFI of the XCPlite server (optional), transport layer and protocol layer

A completely safe measurement and calibration concept is practically impossible to achieve, without massive consequences for the API, which would lead to much more additional boilerplate code to achive calibration.
XCP is a very common approach in the automotive industry and there are many tools, HIL systems and loggers supporting it.
XCP is a development tool, it is not integrated in production code or it is savely disabled.
The memory oriented measurment and calibration approach of XCP is very common in the automotive industry and there are many tools, HIL systems and loggers supporting it.
XCP is used during the development process only, it is never integrated in production code or it is disabled by save code.


## Build
Expand Down
28 changes: 19 additions & 9 deletions benches/xcp_benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
// cargo bench -- --save-baseline parking_lot
// cargo bench -- --baseline parking_lot
// cargo bench -- --save-baseline parking_lot
// cargo bench -- --load-baseline new --baseline parking_lot
// --warm-up-time 0
// --nresamples <nresamples>
//
// cargo bench
//

#![allow(unused_assignments)]
Expand Down Expand Up @@ -57,6 +51,13 @@ impl DaqDecoder {
}

impl XcpDaqDecoder for DaqDecoder {
fn start(&mut self, _timestamp: u64) {
self.event_count = 0;
self.event_lost_count = 0;
}

fn set_timestamp_resolution(&mut self, _timestamp_resolution: u64) {}

fn decode(&mut self, lost: u32, _daq: u16, _odt: u8, _time: u32, _data: &[u8]) {
self.event_count += 1;
self.event_lost_count += lost as u64;
Expand Down Expand Up @@ -131,7 +132,6 @@ async fn xcp_client(dest_addr: std::net::SocketAddr, local_addr: std::net::Socke
xcp_client.create_measurement_object("signal7").expect("measurement signal not found");
xcp_client.create_measurement_object("signal8").expect("measurement signal not found");
// Measure start
xcp_client.init_measurement().await.unwrap();
xcp_client.start_measurement().await.expect("could not start measurement");
}

Expand Down Expand Up @@ -219,10 +219,12 @@ fn xcp_benchmark(c: &mut Criterion) {
let xcp = XcpBuilder::new("xcp_benchmark")
.set_log_level(XcpLogLevel::Info)
.set_epk("EPK_")
.start_server(XcpTransportLayer::Udp, [127, 0, 0, 1], 5555)?;
.start_server(XcpTransportLayer::Udp, [127, 0, 0, 1], 5555)
.unwrap();

// Create a calibration segment
let cal_page = xcp.create_calseg("CalPage", &CAL_PAGE);
cal_page.register_fields();

// Measurement signal
let mut signal1: u32 = 0;
Expand Down Expand Up @@ -258,6 +260,14 @@ fn xcp_benchmark(c: &mut Criterion) {
// Wait a moment
thread::sleep(Duration::from_millis(100));

// Bench deref performance
info!("Start calibration segment deref bench");
c.bench_function("deref", |b| {
b.iter(|| {
let _x = cal_page.ampl;
})
});

// Bench calibration segment sync
// Bench calibration operations (in xcp_client_task)
info!("Start calibration bench");
Expand Down
3 changes: 3 additions & 0 deletions examples/hello_xcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ name = "hello_xcp"
version = "0.1.0"
edition = "2021"



[dependencies]
log = "0.4.21"
env_logger = "0.11.3"
anyhow = "1.0"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
1 change: 1 addition & 0 deletions examples/protobuf_demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
log = "0.4.21"
env_logger = "0.11.3"
anyhow = "1.0"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 2 additions & 0 deletions examples/rayon_demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ edition = "2021"
[dependencies]
log = "0.4.21"
env_logger = "0.11.3"
anyhow = "1.0"
num_cpus = "1.16.0"
rayon = "1.10.0"
num = "0.4.3"
image = "0.25.2"
Expand Down
110 changes: 74 additions & 36 deletions examples/rayon_demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,89 @@
// xcp-lite - rayon demo
// Visualize start and stop of synchronous tasks in worker thread pool
// Taken from the mandelbrot rayon example in the book "Programming Rust" by Jim Blandy and Jason Orendorff
// Inspired by the mandelbrot rayon example in the book "Programming Rust" by Jim Blandy and Jason Orendorff

#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
// cargo r --example rayon_demo
// Creates madelbrot.a2l and mandelbrot.png in current directory

use anyhow::Result;
use image::{ImageBuffer, Rgb};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use num::Complex;
use rayon::prelude::*;
use std::{thread, time::Duration};

use xcp::*;
use xcp_type_description::prelude::*;

// Arrays measured may not exeed 2^15
const X_RES: usize = 1024 * 2;
const Y_RES: usize = 768 * 2;
//---------------------------------------------------------------------------------------
// Calibratable parameters

const IMAGE_FILE_NAME: &str = "mandelbrot.png";
const IMAGE_SIZE: usize = 8;
const X_RES: usize = 1024 * IMAGE_SIZE;
const Y_RES: usize = 768 * IMAGE_SIZE;

#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone, XcpTypeDescription)]
struct Mandelbrot {
x: f64,
x: f64, // Center of the set area to render
y: f64,
width: f64,
width: f64, // Width of the set area to render
}

// Complete set
// const MANDELBROT: Mandelbrot = Mandelbrot {
// x: -0.5,
// y: 0.0,
// width: 3.0,
// };

const MANDELBROT: Mandelbrot = Mandelbrot { x: -1.4, y: 0.0, width: 0.015 };
// Defaults
//const MANDELBROT: Mandelbrot = Mandelbrot { x: -0.5, y: 0.0, width: 3.0 }; // Complete set
//const MANDELBROT: Mandelbrot = Mandelbrot { x: -1.4, y: 0.0, width: 0.015 };
const MANDELBROT: Mandelbrot = Mandelbrot { x: -0.8015, y: 0.1561, width: 0.0055 };

//---------------------------------------------------------------------------------------
// Image rendering
// Coloring

// Write the buffer `pixels` to the file named `filename`.
fn write_image(filename: &str, pixels: &[u8]) {
// Rainbox color map (credits to CoPilot)
// Normalizes color intensity values within RGB range
fn normalize(color: f32, factor: f32) -> u8 {
((color * factor).powf(0.8) * 255.) as u8
}

// Function converting intensity values to RGB
fn iterations_to_rgb(i: u32) -> Rgb<u8> {
let wave = i as f32;

let (r, g, b) = match i {
380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0),
440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0),
490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)),
510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0),
580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0),
645..=780 => (1.0, 0.0, 0.0),
_ => (0.0, 0.0, 0.0),
};

let factor = match i {
380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.),
701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.),
_ => 1.0,
};

let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor));
Rgb::from([r, g, b])
}

fn get_color_mag() -> Vec<Rgb<u8>> {
// Map iterations to colors
let mut color_map = Vec::with_capacity(256);
for i in 0..256 {
let (r, g, b) = match i {
0 => (0, 0, 0), // Black
1..=42 => (255, (i as f32 * 6.0) as u8, 0), // Red to Yellow
43..=85 => (255 - ((i - 43) as f32 * 6.0) as u8, 255, 0), // Yellow to Green
86..=128 => (0, 255, ((i - 86) as f32 * 6.0) as u8), // Green to Cyan
129..=171 => (0, 255 - ((i - 129) as f32 * 6.0) as u8, 255), // Cyan to Blue
172..=214 => (((i - 172) as f32 * 6.0) as u8, 0, 255), // Blue to Magenta
215..=255 => (255, 0, 255 - ((i - 215) as f32 * 6.0) as u8), // Magenta to Red
_ => (0, 0, 0), // Default case (should not be reached)
};
let rgb = Rgb::<u8>([r, g, b]);
let rgb = iterations_to_rgb(785 - i * (800 - 350) / 255);
//let rgb = iterations_to_rgb(i);
color_map.push(rgb);
}
color_map
}

//---------------------------------------------------------------------------------------
// Image rendering

// Write the buffer `pixels` to the file named `filename`.
fn write_image(filename: &str, pixels: &[u8]) {
let color_map = get_color_mag();

// Create rgb image buffer and write to file
let mut imgbuf = ImageBuffer::new(X_RES as u32, Y_RES as u32);
Expand All @@ -75,7 +104,7 @@ fn write_image(filename: &str, pixels: &[u8]) {
/// origin. If `c` seems to be a member (more precisely, if we reached the
/// iteration limit without being able to prove that `c` is not a member),
/// return `None`.
fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
fn mandelbrot(c: Complex<f64>, limit: usize) -> Option<usize> {
let mut z = Complex { re: 0.0, im: 0.0 };
for i in 0..limit {
if z.norm_sqr() > 4.0 {
Expand All @@ -87,6 +116,8 @@ fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
None
}

//---------------------------------------------------------------------------------------

/// Given the row and column of a pixel in the output image, return the
/// corresponding point on the complex plane.
///
Expand Down Expand Up @@ -115,7 +146,7 @@ fn render(pixels: &mut [u8], row: usize, length: usize, upper_left: Complex<f64>
// @@@@
for column in 0..length {
let point = pixel_to_point((column, row), upper_left, lower_right);
pixels[column] = match escape_time(point, 255) {
pixels[column] = match mandelbrot(point, 255) {
None => 0,
Some(count) => 255 - count as u8,
};
Expand Down Expand Up @@ -199,8 +230,15 @@ fn main() -> Result<()> {
}

// Write image to file
write_image("mandelbrot.png", &pixels);
println!("Image written to mandelbrot.png, frame {} {:.4}s", mainloop_counter, elapsed_time);
write_image(IMAGE_FILE_NAME, &pixels);
println!(
"Image written to {}, resolution {}x{} {:.1}MB, duration={:.4}s",
IMAGE_FILE_NAME,
X_RES,
Y_RES,
(X_RES * Y_RES * 3) as f64 / 1000000.0,
elapsed_time
);
update_counter += 1;
event_update.trigger();
}
Expand Down
1 change: 1 addition & 0 deletions examples/single_thread_demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
log = "0.4.21"
env_logger = "0.11.3"
anyhow = "1.0"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
4 changes: 1 addition & 3 deletions examples/type_description_demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//TODO: Remove
#[allow(warnings)]
use xcp::*;
//use xcp::*;
use xcp_type_description::prelude::*;

#[derive(Clone, Copy, XcpTypeDescription, Debug)]
Expand Down
12 changes: 6 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,22 @@ macro_rules! cal_register_static {
( $variable:expr ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let c = RegistryCharacteristic::new(None, name.to_string(), datatype, "", datatype.get_min(), datatype.get_max(), "", 1, 1, addr);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
}};
( $variable:expr, $comment:expr ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let c = RegistryCharacteristic::new(None, name.to_string(), datatype, $comment, datatype.get_min(), datatype.get_max(), "", 1, 1, addr);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
}};

( $variable:expr, $comment:expr, $unit:expr ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let c = RegistryCharacteristic::new(None, name.to_string(), datatype, $comment, datatype.get_min(), datatype.get_max(), $unit, 1, 1, addr);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
}};
Expand All @@ -141,15 +141,15 @@ macro_rules! daq_register_static {
( $variable:expr, $event:ident ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let mut c = RegistryCharacteristic::new(None, name.to_string(), datatype, "", datatype.get_min(), datatype.get_max(), "", 1, 1, addr);
c.set_event($event);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
}};
( $variable:expr, $event:ident, $comment:expr ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let mut c = RegistryCharacteristic::new(None, name.to_string(), datatype, $comment, datatype.get_min(), datatype.get_max(), "", 1, 1, addr);
c.set_event($event);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
Expand All @@ -158,7 +158,7 @@ macro_rules! daq_register_static {
( $variable:expr, $event:ident, $comment:expr, $unit:expr ) => {{
let name = stringify!($variable);
let datatype = ($variable).get_type();
let addr = unsafe { &($variable) as *const _ as u64 };
let addr = &($variable) as *const _ as u64;
let mut c = RegistryCharacteristic::new(None, name.to_string(), datatype, $comment, datatype.get_min(), datatype.get_max(), $unit, 1, 1, addr);
c.set_event($event);
Xcp::get().get_registry().lock().unwrap().add_characteristic(c).expect("Duplicate");
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ static STATIC_VARS: static_cell::StaticCell<StaticVars> = static_cell::StaticCel
// 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 unsafe, a static might be in write protected memory and a const has no memory 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
// Many C,C++ implementations of XCP do not care about this, but this approach is not recommended for rust projects

Expand Down
Loading

0 comments on commit 8953230

Please sign in to comment.