From 5d3b00cf12610b438cbd7687d6c4ce7c92318353 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 26 Feb 2017 02:50:20 +0000 Subject: [PATCH] Implement recording of DMA traces on the core device. --- artiq/coredevice/dma.py | 55 ++++++++++++++++++ artiq/coredevice/exceptions.py | 4 ++ artiq/examples/master/device_db.pyon | 5 ++ artiq/firmware/ksupport/api.rs | 3 + artiq/firmware/ksupport/lib.rs | 76 ++++++++++++++++++++++--- artiq/firmware/libdyld/lib.rs | 12 ++-- artiq/firmware/libproto/kernel_proto.rs | 9 +++ artiq/firmware/runtime/lib.rs | 1 + artiq/firmware/runtime/rtio_dma.rs | 58 +++++++++++++++++++ artiq/firmware/runtime/session.rs | 19 ++++++- doc/manual/core_drivers_reference.rst | 6 ++ 11 files changed, 231 insertions(+), 17 deletions(-) create mode 100644 artiq/coredevice/dma.py create mode 100644 artiq/firmware/runtime/rtio_dma.rs diff --git a/artiq/coredevice/dma.py b/artiq/coredevice/dma.py new file mode 100644 index 000000000..26357cd55 --- /dev/null +++ b/artiq/coredevice/dma.py @@ -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 diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index 58c07fec3..bc1e10998 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -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 diff --git a/artiq/examples/master/device_db.pyon b/artiq/examples/master/device_db.pyon index a8a8f63d8..ec9bc6083 100644 --- a/artiq/examples/master/device_db.pyon +++ b/artiq/examples/master/device_db.pyon @@ -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", diff --git a/artiq/firmware/ksupport/api.rs b/artiq/firmware/ksupport/api.rs index 9a86d6ef5..35fffcb28 100644 --- a/artiq/firmware/ksupport/api.rs +++ b/artiq/firmware/ksupport/api.rs @@ -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), diff --git a/artiq/firmware/ksupport/lib.rs b/artiq/firmware/ksupport/lib.rs index 09c3c6fe2..3a0e7e881 100644 --- a/artiq/firmware/ksupport/lib.rs +++ b/artiq/firmware/ksupport/lib.rs @@ -36,11 +36,11 @@ fn recv 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> = None; #[no_mangle] pub extern fn send_to_core_log(text: CSlice) { @@ -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) { + 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) { + 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::(library.lookup(b"__modinit__").unwrap()))(); + (mem::transmute::(__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); diff --git a/artiq/firmware/libdyld/lib.rs b/artiq/firmware/libdyld/lib.rs index a331bf423..8fe1c6c25 100644 --- a/artiq/firmware/libdyld/lib.rs +++ b/artiq/firmware/libdyld/lib.rs @@ -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) } diff --git a/artiq/firmware/libproto/kernel_proto.rs b/artiq/firmware/libproto/kernel_proto.rs index 5e7238360..a1b75a8f3 100644 --- a/artiq/firmware/libproto/kernel_proto.rs +++ b/artiq/firmware/libproto/kernel_proto.rs @@ -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 }, diff --git a/artiq/firmware/runtime/lib.rs b/artiq/firmware/runtime/lib.rs index 230545957..475c24546 100644 --- a/artiq/firmware/runtime/lib.rs +++ b/artiq/firmware/runtime/lib.rs @@ -41,6 +41,7 @@ mod rtio_mgt; mod urc; mod sched; mod cache; +mod rtio_dma; mod kernel; mod session; diff --git a/artiq/firmware/runtime/rtio_dma.rs b/artiq/firmware/runtime/rtio_dma.rs new file mode 100644 index 000000000..a0ce08350 --- /dev/null +++ b/artiq/firmware/runtime/rtio_dma.rs @@ -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 +} + +#[derive(Debug)] +pub struct Manager { + entries: BTreeMap, + recording: Vec +} + +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 + }); + } +} diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index 13652bbf7..94babe586 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -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 } @@ -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 { 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, diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index adec438e1..6def86ae8 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -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 ----------------------------------