Implement recording of DMA traces on the core device.

This commit is contained in:
whitequark 2017-02-26 02:50:20 +00:00
parent 3a1f14c16c
commit 5d3b00cf12
11 changed files with 231 additions and 17 deletions

55
artiq/coredevice/dma.py Normal file
View File

@ -0,0 +1,55 @@
from artiq.language.core import syscall, kernel
from artiq.language.types import TStr, TNone
from numpy import int64
@syscall
def dma_record_start() -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_record_stop(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
class DMARecordContextManager:
def __init__(self):
self.name = ""
self.saved_now_mu = int64(0)
@kernel
def __enter__(self):
"""Starts recording a DMA trace. All RTIO operations are redirected to
a newly created DMA buffer after this call, and ``now`` is reset to zero."""
dma_record_start() # this may raise, so do it before altering now
self.saved_now_mu = now_mu()
at_mu(0)
@kernel
def __exit__(self, type, value, traceback):
"""Stops recording a DMA trace. All recorded RTIO operations are stored
in a newly created trace called ``self.name``, and ``now`` is restored
to the value it had before ``__enter__`` was called."""
dma_record_stop(self.name) # see above
at_mu(self.saved_now_mu)
class CoreDMA:
"""Core device Direct Memory Access (DMA) driver.
Gives access to the DMA functionality of the core device.
"""
kernel_invariants = {"core", "recorder"}
def __init__(self, dmgr, core_device="core"):
self.core = dmgr.get(core_device)
self.recorder = DMARecordContextManager()
@kernel
def record(self, name):
"""Returns a context manager that will record a DMA trace called ``name``."""
self.recorder.name = name
return self.recorder

View File

@ -120,6 +120,10 @@ class RTIOOverflow(Exception):
"""
artiq_builtin = True
class DMAError(Exception):
"""Raised when performing an invalid DMA operation."""
artiq_builtin = True
class DDSError(Exception):
"""Raised when attempting to start a DDS batch while already in a batch,
when too many commands are batched, and when DDS channel settings are

View File

@ -20,6 +20,11 @@
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"core_dds": {
"type": "local",
"module": "artiq.coredevice.dds",

View File

@ -105,6 +105,9 @@ static mut API: &'static [(&'static str, *const ())] = &[
api!(rtio_input_timestamp = ::rtio::input_timestamp),
api!(rtio_input_data = ::rtio::input_data),
api!(dma_record_start = ::dma_record_start),
api!(dma_record_stop = ::dma_record_stop),
api!(drtio_get_channel_state = ::rtio::drtio_dbg::get_channel_state),
api!(drtio_reset_channel_state = ::rtio::drtio_dbg::reset_channel_state),
api!(drtio_get_fifo_space = ::rtio::drtio_dbg::get_fifo_space),

View File

@ -36,11 +36,11 @@ fn recv<R, F: FnOnce(&Message) -> R>(f: F) -> R {
macro_rules! recv {
($p:pat => $e:expr) => {
recv(|request| {
recv(move |request| {
if let $p = request {
$e
} else {
send(&Log(format_args!("unexpected reply: {:?}", request)));
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
loop {}
}
})
@ -50,7 +50,7 @@ macro_rules! recv {
#[no_mangle]
#[lang = "panic_fmt"]
pub extern fn panic_fmt(args: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
send(&Log(format_args!("panic at {}:{}: {}", file, line, args)));
send(&Log(format_args!("panic at {}:{}: {}\n", file, line, args)));
send(&RunAborted);
loop {}
}
@ -90,6 +90,7 @@ mod api;
mod rtio;
static mut NOW: u64 = 0;
static mut LIBRARY: Option<Library<'static>> = None;
#[no_mangle]
pub extern fn send_to_core_log(text: CSlice<u8>) {
@ -236,6 +237,61 @@ extern fn i2c_read(busno: i32, ack: bool) -> i32 {
recv!(&I2cReadReply { data } => data) as i32
}
static mut DMA_RECORDING: bool = false;
extern fn dma_record_start() {
unsafe {
if DMA_RECORDING {
raise!("DMAError", "DMA is already recording")
}
let library = LIBRARY.as_ref().unwrap();
library.rebind(b"rtio_output",
dma_record_output as *const () as u32).unwrap();
library.rebind(b"rtio_output_wide",
dma_record_output_wide as *const () as u32).unwrap();
DMA_RECORDING = true;
send(&DmaRecordStart);
}
}
extern fn dma_record_stop(name: CSlice<u8>) {
unsafe {
if !DMA_RECORDING {
raise!("DMAError", "DMA is not recording")
}
let library = LIBRARY.as_ref().unwrap();
library.rebind(b"rtio_output",
rtio::output as *const () as u32).unwrap();
library.rebind(b"rtio_output_wide",
rtio::output_wide as *const () as u32).unwrap();
DMA_RECORDING = false;
send(&DmaRecordStop(str::from_utf8(name.as_ref()).unwrap()));
}
}
extern fn dma_record_output(timestamp: i64, channel: i32, address: i32, data: i32) {
send(&DmaRecordAppend {
timestamp: timestamp as u64,
channel: channel as u32,
address: address as u32,
data: &[data as u32]
})
}
extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, data: CSlice<i32>) {
assert!(data.len() <= 16); // enforce the hardware limit
send(&DmaRecordAppend {
timestamp: timestamp as u64,
channel: channel as u32,
address: address as u32,
data: unsafe { mem::transmute::<&[i32], &[u32]>(data.as_ref()) }
})
}
unsafe fn attribute_writeback(typeinfo: *const ()) {
struct Attr {
offset: usize,
@ -248,9 +304,6 @@ unsafe fn attribute_writeback(typeinfo: *const ()) {
objects: *const *const ()
}
// artiq_compile'd kernels don't include type information
if typeinfo.is_null() { return }
let mut tys = typeinfo as *const *const Type;
while !(*tys).is_null() {
let ty = *tys;
@ -299,14 +352,21 @@ pub unsafe fn main() {
let __bss_start = library.lookup(b"__bss_start").unwrap();
let _end = library.lookup(b"_end").unwrap();
let __modinit__ = library.lookup(b"__modinit__").unwrap();
let typeinfo = library.lookup(b"typeinfo");
LIBRARY = Some(library);
ptr::write_bytes(__bss_start as *mut u8, 0, (_end - __bss_start) as usize);
send(&NowInitRequest);
recv!(&NowInitReply(now) => NOW = now);
(mem::transmute::<u32, fn()>(library.lookup(b"__modinit__").unwrap()))();
(mem::transmute::<u32, fn()>(__modinit__))();
send(&NowSave(NOW));
attribute_writeback(library.lookup(b"typeinfo").unwrap_or(0) as *const ());
if let Some(typeinfo) = typeinfo {
attribute_writeback(typeinfo as *const ());
}
send(&RunFinished);

View File

@ -81,7 +81,6 @@ pub struct Library<'a> {
image_sz: usize,
strtab: &'a [u8],
symtab: &'a [Elf32_Sym],
rela: &'a [Elf32_Rela],
pltrel: &'a [Elf32_Rela],
hash_bucket: &'a [Elf32_Word],
hash_chain: &'a [Elf32_Word],
@ -132,8 +131,9 @@ impl<'a> Library<'a> {
Ok(unsafe { *ptr = value })
}
pub fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> {
for rela in self.rela.iter().chain(self.pltrel.iter()) {
// This is unsafe because it mutates global data (the PLT).
pub unsafe fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> {
for rela in self.pltrel.iter() {
match ELF32_R_TYPE(rela.r_info) {
R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT => {
let sym = self.symtab.get(ELF32_R_SYM(rela.r_info) as usize)
@ -315,7 +315,6 @@ impl<'a> Library<'a> {
image_sz: image.len(),
strtab: strtab,
symtab: symtab,
rela: rela,
pltrel: pltrel,
hash_bucket: &hash[..nbucket],
hash_chain: &hash[nbucket..nbucket + nchain],
@ -331,9 +330,8 @@ impl<'a> Library<'a> {
// we never write to the memory they refer to, so it's safe.
mem::drop(image);
for r in rela.iter().chain(pltrel.iter()) {
library.resolve_rela(r, resolve)?
}
for r in rela { library.resolve_rela(r, resolve)? }
for r in pltrel { library.resolve_rela(r, resolve)? }
Ok(library)
}

View File

@ -29,6 +29,15 @@ pub enum Message<'a> {
RtioInitRequest,
DmaRecordStart,
DmaRecordAppend {
timestamp: u64,
channel: u32,
address: u32,
data: &'a [u32]
},
DmaRecordStop(&'a str),
DrtioChannelStateRequest { channel: u32 },
DrtioChannelStateReply { fifo_space: u16, last_timestamp: u64 },
DrtioResetChannelStateRequest { channel: u32 },

View File

@ -41,6 +41,7 @@ mod rtio_mgt;
mod urc;
mod sched;
mod cache;
mod rtio_dma;
mod kernel;
mod session;

View File

@ -0,0 +1,58 @@
use std::vec::Vec;
use std::string::String;
use std::btree_map::BTreeMap;
use std::mem;
use proto::io;
#[derive(Debug)]
struct Entry {
data: Vec<u8>
}
#[derive(Debug)]
pub struct Manager {
entries: BTreeMap<String, Entry>,
recording: Vec<u8>
}
impl Manager {
pub fn new() -> Manager {
Manager {
entries: BTreeMap::new(),
recording: Vec::new()
}
}
pub fn record_start(&mut self) {
self.recording.clear();
}
pub fn record_append(&mut self, timestamp: u64, channel: u32,
address: u32, data: &[u32]) {
// See gateware/rtio/dma.py.
let length = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2 +
/*data*/data.len() * 4;
let writer = &mut self.recording;
io::write_u8(writer, length as u8).unwrap();
io::write_u8(writer, (channel >> 24) as u8).unwrap();
io::write_u8(writer, (channel >> 16) as u8).unwrap();
io::write_u8(writer, (channel >> 8) as u8).unwrap();
io::write_u64(writer, timestamp).unwrap();
io::write_u16(writer, address as u16).unwrap();
for &word in data {
io::write_u32(writer, word).unwrap();
}
}
pub fn record_stop(&mut self, name: &str) {
let mut recorded = Vec::new();
mem::swap(&mut self.recording, &mut recorded);
recorded.shrink_to_fit();
info!("recorded DMA data: {:?}", recorded);
self.entries.insert(String::from(name), Entry {
data: recorded
});
}
}

View File

@ -6,6 +6,7 @@ use std::error::Error;
use {config, rtio_mgt, mailbox, rpc_queue, kernel};
use logger_artiq::BufferLogger;
use cache::Cache;
use rtio_dma::Manager as DmaManager;
use urc::Urc;
use sched::{ThreadHandle, Io};
use sched::{TcpListener, TcpStream};
@ -34,6 +35,7 @@ fn io_error(msg: &str) -> io::Error {
struct Congress {
now: u64,
cache: Cache,
dma_manager: DmaManager,
finished_cleanly: Cell<bool>
}
@ -42,6 +44,7 @@ impl Congress {
Congress {
now: 0,
cache: Cache::new(),
dma_manager: DmaManager::new(),
finished_cleanly: Cell::new(true)
}
}
@ -347,8 +350,7 @@ fn process_host_message(io: &Io,
}
}
fn process_kern_message(io: &Io,
mut stream: Option<&mut TcpStream>,
fn process_kern_message(io: &Io, mut stream: Option<&mut TcpStream>,
session: &mut Session) -> io::Result<bool> {
kern_recv_notrace(io, |request| {
match (request, session.kernel_state) {
@ -394,6 +396,19 @@ fn process_kern_message(io: &Io,
kern_acknowledge()
}
&kern::DmaRecordStart => {
session.congress.dma_manager.record_start();
kern_acknowledge()
}
&kern::DmaRecordAppend { timestamp, channel, address, data } => {
session.congress.dma_manager.record_append(timestamp, channel, address, data);
kern_acknowledge()
}
&kern::DmaRecordStop(name) => {
session.congress.dma_manager.record_stop(name);
kern_acknowledge()
}
&kern::DrtioChannelStateRequest { channel } => {
let (fifo_space, last_timestamp) = rtio_mgt::drtio_dbg::get_channel_state(channel);
kern_send(io, &kern::DrtioChannelStateReply { fifo_space: fifo_space,

View File

@ -21,6 +21,12 @@ These drivers are for the core device and the peripherals closely integrated int
.. automodule:: artiq.coredevice.dds
:members:
:mod:`artiq.coredevice.dma` module
----------------------------------
.. automodule:: artiq.coredevice.dma
:members:
:mod:`artiq.coredevice.spi` module
----------------------------------