use crate::{
rawmaster::{RawMaster, Topic, PduCommand, SlaveAddress},
data::{PduData, PduField, Field},
sdo::{self, Sdo, SyncDirection},
slave::{Slave, CommunicationState},
can::CanError,
registers,
error::EthercatResult,
};
use core::{
fmt,
ops::{Range, Deref},
cell::RefCell,
};
use std::{
collections::{HashMap, HashSet, BTreeSet},
sync::{Arc, Weak, Mutex, RwLock, RwLockWriteGuard},
};
use bilge::prelude::*;
pub struct Allocator {
internal: Mutex<AllocatorInternal>,
}
pub struct AllocatorInternal {
slaves: HashMap<u16, Weak<ConfigSlave>>,
free: BTreeSet<LogicalSlot>,
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
struct LogicalSlot {
size: u32,
position: u32,
}
impl Allocator {
pub fn new() -> Self {
let mut free = BTreeSet::new();
free.insert(LogicalSlot {size: u32::MAX, position: 0});
let internal = Mutex::new(AllocatorInternal {
slaves: HashMap::new(),
free,
});
Self {internal}
}
pub async fn group<'a>(&'a self, master: &'a RawMaster, mapping: &Mapping<'_>) -> Group<'a> {
let size = mapping.offset.borrow().clone();
let mut internal = self.internal.lock().unwrap();
assert!(internal.compatible(&mapping));
let slot;
{
slot = internal.free.range(LogicalSlot {size, position: 0} ..)
.next().expect("no more logical memory")
.clone();
internal.free.remove(&slot);
if slot.size > size {
internal.free.insert(LogicalSlot {
position: slot.position + size,
size: slot.size - size,
});
}
}
let mut slaves = HashMap::<u16, Arc<ConfigSlave>>::new();
let config = mapping.config.slaves.lock().unwrap();
for &k in mapping.slaves.borrow().iter() {
slaves.insert(k,
if let Some(value) = internal.slaves.get(&k).map(|v| v.upgrade()).flatten()
{value}
else {
let new = Arc::new(config[&k].try_read().expect("a slave is still in mapping").clone());
internal.slaves.insert(k, Arc::downgrade(&new));
new
});
}
let mut exchange = mapping.default.borrow().clone();
exchange.extend((exchange.len() .. size as usize).map(|_| 0));
let read = exchange.clone();
let write = exchange.clone();
let topic = master.topic(PduCommand::LRW, SlaveAddress::Logical, slot.position,
unsafe { std::slice::from_raw_parts_mut(
exchange.as_mut_ptr(),
exchange.len(),
)}).await;
Group {
allocator: self,
allocated: slot.size,
offset: slot.position,
size,
config: slaves,
data: tokio::sync::Mutex::new(GroupData {
topic: Some(topic),
exchange,
read,
write,
}),
}
}
pub fn compatible(&self, mapping: &Mapping) -> bool {
self.internal.lock().unwrap().compatible(mapping)
}
pub fn allocated(&self) -> u32 {
self.internal.lock().unwrap().allocated()
}
pub fn free(&self) -> u32 {
self.internal.lock().unwrap().free()
}
}
impl AllocatorInternal {
fn compatible(&self, mapping: &Mapping) -> bool {
for (address, slave) in mapping.config.slaves.lock().unwrap().iter() {
if let Some(alter) = self.slaves.get(address) {
if let Some(alter) = alter.upgrade() {
if slave.try_read().expect("a slave is still in mapping").deref() != alter.as_ref()
{return false}
}
}
}
true
}
fn allocated(&self) -> u32 {
u32::MAX - self.free()
}
fn free(&self) -> u32 {
self.free.iter()
.map(|s| s.size)
.sum::<u32>()
}
}
impl fmt::Debug for Allocator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let internal = self.internal.lock().unwrap();
write!(f, "<Allocator with {} slaves using {} bytes>",
internal.slaves.len(),
internal.allocated(),
)
}
}
pub struct Group<'a> {
allocator: &'a Allocator,
allocated: u32,
offset: u32,
size: u32,
config: HashMap<u16, Arc<ConfigSlave>>,
data: tokio::sync::Mutex<GroupData<'a>>,
}
pub struct GroupData<'a> {
topic: Option<Topic<'a>>,
read: Vec<u8>,
write: Vec<u8>,
#[allow(unused)] exchange: Vec<u8>,
}
impl<'a> Group<'a> {
pub fn config(&self) -> &HashMap<u16, Arc<ConfigSlave>> {&self.config}
pub fn contains(&self, slave: u16) -> bool {
self.config.contains_key(&slave)
}
pub async fn configure(&self, slave: &Slave<'_>) -> EthercatResult<(), CanError> {
let master = unsafe{ slave.raw_master() };
let address = match slave.address() {
SlaveAddress::Fixed(a) => a,
_ => panic!("address must be fixed before configuring a mapping"),
};
let config = &self.config[&address];
assert_eq!(slave.expected(), CommunicationState::PreOperational, "slave must be in preop state to configure a mapping");
let mut coe = slave.coe().await;
let priority = u2::new(1);
for pdo in config.pdos.values() {
if pdo.config.fixed {
for (i, sdo) in pdo.sdos.iter().enumerate() {
assert_eq!(
coe.sdo_read(&pdo.config.item(i), priority).await?,
sdo::PdoEntry::new(
sdo.field.len.try_into().expect("field too big for a subitem"),
sdo.sub.unwrap(),
sdo.index,
),
"slave {} fixed pdo {}", address, pdo.config.item(i));
}
}
else {
coe.sdo_write(&pdo.config.len(), priority, 0).await?;
for (i, sdo) in pdo.sdos.iter().enumerate() {
coe.sdo_write(&pdo.config.item(i), priority, sdo::PdoEntry::new(
sdo.field.len.try_into().expect("field too big for a subitem"),
sdo.sub.unwrap(),
sdo.index,
)).await?;
}
coe.sdo_write(&pdo.config.len(), priority, pdo.sdos.len() as u8).await?;
}
}
for channel in config.channels.values() {
let mut size = 0;
coe.sdo_write(&channel.config.len(), priority, 0).await?;
for (j, &pdo) in channel.pdos.iter().enumerate() {
coe.sdo_write(&channel.config.slot(j as u8), priority, pdo).await?;
size += config.pdos[&pdo].sdos.iter()
.map(|sdo| (sdo.field.len / 8) as u16)
.sum::<u16>();
}
coe.sdo_write(&channel.config.len(), priority, channel.pdos.len() as u8).await?;
master.fpwr(address, channel.config.register(), {
let mut config = registers::SyncManagerChannel::default();
config.set_address(channel.start);
config.set_length(size);
config.set_mode(registers::SyncMode::Buffered);
config.set_direction(channel.config.direction);
config.set_dls_user_event(true);
config.set_watchdog(channel.config.direction == registers::SyncDirection::Write);
config.set_enable(channel.pdos.len() != 0);
config
}).await.one()?;
}
for (i, entry) in config.fmmu.iter().enumerate() {
master.fpwr(address, registers::fmmu.entry(i as u8), {
let mut config = registers::FmmuEntry::default();
config.set_logical_start_byte(entry.logical + (self.offset as u32));
config.set_logical_len_byte(entry.length);
config.set_logical_start_bit(u3::new(0));
config.set_logical_end_bit(u3::new(7));
config.set_physical_start_byte(entry.physical);
config.set_physical_start_bit(u3::new(0));
config.set_read(entry.direction == SyncDirection::Read);
config.set_write(entry.direction == SyncDirection::Write);
config.set_enable(true);
config
}).await.one()?;
}
Ok(())
}
pub async fn data(&self) -> tokio::sync::MutexGuard<GroupData<'a>> {
self.data.lock().await
}
pub fn data_mut(&mut self) -> &mut GroupData<'a> {
self.data.get_mut()
}
}
impl<'a> GroupData<'a> {
pub async fn exchange(&mut self) -> &'_ mut [u8] {
self.topic.as_mut().unwrap().send(Some(self.write.as_mut_slice())).await;
self.topic.as_mut().unwrap().receive(Some(self.read.as_mut_slice()));
self.read.as_mut_slice()
}
pub async fn read(&mut self) {
self.topic.as_mut().unwrap().receive(Some(self.read.as_mut_slice()));
}
pub async fn write(&mut self) {
self.topic.as_mut().unwrap().send(Some(self.write.as_mut_slice())).await;
}
pub fn read_buffer(&mut self) -> &'_ mut [u8] {self.read.as_mut_slice()}
pub fn write_buffer(&mut self) -> &'_ mut [u8] {self.write.as_mut_slice()}
pub fn get<T: PduData>(&self, field: impl PduField<T>) -> T
{field.get(&self.read)}
pub fn set<T: PduData>(&mut self, field: impl PduField<T>, value: T)
{field.set(&mut self.write, value)}
}
impl Drop for Group<'_> {
fn drop(&mut self) {
self.data.try_lock().unwrap().topic.take();
self.allocator.internal.lock().unwrap()
.free.remove(&LogicalSlot {size: self.allocated, position: self.offset});
}
}
impl fmt::Debug for Group<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<Group at offset: 0x{:x}, {} bytes, {} slaves>",
self.offset, self.size, self.config.len())
}
}
#[derive(Default, Debug)]
pub struct Config {
pub slaves: Mutex<HashMap<u16, Box<RwLock<ConfigSlave>>>>,
}
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct ConfigSlave {
pub pdos: HashMap<u16, ConfigPdo>,
pub channels: HashMap<u16, ConfigChannel>,
pub fmmu: Vec<ConfigFmmu>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigPdo {
pub config: sdo::Pdo,
pub sdos: Vec<Sdo>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigChannel {
pub config: sdo::SyncChannel,
pub pdos: Vec<u16>,
pub start: u16,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigFmmu {
pub direction: SyncDirection,
pub length: u16,
pub physical: u16,
pub logical: u32,
}
pub struct Mapping<'a> {
config: &'a Config,
offset: RefCell<u32>,
default: RefCell<Vec<u8>>,
slaves: RefCell<HashSet<u16>>,
}
impl<'a> Mapping<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config,
offset: RefCell::new(0),
default: RefCell::new(Vec::new()),
slaves: RefCell::new(HashSet::new()),
}
}
pub fn config(&self) -> &'a Config {
self.config
}
pub fn slave(&self, address: u16) -> MappingSlave<'_> {
self.slaves.borrow_mut().insert(address);
let mut slaves = self.config.slaves.lock().unwrap();
slaves
.entry(address)
.or_insert_with(|| Box::new(RwLock::new(ConfigSlave {
pdos: HashMap::new(),
channels: HashMap::new(),
fmmu: Vec::new(),
})));
let slave = unsafe {core::mem::transmute::<_, &Box<RwLock<_>>>(
slaves.get(&address).unwrap()
)};
MappingSlave {
config: slave.try_write().expect("slave already in mapping"),
mapping: self,
}
}
pub fn size(&self) -> u32 {
self.offset.borrow().clone()
}
}
pub struct MappingSlave<'a> {
mapping: &'a Mapping<'a>,
config: RwLockWriteGuard<'a, ConfigSlave>,
}
impl MappingSlave<'_> {
fn insert(&mut self, direction: SyncDirection, length: u16, position: u16) -> usize {
let mut offset = self.mapping.offset.borrow_mut();
let change = if let Some(fmmu) = self.config.fmmu.last() {
fmmu.logical + u32::from(fmmu.length) != *offset
|| fmmu.physical + fmmu.length != position
|| fmmu.direction != direction
}
else {true};
if change {
self.config.fmmu.push(ConfigFmmu {
direction,
length: 0,
logical: *offset,
physical: position,
});
}
let fmmu = self.config.fmmu.last_mut().unwrap();
fmmu.length += length as u16;
let inserted = offset.clone().try_into().unwrap();
*offset += length as u32;
inserted
}
fn default<T: PduData>(&self, field: Field<T>, value: T) {
let mut default = self.mapping.default.borrow_mut();
let range = default.len() .. field.byte + field.len;
default.extend(range.map(|_| 0));
field.set(&mut default, value);
}
pub fn range(&mut self, direction: SyncDirection, range: Range<u16>) -> Range<usize> {
let size = range.end - range.start;
let start = self.insert(direction, size, range.start);
Range {start, end: start+usize::from(size)}
}
pub fn register<T: PduData>(&mut self, direction: SyncDirection, field: Field<T>) -> Field<T> {
let o = self.insert(direction, field.len as u16, field.byte as u16);
Field::new(o, field.len)
}
pub fn channel(&mut self, sdo: sdo::SyncChannel, buffer: Range<u16>) -> MappingChannel<'_> {
if sdo.register() == registers::sync_manager::interface.mailbox_write()
|| sdo.register() == registers::sync_manager::interface.mailbox_read()
{panic!("mapping on the mailbox channels (0x1c10, 0x1c11) is forbidden");}
self.config.channels.insert(sdo.index, ConfigChannel {
config: sdo,
pdos: Vec::new(),
start: buffer.start,
});
MappingChannel {
entries: unsafe {&mut *(&mut self.config.channels.get_mut(&sdo.index).unwrap().pdos as *mut _)},
slave: unsafe {&mut *(self as *mut _ as usize as *mut _)},
direction: sdo.direction,
capacity: sdo.capacity as usize,
position: buffer.start,
max: (buffer.end - buffer.start) / 3 + buffer.start,
}
}
}
pub struct MappingChannel<'a> {
slave: &'a mut MappingSlave<'a>,
entries: &'a mut Vec<u16>,
direction: SyncDirection,
capacity: usize,
position: u16,
max: u16,
}
impl MappingChannel<'_> {
pub fn push(&mut self, pdo: sdo::Pdo) -> MappingPdo<'_> {
assert!(self.entries.len()+1 < self.capacity);
self.entries.push(pdo.index);
self.slave.config.pdos.insert(pdo.index, ConfigPdo {
config: pdo,
sdos: Vec::new(),
});
MappingPdo {
direction: self.direction,
capacity: pdo.capacity as usize,
entries: unsafe {&mut *(&mut self.slave.config.pdos.get_mut(&pdo.index).unwrap().sdos as *mut _)},
channel: unsafe {&mut *(self as *mut _ as usize as *mut _)},
}
}
}
pub struct MappingPdo<'a> {
channel: &'a mut MappingChannel<'a>,
entries: &'a mut Vec<Sdo>,
direction: SyncDirection,
capacity: usize,
}
impl<'a> MappingPdo<'a> {
pub fn push<T: PduData>(&mut self, sdo: Sdo<T>) -> Field<T> {
let len = ((sdo.field.len + 7) / 8) as u16;
assert!(self.entries.len()+1 < self.capacity, "sync channel cannot have more entries");
assert!(self.channel.position + len < self.channel.max, "sync channel buffer is too small");
self.entries.push(sdo.clone().downcast());
let offset = Field::new(self.channel.slave.insert(self.direction, len, self.channel.position), usize::from(len));
self.channel.position += len;
offset
}
pub fn set<T: PduData>(&mut self, sdo: Sdo<T>, initial: T) -> Field<T> {
let offset = self.push(sdo);
self.channel.slave.default(offset, initial);
offset
}
}