mirror of https://github.com/m-labs/artiq.git
1013 lines
37 KiB
Rust
1013 lines
37 KiB
Rust
use core::mem;
|
|
use alloc::{string::String, format, vec::Vec, collections::btree_map::BTreeMap};
|
|
use cslice::AsCSlice;
|
|
|
|
use board_artiq::{drtioaux, drtio_routing::RoutingTable, mailbox, spi};
|
|
use board_misoc::{csr, clock, i2c};
|
|
use proto_artiq::{
|
|
drtioaux_proto::PayloadStatus,
|
|
kernel_proto as kern,
|
|
session_proto::Reply::KernelException as HostKernelException,
|
|
rpc_proto as rpc};
|
|
use eh::eh_artiq;
|
|
use io::Cursor;
|
|
use kernel::eh_artiq::StackPointerBacktrace;
|
|
|
|
use ::{cricon_select, RtioMaster};
|
|
use cache::Cache;
|
|
use dma::{Manager as DmaManager, Error as DmaError};
|
|
use routing::{Router, Sliceable, SliceMeta};
|
|
use SAT_PAYLOAD_MAX_SIZE;
|
|
use MASTER_PAYLOAD_MAX_SIZE;
|
|
|
|
mod kernel_cpu {
|
|
use super::*;
|
|
use core::ptr;
|
|
|
|
use proto_artiq::kernel_proto::{KERNELCPU_EXEC_ADDRESS, KERNELCPU_LAST_ADDRESS, KSUPPORT_HEADER_SIZE};
|
|
|
|
pub unsafe fn start() {
|
|
if csr::kernel_cpu::reset_read() == 0 {
|
|
panic!("attempted to start kernel CPU when it is already running")
|
|
}
|
|
|
|
stop();
|
|
|
|
extern {
|
|
static _binary____ksupport_ksupport_elf_start: u8;
|
|
static _binary____ksupport_ksupport_elf_end: u8;
|
|
}
|
|
let ksupport_start = &_binary____ksupport_ksupport_elf_start as *const _;
|
|
let ksupport_end = &_binary____ksupport_ksupport_elf_end as *const _;
|
|
ptr::copy_nonoverlapping(ksupport_start,
|
|
(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as *mut u8,
|
|
ksupport_end as usize - ksupport_start as usize);
|
|
|
|
csr::kernel_cpu::reset_write(0);
|
|
}
|
|
|
|
pub unsafe fn stop() {
|
|
csr::kernel_cpu::reset_write(1);
|
|
cricon_select(RtioMaster::Drtio);
|
|
|
|
mailbox::acknowledge();
|
|
}
|
|
|
|
pub fn validate(ptr: usize) -> bool {
|
|
ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
enum KernelState {
|
|
Absent,
|
|
Loaded,
|
|
Running,
|
|
MsgAwait { id: u32, max_time: i64, tags: Vec<u8> },
|
|
MsgSending,
|
|
SubkernelAwaitLoad,
|
|
SubkernelAwaitFinish { max_time: i64, id: u32 },
|
|
DmaUploading { max_time: u64 },
|
|
DmaAwait { max_time: u64 },
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
Load(String),
|
|
KernelNotFound,
|
|
InvalidPointer(usize),
|
|
Unexpected(String),
|
|
NoMessage,
|
|
AwaitingMessage,
|
|
SubkernelIoError,
|
|
DrtioError,
|
|
KernelException(Sliceable),
|
|
DmaError(DmaError),
|
|
}
|
|
|
|
impl From<io::Error<!>> for Error {
|
|
fn from(_value: io::Error<!>) -> Error {
|
|
Error::SubkernelIoError
|
|
}
|
|
}
|
|
|
|
impl From<drtioaux::Error<!>> for Error {
|
|
fn from(_value: drtioaux::Error<!>) -> Error {
|
|
Error::DrtioError
|
|
}
|
|
}
|
|
|
|
impl From<DmaError> for Error {
|
|
fn from(value: DmaError) -> Error {
|
|
Error::DmaError(value)
|
|
}
|
|
}
|
|
|
|
macro_rules! unexpected {
|
|
($($arg:tt)*) => (return Err(Error::Unexpected(format!($($arg)*))));
|
|
}
|
|
|
|
/* represents interkernel messages */
|
|
struct Message {
|
|
id: u32,
|
|
count: u8,
|
|
data: Vec<u8>
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum OutMessageState {
|
|
NoMessage,
|
|
MessageBeingSent,
|
|
MessageSent,
|
|
MessageAcknowledged
|
|
}
|
|
|
|
/* for dealing with incoming and outgoing interkernel messages */
|
|
struct MessageManager {
|
|
out_message: Option<Sliceable>,
|
|
out_state: OutMessageState,
|
|
in_queue: Vec<Message>,
|
|
in_buffer: Option<Message>,
|
|
}
|
|
|
|
// Per-run state
|
|
struct Session {
|
|
kernel_state: KernelState,
|
|
log_buffer: String,
|
|
last_exception: Option<Sliceable>,
|
|
source: u8, // which destination requested running the kernel
|
|
messages: MessageManager,
|
|
subkernels_finished: Vec<u32> // ids of subkernels finished
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct KernelLibrary {
|
|
library: Vec<u8>,
|
|
complete: bool
|
|
}
|
|
|
|
pub struct Manager {
|
|
kernels: BTreeMap<u32, KernelLibrary>,
|
|
current_id: u32,
|
|
session: Session,
|
|
cache: Cache,
|
|
last_finished: Option<SubkernelFinished>
|
|
}
|
|
|
|
pub struct SubkernelFinished {
|
|
pub id: u32,
|
|
pub with_exception: bool,
|
|
pub exception_source: u8,
|
|
pub source: u8
|
|
}
|
|
|
|
impl MessageManager {
|
|
pub fn new() -> MessageManager {
|
|
MessageManager {
|
|
out_message: None,
|
|
out_state: OutMessageState::NoMessage,
|
|
in_queue: Vec::new(),
|
|
in_buffer: None
|
|
}
|
|
}
|
|
|
|
pub fn handle_incoming(&mut self, status: PayloadStatus, length: usize, id: u32, data: &[u8; MASTER_PAYLOAD_MAX_SIZE]) {
|
|
// called when receiving a message from master
|
|
if status.is_first() {
|
|
// clear the buffer for first message
|
|
self.in_buffer = None;
|
|
}
|
|
match self.in_buffer.as_mut() {
|
|
Some(message) => message.data.extend(&data[..length]),
|
|
None => {
|
|
self.in_buffer = Some(Message {
|
|
id: id,
|
|
count: data[0],
|
|
data: data[1..length].to_vec()
|
|
});
|
|
}
|
|
};
|
|
if status.is_last() {
|
|
// when done, remove from working queue
|
|
self.in_queue.push(self.in_buffer.take().unwrap());
|
|
}
|
|
}
|
|
|
|
pub fn was_message_acknowledged(&mut self) -> bool {
|
|
match self.out_state {
|
|
OutMessageState::MessageAcknowledged => {
|
|
self.out_state = OutMessageState::NoMessage;
|
|
true
|
|
},
|
|
_ => false
|
|
}
|
|
}
|
|
|
|
pub fn get_outgoing_slice(&mut self, data_slice: &mut [u8; MASTER_PAYLOAD_MAX_SIZE]) -> Option<SliceMeta> {
|
|
if self.out_state != OutMessageState::MessageBeingSent {
|
|
return None;
|
|
}
|
|
let meta = self.out_message.as_mut()?.get_slice_master(data_slice);
|
|
if meta.status.is_last() {
|
|
// clear the message slot
|
|
self.out_message = None;
|
|
// notify kernel with a flag that message is sent
|
|
self.out_state = OutMessageState::MessageSent;
|
|
}
|
|
Some(meta)
|
|
}
|
|
|
|
pub fn ack_slice(&mut self) -> bool {
|
|
// returns whether or not there's more to be sent
|
|
match self.out_state {
|
|
OutMessageState::MessageBeingSent => true,
|
|
OutMessageState::MessageSent => {
|
|
self.out_state = OutMessageState::MessageAcknowledged;
|
|
false
|
|
},
|
|
_ => {
|
|
warn!("received unsolicited SubkernelMessageAck");
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn accept_outgoing(&mut self, id: u32, self_destination: u8, destination: u8,
|
|
count: u8, tag: &[u8], data: *const *const (),
|
|
routing_table: &RoutingTable, rank: u8, router: &mut Router
|
|
) -> Result<(), Error> {
|
|
let mut writer = Cursor::new(Vec::new());
|
|
rpc::send_args(&mut writer, 0, tag, data, false)?;
|
|
// skip service tag, but write the count
|
|
let mut data = writer.into_inner().split_off(3);
|
|
data[0] = count;
|
|
self.out_message = Some(Sliceable::new(destination, data));
|
|
|
|
let mut data_slice: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
|
self.out_state = OutMessageState::MessageBeingSent;
|
|
let meta = self.get_outgoing_slice(&mut data_slice).unwrap();
|
|
router.route(drtioaux::Packet::SubkernelMessage {
|
|
source: self_destination, destination: destination, id: id,
|
|
status: meta.status, length: meta.len as u16, data: data_slice
|
|
}, routing_table, rank, self_destination);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_incoming(&mut self, id: u32) -> Option<Message> {
|
|
for i in 0..self.in_queue.len() {
|
|
if self.in_queue[i].id == id {
|
|
return Some(self.in_queue.remove(i));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn pending_ids(&self) -> Vec<u32> {
|
|
let mut pending_ids: Vec<u32> = Vec::new();
|
|
for msg in self.in_queue.iter() {
|
|
pending_ids.push(msg.id);
|
|
}
|
|
pending_ids
|
|
}
|
|
}
|
|
|
|
impl Session {
|
|
pub fn new() -> Session {
|
|
Session {
|
|
kernel_state: KernelState::Absent,
|
|
log_buffer: String::new(),
|
|
last_exception: None,
|
|
source: 0,
|
|
messages: MessageManager::new(),
|
|
subkernels_finished: Vec::new()
|
|
}
|
|
}
|
|
|
|
fn running(&self) -> bool {
|
|
match self.kernel_state {
|
|
KernelState::Absent | KernelState::Loaded => false,
|
|
_ => true
|
|
}
|
|
}
|
|
|
|
fn flush_log_buffer(&mut self) {
|
|
if &self.log_buffer[self.log_buffer.len() - 1..] == "\n" {
|
|
for line in self.log_buffer.lines() {
|
|
info!(target: "kernel", "{}", line);
|
|
}
|
|
self.log_buffer.clear()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Manager {
|
|
pub fn new() -> Manager {
|
|
Manager {
|
|
kernels: BTreeMap::new(),
|
|
current_id: 0,
|
|
session: Session::new(),
|
|
cache: Cache::new(),
|
|
last_finished: None,
|
|
}
|
|
}
|
|
|
|
pub fn add(&mut self, id: u32, status: PayloadStatus, data: &[u8], data_len: usize) -> Result<(), Error> {
|
|
if status.is_first() {
|
|
// in case master is interrupted, and subkernel is sent again, clean the state
|
|
self.kernels.remove(&id);
|
|
}
|
|
let kernel = match self.kernels.get_mut(&id) {
|
|
Some(kernel) => {
|
|
if kernel.complete {
|
|
// replace entry
|
|
self.kernels.remove(&id);
|
|
self.kernels.insert(id, KernelLibrary {
|
|
library: Vec::new(),
|
|
complete: false });
|
|
self.kernels.get_mut(&id).unwrap()
|
|
} else {
|
|
kernel
|
|
}
|
|
},
|
|
None => {
|
|
self.kernels.insert(id, KernelLibrary {
|
|
library: Vec::new(),
|
|
complete: false });
|
|
self.kernels.get_mut(&id).unwrap()
|
|
},
|
|
};
|
|
kernel.library.extend(&data[0..data_len]);
|
|
|
|
kernel.complete = status.is_last();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn is_running(&self) -> bool {
|
|
self.session.running()
|
|
}
|
|
|
|
pub fn get_current_id(&self) -> Option<u32> {
|
|
match self.is_running() {
|
|
true => Some(self.current_id),
|
|
false => None
|
|
}
|
|
}
|
|
|
|
pub fn stop(&mut self) {
|
|
unsafe { kernel_cpu::stop() }
|
|
self.session.kernel_state = KernelState::Absent;
|
|
unsafe { self.cache.unborrow() }
|
|
}
|
|
|
|
pub fn run(&mut self, source: u8, id: u32) -> Result<(), Error> {
|
|
info!("starting subkernel #{}", id);
|
|
if self.session.kernel_state != KernelState::Loaded
|
|
|| self.current_id != id {
|
|
self.load(id)?;
|
|
}
|
|
self.session.source = source;
|
|
self.session.kernel_state = KernelState::Running;
|
|
cricon_select(RtioMaster::Kernel);
|
|
|
|
kern_acknowledge()
|
|
}
|
|
|
|
pub fn message_handle_incoming(&mut self, status: PayloadStatus, length: usize, id: u32, slice: &[u8; MASTER_PAYLOAD_MAX_SIZE]) {
|
|
if !self.is_running() {
|
|
return;
|
|
}
|
|
self.session.messages.handle_incoming(status, length, id, slice);
|
|
}
|
|
|
|
pub fn message_get_slice(&mut self, slice: &mut [u8; MASTER_PAYLOAD_MAX_SIZE]) -> Option<SliceMeta> {
|
|
if !self.is_running() {
|
|
return None;
|
|
}
|
|
self.session.messages.get_outgoing_slice(slice)
|
|
}
|
|
|
|
pub fn message_ack_slice(&mut self) -> bool {
|
|
if !self.is_running() {
|
|
warn!("received unsolicited SubkernelMessageAck");
|
|
return false;
|
|
}
|
|
self.session.messages.ack_slice()
|
|
}
|
|
|
|
pub fn load(&mut self, id: u32) -> Result<(), Error> {
|
|
if self.current_id == id && self.session.kernel_state == KernelState::Loaded {
|
|
return Ok(())
|
|
}
|
|
if !self.kernels.get(&id).ok_or(Error::KernelNotFound)?.complete {
|
|
return Err(Error::KernelNotFound)
|
|
}
|
|
self.current_id = id;
|
|
self.session = Session::new();
|
|
self.stop();
|
|
|
|
unsafe {
|
|
kernel_cpu::start();
|
|
|
|
kern_send(&kern::LoadRequest(&self.kernels.get(&id).unwrap().library)).unwrap();
|
|
kern_recv(|reply| {
|
|
match reply {
|
|
kern::LoadReply(Ok(())) => {
|
|
self.session.kernel_state = KernelState::Loaded;
|
|
Ok(())
|
|
}
|
|
kern::LoadReply(Err(error)) => {
|
|
kernel_cpu::stop();
|
|
error!("load error: {:?}", error);
|
|
Err(Error::Load(format!("{}", error)))
|
|
}
|
|
other => {
|
|
unexpected!("unexpected kernel CPU reply to load request: {:?}", other)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn exception_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta {
|
|
match self.session.last_exception.as_mut() {
|
|
Some(exception) => exception.get_slice_sat(data_slice),
|
|
None => SliceMeta { destination: 0, len: 0, status: PayloadStatus::FirstAndLast }
|
|
}
|
|
}
|
|
|
|
fn runtime_exception(&mut self, cause: Error) {
|
|
let raw_exception: Vec<u8> = Vec::new();
|
|
let mut writer = Cursor::new(raw_exception);
|
|
match (HostKernelException {
|
|
exceptions: &[Some(eh_artiq::Exception {
|
|
id: 11, // SubkernelError, defined in ksupport
|
|
message: format!("in subkernel id {}: {:?}", self.current_id, cause).as_c_slice(),
|
|
param: [0, 0, 0],
|
|
file: file!().as_c_slice(),
|
|
line: line!(),
|
|
column: column!(),
|
|
function: format!("subkernel id {}", self.current_id).as_c_slice(),
|
|
})],
|
|
stack_pointers: &[StackPointerBacktrace {
|
|
stack_pointer: 0,
|
|
initial_backtrace_size: 0,
|
|
current_backtrace_size: 0
|
|
}],
|
|
backtrace: &[],
|
|
async_errors: 0
|
|
}).write_to(&mut writer) {
|
|
Ok(_) => self.session.last_exception = Some(Sliceable::new(0, writer.into_inner())),
|
|
Err(_) => error!("Error writing exception data")
|
|
}
|
|
}
|
|
|
|
pub fn ddma_finished(&mut self, error: u8, channel: u32, timestamp: u64) {
|
|
if let KernelState::DmaAwait { .. } = self.session.kernel_state {
|
|
kern_send(&kern::DmaAwaitRemoteReply {
|
|
timeout: false, error: error, channel: channel, timestamp: timestamp
|
|
}).unwrap();
|
|
self.session.kernel_state = KernelState::Running;
|
|
}
|
|
}
|
|
|
|
pub fn ddma_nack(&mut self) {
|
|
// for simplicity treat it as a timeout for now...
|
|
if let KernelState::DmaAwait { .. } = self.session.kernel_state {
|
|
kern_send(&kern::DmaAwaitRemoteReply {
|
|
timeout: true, error: 0, channel: 0, timestamp: 0
|
|
}).unwrap();
|
|
self.session.kernel_state = KernelState::Running;
|
|
}
|
|
}
|
|
|
|
pub fn ddma_remote_uploaded(&mut self, succeeded: bool) {
|
|
if let KernelState::DmaUploading { .. } = self.session.kernel_state {
|
|
if succeeded {
|
|
self.session.kernel_state = KernelState::Running;
|
|
kern_acknowledge().unwrap();
|
|
} else {
|
|
self.stop();
|
|
self.runtime_exception(Error::DmaError(DmaError::UploadFail));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn process_kern_requests(&mut self, router: &mut Router, routing_table: &RoutingTable, rank: u8, destination: u8, dma_manager: &mut DmaManager) {
|
|
macro_rules! finished {
|
|
($with_exception:expr) => {{ Some(SubkernelFinished {
|
|
source: self.session.source, id: self.current_id,
|
|
with_exception: $with_exception, exception_source: destination
|
|
}) }}
|
|
}
|
|
|
|
if let Some(subkernel_finished) = self.last_finished.take() {
|
|
info!("subkernel {} finished, with exception: {}", subkernel_finished.id, subkernel_finished.with_exception);
|
|
let pending = self.session.messages.pending_ids();
|
|
if pending.len() > 0 {
|
|
warn!("subkernel terminated with messages still pending: {:?}", pending);
|
|
}
|
|
router.route(drtioaux::Packet::SubkernelFinished {
|
|
destination: subkernel_finished.source, id: subkernel_finished.id,
|
|
with_exception: subkernel_finished.with_exception, exception_src: subkernel_finished.exception_source
|
|
}, &routing_table, rank, destination);
|
|
dma_manager.cleanup(router, rank, destination, routing_table);
|
|
}
|
|
|
|
if !self.is_running() {
|
|
return;
|
|
}
|
|
|
|
match self.process_external_messages() {
|
|
Ok(()) => (),
|
|
Err(Error::AwaitingMessage) => return, // kernel still waiting, do not process kernel messages
|
|
Err(Error::KernelException(exception)) => {
|
|
unsafe { kernel_cpu::stop() }
|
|
self.session.kernel_state = KernelState::Absent;
|
|
unsafe { self.cache.unborrow() }
|
|
self.session.last_exception = Some(exception);
|
|
self.last_finished = finished!(true);
|
|
},
|
|
Err(e) => {
|
|
error!("Error while running processing external messages: {:?}", e);
|
|
self.stop();
|
|
self.runtime_exception(e);
|
|
self.last_finished = finished!(true);
|
|
}
|
|
}
|
|
|
|
match self.process_kern_message(router, routing_table, rank, destination, dma_manager) {
|
|
Ok(Some(with_exception)) => {
|
|
self.last_finished = finished!(with_exception)
|
|
},
|
|
Ok(None) | Err(Error::NoMessage) => (),
|
|
Err(e) => {
|
|
error!("Error while running kernel: {:?}", e);
|
|
self.stop();
|
|
self.runtime_exception(e);
|
|
self.last_finished = finished!(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process_external_messages(&mut self) -> Result<(), Error> {
|
|
match &self.session.kernel_state {
|
|
KernelState::MsgAwait { id, max_time, tags } => {
|
|
if *max_time > 0 && clock::get_ms() > *max_time as u64 {
|
|
kern_send(&kern::SubkernelMsgRecvReply { status: kern::SubkernelStatus::Timeout, count: 0 })?;
|
|
self.session.kernel_state = KernelState::Running;
|
|
return Ok(())
|
|
}
|
|
if let Some(message) = self.session.messages.get_incoming(*id) {
|
|
kern_send(&kern::SubkernelMsgRecvReply { status: kern::SubkernelStatus::NoError, count: message.count })?;
|
|
let tags = tags.clone();
|
|
self.session.kernel_state = KernelState::Running;
|
|
pass_message_to_kernel(&message, &tags)
|
|
} else {
|
|
Err(Error::AwaitingMessage)
|
|
}
|
|
},
|
|
KernelState::MsgSending => {
|
|
if self.session.messages.was_message_acknowledged() {
|
|
self.session.kernel_state = KernelState::Running;
|
|
kern_acknowledge()
|
|
} else {
|
|
Err(Error::AwaitingMessage)
|
|
}
|
|
},
|
|
KernelState::SubkernelAwaitFinish { max_time, id } => {
|
|
if *max_time > 0 && clock::get_ms() > *max_time as u64 {
|
|
kern_send(&kern::SubkernelAwaitFinishReply { status: kern::SubkernelStatus::Timeout })?;
|
|
self.session.kernel_state = KernelState::Running;
|
|
} else {
|
|
let mut i = 0;
|
|
for status in &self.session.subkernels_finished {
|
|
if *status == *id {
|
|
kern_send(&kern::SubkernelAwaitFinishReply { status: kern::SubkernelStatus::NoError })?;
|
|
self.session.kernel_state = KernelState::Running;
|
|
self.session.subkernels_finished.swap_remove(i);
|
|
break;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
KernelState::DmaAwait { max_time } => {
|
|
if clock::get_ms() > *max_time {
|
|
kern_send(&kern::DmaAwaitRemoteReply { timeout: true, error: 0, channel: 0, timestamp: 0 })?;
|
|
self.session.kernel_state = KernelState::Running;
|
|
}
|
|
// ddma_finished() and nack() covers the other case
|
|
Ok(())
|
|
}
|
|
KernelState::DmaUploading { max_time } => {
|
|
if clock::get_ms() > *max_time {
|
|
unexpected!("DMAError: Timed out sending traces to remote");
|
|
}
|
|
Ok(())
|
|
}
|
|
_ => Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn subkernel_load_run_reply(&mut self, succeeded: bool, self_destination: u8) {
|
|
if self.session.kernel_state == KernelState::SubkernelAwaitLoad {
|
|
if let Err(e) = kern_send(&kern::SubkernelLoadRunReply { succeeded: succeeded }) {
|
|
self.stop();
|
|
self.runtime_exception(e);
|
|
self.last_finished = Some(SubkernelFinished {
|
|
source: self.session.source, id: self.current_id,
|
|
with_exception: true, exception_source: self_destination
|
|
})
|
|
} else {
|
|
self.session.kernel_state = KernelState::Running;
|
|
}
|
|
} else {
|
|
warn!("received unsolicited SubkernelLoadRunReply");
|
|
}
|
|
}
|
|
|
|
pub fn remote_subkernel_finished(&mut self, id: u32, with_exception: bool, exception_source: u8) {
|
|
if with_exception {
|
|
unsafe { kernel_cpu::stop() }
|
|
self.session.kernel_state = KernelState::Absent;
|
|
unsafe { self.cache.unborrow() }
|
|
self.last_finished = Some(SubkernelFinished {
|
|
source: self.session.source, id: self.current_id,
|
|
with_exception: true, exception_source: exception_source
|
|
})
|
|
} else {
|
|
self.session.subkernels_finished.push(id);
|
|
}
|
|
}
|
|
|
|
fn process_kern_message(&mut self, router: &mut Router,
|
|
routing_table: &RoutingTable,
|
|
rank: u8, destination: u8,
|
|
dma_manager: &mut DmaManager
|
|
) -> Result<Option<bool>, Error> {
|
|
// returns Ok(with_exception) on finish
|
|
// None if the kernel is still running
|
|
kern_recv(|request| {
|
|
match (request, &self.session.kernel_state) {
|
|
(&kern::LoadReply(_), KernelState::Loaded) |
|
|
(_, KernelState::DmaUploading { .. }) |
|
|
(_, KernelState::DmaAwait { .. }) |
|
|
(_, KernelState::MsgSending) |
|
|
(_, KernelState::SubkernelAwaitLoad) |
|
|
(_, KernelState::SubkernelAwaitFinish { .. }) => {
|
|
// We're standing by; ignore the message.
|
|
return Ok(None)
|
|
}
|
|
(_, KernelState::Running) => (),
|
|
_ => {
|
|
unexpected!("unexpected request {:?} from kernel CPU in {:?} state",
|
|
request, self.session.kernel_state)
|
|
},
|
|
}
|
|
|
|
if process_kern_hwreq(request, destination)? {
|
|
return Ok(None)
|
|
}
|
|
|
|
match request {
|
|
&kern::Log(args) => {
|
|
use core::fmt::Write;
|
|
self.session.log_buffer
|
|
.write_fmt(args)
|
|
.unwrap_or_else(|_| warn!("cannot append to session log buffer"));
|
|
self.session.flush_log_buffer();
|
|
kern_acknowledge()
|
|
}
|
|
|
|
&kern::LogSlice(arg) => {
|
|
self.session.log_buffer += arg;
|
|
self.session.flush_log_buffer();
|
|
kern_acknowledge()
|
|
}
|
|
|
|
&kern::RpcFlush => {
|
|
// we do not have to do anything about this request,
|
|
// it is sent by the kernel firmware regardless of RPC being used
|
|
kern_acknowledge()
|
|
}
|
|
|
|
&kern::CacheGetRequest { key } => {
|
|
let value = self.cache.get(key);
|
|
kern_send(&kern::CacheGetReply {
|
|
value: unsafe { mem::transmute(value) }
|
|
})
|
|
}
|
|
|
|
&kern::CachePutRequest { key, value } => {
|
|
let succeeded = self.cache.put(key, value).is_ok();
|
|
kern_send(&kern::CachePutReply { succeeded: succeeded })
|
|
}
|
|
|
|
&kern::RunFinished => {
|
|
unsafe { kernel_cpu::stop() }
|
|
self.session.kernel_state = KernelState::Absent;
|
|
unsafe { self.cache.unborrow() }
|
|
|
|
return Ok(Some(false))
|
|
}
|
|
&kern::RunException { exceptions, stack_pointers, backtrace } => {
|
|
unsafe { kernel_cpu::stop() }
|
|
self.session.kernel_state = KernelState::Absent;
|
|
unsafe { self.cache.unborrow() }
|
|
let exception = slice_kernel_exception(&exceptions, &stack_pointers, &backtrace)?;
|
|
self.session.last_exception = Some(exception);
|
|
return Ok(Some(true))
|
|
}
|
|
|
|
&kern::DmaRecordStart(name) => {
|
|
dma_manager.record_start(name);
|
|
kern_acknowledge()
|
|
}
|
|
&kern::DmaRecordAppend(data) => {
|
|
dma_manager.record_append(data);
|
|
kern_acknowledge()
|
|
}
|
|
&kern::DmaRecordStop { duration, enable_ddma: _ } => {
|
|
// ddma is always used on satellites
|
|
if let Ok(id) = dma_manager.record_stop(duration, destination) {
|
|
let remote_count = dma_manager.upload_traces(id, router, rank, destination, routing_table)?;
|
|
if remote_count > 0 {
|
|
let max_time = clock::get_ms() + 10_000 as u64;
|
|
self.session.kernel_state = KernelState::DmaUploading { max_time: max_time };
|
|
Ok(())
|
|
} else {
|
|
kern_acknowledge()
|
|
}
|
|
} else {
|
|
unexpected!("DMAError: found an unsupported call to RTIO devices on master")
|
|
}
|
|
}
|
|
&kern::DmaEraseRequest { name } => {
|
|
dma_manager.erase_name(name, router, rank, destination, routing_table);
|
|
kern_acknowledge()
|
|
}
|
|
&kern::DmaRetrieveRequest { name } => {
|
|
dma_manager.with_trace(destination, name, |trace, duration| {
|
|
kern_send(&kern::DmaRetrieveReply {
|
|
trace: trace,
|
|
duration: duration,
|
|
uses_ddma: true,
|
|
})
|
|
})
|
|
}
|
|
&kern::DmaStartRemoteRequest { id, timestamp } => {
|
|
let max_time = clock::get_ms() + 10_000 as u64;
|
|
self.session.kernel_state = KernelState::DmaAwait { max_time: max_time };
|
|
dma_manager.playback_remote(id as u32, timestamp as u64, router, rank, destination, routing_table)?;
|
|
dma_manager.playback(destination, id as u32, timestamp as u64)?;
|
|
Ok(())
|
|
}
|
|
|
|
&kern::SubkernelMsgSend { id, destination: msg_dest, count, tag, data } => {
|
|
let message_destination;
|
|
let message_id;
|
|
if let Some(dest) = msg_dest {
|
|
message_destination = dest;
|
|
message_id = id;
|
|
} else {
|
|
// return message, return to source
|
|
message_destination = self.session.source;
|
|
message_id = self.current_id;
|
|
}
|
|
self.session.messages.accept_outgoing(message_id, destination,
|
|
message_destination, count, tag, data,
|
|
routing_table, rank, router)?;
|
|
// acknowledge after the message is sent
|
|
self.session.kernel_state = KernelState::MsgSending;
|
|
Ok(())
|
|
}
|
|
|
|
&kern::SubkernelMsgRecvRequest { id, timeout, tags } => {
|
|
// negative timeout value means no timeout
|
|
let max_time = if timeout > 0 { clock::get_ms() as i64 + timeout } else { timeout };
|
|
// ID equal to -1 indicates wildcard for receiving arguments
|
|
let id = if id == -1 { self.current_id } else { id as u32 };
|
|
self.session.kernel_state = KernelState::MsgAwait {
|
|
id: id, max_time: max_time, tags: tags.to_vec() };
|
|
Ok(())
|
|
},
|
|
|
|
&kern::SubkernelLoadRunRequest { id, destination: sk_destination, run } => {
|
|
self.session.kernel_state = KernelState::SubkernelAwaitLoad;
|
|
router.route(drtioaux::Packet::SubkernelLoadRunRequest {
|
|
source: destination, destination: sk_destination, id: id, run: run
|
|
}, routing_table, rank, destination);
|
|
Ok(())
|
|
}
|
|
|
|
&kern::SubkernelAwaitFinishRequest{ id, timeout } => {
|
|
let max_time = if timeout > 0 { clock::get_ms() as i64 + timeout } else { timeout };
|
|
self.session.kernel_state = KernelState::SubkernelAwaitFinish { max_time: max_time, id: id };
|
|
Ok(())
|
|
}
|
|
|
|
request => unexpected!("unexpected request {:?} from kernel CPU", request)
|
|
}.and(Ok(None))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Drop for Manager {
|
|
fn drop(&mut self) {
|
|
cricon_select(RtioMaster::Drtio);
|
|
unsafe {
|
|
kernel_cpu::stop()
|
|
};
|
|
}
|
|
}
|
|
|
|
fn kern_recv<R, F>(f: F) -> Result<R, Error>
|
|
where F: FnOnce(&kern::Message) -> Result<R, Error> {
|
|
if mailbox::receive() == 0 {
|
|
return Err(Error::NoMessage);
|
|
};
|
|
if !kernel_cpu::validate(mailbox::receive()) {
|
|
return Err(Error::InvalidPointer(mailbox::receive()))
|
|
}
|
|
f(unsafe { &*(mailbox::receive() as *const kern::Message) })
|
|
}
|
|
|
|
fn kern_recv_w_timeout<R, F>(timeout: u64, f: F) -> Result<R, Error>
|
|
where F: FnOnce(&kern::Message) -> Result<R, Error> + Copy {
|
|
// sometimes kernel may be too slow to respond immediately
|
|
// (e.g. when receiving external messages)
|
|
// we cannot wait indefinitely to keep the satellite responsive
|
|
// so a timeout is used instead
|
|
let max_time = clock::get_ms() + timeout;
|
|
while clock::get_ms() < max_time {
|
|
match kern_recv(f) {
|
|
Err(Error::NoMessage) => continue,
|
|
anything_else => return anything_else
|
|
}
|
|
}
|
|
Err(Error::NoMessage)
|
|
}
|
|
|
|
fn kern_acknowledge() -> Result<(), Error> {
|
|
mailbox::acknowledge();
|
|
Ok(())
|
|
}
|
|
|
|
fn kern_send(request: &kern::Message) -> Result<(), Error> {
|
|
unsafe { mailbox::send(request as *const _ as usize) }
|
|
while !mailbox::acknowledged() {}
|
|
Ok(())
|
|
}
|
|
|
|
fn slice_kernel_exception(exceptions: &[Option<eh_artiq::Exception>],
|
|
stack_pointers: &[eh_artiq::StackPointerBacktrace],
|
|
backtrace: &[(usize, usize)]
|
|
) -> Result<Sliceable, Error> {
|
|
error!("exception in kernel");
|
|
for exception in exceptions {
|
|
error!("{:?}", exception.unwrap());
|
|
}
|
|
error!("stack pointers: {:?}", stack_pointers);
|
|
error!("backtrace: {:?}", backtrace);
|
|
// master will only pass the exception data back to the host:
|
|
let raw_exception: Vec<u8> = Vec::new();
|
|
let mut writer = Cursor::new(raw_exception);
|
|
match (HostKernelException {
|
|
exceptions: exceptions,
|
|
stack_pointers: stack_pointers,
|
|
backtrace: backtrace,
|
|
async_errors: 0
|
|
}).write_to(&mut writer) {
|
|
// save last exception data to be received by master
|
|
Ok(_) => Ok(Sliceable::new(0, writer.into_inner())),
|
|
Err(_) => Err(Error::SubkernelIoError)
|
|
}
|
|
}
|
|
|
|
fn pass_message_to_kernel(message: &Message, tags: &[u8]) -> Result<(), Error> {
|
|
let mut reader = Cursor::new(&message.data);
|
|
let mut current_tags = tags;
|
|
let mut i = 0;
|
|
loop {
|
|
let slot = kern_recv_w_timeout(100, |reply| {
|
|
match reply {
|
|
&kern::RpcRecvRequest(slot) => Ok(slot),
|
|
&kern::RunException { exceptions, stack_pointers, backtrace } => {
|
|
let exception = slice_kernel_exception(&exceptions, &stack_pointers, &backtrace)?;
|
|
Err(Error::KernelException(exception))
|
|
},
|
|
other => unexpected!(
|
|
"expected root value slot from kernel CPU, not {:?}", other)
|
|
}
|
|
})?;
|
|
let res = rpc::recv_return(&mut reader, current_tags, slot, &|size| -> Result<_, Error> {
|
|
if size == 0 {
|
|
return Ok(0 as *mut ())
|
|
}
|
|
kern_send(&kern::RpcRecvReply(Ok(size)))?;
|
|
Ok(kern_recv_w_timeout(100, |reply| {
|
|
match reply {
|
|
&kern::RpcRecvRequest(slot) => Ok(slot),
|
|
&kern::RunException {
|
|
exceptions,
|
|
stack_pointers,
|
|
backtrace
|
|
}=> {
|
|
let exception = slice_kernel_exception(&exceptions, &stack_pointers, &backtrace)?;
|
|
Err(Error::KernelException(exception))
|
|
},
|
|
other => unexpected!(
|
|
"expected nested value slot from kernel CPU, not {:?}", other)
|
|
}
|
|
})?)
|
|
});
|
|
match res {
|
|
Ok(new_tags) => {
|
|
kern_send(&kern::RpcRecvReply(Ok(0)))?;
|
|
i += 1;
|
|
if i < message.count {
|
|
// update the tag for next read
|
|
current_tags = new_tags;
|
|
} else {
|
|
// should be done by then
|
|
break;
|
|
}
|
|
},
|
|
Err(_) => unexpected!("expected valid subkernel message data")
|
|
};
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn process_kern_hwreq(request: &kern::Message, self_destination: u8) -> Result<bool, Error> {
|
|
match request {
|
|
&kern::RtioInitRequest => {
|
|
unsafe {
|
|
csr::drtiosat::reset_write(1);
|
|
clock::spin_us(100);
|
|
csr::drtiosat::reset_write(0);
|
|
}
|
|
kern_acknowledge()
|
|
}
|
|
|
|
&kern::RtioDestinationStatusRequest { destination } => {
|
|
// only local destination is considered "up"
|
|
// no access to other DRTIO destinations
|
|
kern_send(&kern::RtioDestinationStatusReply {
|
|
up: destination == self_destination })
|
|
}
|
|
|
|
&kern::I2cStartRequest { busno } => {
|
|
let succeeded = i2c::start(busno as u8).is_ok();
|
|
kern_send(&kern::I2cBasicReply { succeeded: succeeded })
|
|
}
|
|
&kern::I2cRestartRequest { busno } => {
|
|
let succeeded = i2c::restart(busno as u8).is_ok();
|
|
kern_send(&kern::I2cBasicReply { succeeded: succeeded })
|
|
}
|
|
&kern::I2cStopRequest { busno } => {
|
|
let succeeded = i2c::stop(busno as u8).is_ok();
|
|
kern_send(&kern::I2cBasicReply { succeeded: succeeded })
|
|
}
|
|
&kern::I2cWriteRequest { busno, data } => {
|
|
match i2c::write(busno as u8, data) {
|
|
Ok(ack) => kern_send(
|
|
&kern::I2cWriteReply { succeeded: true, ack: ack }),
|
|
Err(_) => kern_send(
|
|
&kern::I2cWriteReply { succeeded: false, ack: false })
|
|
}
|
|
}
|
|
&kern::I2cReadRequest { busno, ack } => {
|
|
match i2c::read(busno as u8, ack) {
|
|
Ok(data) => kern_send(
|
|
&kern::I2cReadReply { succeeded: true, data: data }),
|
|
Err(_) => kern_send(
|
|
&kern::I2cReadReply { succeeded: false, data: 0xff })
|
|
}
|
|
}
|
|
&kern::I2cSwitchSelectRequest { busno, address, mask } => {
|
|
let succeeded = i2c::switch_select(busno as u8, address, mask).is_ok();
|
|
kern_send(&kern::I2cBasicReply { succeeded: succeeded })
|
|
}
|
|
|
|
&kern::SpiSetConfigRequest { busno, flags, length, div, cs } => {
|
|
let succeeded = spi::set_config(busno as u8, flags, length, div, cs).is_ok();
|
|
kern_send(&kern::SpiBasicReply { succeeded: succeeded })
|
|
},
|
|
&kern::SpiWriteRequest { busno, data } => {
|
|
let succeeded = spi::write(busno as u8, data).is_ok();
|
|
kern_send(&kern::SpiBasicReply { succeeded: succeeded })
|
|
}
|
|
&kern::SpiReadRequest { busno } => {
|
|
match spi::read(busno as u8) {
|
|
Ok(data) => kern_send(
|
|
&kern::SpiReadReply { succeeded: true, data: data }),
|
|
Err(_) => kern_send(
|
|
&kern::SpiReadReply { succeeded: false, data: 0 })
|
|
}
|
|
}
|
|
|
|
_ => return Ok(false)
|
|
}.and(Ok(true))
|
|
} |