diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 3d9dcd923..373910864 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -22,6 +22,8 @@ ARTIQ-9 (Unreleased) * Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config. * New support for the EBAZ4205 Zynq-SoC control card. * New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module. +* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``. +* ``artiq_coremgmt`` now supports configuring satellites. * artiq.coredevice.fmcdio_vhdci_eem has been removed. ARTIQ-8 diff --git a/artiq/coredevice/comm_mgmt.py b/artiq/coredevice/comm_mgmt.py index 870e2759d..c77d0b9ec 100644 --- a/artiq/coredevice/comm_mgmt.py +++ b/artiq/coredevice/comm_mgmt.py @@ -1,5 +1,7 @@ from enum import Enum +import binascii import logging +import io import struct from sipyco.keepalive import create_connection @@ -23,6 +25,8 @@ class Request(Enum): DebugAllocator = 8 + Flash = 9 + class Reply(Enum): Success = 1 @@ -46,15 +50,17 @@ class LogLevel(Enum): class CommMgmt: - def __init__(self, host, port=1380): + def __init__(self, host, port=1380, drtio_dest=0): self.host = host self.port = port + self.drtio_dest = drtio_dest def open(self): if hasattr(self, "socket"): return self.socket = create_connection(self.host, self.port) self.socket.sendall(b"ARTIQ management\n") + self._write_int8(self.drtio_dest) endian = self._read(1) if endian == b"e": self.endian = "<" @@ -194,3 +200,22 @@ class CommMgmt: def debug_allocator(self): self._write_header(Request.DebugAllocator) + + def flash(self, bin_paths): + self._write_header(Request.Flash) + + with io.BytesIO() as image_buf: + for filename in bin_paths: + with open(filename, "rb") as fi: + bin_ = fi.read() + if (len(bin_paths) > 1): + image_buf.write( + struct.pack(self.endian + "I", len(bin_))) + image_buf.write(bin_) + + crc = binascii.crc32(image_buf.getvalue()) + image_buf.write(struct.pack(self.endian + "I", crc)) + + self._write_bytes(image_buf.getvalue()) + + self._read_expect(Reply.RebootImminent) diff --git a/artiq/firmware/Cargo.lock b/artiq/firmware/Cargo.lock index 527a662ee..98d7d730f 100644 --- a/artiq/firmware/Cargo.lock +++ b/artiq/firmware/Cargo.lock @@ -513,6 +513,7 @@ dependencies = [ "board_misoc", "build_misoc", "byteorder", + "crc", "cslice", "dyld", "eh", @@ -553,10 +554,13 @@ dependencies = [ "board_artiq", "board_misoc", "build_misoc", + "byteorder", + "crc", "cslice", "eh", "io", "log", + "logger_artiq", "proto_artiq", "riscv", ] diff --git a/artiq/firmware/libboard_misoc/spiflash.rs b/artiq/firmware/libboard_misoc/spiflash.rs index 598d96633..e686d17e6 100644 --- a/artiq/firmware/libboard_misoc/spiflash.rs +++ b/artiq/firmware/libboard_misoc/spiflash.rs @@ -114,7 +114,17 @@ pub unsafe fn write(mut addr: usize, mut data: &[u8]) { } } -#[cfg(any(soc_platform = "kasli", soc_platform = "kc705"))] +pub unsafe fn flash_binary(origin: usize, payload: &[u8]) { + assert!((origin & (SECTOR_SIZE - 1)) == 0); + let mut offset = 0; + while offset < payload.len() { + erase_sector(origin + offset); + offset += SECTOR_SIZE; + } + write(origin, payload); +} + +#[cfg(any(soc_platform = "kasli", soc_platform = "kc705", soc_platform = "efc"))] pub unsafe fn reload () -> ! { csr::icap::iprog_write(1); loop {} diff --git a/artiq/firmware/libproto_artiq/drtioaux_proto.rs b/artiq/firmware/libproto_artiq/drtioaux_proto.rs index a1b17e888..4891bedf1 100644 --- a/artiq/firmware/libproto_artiq/drtioaux_proto.rs +++ b/artiq/firmware/libproto_artiq/drtioaux_proto.rs @@ -127,6 +127,25 @@ pub enum Packet { SubkernelException { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] }, SubkernelMessage { source: u8, destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] }, SubkernelMessageAck { destination: u8 }, + + CoreMgmtGetLogRequest { destination: u8, clear: bool }, + CoreMgmtClearLogRequest { destination: u8 }, + CoreMgmtSetLogLevelRequest { destination: u8, log_level: u8 }, + CoreMgmtSetUartLogLevelRequest { destination: u8, log_level: u8 }, + CoreMgmtConfigReadRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] }, + CoreMgmtConfigReadContinue { destination: u8 }, + CoreMgmtConfigWriteRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] }, + CoreMgmtConfigRemoveRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] }, + CoreMgmtConfigEraseRequest { destination: u8 }, + CoreMgmtRebootRequest { destination: u8 }, + CoreMgmtAllocatorDebugRequest { destination: u8 }, + CoreMgmtFlashRequest { destination: u8, payload_length: u32 }, + CoreMgmtFlashAddDataRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] }, + CoreMgmtDropLinkAck { destination: u8 }, + CoreMgmtDropLink, + CoreMgmtGetLogReply { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] }, + CoreMgmtConfigReadReply { last: bool, length: u16, value: [u8; SAT_PAYLOAD_MAX_SIZE] }, + CoreMgmtReply { succeeded: bool }, } impl Packet { @@ -405,6 +424,115 @@ impl Packet { destination: reader.read_u8()? }, + 0xd0 => Packet::CoreMgmtGetLogRequest { + destination: reader.read_u8()?, + clear: reader.read_bool()?, + }, + 0xd1 => Packet::CoreMgmtClearLogRequest { + destination: reader.read_u8()?, + }, + 0xd2 => Packet::CoreMgmtSetLogLevelRequest { + destination: reader.read_u8()?, + log_level: reader.read_u8()?, + }, + 0xd3 => Packet::CoreMgmtSetUartLogLevelRequest { + destination: reader.read_u8()?, + log_level: reader.read_u8()?, + }, + 0xd4 => { + let destination = reader.read_u8()?; + let length = reader.read_u16()?; + let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut key[0..length as usize])?; + Packet::CoreMgmtConfigReadRequest { + destination: destination, + length: length, + key: key, + } + }, + 0xd5 => Packet::CoreMgmtConfigReadContinue { + destination: reader.read_u8()?, + }, + 0xd6 => { + let destination = reader.read_u8()?; + let last = reader.read_bool()?; + let length = reader.read_u16()?; + let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut data[0..length as usize])?; + Packet::CoreMgmtConfigWriteRequest { + destination: destination, + last: last, + length: length, + data: data, + } + }, + 0xd7 => { + let destination = reader.read_u8()?; + let length = reader.read_u16()?; + let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut key[0..length as usize])?; + Packet::CoreMgmtConfigRemoveRequest { + destination: destination, + length: length, + key: key, + } + }, + 0xd8 => Packet::CoreMgmtConfigEraseRequest { + destination: reader.read_u8()?, + }, + 0xd9 => Packet::CoreMgmtRebootRequest { + destination: reader.read_u8()?, + }, + 0xda => Packet::CoreMgmtAllocatorDebugRequest { + destination: reader.read_u8()?, + }, + 0xdb => Packet::CoreMgmtFlashRequest { + destination: reader.read_u8()?, + payload_length: reader.read_u32()?, + }, + 0xdc => { + let destination = reader.read_u8()?; + let last = reader.read_bool()?; + let length = reader.read_u16()?; + let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut data[0..length as usize])?; + Packet::CoreMgmtFlashAddDataRequest { + destination: destination, + last: last, + length: length, + data: data, + } + }, + 0xdd => Packet::CoreMgmtDropLinkAck { + destination: reader.read_u8()?, + }, + 0xde => Packet::CoreMgmtDropLink, + 0xdf => { + let last = reader.read_bool()?; + let length = reader.read_u16()?; + let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut data[0..length as usize])?; + Packet::CoreMgmtGetLogReply { + last: last, + length: length, + data: data, + } + }, + 0xe0 => { + let last = reader.read_bool()?; + let length = reader.read_u16()?; + let mut value: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE]; + reader.read_exact(&mut value[0..length as usize])?; + Packet::CoreMgmtConfigReadReply { + last: last, + length: length, + value: value, + } + }, + 0xe1 => Packet::CoreMgmtReply { + succeeded: reader.read_bool()?, + }, + ty => return Err(Error::UnknownPacket(ty)) }) } @@ -693,6 +821,108 @@ impl Packet { writer.write_u8(0xcc)?; writer.write_u8(destination)?; }, + + Packet::CoreMgmtGetLogRequest { destination, clear } => { + writer.write_u8(0xd0)?; + writer.write_u8(destination)?; + writer.write_bool(clear)?; + }, + Packet::CoreMgmtClearLogRequest { destination } => { + writer.write_u8(0xd1)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtSetLogLevelRequest { destination, log_level } => { + writer.write_u8(0xd2)?; + writer.write_u8(destination)?; + writer.write_u8(log_level)?; + }, + Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level } => { + writer.write_u8(0xd3)?; + writer.write_u8(destination)?; + writer.write_u8(log_level)?; + }, + Packet::CoreMgmtConfigReadRequest { + destination, + length, + key, + } => { + writer.write_u8(0xd4)?; + writer.write_u8(destination)?; + writer.write_u16(length)?; + writer.write_all(&key[0..length as usize])?; + }, + Packet::CoreMgmtConfigReadContinue { destination } => { + writer.write_u8(0xd5)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtConfigWriteRequest { + destination, + last, + length, + data, + } => { + writer.write_u8(0xd6)?; + writer.write_u8(destination)?; + writer.write_bool(last)?; + writer.write_u16(length)?; + writer.write_all(&data[0..length as usize])?; + }, + Packet::CoreMgmtConfigRemoveRequest { + destination, + length, + key, + } => { + writer.write_u8(0xd7)?; + writer.write_u8(destination)?; + writer.write_u16(length)?; + writer.write_all(&key[0..length as usize])?; + }, + Packet::CoreMgmtConfigEraseRequest { destination } => { + writer.write_u8(0xd8)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtRebootRequest { destination } => { + writer.write_u8(0xd9)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtAllocatorDebugRequest { destination } => { + writer.write_u8(0xda)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtFlashRequest { destination, payload_length } => { + writer.write_u8(0xdb)?; + writer.write_u8(destination)?; + writer.write_u32(payload_length)?; + }, + Packet::CoreMgmtFlashAddDataRequest { destination, last, length, data } => { + writer.write_u8(0xdc)?; + writer.write_u8(destination)?; + writer.write_bool(last)?; + writer.write_u16(length)?; + writer.write_all(&data[..length as usize])?; + }, + Packet::CoreMgmtDropLinkAck { destination } => { + writer.write_u8(0xdd)?; + writer.write_u8(destination)?; + }, + Packet::CoreMgmtDropLink => + writer.write_u8(0xde)?, + Packet::CoreMgmtGetLogReply { last, length, data } => { + writer.write_u8(0xdf)?; + writer.write_bool(last)?; + writer.write_u16(length)?; + writer.write_all(&data[0..length as usize])?; + }, + Packet::CoreMgmtConfigReadReply { last, length, value } => { + writer.write_u8(0xe0)?; + writer.write_bool(last)?; + writer.write_u16(length)?; + writer.write_all(&value[0..length as usize])?; + }, + Packet::CoreMgmtReply { succeeded } => { + writer.write_u8(0xe1)?; + writer.write_bool(succeeded)?; + }, } Ok(()) } @@ -726,7 +956,7 @@ impl Packet { Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } | Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } | Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } | - Packet::SubkernelFinished { .. } => false, + Packet::SubkernelFinished { .. } | Packet::CoreMgmtDropLinkAck { .. } => false, _ => true } } diff --git a/artiq/firmware/libproto_artiq/mgmt_proto.rs b/artiq/firmware/libproto_artiq/mgmt_proto.rs index 911799ef4..bfc8cc004 100644 --- a/artiq/firmware/libproto_artiq/mgmt_proto.rs +++ b/artiq/firmware/libproto_artiq/mgmt_proto.rs @@ -16,7 +16,9 @@ pub enum Error { #[fail(display = "invalid UTF-8: {}", _0)] Utf8(Utf8Error), #[fail(display = "{}", _0)] - Io(#[cause] IoError) + Io(#[cause] IoError), + #[fail(display = "drtio error")] + DrtioError, } impl From> for Error { @@ -65,6 +67,8 @@ pub enum Request { Reboot, + Flash { image: Vec }, + DebugAllocator, } @@ -123,6 +127,10 @@ impl Request { 8 => Request::DebugAllocator, + 9 => Request::Flash { + image: reader.read_bytes()?, + }, + ty => return Err(Error::UnknownPacket(ty)) }) } diff --git a/artiq/firmware/runtime/Cargo.toml b/artiq/firmware/runtime/Cargo.toml index 0d132d5a9..f06b93e49 100644 --- a/artiq/firmware/runtime/Cargo.toml +++ b/artiq/firmware/runtime/Cargo.toml @@ -16,6 +16,7 @@ build_misoc = { path = "../libbuild_misoc" } failure = { version = "0.1", default-features = false } failure_derive = { version = "0.1", default-features = false } byteorder = { version = "1.0", default-features = false } +crc = { version = "1.7", default-features = false } cslice = { version = "0.3" } log = { version = "=0.4.14", default-features = false } managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] } diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index dd52aabaf..a66b48fcf 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -1,6 +1,7 @@ #![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by, never_type)] #![no_std] +extern crate crc; extern crate dyld; extern crate eh; #[macro_use] @@ -209,7 +210,11 @@ fn startup() { rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex); { let restart_idle = restart_idle.clone(); - io.spawn(4096, move |io| { mgmt::thread(io, &restart_idle) }); + let aux_mutex = aux_mutex.clone(); + let ddma_mutex = ddma_mutex.clone(); + let subkernel_mutex = subkernel_mutex.clone(); + let drtio_routing_table = drtio_routing_table.clone(); + io.spawn(4096, move |io| { mgmt::thread(io, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table) }); } { let aux_mutex = aux_mutex.clone(); diff --git a/artiq/firmware/runtime/mgmt.rs b/artiq/firmware/runtime/mgmt.rs index 5b6fc6d01..d0c6d8d95 100644 --- a/artiq/firmware/runtime/mgmt.rs +++ b/artiq/firmware/runtime/mgmt.rs @@ -1,11 +1,9 @@ -use log::{self, LevelFilter}; -use core::cell::Cell; +use core::cell::{Cell, RefCell}; -use io::{Write, ProtoWrite, Error as IoError}; -use board_misoc::{config, spiflash}; -use logger_artiq::BufferLogger; +use board_artiq::drtio_routing::RoutingTable; +use io::{ProtoRead, Write, Error as IoError}; use mgmt_proto::*; -use sched::{Io, TcpListener, TcpStream, Error as SchedError}; +use sched::{Io, Mutex, TcpListener, TcpStream, Error as SchedError}; use urc::Urc; impl From for Error { @@ -14,138 +12,660 @@ impl From for Error { } } -fn worker(io: &Io, stream: &mut TcpStream, restart_idle: &Urc>) -> Result<(), Error> { +mod local_coremgmt { + use alloc::{string::String, vec::Vec}; + use byteorder::{ByteOrder, NativeEndian}; + use crc::crc32; + use log::LevelFilter; + + use board_misoc::{config, mem, spiflash}; + use io::ProtoWrite; + use logger_artiq::BufferLogger; + + use super::*; + + + pub fn get_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error> { + BufferLogger::with(|logger| { + let mut buffer = io.until_ok(|| logger.buffer())?; + Reply::LogContent(buffer.extract()).write_to(stream) + })?; + Ok(()) + } + + pub fn clear_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error> { + BufferLogger::with(|logger| -> Result<(), IoError> { + let mut buffer = io.until_ok(|| logger.buffer())?; + Ok(buffer.clear()) + })?; + + Reply::Success.write_to(stream)?; + Ok(()) + } + + pub fn pull_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error> { + BufferLogger::with(|logger| -> Result<(), IoError> { + loop { + // Do this *before* acquiring the buffer, since that sets the log level + // to OFF. + let log_level = log::max_level(); + + let mut buffer = io.until_ok(|| logger.buffer())?; + if buffer.is_empty() { continue } + + stream.write_string(buffer.extract())?; + + if log_level == LevelFilter::Trace { + // Hold exclusive access over the logger until we get positive + // acknowledgement; otherwise we get an infinite loop of network + // trace messages being transmitted and causing more network + // trace messages to be emitted. + // + // Any messages unrelated to this management socket that arrive + // while it is flushed are lost, but such is life. + stream.flush()?; + } + + // Clear the log *after* flushing the network buffers, or we're just + // going to resend all the trace messages on the next iteration. + buffer.clear(); + } + })?; + Ok(()) + } + + pub fn set_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error> { + info!("changing log level to {}", level); + log::set_max_level(level); + Reply::Success.write_to(stream)?; + Ok(()) + } + + pub fn set_uart_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error> { + info!("changing UART log level to {}", level); + BufferLogger::with(|logger| + logger.set_uart_log_level(level)); + Reply::Success.write_to(stream)?; + Ok(()) + } + + pub fn config_read(_io: &Io, stream: &mut TcpStream, key: &String) -> Result<(), Error>{ + config::read(key, |result| { + match result { + Ok(value) => Reply::ConfigData(&value).write_to(stream), + Err(_) => Reply::Error.write_to(stream) + } + })?; + Ok(()) + } + + pub fn config_write(io: &Io, stream: &mut TcpStream, key: &String, value: &Vec, restart_idle: &Urc>) -> Result<(), Error> { + match config::write(key, value) { + Ok(_) => { + if key == "idle_kernel" { + io.until(|| !restart_idle.get())?; + restart_idle.set(true); + } + Reply::Success.write_to(stream) + }, + Err(_) => Reply::Error.write_to(stream) + }?; + Ok(()) + } + + pub fn config_remove(io: &Io, stream: &mut TcpStream, key: &String, restart_idle: &Urc>) -> Result<(), Error> { + match config::remove(key) { + Ok(()) => { + if key == "idle_kernel" { + io.until(|| !restart_idle.get())?; + restart_idle.set(true); + } + Reply::Success.write_to(stream) + }, + Err(_) => Reply::Error.write_to(stream) + }?; + Ok(()) + } + + pub fn config_erase(io: &Io, stream: &mut TcpStream, restart_idle: &Urc>) -> Result<(), Error> { + match config::erase() { + Ok(()) => { + io.until(|| !restart_idle.get())?; + restart_idle.set(true); + Reply::Success.write_to(stream) + }, + Err(_) => Reply::Error.write_to(stream) + }?; + Ok(()) + } + + pub fn reboot(_io: &Io, stream: &mut TcpStream) -> Result<(), Error> { + Reply::RebootImminent.write_to(stream)?; + stream.close()?; + stream.flush()?; + + warn!("restarting"); + unsafe { spiflash::reload(); } + } + + pub fn debug_allocator(_io: &Io, _stream: &mut TcpStream) -> Result<(), Error> { + unsafe { println!("{}", ::ALLOC) } + Ok(()) + } + + pub fn flash(_io: &Io, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error> { + let (expected_crc, mut image) = { + let (image, crc_slice) = image.split_at(image.len() - 4); + (NativeEndian::read_u32(crc_slice), image) + }; + + let actual_crc = crc32::checksum_ieee(image); + + if actual_crc == expected_crc { + let bin_origins = [ + ("gateware" , 0 ), + ("bootloader", mem::ROM_BASE ), + ("firmware" , mem::FLASH_BOOT_ADDRESS), + ]; + + for (name, origin) in bin_origins { + info!("Flashing {} binary...", name); + let size = NativeEndian::read_u32(&image[..4]) as usize; + image = &image[4..]; + + let (bin, remaining) = image.split_at(size); + image = remaining; + + unsafe { spiflash::flash_binary(origin, bin) }; + } + + reboot(_io, stream)?; + } else { + error!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc); + Reply::Error.write_to(stream)?; + } + Ok(()) + } +} + +#[cfg(has_drtio)] +mod remote_coremgmt { + use alloc::{string::String, vec::Vec}; + use log::LevelFilter; + + use board_artiq::{drtioaux, drtioaux::Packet}; + use io::ProtoWrite; + use rtio_mgt::drtio; + use proto_artiq::drtioaux_proto::MASTER_PAYLOAD_MAX_SIZE; + + use super::*; + + impl From for Error { + fn from(_value: drtio::Error) -> Error { + Error::DrtioError + } + } + + pub fn get_log(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream) -> Result<(), Error> { + let mut buffer = String::new(); + loop { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtGetLogRequest { destination, clear: false } + ); + + match reply { + Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => { + buffer.push_str( + core::str::from_utf8(&data[..length as usize]).map_err(|_| Error::DrtioError)?); + if last { + Reply::LogContent(&buffer).write_to(stream)?; + return Ok(()); + } + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + return Err(drtio::Error::UnexpectedReply.into()); + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + return Err(e.into()); + } + } + } + } + + pub fn clear_log(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtClearLogRequest { destination } + ); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::Success.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn pull_log(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream) -> Result<(), Error> { + let mut buffer = Vec::new(); + loop { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtGetLogRequest { destination, clear: true } + ); + + match reply { + Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => { + buffer.extend(&data[..length as usize]); + + if last { + stream.write_bytes(&buffer)?; + buffer.clear(); + } + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + return Err(drtio::Error::UnexpectedReply.into()); + } + Err(e) => { + error!("aux packet error ({})", e); + return Err(e.into()); + } + } + } + } + + pub fn set_log_filter(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtSetLogLevelRequest { destination, log_level: level as u8 } + ); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::Success.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn set_uart_log_filter(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level: level as u8 } + ); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::Success.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn config_read(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, key: &String) -> Result<(), Error> { + let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + let len = key.len(); + config_key[..len].clone_from_slice(key.as_bytes()); + + let mut reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtConfigReadRequest { + destination: destination, + length: len as u16, + key: config_key, + } + ); + + let mut buffer = Vec::::new(); + loop { + match reply { + Ok(Packet::CoreMgmtConfigReadReply { length, last, value }) => { + buffer.extend(&value[..length as usize]); + + if last { + Reply::ConfigData(&buffer).write_to(stream)?; + return Ok(()); + } + + reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtConfigReadContinue { + destination: destination, + } + ); + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + return Err(drtio::Error::UnexpectedReply.into()); + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + return Err(e.into()); + } + } + } + } + + pub fn config_write(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, key: &String, value: &Vec, + _restart_idle: &Urc>) -> Result<(), Error> { + let mut message = Vec::with_capacity(key.len() + value.len() + 4 * 2); + message.write_string(key).unwrap(); + message.write_bytes(value).unwrap(); + + match drtio::partition_data(&message, |slice, status, len: usize| { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtConfigWriteRequest { + destination: destination, length: len as u16, last: status.is_last(), data: *slice}); + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()), + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Err(drtio::Error::UnexpectedReply) + } + Err(e) => { + error!("aux packet error ({})", e); + Err(e) + } + } + }) { + Ok(()) => { + Reply::Success.write_to(stream)?; + Ok(()) + }, + Err(e) => { + Reply::Error.write_to(stream)?; + Err(e.into()) + }, + } + } + + pub fn config_remove(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, key: &String, + _restart_idle: &Urc>) -> Result<(), Error> { + let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; + let len = key.len(); + config_key[..len].clone_from_slice(key.as_bytes()); + + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtConfigRemoveRequest { + destination: destination, + length: key.len() as u16, + key: config_key, + }); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::Success.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn config_erase(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, _restart_idle: &Urc>) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtConfigEraseRequest { + destination: destination, + }); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::Success.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn reboot(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtRebootRequest { + destination: destination, + }); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => { + Reply::RebootImminent.write_to(stream)?; + Ok(()) + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e.into()) + } + } + } + + pub fn debug_allocator(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, _stream: &mut TcpStream) -> Result<(), Error> { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtAllocatorDebugRequest { + destination: destination, + }); + + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()), + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Err(drtio::Error::UnexpectedReply.into()) + } + Err(e) => { + error!("aux packet error ({})", e); + Err(e.into()) + } + } + } + + pub fn flash(io: &Io, aux_mutex: &Mutex, + ddma_mutex: &Mutex, subkernel_mutex: &Mutex, + routing_table: &RoutingTable, linkno: u8, + destination: u8, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error> { + + let alloc_reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtFlashRequest { + destination: destination, + payload_length: image.len() as u32, + }); + + match alloc_reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()), + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Reply::Error.write_to(stream)?; + Err(drtio::Error::UnexpectedReply) + } + Err(e) => { + error!("aux packet error ({})", e); + Reply::Error.write_to(stream)?; + Err(e) + } + }?; + + match drtio::partition_data(&image, |slice, status, len: usize| { + let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, + &Packet::CoreMgmtFlashAddDataRequest { + destination: destination, length: len as u16, last: status.is_last(), data: *slice}); + match reply { + Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()), + Ok(Packet::CoreMgmtDropLink) => { + if status.is_last() { + drtioaux::send( + linkno, &Packet::CoreMgmtDropLinkAck { destination: destination } + ).map_err(|_| drtio::Error::AuxError) + } else { + error!("received unexpected drop link packet"); + Err(drtio::Error::UnexpectedReply) + } + } + Ok(packet) => { + error!("received unexpected aux packet: {:?}", packet); + Err(drtio::Error::UnexpectedReply) + } + Err(e) => { + error!("aux packet error ({})", e); + Err(e) + } + } + }) { + Ok(()) => { + Reply::RebootImminent.write_to(stream)?; + Ok(()) + }, + Err(e) => { + Reply::Error.write_to(stream)?; + Err(e.into()) + }, + } + } +} + +#[cfg(has_drtio)] +macro_rules! process { + ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $destination: ident, $func:ident $(, $param:expr)*) => {{ + let hop = $routing_table.0[$destination as usize][0]; + if hop == 0 { + local_coremgmt::$func($io, $tcp_stream, $($param, )*) + } else { + let linkno = hop - 1; + remote_coremgmt::$func($io, $aux_mutex, $ddma_mutex, $subkernel_mutex, $routing_table, linkno, $destination, $tcp_stream, $($param, )*) + } + }} +} + +#[cfg(not(has_drtio))] +macro_rules! process { + ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $_destination: ident, $func:ident $(, $param:expr)*) => {{ + local_coremgmt::$func($io, $tcp_stream, $($param, )*) + }} +} + +fn worker(io: &Io, stream: &mut TcpStream, restart_idle: &Urc>, + _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _subkernel_mutex: &Mutex, + _routing_table: &RoutingTable) -> Result<(), Error> { read_magic(stream)?; + let _destination = stream.read_u8()?; Write::write_all(stream, "e".as_bytes())?; info!("new connection from {}", stream.remote_endpoint()); loop { match Request::read_from(stream)? { - Request::GetLog => { - BufferLogger::with(|logger| { - let mut buffer = io.until_ok(|| logger.buffer())?; - Reply::LogContent(buffer.extract()).write_to(stream) - })?; - } - Request::ClearLog => { - BufferLogger::with(|logger| -> Result<(), Error> { - let mut buffer = io.until_ok(|| logger.buffer())?; - Ok(buffer.clear()) - })?; - - Reply::Success.write_to(stream)?; - } - Request::PullLog => { - BufferLogger::with(|logger| -> Result<(), Error> { - loop { - // Do this *before* acquiring the buffer, since that sets the log level - // to OFF. - let log_level = log::max_level(); - - let mut buffer = io.until_ok(|| logger.buffer())?; - if buffer.is_empty() { continue } - - stream.write_string(buffer.extract())?; - - if log_level == LevelFilter::Trace { - // Hold exclusive access over the logger until we get positive - // acknowledgement; otherwise we get an infinite loop of network - // trace messages being transmitted and causing more network - // trace messages to be emitted. - // - // Any messages unrelated to this management socket that arrive - // while it is flushed are lost, but such is life. - stream.flush()?; - } - - // Clear the log *after* flushing the network buffers, or we're just - // going to resend all the trace messages on the next iteration. - buffer.clear(); - } - })?; - } - Request::SetLogFilter(level) => { - info!("changing log level to {}", level); - log::set_max_level(level); - Reply::Success.write_to(stream)?; - } - Request::SetUartLogFilter(level) => { - info!("changing UART log level to {}", level); - BufferLogger::with(|logger| - logger.set_uart_log_level(level)); - Reply::Success.write_to(stream)?; - } - - Request::ConfigRead { ref key } => { - config::read(key, |result| { - match result { - Ok(value) => Reply::ConfigData(&value).write_to(stream), - Err(_) => Reply::Error.write_to(stream) - } - })?; - } - Request::ConfigWrite { ref key, ref value } => { - match config::write(key, value) { - Ok(_) => { - if key == "idle_kernel" { - io.until(|| !restart_idle.get())?; - restart_idle.set(true); - } - Reply::Success.write_to(stream) - }, - Err(_) => Reply::Error.write_to(stream) - }?; - } - Request::ConfigRemove { ref key } => { - match config::remove(key) { - Ok(()) => { - if key == "idle_kernel" { - io.until(|| !restart_idle.get())?; - restart_idle.set(true); - } - Reply::Success.write_to(stream) - }, - Err(_) => Reply::Error.write_to(stream) - }?; - - } - Request::ConfigErase => { - match config::erase() { - Ok(()) => { - io.until(|| !restart_idle.get())?; - restart_idle.set(true); - Reply::Success.write_to(stream) - }, - Err(_) => Reply::Error.write_to(stream) - }?; - } - - Request::Reboot => { - Reply::RebootImminent.write_to(stream)?; - stream.close()?; - stream.flush()?; - - warn!("restarting"); - unsafe { spiflash::reload(); } - } - - Request::DebugAllocator => - unsafe { println!("{}", ::ALLOC) }, - }; + Request::GetLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, get_log), + Request::ClearLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, clear_log), + Request::PullLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, pull_log), + Request::SetLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_log_filter, level), + Request::SetUartLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_uart_log_filter, level), + Request::ConfigRead { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_read, key), + Request::ConfigWrite { ref key, ref value } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_write, key, value, restart_idle), + Request::ConfigRemove { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_remove, key, restart_idle), + Request::ConfigErase => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_erase, restart_idle), + Request::Reboot => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, reboot), + Request::DebugAllocator => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, debug_allocator), + Request::Flash { ref image } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, flash, &image[..]), + }?; } } -pub fn thread(io: Io, restart_idle: &Urc>) { +pub fn thread(io: Io, restart_idle: &Urc>, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &Urc>) { let listener = TcpListener::new(&io, 8192); listener.listen(1380).expect("mgmt: cannot listen"); info!("management interface active"); loop { - let stream = listener.accept().expect("mgmt: cannot accept").into_handle(); let restart_idle = restart_idle.clone(); - io.spawn(4096, move |io| { + let aux_mutex = aux_mutex.clone(); + let ddma_mutex = ddma_mutex.clone(); + let subkernel_mutex = subkernel_mutex.clone(); + let routing_table = routing_table.clone(); + let stream = listener.accept().expect("mgmt: cannot accept").into_handle(); + io.spawn(16384, move |io| { + let routing_table = routing_table.borrow(); let mut stream = TcpStream::from_handle(&io, stream); - match worker(&io, &mut stream, &restart_idle) { + match worker(&io, &mut stream, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &routing_table) { Ok(()) => (), Err(Error::Io(IoError::UnexpectedEnd)) => (), Err(err) => error!("aborted: {}", err) diff --git a/artiq/firmware/runtime/rtio_mgt.rs b/artiq/firmware/runtime/rtio_mgt.rs index 120a4a1c6..bc3ac7080 100644 --- a/artiq/firmware/runtime/rtio_mgt.rs +++ b/artiq/firmware/runtime/rtio_mgt.rs @@ -506,7 +506,7 @@ pub mod drtio { } } - fn partition_data(data: &[u8], send_f: F) -> Result<(), Error> + pub fn partition_data(data: &[u8], send_f: F) -> Result<(), Error> where F: Fn(&[u8; MASTER_PAYLOAD_MAX_SIZE], PayloadStatus, usize) -> Result<(), Error> { let mut i = 0; while i < data.len() { diff --git a/artiq/firmware/satman/Cargo.toml b/artiq/firmware/satman/Cargo.toml index f8016f576..dca0c91f9 100644 --- a/artiq/firmware/satman/Cargo.toml +++ b/artiq/firmware/satman/Cargo.toml @@ -15,9 +15,12 @@ build_misoc = { path = "../libbuild_misoc" } [dependencies] log = { version = "0.4", default-features = false } io = { path = "../libio", features = ["byteorder", "alloc"] } +byteorder = { version = "1.0", default-features = false } +crc = { version = "1.7", default-features = false } cslice = { version = "0.3" } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] } board_artiq = { path = "../libboard_artiq", features = ["alloc"] } +logger_artiq = { path = "../liblogger_artiq" } alloc_list = { path = "../liballoc_list" } riscv = { version = "0.6.0", features = ["inline-asm"] } proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] } diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 8e265999e..c289b6b50 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -6,21 +6,25 @@ extern crate log; #[macro_use] extern crate board_misoc; extern crate board_artiq; +extern crate logger_artiq; extern crate riscv; extern crate alloc; extern crate proto_artiq; +extern crate byteorder; +extern crate crc; extern crate cslice; extern crate io; extern crate eh; use core::convert::TryFrom; -use board_misoc::{csr, ident, clock, config, uart_logger, i2c, pmp}; +use board_misoc::{csr, ident, clock, config, i2c, pmp}; #[cfg(has_si5324)] use board_artiq::si5324; #[cfg(has_si549)] use board_artiq::si549; #[cfg(soc_platform = "kasli")] use board_misoc::irq; +use board_misoc::{boot, spiflash}; use board_artiq::{spi, drtioaux, drtio_routing}; #[cfg(soc_platform = "efc")] use board_artiq::ad9117; @@ -30,6 +34,7 @@ use board_artiq::drtio_eem; use riscv::register::{mcause, mepc, mtval}; use dma::Manager as DmaManager; use kernel::Manager as KernelManager; +use mgmt::Manager as CoreManager; use analyzer::Analyzer; #[global_allocator] @@ -41,6 +46,7 @@ mod dma; mod analyzer; mod kernel; mod cache; +mod mgmt; fn drtiosat_reset(reset: bool) { unsafe { @@ -129,7 +135,7 @@ macro_rules! forward { ($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {} } -fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager, +fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager, coremgr: &mut CoreManager, _repeaters: &mut [repeater::Repeater], _routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, router: &mut routing::Router, self_destination: &mut u8, packet: drtioaux::Packet ) -> Result<(), drtioaux::Error> { @@ -495,6 +501,167 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg Ok(()) } + drtioaux::Packet::CoreMgmtGetLogRequest { destination: _destination, clear } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + let mut data_slice = [0; SAT_PAYLOAD_MAX_SIZE]; + if let Ok(meta) = coremgr.log_get_slice(&mut data_slice, clear) { + drtioaux::send( + 0, + &drtioaux::Packet::CoreMgmtGetLogReply { + last: meta.status.is_last(), + length: meta.len as u16, + data: data_slice, + }, + ) + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }) + } + } + drtioaux::Packet::CoreMgmtClearLogRequest { destination: _destination } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: mgmt::clear_log().is_ok() }) + } + drtioaux::Packet::CoreMgmtSetLogLevelRequest {destination: _destination, log_level } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) { + info!("changing log level to {}", level_filter); + log::set_max_level(level_filter); + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true }) + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }) + } + } + drtioaux::Packet::CoreMgmtSetUartLogLevelRequest { destination: _destination, log_level } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) { + info!("changing UART log level to {}", level_filter); + logger_artiq::BufferLogger::with(|logger| + logger.set_uart_log_level(level_filter)); + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true }) + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }) + } + } + drtioaux::Packet::CoreMgmtConfigReadRequest { + destination: _destination, + length, + key, + } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE]; + + let key_slice = &key[..length as usize]; + if !key_slice.is_ascii() { + error!("invalid key"); + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }) + } else { + let key = core::str::from_utf8(key_slice).unwrap(); + if coremgr.fetch_config_value(key).is_ok() { + let meta = coremgr.get_config_value_slice(&mut value_slice); + drtioaux::send( + 0, + &drtioaux::Packet::CoreMgmtConfigReadReply { + length: meta.len as u16, + last: meta.status.is_last(), + value: value_slice, + }, + ) + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }) + } + } + } + drtioaux::Packet::CoreMgmtConfigReadContinue { + destination: _destination, + } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE]; + let meta = coremgr.get_config_value_slice(&mut value_slice); + drtioaux::send( + 0, + &drtioaux::Packet::CoreMgmtConfigReadReply { + length: meta.len as u16, + last: meta.status.is_last(), + value: value_slice, + }, + ) + } + drtioaux::Packet::CoreMgmtConfigWriteRequest { destination: _destination, last, length, data } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + coremgr.add_config_data(&data, length as usize); + if last { + coremgr.write_config() + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true }) + } + } + drtioaux::Packet::CoreMgmtConfigRemoveRequest { destination: _destination, length, key } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + let key = core::str::from_utf8(&key[..length as usize]).unwrap(); + let succeeded = config::remove(key) + .map_err(|err| warn!("error on removing config: {:?}", err)) + .is_ok(); + + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded }) + } + drtioaux::Packet::CoreMgmtConfigEraseRequest { destination: _destination } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + let succeeded = config::erase() + .map_err(|err| warn!("error on erasing config: {:?}", err)) + .is_ok(); + + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded }) + } + drtioaux::Packet::CoreMgmtRebootRequest { destination: _destination } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })?; + warn!("restarting"); + unsafe { spiflash::reload(); } + } + drtioaux::Packet::CoreMgmtFlashRequest { destination: _destination, payload_length } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + coremgr.allocate_image_buffer(payload_length as usize); + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true }) + } + drtioaux::Packet::CoreMgmtFlashAddDataRequest { destination: _destination, last, length, data } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + coremgr.add_image_data(&data, length as usize); + if last { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtDropLink) + } else { + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true }) + } + } + drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => { + forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); + + #[cfg(not(has_drtio_eem))] + unsafe { + csr::gt_drtio::txenable_write(0); + } + + #[cfg(has_drtio_eem)] + unsafe { + csr::eem_transceiver::txenable_write(0); + } + + coremgr.flash_image(); + warn!("restarting"); + unsafe { spiflash::reload(); } + } + _ => { warn!("received unexpected aux packet"); Ok(()) @@ -503,13 +670,13 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg } fn process_aux_packets(dma_manager: &mut DmaManager, analyzer: &mut Analyzer, - kernelmgr: &mut KernelManager, repeaters: &mut [repeater::Repeater], + kernelmgr: &mut KernelManager, coremgr: &mut CoreManager, repeaters: &mut [repeater::Repeater], routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, router: &mut routing::Router, destination: &mut u8) { let result = drtioaux::recv(0).and_then(|packet| { if let Some(packet) = packet.or_else(|| router.get_local_packet()) { - process_aux_packet(dma_manager, analyzer, kernelmgr, + process_aux_packet(dma_manager, analyzer, kernelmgr, coremgr, repeaters, routing_table, rank, router, destination, packet) } else { Ok(()) @@ -664,6 +831,27 @@ fn sysclk_setup() { } } +fn setup_log_levels() { + match config::read_str("log_level", |r| r.map(|s| s.parse())) { + Ok(Ok(log_level_filter)) => { + info!("log level set to {} by `log_level` config key", + log_level_filter); + log::set_max_level(log_level_filter); + } + _ => info!("log level set to INFO by default") + } + match config::read_str("uart_log_level", |r| r.map(|s| s.parse())) { + Ok(Ok(uart_log_level_filter)) => { + info!("UART log level set to {} by `uart_log_level` config key", + uart_log_level_filter); + logger_artiq::BufferLogger::with(|logger| + logger.set_uart_log_level(uart_log_level_filter)); + } + _ => info!("UART log level set to INFO by default") + } +} + +static mut LOG_BUFFER: [u8; 1<<17] = [0; 1<<17]; #[no_mangle] pub extern fn main() -> i32 { @@ -683,12 +871,21 @@ pub extern fn main() -> i32 { irq::enable(csr::WRPLL_INTERRUPT); clock::init(); - uart_logger::ConsoleLogger::register(); + unsafe { + logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(|| + boot::start_user(startup as usize)); + } + 0 +} + +fn startup() { info!("ARTIQ satellite manager starting..."); info!("software ident {}", csr::CONFIG_IDENTIFIER_STR); info!("gateware ident {}", ident::read(&mut [0; 64])); + setup_log_levels(); + #[cfg(has_i2c)] i2c::init().expect("I2C initialization failed"); #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] @@ -834,6 +1031,7 @@ pub extern fn main() -> i32 { let mut dma_manager = DmaManager::new(); let mut analyzer = Analyzer::new(); let mut kernelmgr = KernelManager::new(); + let mut coremgr = CoreManager::new(); cricon_select(RtioMaster::Drtio); drtioaux::reset(0); @@ -843,7 +1041,7 @@ pub extern fn main() -> i32 { while drtiosat_link_rx_up() { drtiosat_process_errors(); process_aux_packets(&mut dma_manager, &mut analyzer, - &mut kernelmgr, &mut repeaters, &mut routing_table, + &mut kernelmgr, &mut coremgr, &mut repeaters, &mut routing_table, &mut rank, &mut router, &mut destination); for rep in repeaters.iter_mut() { rep.service(&routing_table, rank, destination, &mut router); diff --git a/artiq/firmware/satman/mgmt.rs b/artiq/firmware/satman/mgmt.rs new file mode 100644 index 000000000..d7058b837 --- /dev/null +++ b/artiq/firmware/satman/mgmt.rs @@ -0,0 +1,149 @@ +use alloc::vec::Vec; +use byteorder::{ByteOrder, NativeEndian}; +use crc::crc32; + +use routing::{Sliceable, SliceMeta}; +use board_artiq::drtioaux; +use board_misoc::{mem, config, spiflash}; +use log::LevelFilter; +use logger_artiq::BufferLogger; +use io::{Cursor, ProtoRead, ProtoWrite}; +use proto_artiq::drtioaux_proto::SAT_PAYLOAD_MAX_SIZE; + + +pub fn clear_log() -> Result<(), ()> { + BufferLogger::with(|logger| { + let mut buffer = logger.buffer()?; + Ok(buffer.clear()) + }).map_err(|()| error!("error on clearing log buffer")) +} + +pub fn byte_to_level_filter(level_byte: u8) -> Result { + Ok(match level_byte { + 0 => LevelFilter::Off, + 1 => LevelFilter::Error, + 2 => LevelFilter::Warn, + 3 => LevelFilter::Info, + 4 => LevelFilter::Debug, + 5 => LevelFilter::Trace, + lv => { + error!("unknown log level: {}", lv); + return Err(()); + } + }) +} + +pub struct Manager { + config_payload: Cursor>, + image_payload: Cursor>, + last_value: Sliceable, + last_log: Sliceable, +} + +impl Manager { + pub fn new() -> Manager { + Manager { + config_payload: Cursor::new(Vec::new()), + image_payload: Cursor::new(Vec::new()), + last_value: Sliceable::new(0, Vec::new()), + last_log: Sliceable::new(0, Vec::new()), + } + } + + pub fn fetch_config_value(&mut self, key: &str) -> Result<(), ()> { + config::read(key, |result| result.map( + |value| self.last_value = Sliceable::new(0, value.to_vec()) + )).map_err(|_err| warn!("read error: no such key")) + } + + pub fn log_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE], consume: bool) -> Result { + // Populate buffer if depleted + if self.last_log.at_end() { + BufferLogger::with(|logger| { + let mut buffer = logger.buffer()?; + self.last_log = Sliceable::new(0, buffer.extract().as_bytes().to_vec()); + if consume { + buffer.clear(); + } + Ok(()) + }).map_err(|()| error!("error on getting log buffer"))?; + } + + Ok(self.last_log.get_slice_satellite(data_slice)) + } + + pub fn get_config_value_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta { + self.last_value.get_slice_satellite(data_slice) + } + + pub fn add_config_data(&mut self, data: &[u8], data_len: usize) { + self.config_payload.write_all(&data[..data_len]).unwrap(); + } + + pub fn clear_config_data(&mut self) { + self.config_payload.get_mut().clear(); + self.config_payload.set_position(0); + } + + pub fn write_config(&mut self) -> Result<(), drtioaux::Error> { + let key = match self.config_payload.read_string() { + Ok(key) => key, + Err(err) => { + self.clear_config_data(); + error!("error on reading key: {:?}", err); + return drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false }); + } + }; + + let value = self.config_payload.read_bytes().unwrap(); + + let succeeded = config::write(&key, &value).map_err(|err| { + error!("error on writing config: {:?}", err); + }).is_ok(); + + self.clear_config_data(); + + drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded }) + } + + pub fn allocate_image_buffer(&mut self, image_size: usize) { + self.image_payload = Cursor::new(Vec::with_capacity(image_size)); + } + + pub fn add_image_data(&mut self, data: &[u8], data_len: usize) { + self.image_payload.write_all(&data[..data_len]).unwrap(); + } + + pub fn flash_image(&self) { + let image = &self.image_payload.get_ref()[..]; + + let (expected_crc, mut image) = { + let (image, crc_slice) = image.split_at(image.len() - 4); + (NativeEndian::read_u32(crc_slice), image) + }; + + let actual_crc = crc32::checksum_ieee(image); + + if actual_crc == expected_crc { + let bin_origins = [ + ("gateware" , 0 ), + ("bootloader", mem::ROM_BASE ), + ("firmware" , mem::FLASH_BOOT_ADDRESS), + ]; + + for (name, origin) in bin_origins { + info!("flashing {} binary...", name); + let size = NativeEndian::read_u32(&image[..4]) as usize; + image = &image[4..]; + + let (bin, remaining) = image.split_at(size); + image = remaining; + + unsafe { spiflash::flash_binary(origin, bin) }; + } + + } else { + panic!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc); + } + } +} diff --git a/artiq/firmware/satman/routing.rs b/artiq/firmware/satman/routing.rs index f8e0e6d24..2861f9fe6 100644 --- a/artiq/firmware/satman/routing.rs +++ b/artiq/firmware/satman/routing.rs @@ -4,6 +4,7 @@ use board_artiq::{drtioaux, drtio_routing}; use board_misoc::csr; use core::cmp::min; use proto_artiq::drtioaux_proto::PayloadStatus; +use SAT_PAYLOAD_MAX_SIZE; use MASTER_PAYLOAD_MAX_SIZE; /* represents data that has to be sent with the aux protocol */ @@ -56,6 +57,7 @@ impl Sliceable { self.data.extend(data); } + get_slice_fn!(get_slice_satellite, SAT_PAYLOAD_MAX_SIZE); get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE); } diff --git a/artiq/frontend/aqctl_corelog.py b/artiq/frontend/aqctl_corelog.py index c5f5a8eee..cd954f590 100755 --- a/artiq/frontend/aqctl_corelog.py +++ b/artiq/frontend/aqctl_corelog.py @@ -25,6 +25,9 @@ def get_argparser(): help="Simulation - does not connect to device") parser.add_argument("core_addr", metavar="CORE_ADDR", help="hostname or IP address of the core device") + parser.add_argument("-s", "--drtio-dest", default=0, + metavar="DRTIO_DEST", type=int, + help="specifies the DRTIO destination") return parser @@ -39,7 +42,7 @@ async def get_logs_sim(host): log_with_name("firmware.simulation", logging.INFO, "hello " + host) -async def get_logs(host): +async def get_logs(host, drtio_dest): try: reader, writer = await async_open_connection( host, @@ -49,6 +52,7 @@ async def get_logs(host): max_fails=3, ) writer.write(b"ARTIQ management\n") + writer.write(drtio_dest.to_bytes(1)) endian = await reader.readexactly(1) if endian == b"e": endian = "<" @@ -96,7 +100,7 @@ def main(): signal_handler.setup() try: get_logs_task = asyncio.ensure_future( - get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr), + get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr, args.drtio_dest), loop=loop) try: server = Server({"corelog": PingTarget()}, None, True) diff --git a/artiq/frontend/artiq_coremgmt.py b/artiq/frontend/artiq_coremgmt.py index 789c26bf7..a137a402a 100755 --- a/artiq/frontend/artiq_coremgmt.py +++ b/artiq/frontend/artiq_coremgmt.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 import argparse +import os import struct +import tempfile +import atexit from sipyco import common_args @@ -9,6 +12,7 @@ from artiq import __version__ as artiq_version from artiq.master.databases import DeviceDB from artiq.coredevice.comm_kernel import CommKernel from artiq.coredevice.comm_mgmt import CommMgmt +from artiq.frontend.flash_tools import bit2bin, fetch_bin def get_argparser(): @@ -85,6 +89,20 @@ def get_argparser(): t_boot = tools.add_parser("reboot", help="reboot the running system") + # flashing + t_flash = tools.add_parser("flash", + help="flash the running system") + + p_directory = t_flash.add_argument("directory", + metavar="DIRECTORY", type=str, + help="directory that contains the " + "binaries") + + p_srcbuild = t_flash.add_argument("--srcbuild", + help="board binaries directory is laid " + "out as a source build tree", + default=False, action="store_true") + # misc debug t_debug = tools.add_parser("debug", help="specialized debug functions") @@ -95,6 +113,12 @@ def get_argparser(): p_allocator = subparsers.add_parser("allocator", help="show heap layout") + # manage target + p_drtio_dest = parser.add_argument("-s", "--drtio-dest", default=0, + metavar="DRTIO_DEST", type=int, + help="specify DRTIO destination that " + "receives this command") + return parser @@ -107,7 +131,7 @@ def main(): core_addr = ddb.get("core", resolve_alias=True)["arguments"]["host"] else: core_addr = args.device - mgmt = CommMgmt(core_addr) + mgmt = CommMgmt(core_addr, drtio_dest=args.drtio_dest) if args.tool == "log": if args.action == "set_level": @@ -138,6 +162,39 @@ def main(): if args.action == "erase": mgmt.config_erase() + if args.tool == "flash": + retrieved_bins = [] + bin_dict = { + "zynq":[ + ["boot"] + ], + "riscv": [ + ["gateware"], + ["bootloader"], + ["runtime", "satman"], + ], + } + + for bin_list in bin_dict.values(): + try: + bins = [] + for bin_name in bin_list: + bins.append(fetch_bin( + args.directory, bin_name, args.srcbuild)) + retrieved_bins.append(bins) + except FileNotFoundError: + pass + + if retrieved_bins is None: + raise FileNotFoundError("neither risc-v nor zynq binaries were found") + + if len(retrieved_bins) > 1: + raise ValueError("both risc-v and zynq binaries were found, " + "please clean up your build directory. ") + + bins = retrieved_bins[0] + mgmt.flash(bins) + if args.tool == "reboot": mgmt.reboot() diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index b1914a4b5..5a5296979 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -14,7 +14,7 @@ from sipyco import common_args from artiq import __version__ as artiq_version from artiq.remoting import SSHClient, LocalClient -from artiq.frontend.bit2bin import bit2bin +from artiq.frontend.flash_tools import artifact_path, bit2bin, fetch_bin def get_argparser(): @@ -302,46 +302,19 @@ def main(): programmer = config["programmer"](client, preinit_script=args.preinit_command) - def artifact_path(this_binary_dir, *path_filename): - if args.srcbuild: - # source tree - use path elements to locate file - return os.path.join(this_binary_dir, *path_filename) - else: - # flat tree - all files in the same directory, discard path elements - *_, filename = path_filename - return os.path.join(this_binary_dir, filename) - - def convert_gateware(bit_filename): - bin_handle, bin_filename = tempfile.mkstemp( - prefix="artiq_", suffix="_" + os.path.basename(bit_filename)) - with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file: - bit2bin(bit_file, bin_file) - atexit.register(lambda: os.unlink(bin_filename)) - return bin_filename - for action in args.action: if action == "gateware": - gateware_bin = convert_gateware( - artifact_path(binary_dir, "gateware", "top.bit")) + gateware_bin = fetch_bin(binary_dir, ["gateware"], args.srcbuild) programmer.write_binary(*config["gateware"], gateware_bin) elif action == "bootloader": - bootloader_bin = artifact_path(binary_dir, "software", "bootloader", "bootloader.bin") + bootloader_bin = fetch_bin(binary_dir, ["bootloader"], args.srcbuild) programmer.write_binary(*config["bootloader"], bootloader_bin) elif action == "storage": storage_img = args.storage programmer.write_binary(*config["storage"], storage_img) elif action == "firmware": - firmware_fbis = [] - for firmware in "satman", "runtime": - filename = artifact_path(binary_dir, "software", firmware, firmware + ".fbi") - if os.path.exists(filename): - firmware_fbis.append(filename) - if not firmware_fbis: - raise FileNotFoundError("no firmware found") - if len(firmware_fbis) > 1: - raise ValueError("more than one firmware file, please clean up your build directory. " - "Found firmware files: {}".format(" ".join(firmware_fbis))) - programmer.write_binary(*config["firmware"], firmware_fbis[0]) + firmware_fbi = fetch_bin(binary_dir, ["satman", "runtime"], args.srcbuild) + programmer.write_binary(*config["firmware"], firmware_fbi) elif action == "load": gateware_bit = artifact_path(binary_dir, "gateware", "top.bit") programmer.load(gateware_bit, 0) diff --git a/artiq/frontend/bit2bin.py b/artiq/frontend/bit2bin.py deleted file mode 100755 index df3927415..000000000 --- a/artiq/frontend/bit2bin.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2014-2017 Robert Jordens -# after -# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py - -import struct - - -def flip32(data): - sl = struct.Struct("I") - b = memoryview(data) - d = bytearray(len(data)) - for offset in range(0, len(data), sl.size): - sb.pack_into(d, offset, *sl.unpack_from(b, offset)) - return d - - -def bit2bin(bit, bin, flip=False): - l, = struct.unpack(">H", bit.read(2)) - if l != 9: - raise ValueError("Missing <0009> header, not a bit file") - _ = bit.read(l) # unknown data - l, = struct.unpack(">H", bit.read(2)) - if l != 1: - raise ValueError("Missing <0001> header, not a bit file") - - while True: - key = bit.read(1).decode() - if not key: - break - if key in "abcd": - d = bit.read(*struct.unpack(">H", bit.read(2))) - assert d.endswith(b"\x00") - d = d[:-1].decode() - name = { - "a": "Design", - "b": "Part name", - "c": "Date", - "d": "Time" - }[key] - print("{}: {}".format(name, d)) - elif key == "e": - l, = struct.unpack(">I", bit.read(4)) - print("Bitstream payload length: {:#x}".format(l)) - d = bit.read(l) - if flip: - d = flip32(d) - bin.write(d) - else: - d = bit.read(*struct.unpack(">H", bit.read(2))) - print("Unexpected key: {}: {}".format(key, d)) - - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser( - description="Convert FPGA bit files to raw bin format " - "suitable for flashing") - parser.add_argument("-f", "--flip", dest="flip", action="store_true", - default=False, help="Flip 32-bit endianess (needed for Zynq)") - parser.add_argument("bitfile", metavar="BITFILE", - help="Input bit file name") - parser.add_argument("binfile", metavar="BINFILE", - help="Output bin file name") - args = parser.parse_args() - - with open(args.bitfile, "rb") as f, open(args.binfile, "wb") as g: - bit2bin(f, g, args.flip) diff --git a/artiq/frontend/flash_tools.py b/artiq/frontend/flash_tools.py new file mode 100644 index 000000000..432d03f37 --- /dev/null +++ b/artiq/frontend/flash_tools.py @@ -0,0 +1,112 @@ +import atexit +import os +import tempfile +import struct + + +def artifact_path(this_binary_dir, *path_filename, srcbuild=False): + if srcbuild: + # source tree - use path elements to locate file + return os.path.join(this_binary_dir, *path_filename) + else: + # flat tree - all files in the same directory, discard path elements + *_, filename = path_filename + return os.path.join(this_binary_dir, filename) + + +def fetch_bin(binary_dir, components, srcbuild=False): + def convert_gateware(bit_filename): + bin_handle, bin_filename = tempfile.mkstemp( + prefix="artiq_", suffix="_" + os.path.basename(bit_filename)) + with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file: + bit2bin(bit_file, bin_file) + atexit.register(lambda: os.unlink(bin_filename)) + return bin_filename + + if len(components) > 1: + bins = [] + for option in components: + try: + bins.append(fetch_bin(binary_dir, [option], srcbuild)) + except FileNotFoundError: + pass + + if bins is None: + raise FileNotFoundError("multiple components not found: {}".format( + " ".join(components))) + + if len(bins) > 1: + raise ValueError("more than one file, " + "please clean up your build directory. " + "Found files: {}".format( + " ".join(bins))) + + return bins[0] + + else: + component = components[0] + path = artifact_path(binary_dir, *{ + "gateware": ["gateware", "top.bit"], + "boot": ["boot.bin"], + "bootloader": ["software", "bootloader", "bootloader.bin"], + "runtime": ["software", "runtime", "runtime.fbi"], + "satman": ["software", "satman", "satman.fbi"], + }[component], srcbuild=srcbuild) + + if not os.path.exists(path): + raise FileNotFoundError("{} not found".format(component)) + + if component == "gateware": + path = convert_gateware(path) + + return path + + +# Copyright 2014-2017 Robert Jordens +# after +# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py + +def flip32(data): + sl = struct.Struct("I") + b = memoryview(data) + d = bytearray(len(data)) + for offset in range(0, len(data), sl.size): + sb.pack_into(d, offset, *sl.unpack_from(b, offset)) + return d + + +def bit2bin(bit, bin, flip=False): + l, = struct.unpack(">H", bit.read(2)) + if l != 9: + raise ValueError("Missing <0009> header, not a bit file") + _ = bit.read(l) # unknown data + l, = struct.unpack(">H", bit.read(2)) + if l != 1: + raise ValueError("Missing <0001> header, not a bit file") + + while True: + key = bit.read(1).decode() + if not key: + break + if key in "abcd": + d = bit.read(*struct.unpack(">H", bit.read(2))) + assert d.endswith(b"\x00") + d = d[:-1].decode() + name = { + "a": "Design", + "b": "Part name", + "c": "Date", + "d": "Time" + }[key] + print("{}: {}".format(name, d)) + elif key == "e": + l, = struct.unpack(">I", bit.read(4)) + print("Bitstream payload length: {:#x}".format(l)) + d = bit.read(l) + if flip: + d = flip32(d) + bin.write(d) + else: + d = bit.read(*struct.unpack(">H", bit.read(2))) + print("Unexpected key: {}: {}".format(key, d))