Compare commits

...
Sign in to create a new pull request.

37 commits

Author SHA1 Message Date
fd07adbb75
Fix POST endpoint to run properly 2024-04-18 15:55:27 -04:00
abb5263763
Explain CORS call 2024-04-18 14:43:26 -04:00
c5c62c5dbc
Update to include CORS functionality 2024-04-18 13:52:45 -04:00
57c514a289
Update rest api to bind to real IP address 2024-04-18 13:14:38 -04:00
0dff50269f
Add rocket config file 2024-04-17 23:35:10 -04:00
57957e9285
remove blocker for build
Something for later me to fix
2024-04-17 23:22:08 -04:00
1c3f60196c
Start commenting; add VDMA to worldserver 2024-04-17 19:46:41 -04:00
69fb508486
Updated comments, insert VDMA docs
Docs courtesy of AMD; publicly accessible, and indexed by Google at time
of writing
2024-04-17 18:40:49 -04:00
7f34d56ba3
Comment rest api 2024-04-17 18:38:16 -04:00
6a78a80e85
Add all data; update rest endpoint
REST endpoint now responds to a blanket request to
`localhost:8000/serve` with the most recently updated file available to
the server
2024-04-17 18:34:23 -04:00
0f03ec54ff
Remove world-server requirement 2024-04-17 17:14:53 -04:00
4d4af9cf19
Finish first try at VDMA 2024-04-14 18:16:15 -04:00
7197b71f0a
Continue VDMA development 2024-04-10 17:17:31 -04:00
eefaa646b1
Continue VDMA coding
VDMA API is incredibly unstable; subject to change as development
continue
2024-04-08 17:17:23 -04:00
572734ff0d
Start VDMA Framework
Loosely based on Kaputa's design; using Rust tools to make the process
simpler/easier to read
2024-04-08 11:44:44 -04:00
445cf844c8
Add submodule for VDMA ref material 2024-04-07 16:49:56 -04:00
cdbe8e4659
Implement REST framework API calls 2024-04-07 15:11:44 -04:00
67be087183
Increase modularity
Camera calls are now a unique module; working on starting the REST
endpoint development. Will require researching previous projects.
2024-04-07 13:43:37 -04:00
c72be40454
Finish base communication
Communication with the world server is now complete. Starting work on
REST prototyping
2024-04-07 12:24:44 -04:00
34e6699adf
Include force build for ARM target 2024-03-31 12:54:57 -04:00
5bf7745ca6
Update readme 2024-03-28 16:34:37 -04:00
2738513f3e
Merge branch 'devel' of ssh://git.blizzard.systems:25/esd2-groupwork/communication-layer into devel 2024-03-28 16:16:29 -04:00
a68d4efebc
Merge branch 'main' into devel 2024-03-28 16:14:38 -04:00
f8d6645690
Continue making code generic 2024-03-25 21:06:49 -04:00
5b0172b2b9
Simplify design 2024-03-25 20:47:15 -04:00
c01fd63211
Rethink connection
Connection now forks depending on current host IP location
2024-03-25 20:23:22 -04:00
1d775a5e19
Create complete handshake
Handshake now consists of:
- processing timestamp (f64) sent SoC -> World-Server (WS)
- Length of image packet 	  SoC <- WS
- image width 			  SoC <- WS
- image height			  SoC <- WS
- calculated image bitdepth       SoC -> WS
- raw image bytes		  SoC <- WS

Also, increase verbosity of logs; add screenshot of logs
2024-03-25 13:33:10 -04:00
bb6b883c4b
Continue development of comms layer 2024-03-25 12:32:23 -04:00
e1b4dcafde
Force toolchain version 2024-03-25 10:32:36 -04:00
509685ff0e
Add notes 2024-03-25 10:32:11 -04:00
fbd0ac4ca0
Update Cargo config to build safely 2024-03-24 18:09:16 -04:00
2f09bfb0f6
Continue commenting 2024-03-21 15:30:19 -04:00
eec82cdfb5
Add logging for tie estimation purposes
Also, dynamically determine the IP of the world server, for more
realistic interactions
2024-03-21 14:54:02 -04:00
beb0cd25dd
Comment read layer 2024-03-21 12:58:37 -04:00
0b367c019f
First attempt at importing network images 2024-03-20 18:43:21 -04:00
23276e94d2
Static send variable for demonstration and testing 2024-03-20 15:18:40 -04:00
6f4ddf5e00
Start developing communication layer
Currently, in testing phase
2024-03-20 14:39:00 -04:00
31 changed files with 26965 additions and 5 deletions

1
.cargo/config.toml Normal file
View file

@ -0,0 +1 @@
[build]

4
.gitignore vendored
View file

@ -1 +1,3 @@
/target
target/
logs/
*.png

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "reference_material"]
path = reference_material
url = https://github.com/ravvenlabs/userspace-vdma-driver

1974
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,3 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.35"
fern = "0.6.2"
image = { version = "0.25.0", default-features = false, features = ["png","ff","gif"] }
ipnet = "2.9.0"
local-ip-address = "0.6.1"
log = "0.4.21"
rand = "0.8.5"
rocket = "0.5.0"
rocket_cors = "0.6.0"
sudo = "0.6.0"

View file

@ -1,3 +1,13 @@
# virtual-camera
Library and server for dissemination of images obtained from the world server.
Library and server for dissemination of images obtained from the world server.
Currently uses TCP to stream images from the world server. Images are saved temporarily for testing purposes.
## Development
For development, you must first have Rust installed. This can be done by following the instructions found at [https://rustup.rs/](https://rustup.rs/). Once this is done, clone this repository, and in the root folder of the repository, run `cargo build --release`. This will both build the dependencies, install the correct toolchains, and create a final executable.
## Testing
To test this, first download the `comms-testing` branch of the World Server repository, open the `tennis` project, and run the project. Once this is done, build the latest version of this repository, as mentioned above. Finally, run this project, using `cargo run`.

BIN
logScreeenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

View file

@ -0,0 +1,7 @@
2024-03-21T14:47:31.759962308-04:00 - [INFO, communication_layer] - aggregating all IPs... This may take several minutes...
2024-03-21T14:47:36.825148581-04:00 - [INFO, communication_layer] - Stream connected to address: Ok(192.168.0.80:55001)
2024-03-21T14:47:36.825292754-04:00 - [INFO, communication_layer] - Start reading...
2024-03-21T14:47:38.021199638-04:00 - [INFO, communication_layer] - Image successfully recieved
2024-03-21T14:47:38.021453459-04:00 - [INFO, communication_layer] - Building image...
2024-03-21T14:47:39.127447553-04:00 - [INFO, communication_layer] - Image built, saving to file...
2024-03-21T14:47:44.733286335-04:00 - [INFO, communication_layer] - image saved!

View file

@ -1,5 +1,13 @@
<<<<<<< HEAD
Communication layer:
Add image resolution to initial handshake
unity: send x and y resolution
SOC: send back colour bit depth
unity: check bitdepth before beginning image transfer
=======
Rust Crate: `image-0.24.9`
Read image using `ImageBuffer::from_raw(w,h,bytes)`
image size: 4608 x 2592 x 3 [BGR]
>>>>>>> main

1
reference_material Submodule

@ -0,0 +1 @@
Subproject commit cfbb72d5ffac8d253ac6653e268efc76f7d0a3d5

View file

@ -1,5 +1,5 @@
[toolchain]
channel="1.76.0-2024-02-08"
components = [ "rustc", "cargo", "rust-std", "rustfmt", "rust-docs", "rust-analyzer" ]
targets = [ "armv7-unknown-linux-musleabi" ]
targets = [ "armv7-unknown-linux-gnueabihf" ]
profile = "default"

1774
serve1.csv Normal file

File diff suppressed because it is too large Load diff

1774
serve1.dat Normal file

File diff suppressed because it is too large Load diff

1853
serve2.csv Normal file

File diff suppressed because it is too large Load diff

1921
serve3.csv Normal file

File diff suppressed because it is too large Load diff

2029
serve4.csv Normal file

File diff suppressed because it is too large Load diff

1739
serve5.csv Normal file

File diff suppressed because it is too large Load diff

264
src/cam_comm.rs Normal file
View file

@ -0,0 +1,264 @@
use local_ip_address::local_ip;
use std::{ io::{Bytes, stdin, BufReader, Error, ErrorKind, Read, Write}, net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpStream}, thread::{self, JoinHandle}, time::Duration};
use image::{Rgb, RgbImage};
use ipnet::{Ipv4Net,PrefixLenError};
use log::{trace,debug,warn,error,info};
//use crate::vdma_facade::feedthrough::VdmaHandle;
///RIT owns the subnet 129.21.0.0/16; probing by attempting to open thousands of TCP streams
///simultaneously is unwise.
const UNSAFE_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(129,21,0,0),16);
///This subnet is universally considered the "local" subnet. This results in 256 addresses, and can
///be probed both safely and efficiently, and as long as the world server on the LAN is running it
///is functionally guaranteeed to be found.
const HOME_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(192,168,0,0),24);
///Current communications takes place on custom port.
///TODO: RTSP port is 554; prepare for real world situations
const PORT_NUMBER:u16 = 55001;
///Port probe timeout, for
const PORT_PROBE_TIMEOUT:Duration = Duration::new(5,0);
///The standard subnet is a /24
const SUBNET_SIZE:u8 = 24;
///Storage of all world serve connection information
pub struct WorldServerConnection{
///Store all open TcpStreams
port_list:Vec<(TcpStream,BufReader<TcpStream>)>,
///Unique image identifier
image_index:u64,
//VDMA instance
//vdma:Option<VdmaHandle>,
}
impl WorldServerConnection{
pub fn new() -> Result<Self,Error>{
let host_ip = local_ip().expect("No real IP address!");
trace!("Host IP: {:?}",host_ip);
let mut thread_list:Vec<JoinHandle<Result<TcpStream,Error>>> = Vec::new();
let mut port_list:Vec<(TcpStream,BufReader<TcpStream>)> = Vec::new();
if let IpAddr::V4(host_v4_ip) = host_ip{
//Check current subnet
let net = Ipv4Net::new(host_v4_ip,SUBNET_SIZE);
if let Ok(net) = net{
//RIT owns 129.21.0.0/16; DO NOT SPAM THIS NETWORK
if UNSAFE_SUBNET.unwrap().contains(&net){
//Request world server address from user
let address = manual_address_prompt();
if address.is_ok(){
let real_address = address.unwrap();
let reader = BufReader::new(real_address.try_clone().expect("Failed clone"));
port_list.push((real_address,reader));
//thread_list won't be added to, so the thread for loop won't execute
}
} else if net.contains(&HOME_SUBNET.unwrap()){
//Collect all Hosts on the current subnet
info!("aggregating all IPs... This may take several minutes...");
for net_address in net.hosts(){
thread_list.push(
thread::spawn(move ||{
let ip_port = std::net::SocketAddr::V4(SocketAddrV4::new(net_address, PORT_NUMBER));
//Attempt to open a TCP stream with this IP address, exit the thread
//returning the open TcpStream, or an error
return TcpStream::connect_timeout(&ip_port, PORT_PROBE_TIMEOUT);
})
);
}
}
}
}
//Multithread the port-sniffing, only save sucessful connections
for thread in thread_list{
let output = thread.join().unwrap();
if let Ok(real_output) = output{
let reader = BufReader::new(real_output.try_clone().expect("Failed clone"));
port_list.push((real_output,reader));
}
}
return Ok(Self{port_list, image_index:0});//, vdma:None});
}
pub fn get_next_images(&mut self,index:u32){
for (ref mut stream,ref mut reader) in self.port_list.iter_mut(){
info!("Stream connected to address: {:?}",stream.peer_addr());
send_processing_time(stream, index);
let header_values = read_image_header(stream, reader, &mut self.image_index);
//if self.vdma.is_none(){
// self.vdma = Some(VdmaHandle::new(header_values.0, header_values.1, header_values.2).expect("Vdma init failed!"));
//}
read_image(stream, reader, header_values.clone());
let header_values = read_image_header(stream, reader, &mut self.image_index);
read_image(stream, reader, header_values.clone());
self.image_index += 1;
}
}
}
///Borrows an iterator over the bytes in a buffered-reader of a TcpStream; reads the next 4 bytes,
///outputs the generated u32
fn bytes_to_u32(iter:&mut Bytes<&mut BufReader<TcpStream>>) -> u32{
let mut array:[u8;4] = [0;4];
for i in 0..4{
array[i] = iter.next().unwrap().unwrap();
}
return u32::from_le_bytes(array);
}
fn bytes_to_u64(iter:&mut Bytes<&mut BufReader<TcpStream>>) -> u64{
let mut array:[u8;8] = [0;8];
for i in 0..8{
array[i] = iter.next().unwrap().unwrap();
debug!("{:?}",array[i]);
}
return u64::from_le_bytes(array);
}
///Request the user to input the world server's address into stdin
fn manual_address_prompt() -> Result<TcpStream,Error>{
let return_val:Result<TcpStream,Error> = Err(Error::new(ErrorKind::AddrNotAvailable,"Invalid Address"));
while return_val.is_err(){
let mut user_input:String = String::default();
match stdin().read_line(&mut user_input){
Ok(_) => {
match user_input.trim().parse::<SocketAddrV4>(){
Ok(address) => {
//Test if the address is accurate
let stream = TcpStream::connect_timeout(&std::net::SocketAddr::V4(address), PORT_PROBE_TIMEOUT);
if stream.is_ok(){
return stream;
} else {
warn!("Address unavailable!");
debug!("{:?}",stream.unwrap_err());
}
},
Err(error) => {
error!("Unable to properly parse user input!");
debug!("{:?}",error);
}
}
},
Err(error) => {
error!("Unable to read user input!");
debug!("{:?}",error);
}
}
}
return return_val;
}
//Used to send the calculated time value to the world server
fn send_processing_time(stream:&mut TcpStream ,sent_value:u32){
debug!("Begin handshake; sending timestamp float...");
//Write the f64 value, in bytes, to the stream, and ensure it has been sent
stream.write(&sent_value.to_le_bytes()).unwrap();
stream.flush().unwrap();
debug!("Timestamp sent!");
}
//Used to recieve images from the world server
fn read_image_header(stream:&mut TcpStream, reader:&mut BufReader<TcpStream>, image_index:&mut u64) -> (u32,u32,u32,u64,String){
//Create an iterator over the stream's bytes
let mut iter = reader.bytes();
debug!("Continue handshake, read back image packet length...");
//The first values sent will always be a u32 containing the length of the message (without the
//length counted), followed by the image width and height
let length = bytes_to_u64(&mut iter);
debug!("Image packet length read as {:?}!",length);
debug!("Continue handshake, read back image width...");
let image_width = bytes_to_u32(&mut iter);
debug!("Image width read as {:?}!",image_width);
debug!("Continue handshake, read back image height...");
let image_height = bytes_to_u32(&mut iter);
debug!("Image height read as {:?}!",image_height);
//The length of the image is the total number of bytes
let byte_depth = (length / ((image_width * image_height) as u64)) as u32;
debug!("Image bitdepth calculated as {:?}!", byte_depth);
//Confirm to world server the correct number of bytes
stream.write(&byte_depth.to_le_bytes()).unwrap();
stream.flush().unwrap();
debug!("Reading camera source info...");
let camera_source = bytes_to_u32(&mut iter);
let camera_name:&str;
let top_cam_img = "./Top".to_owned() + &image_index.to_string() + ".png";
let side_cam_img = "./Side".to_owned() + &image_index.to_string() + ".png";
if camera_source == u32::MIN{
debug!("Image source defined as \"Top\"!");
camera_name = &top_cam_img;
} else if camera_source == u32::MAX {
debug!("Image source defined as \"Side\"!");
camera_name = &side_cam_img;
} else {
error!("Unknown camera value!!!");
camera_name = "./unknown.png";
}
debug!("Writing {:?} to stream...", camera_source.to_le_bytes());
stream.write(&camera_source.to_le_bytes()).unwrap();
stream.flush().unwrap();
(image_width,image_height,byte_depth,length,camera_name.to_string())
}
fn read_image(stream:&mut TcpStream, reader:&mut BufReader<TcpStream>, header_info:(u32,u32,u32,u64,String)){
let image_width = header_info.0;
let image_height = header_info.1;
let byte_depth = header_info.2;
let image_length = header_info.3;
let image_name = header_info.4;
//Create a storage point for the bytes
let mut recieved_bytes:Vec<u8> = Vec::new();
//Create an iterator over the stream's bytes
let mut iter = reader.bytes();
//TODO: Remove assumption
//Always assumes world server will send image
info!("Start reading...");
//Recieve the rest of the message
for _ in 0..image_length{
recieved_bytes.push(iter.next().unwrap().unwrap_or(0));
}
info!("Image successfully recieved");
//Create a blank pane on which to store the read in pixels
let mut img = RgbImage::new(image_width,image_height);
//Create a temporary storage point for u8s when generating pixels
let mut temp:Vec<u8> = Vec::new();
//Iterate over bytes
info!("Building image...");
for i in 0..recieved_bytes.len(){
let byte = recieved_bytes.get(i).expect("Index does not exist!");
temp.push(byte.clone());
//Each pixel is composed of [byte_depth] u8s
if ((i as u32)+1) % byte_depth == 0{
//Coordinate in the pane = i/3
let pixel_number:u32 = ((i as u32) /byte_depth).try_into().unwrap();
//Insert the pixel into the pane
img.put_pixel(
//Pixels coming directly from Unity are sent in reference to Unity; that is,
//starting at the bottom left, and iterating horizontally, then vertically.
//This line compensates and creates a properly-oriented image
pixel_number%image_width, (image_height-1)-(pixel_number / image_width),
//Extract image from temporary storage, ang generate pixel
Rgb::<u8>{0:temp.clone().try_into().expect("bad vector length!")}
);
//Clear temporary storage
temp.clear();
}
}
//Dump image to file
//ONLY FOR EXPERIMENTATION AND TESTING PURPOSES
info!("Image built, saving to file...");
_ = img.save(image_name);
info!("image saved!");
}

4
src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
#[macro_use] extern crate rocket;
pub mod cam_comm;
pub mod rest_api;
pub mod vdma_facade;

View file

@ -1,3 +1,66 @@
fn main() {
println!("Hello, world!");
mod cam_comm;
use std::{ fs, path::Path};
use chrono::{DateTime, Local};
use communication_layer::rest_api;
use log::LevelFilter;
use fern::{log_file, Dispatch};
use sudo::escalate_if_needed;
#[rocket::main]
async fn main() {
setup_logs(&true);
_ = escalate_if_needed();
rest_api::start_rest_endpoint().await;
}
///Set up logging macros to be used throughout the program
fn setup_logs(debug:&bool){
//Get the current time
let chrono_now: DateTime<Local> = Local::now();
//Create a log directory, if it does not exist
if ! Path::new("logs").is_dir(){
_ = fs::create_dir("logs");
};
//Create a log macro listener
_ = fern::Dispatch::new()
//Format the output messages
.format(|out,message,record|{
out.finish(format_args!(
"{} - [{}, {}] - {}",
Local::now().to_rfc3339(),
record.level(),
record.target(),
message
))
})
//Output to the file
.chain({
let mut file_logger = Dispatch::new();
let date_format = chrono_now.format("%Y-%m-%d_%H.%M").to_string();
let local_log_file = log_file(format!("logs/{}.log",date_format)).unwrap();
//Set filter based on whether the command is being run in "debug" mode
if *debug{
file_logger = file_logger.level(LevelFilter::Trace);
}
else {
file_logger = file_logger.level(LevelFilter::Debug);
}
//Apply the filter and file formatting rules described above
file_logger.chain(local_log_file)
})
.chain({
//Also log to stdout
let mut stdout_logger = fern::Dispatch::new();
if *debug {
stdout_logger = stdout_logger.level(LevelFilter::Debug);
}
else {
stdout_logger = stdout_logger.level(LevelFilter::Info);
}
stdout_logger.chain(std::io::stdout())
})
//Start listening to macros
.apply();
}

57
src/rest_api.rs Normal file
View file

@ -0,0 +1,57 @@
use std::{path::{Path, PathBuf}, sync::Mutex};
use crate::cam_comm::WorldServerConnection;
use local_ip_address::local_ip;
use rocket::{figment::Figment, fs::NamedFile, http::Method, response::status, Config, State};
use rocket_cors::{AllowedOrigins, CorsOptions};
#[post("/serve", data = "<raw_index>")]
fn start_serve(raw_index:String,/* cams:&State<Mutex<WorldServerConnection>>*/) -> status::Accepted<()> {
println!("{:?}",raw_index);
//let mut lock = cams.lock().unwrap();
//lock.get_next_images(index);
status::Accepted(())
}
//Get most recently updated file
#[get("/serve")]
async fn get_serve_data() -> Option<NamedFile>{
let last_modified_file = std::fs::read_dir(".") //Read in the current directory
.expect("couldn't access local dir") //assume the directory is real
.flatten() //Ignore all the files that have permissions issues
.filter(|file| file.metadata().unwrap().is_file()) //Ignore files that aren't actually files
.max_by_key(|x| x.metadata().unwrap().modified().unwrap()) //Find newest file
.unwrap(); //Assume the newest file actually exists
NamedFile::open(last_modified_file.path()).await.ok()
}
///Get specific file, as defined by request
#[deprecated(since = "TBD")]
#[get("/serve/<file..>")]
async fn get_data(file:PathBuf) -> Option<NamedFile>{
NamedFile::open(file).await.ok()
}
///Initialises the world server connection, spins Rocket into a unique process
pub async fn start_rest_endpoint(){
//Rocket defaults to using the localhost loopback address. This isn't ideal for prod usage, so
//instead we bind to the host IP address.
let host_ip = local_ip().expect("No real IP address!");
let figment = Figment::from(Config::default())
.merge(("address",host_ip));
//Allow cross-origin resource sharing
let cors = CorsOptions::default()
.allowed_origins(AllowedOrigins::all())
.allowed_methods(
vec![Method::Get, Method::Post]
.into_iter()
.map(From::from)
.collect(),
).allow_credentials(true);
let _rocket = rocket::custom(figment)
.attach(cors.to_cors().expect("CORS init failure!"))
//.manage(Mutex::new(WorldServerConnection::new().unwrap()))
.mount("/", routes![get_data,start_serve,get_serve_data])
.launch().await;
}

187
src/vdma_facade/common.rs Normal file
View file

@ -0,0 +1,187 @@
use std::{collections::VecDeque, fs::{File, OpenOptions}, io::{BufRead, BufReader, Error}, os::unix::fs::OpenOptionsExt, path::Path};
use crate::vdma_facade::O_SYNC;
///Stores the result of mapping a given memory section.
#[derive(Debug,Copy,Clone)]
enum MapResult{
UNSET,
OK,
FAILED
}
///The value to get back from the filesystem.
///MapAddress and MapSize require the index of the map being initialised
enum MemoryValue{
MapAddress(usize),
MapSize(usize),
Event,
Name,
Version,
}
///A virtual memory mapping
///Includes the address, size of the memory block, and whether it was successfully initialised.
#[derive(Debug,Copy,Clone,Default)]
pub struct UioMap{
address:u32,
size:u32,
}
impl UioMap {
///Getter for the address
pub fn get_address(&self) -> u32 { self.address }
///Getter for the size
pub fn get_size(&self) -> u32 { self.size }
}
///Information for a given block of virtual memory mappings
#[derive(Debug)]
pub struct UioInfo{
uio_file:File,
uio_file_name:String,
maps:Vec<UioMap>,
event_count:u64,
name:String,
version:String,
dev_attributes:VecDeque<(String,String)>,
}
impl UioInfo{
///Creates a new UioInfo object, if possible
pub fn new(path:&Path) -> Result<Self,Error> {
let uio_file = File::open(path).expect("Unexpected file failure!");
let uio_file_name:String = path.file_name().expect("Path cannot be parsed!").to_string_lossy().into();
let mut new_info = UioInfo{
uio_file,
uio_file_name,
maps:Vec::new(),
event_count:0,
name:String::default(),
version:String::default(),
dev_attributes:VecDeque::new(),
};
new_info.get_all_info();
Ok(new_info)
}
///Pseudo-Display of the UIO device
pub fn show_device(&self){
println!("{}, name={}, version={}, events={}",
self.uio_file_name, self.name, self.version, self.event_count);
}
///Get the UioMap of a particular index from the info, if it exists
pub fn get_map(&self,index:u32) -> Option<&UioMap>{
self.maps.get(index as usize)
}
///Pseudo-Display of a particular map within the UIO device
pub fn show_map(&self, index:usize) {
if index > 0 && index < self.maps.len(){
let map = &self.maps[index];
println!("Map[{}]: {:?}", index, map );
}
}
///Pseudo-Display of all maps in the UIO device
pub fn show_maps(&self) {
for i in 0..self.maps.len(){
self.show_map(i);
}
}
///Gets the name of the object as found in /sys/class/uio
pub fn get_name(&self) -> String { self.name.clone() }
///Get the file that represents the uio device.
pub fn get_device(&self) -> File { self.uio_file.try_clone().expect("File cannot be cloned!") }
///Get a given memory value from the filesystem.
///This should only be used for initialisation.
fn get_memory_value(&mut self, search_term:MemoryValue){
let uio_path;
match search_term{
MemoryValue::MapAddress(map_index) =>
uio_path = format!("/sys/class/uio/{}/maps/map{}/addr",
self.uio_file_name, map_index),
MemoryValue::MapSize(map_index) =>
uio_path = format!("/sys/class/uio/{}/maps/map{}/size",
self.uio_file_name, map_index),
MemoryValue::Name =>
uio_path = format!("/sys/class/uio/{}/name",
self.uio_file_name),
MemoryValue::Event =>
uio_path = format!("/sys/class/uio/{}/event",
self.uio_file_name),
MemoryValue::Version =>
uio_path = format!("/sys/class/uio/{}/version",
self.uio_file_name),
}
let possible_file = File::open(uio_path);
match possible_file{
Ok(file) => {
//All files contain a single value, to which they are being referenced
let lines:String = BufReader::new(file).lines().map(|line| line.expect("Bad line!")).collect();
match search_term{
MemoryValue::MapSize(index) => {
let filtered_line = lines.strip_prefix("0x").expect("Bad location!");
self.maps[index].size = filtered_line.parse().expect("Impossible error");
},
MemoryValue::MapAddress(index) => {
let filtered_line = lines.strip_prefix("0x").expect("Bad location!");
self.maps[index].address = filtered_line.parse().expect("Impossible error");
},
MemoryValue::Event => {
self.event_count = lines.parse().expect("This shouldn't happen");
},
MemoryValue::Name |
MemoryValue::Version => {
self.name = lines;
}
}
},
Err(error) => {
println!("{:?}",error);
}
}
}
///Wrapper for get_memory_value(); runs all iterations for this object
fn get_all_info(&mut self){
self.get_memory_value(MemoryValue::Name);
self.get_memory_value(MemoryValue::Version);
self.get_memory_value(MemoryValue::Event);
let sys_location = format!("/sys/class/uio/{}/maps",self.uio_file_name);
for i in 0..Path::new(&sys_location).read_dir().expect("").count(){
self.get_memory_value(MemoryValue::MapSize(i));
self.get_memory_value(MemoryValue::MapAddress(i));
}
}
}
///Find all devices on the filesystem, and return them as a VecDeque
pub fn find_devices() -> VecDeque<UioInfo>{
let mut file_options = OpenOptions::new();
file_options.read(true).write(true).custom_flags(O_SYNC);
let dev_folder = Path::new("/dev");
let mut uio_devices:VecDeque<UioInfo> = VecDeque::new();
if dev_folder.is_dir(){
for file in dev_folder.read_dir().expect("Invalid path! Check this is being run on Linux"){
if let Ok(real_file) = file{
let raw_filename = real_file.file_name();
let filename:String = raw_filename.to_string_lossy().into();
if filename.contains("uio"){
let uio_device = UioInfo::new(real_file.path().as_path());
match uio_device{
Ok(real_device) => uio_devices.push_back(real_device),
Err(error) => {
error!("{:?}",error);
panic!();
}
}
}
}
}
}
uio_devices
}

View file

@ -0,0 +1,225 @@
use std::{fs::File, io::Error, os::unix::fs::FileExt, thread, time::Duration};
use crate::vdma_facade::common::{find_devices, UioInfo};
///Object for VDMA control
pub struct VdmaHandle{
/// File pointing to the VDMA control buffer, along with its information
control_buffer:(File, UioInfo),
/// Width of the images being parsed; measured in pixels
width:u32,
/// Height of the images being parsed; measured in pixels
height:u32,
/// Pixel length, measured in bytes
pixel_length:u32,
/// Storage of all available framebuffers, along with their information
framebuffers:Vec<(File,UioInfo)>,
}
/// Gonna be honest, not sure what this thing is.
const OFFSET_PARK_POINTER_REGISTER:u64 = 0x28;
const REGISTER_RESET:u32 = 0;
const NO_MASK_INTERRUPTS:u32 = 0xf;
//Commented = unused
//const TAIL:u32 = 0x34;
//const OFFSET_VERSION:u32 = 0x2c;
//* Register offsets */
//const READ_CONTROL_REGISTER:u64 = 0x00;
//const READ_STATUS_REGISTER:u64 = 0x04;
//const READ_VSIZE:u64 = 0x50;
//const READ_HSIZE:u64 = 0x54;
//const READ_FRAMEDELAY_STRIDE:u64 = 0x58;
//const READ_FRAMEBUFFER_ONE:u64 = 0x5c;
//const READ_FRAMEBUFFER_TWO:u64 = 0x60;
//const READ_FRAMEBUFFER_THREE:u64 = 0x64;
//const READ_FRAMEBUFFER_FOUR:u64 = 0x68;
const WRITE_CONTROL_REGISTER:u64 = 0x30;
const WRITE_STATUS_REGISTER:u64 = 0x34;
const WRITE_IRQ_MASK:u64 = 0x3c;
const WRITE_REGISTER_INDEX:u64 = 0x44;
const WRITE_VSIZE:u64 = 0xa0;
const WRITE_HSIZE:u64 = 0xa4;
const WRITE_FRAMEDELAY_STRIDE:u64 = 0xa8;
const WRITE_FRAMEBUFFER_ONE:u64 = 0xac;
const WRITE_FRAMEBUFFER_TWO:u64 = 0xb0;
const WRITE_FRAMEBUFFER_THREE:u64 = 0xb4;
//const WRITE_FRAMEBUFFER_FOUR:u64 = 0xb8;
///BIT MASKS FOR CONTROL REGISTER
///https://docs.amd.com/r/en-US/pg020_axi_vdma/S2MM_VDMACR-S2MM-VDMA-Control-Register-Offset-30h
///https://docs.amd.com/r/en-US/pg020_axi_vdma/MM2S_VDMACR-MM2S-VDMA-Control-Register-Offset-00h
/// Further reading can be done at the above linked locations, or the pdf in this repository
const CONTROL_REGISTER_START:u32 = 0x00000001;
const CONTROL_REGISTER_CIRCULAR_PARK:u32 = 0x00000002;
const CONTROL_REGISTER_RESET:u32 = 0x00000004;
const CONTROL_REGISTER_GENLOCK_ENABLE:u32 = 0x00000008;
//const CONTROL_REGISTER_FRAME_COUNT_ENABLE:u32 = 0x00000010;
const CONTROL_REGISTER_INTERNAL_GENLOCK:u32 = 0x00000080;
//const CONTROL_REGISTER_WRITE_POINTER:u32 = 0x00000f00;
//const CONTROL_REGISTER_FRAMECOUNTER_IRQENABLE:u32 = 0x00001000;
//const CONTROL_REGISTER_DELAYCOUNT_IRQENABLE:u32 = 0x00002000;
//const CONTROL_REGISTER_ERROR_IRQENABLE:u32 = 0x00004000;
//const CONTROL_REGISTER_REPEAT_ENABLE:u32 = 0x00008000;
//const CONTROL_REGISTER_INTERRUPT_FRAME_COUNT:u32 = 0x00ff0000;
//const CONTROL_REGISTER_IRQ_DELAY_COUNT:u32 = 0xff000000;
///BIT MASKS FOR STATUS REGISTER
///https://docs.amd.com/r/en-US/pg020_axi_vdma/S2MM_VDMASR-S2MM-VDMA-Status-Register-Offset-34h
///https://docs.amd.com/r/en-US/pg020_axi_vdma/MM2S_VDMASR-MM2S-VDMA-Status-Register-Offset-04h
/// Further reading can be done at the above linked locations, or the pdf in this repository
const STATUS_REGISTER_HALTED:u32 = 0x00000001;
//const STATUS_REGISTER_VDMA_INTENAL_ERROR:u32 = 0x00000010;
//const STATUS_REGISTER_VDMA_SLAVE_ERROR:u32 = 0x00000020;
//const STATUS_REGISTER_VDMA_DECODE_ERROR:u32 = 0x00000040;
//const STATUS_REGISTER_START_OF_FRAME_EARLY_ERROR:u32= 0x00000080;
//const STATUS_REGISTER_END_OF_LINE_EARLY_ERROR:u32 = 0x00000100;
//const STATUS_REGISTER_START_OF_FRAME_LATE_ERROR:u32 = 0x00000800;
//const STATUS_REGISTER_FRAME_COUNT_INTERRUPT:u32 = 0x00001000;
//const STATUS_REGISTER_DELAY_COUNT_INTERRUPT:u32 = 0x00002000;
//const STATUS_REGISTER_ERROR_INTERRUPT:u32 = 0x00004000;
//const STATUS_REGISTER_END_OF_LINE_LATE_ERROR:u32 = 0x00008000;
//const STATUS_REGISTER_FRAME_COUNT:u32 = 0x00ff0000;
//const STATUS_REGISTER_DELAY_COUNT:u32 = 0xff000000;
impl VdmaHandle {
///Creates a new VDMAHandle
pub fn new(width:u32,height:u32,pixel_length:u32) -> Result<VdmaHandle,Error>{
//uio0 is control
//uio1-3 are framebuffers
// for each device in /dev/uio*
let mut framebuffers:Vec<(File,UioInfo)> = Vec::new();
let mut uio_devices = find_devices();
//Control device should always be first; dependent upon DeviceTree implementation
let control_device = uio_devices.pop_front().unwrap();
let control_buffer = (control_device.get_device(),control_device);
//Collect all devices
for device in uio_devices{
framebuffers.push((device.get_device(),device));
}
//Create a new VdmaHandle object
let mut new_object = VdmaHandle{ control_buffer,width,height,pixel_length,framebuffers };
//Start triple buffering.... whatever that means. Kaputa didn't make it very clear,
//but it seems to be initialisation things
new_object.vdma_start_triple_buffering();
//Return new initialised object
Ok(new_object)
}
///Set a flag in the Vdma control buffer
fn set_vdma_flag(&mut self, offset:u64, value:u32) {
_ = self.control_buffer.0.write_at(&value.to_le_bytes(), offset);
}
///Get a value from the VDMA control buffer, assuming it exists
/// TODO: This might need a unique offset, if the status register != control register.
/// I was skimming, so I'm not sure how accurate this is.
fn get_vdma_flag(&self, offset:u64) -> Result<u32,Error> {
let mut buf = [0;4];
let read_length = self.control_buffer.0.read_at(&mut buf, offset);
match read_length{
Ok(length) => {
if length < 4{
return Ok(u32::from_le_bytes(buf));
} else {
return self.get_vdma_flag(offset);
}
},
Err(error) => {
error!("{:?}",error);
return Err(error);
},
}
}
///Check if a flag is set to a certain value
///
///Checking flags is fairly regular on initialisation, this makes the code further down
///easier to read.
fn flag_is_set(&self, offset:u64, check_val:u32) -> bool{
match self.get_vdma_flag(offset){
Ok(return_val) => {
return_val & check_val == check_val
},
Err(_) => { false }
}
}
///Start triple buffering; aka initialise the buffers
///
///Still need to poke around and find out what the hell this actually means, but uh...
///later me problem. For now, just a translated version of what Kaputa wrote
fn vdma_start_triple_buffering(&mut self) {
self.set_vdma_flag(WRITE_CONTROL_REGISTER, REGISTER_RESET);
while !self.flag_is_set(WRITE_CONTROL_REGISTER, CONTROL_REGISTER_RESET)
{ thread::sleep(Duration::from_millis(50)); }
self.set_vdma_flag(WRITE_STATUS_REGISTER, CONTROL_REGISTER_RESET);
self.set_vdma_flag(WRITE_IRQ_MASK, NO_MASK_INTERRUPTS);
let interrupt_frame_count = 3;
//Interrupt frame count is stored in mask
//CONTROL_REGISTER_INTERRUPT_FRAME_COUNT: 0x00ff0000
//The value should, therefore, be shifted left 16
let start_flag = (interrupt_frame_count << 16) |
//Start VDMA
CONTROL_REGISTER_START |
// Enable """mutex""" for r/w register.
//https://docs.amd.com/r/en-US/pg020_axi_vdma/Genlock-Synchronization?tocId=jzZsIGLYleCfhtxUkvGoug
CONTROL_REGISTER_GENLOCK_ENABLE |
CONTROL_REGISTER_INTERNAL_GENLOCK |
//Enable circular buffer rather than parking
//https://docs.amd.com/r/en-US/pg020_axi_vdma/Stream-to-Memory-Map-Register-Detail
CONTROL_REGISTER_CIRCULAR_PARK;
self.set_vdma_flag(WRITE_CONTROL_REGISTER,start_flag);
while !self.flag_is_set(WRITE_CONTROL_REGISTER, CONTROL_REGISTER_START) ||
self.flag_is_set(WRITE_STATUS_REGISTER, STATUS_REGISTER_HALTED)
{ thread::sleep(Duration::from_millis(50)); }
self.set_vdma_flag(WRITE_REGISTER_INDEX, REGISTER_RESET);
let framebuffers = &self.framebuffers;
let frame_buf_one = framebuffers.get(0).expect("No framebuffers!");
let frame_buf_two = framebuffers.get(1).expect("Only one framebuffer!");
let frame_buf_three = framebuffers.get(2).expect("Not enough framebuffers");
let frame_buf_one_addr = frame_buf_one.1.get_map(0).expect("Bad map!").get_address();
let frame_buf_two_addr = frame_buf_two.1.get_map(0).expect("Bad map!").get_address();
let frame_buf_three_addr = frame_buf_three.1.get_map(0).expect("Bad map!").get_address();
self.set_vdma_flag(WRITE_FRAMEBUFFER_ONE, frame_buf_one_addr );
self.set_vdma_flag(WRITE_FRAMEBUFFER_TWO, frame_buf_two_addr );
self.set_vdma_flag(WRITE_FRAMEBUFFER_THREE, frame_buf_three_addr);
self.set_vdma_flag(OFFSET_PARK_POINTER_REGISTER, REGISTER_RESET);
self.set_vdma_flag(WRITE_FRAMEDELAY_STRIDE, self.width*self.pixel_length);
self.set_vdma_flag(WRITE_HSIZE, self.width*self.pixel_length);
self.set_vdma_flag(WRITE_VSIZE, self.height);
}
///Send image to VDMA
///
///TODO: Define format being used, be it raw image, or file
pub fn set_frame() {
//amountWritten = vdma_endpoint.write_at(&[u8;arraySize], offset)
//amountWritten = framebuffer.write(&[u8;arraySize]);
//From python file:
//seek back to the beginning of the file (probably won't need to??? Gonna have to check)
//write to file
todo!();
}
///Recieve image from VDMA
///
///TODO: Not sure how to get this output to file system yet...
pub fn get_frame() {
//amountRead = vdma_endpoint.read_at(&mut [u8;arraySize], offset)
//amountRead = framebuffer.read(&mut [u8;arraySize]);
todo!();
}
}

5
src/vdma_facade/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod feedthrough;
pub mod common;
///See https://docs.rs/libc/latest/libc/constant.O_SYNC.html
const O_SYNC:i32 = 1052672;

BIN
vdma_docs.pdf Normal file

Binary file not shown.

1956
volley1.csv Normal file

File diff suppressed because it is too large Load diff

1447
volley2.csv Normal file

File diff suppressed because it is too large Load diff

4182
volley3.csv Normal file

File diff suppressed because it is too large Load diff

2067
volley4.csv Normal file

File diff suppressed because it is too large Load diff

1396
volley5.csv Normal file

File diff suppressed because it is too large Load diff

2001
wind.csv Normal file

File diff suppressed because it is too large Load diff