Skip to content

Commit

Permalink
Cleanup to grapefruit-seq in preparation for Cosmo (#1990)
Browse files Browse the repository at this point in the history
Three relatively standalone commits:

- Add a helper function `AuxFlash::get_compressed_blob_streaming` to do
  streaming decompression of a blob in auxflash. We'll use this for both
  FPGAs on Cosmo.
- Move Spartan7 initialization to a separate crate, since we need to do
  it on both Grapefruit and Cosmo. This is mostly modeled after
  `drv-ice40-spi-program`.
- Remove `fmc` peripheral from the `fmc_demo` task, because it's now
  initialized pre-Hubris.
  • Loading branch information
mkeeter authored Jan 23, 2025
1 parent 8d3e383 commit 9c63d4c
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 113 deletions.
13 changes: 12 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/grapefruit/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ features = ["h753"]
priority = 4
start = true
task-slots = ["sys", "net"]
uses = ["fmc", "fmc_nor_psram_bank_1"]
uses = ["fmc_nor_psram_bank_1"]
notifications = ["socket"]

[tasks.hf]
Expand Down
1 change: 1 addition & 0 deletions drv/auxflash-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ zerocopy.workspace = true
counters = { path = "../../lib/counters" }
derive-idol-err = { path = "../../lib/derive-idol-err" }
drv-qspi-api = { path = "../qspi-api" }
gnarle = { path = "../../lib/gnarle" }
userlib = { path = "../../sys/userlib" }

[build-dependencies]
Expand Down
50 changes: 50 additions & 0 deletions drv/auxflash-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,56 @@ where
}
}

/// Extension functions on the autogenerated `AuxFlash` type
impl AuxFlash {
/// Reads a compressed blob, streaming it to a callback function
pub fn get_compressed_blob_streaming<F, E>(
&self,
tag: [u8; 4],
f: F,
) -> Result<[u8; 32], E>
where
F: Fn(&[u8]) -> Result<(), E>,
E: From<AuxFlashError>,
{
let blob = self.get_blob_by_tag(tag)?;
let mut scratch_buf = [0u8; 128];
let mut pos = blob.start;
let mut sha = Sha3_256::new();
let mut decompressor = gnarle::Decompressor::default();

while pos < blob.end {
let amount = (blob.end - pos).min(scratch_buf.len() as u32);
let chunk = &mut scratch_buf[0..(amount as usize)];
self.read_slot_with_offset(blob.slot, pos, chunk)?;
sha.update(&chunk);
pos += amount;

// Reborrow as an immutable chunk, then decompress
let mut chunk = &scratch_buf[0..(amount as usize)];
let mut decompress_buffer = [0; 512];

while !chunk.is_empty() {
let decompressed_chunk = gnarle::decompress(
&mut decompressor,
&mut chunk,
&mut decompress_buffer,
);

// The decompressor may have encountered a partial run at the
// end of the `chunk`, in which case `decompressed_chunk`
// will be empty since more data is needed before output is
// generated.
if !decompressed_chunk.is_empty() {
// Perform the callback
f(decompressed_chunk)?;
}
}
}
Ok(sha.finalize().into())
}
}

////////////////////////////////////////////////////////////////////////////////

include!(concat!(env!("OUT_DIR"), "/client_stub.rs"));
Expand Down
2 changes: 1 addition & 1 deletion drv/grapefruit-seq-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ counters = { path = "../../lib/counters" }
drv-auxflash-api = { path = "../auxflash-api" }
drv-cpu-power-state = { path = "../cpu-power-state" }
drv-cpu-seq-api = { path = "../cpu-seq-api" }
drv-spartan7-spi-program = { path = "../spartan7-spi-program" }
drv-spi-api = { path = "../spi-api" }
drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" }
gnarle = { path = "../../lib/gnarle" }
Expand All @@ -19,7 +20,6 @@ task-jefe-api = { path = "../../task/jefe-api" }
cfg-if = { workspace = true }
idol-runtime.workspace = true
num-traits = { workspace = true }
sha3 = { workspace = true }
zerocopy = { workspace = true }

[build-dependencies]
Expand Down
148 changes: 38 additions & 110 deletions drv/grapefruit-seq-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use drv_cpu_seq_api::{PowerState, StateChangeReason};
use drv_spi_api::{SpiDevice, SpiServer};
use drv_stm32xx_sys_api as sys_api;
use idol_runtime::{NotificationHandler, RequestError};
use sha3::{Digest, Sha3_256};
use task_jefe_api::Jefe;
use task_packrat_api::{CacheSetError, MacAddressBlock, Packrat, VpdIdentity};
use userlib::{
Expand All @@ -25,7 +24,7 @@ task_slot!(JEFE, jefe);

#[derive(Copy, Clone, PartialEq, Count)]
enum Trace {
FpgaInit(#[count(children)] bool),
FpgaInit,
StartFailed(#[count(children)] SeqError),
ContinueBitstreamLoad(usize),
WaitForDone,
Expand All @@ -39,11 +38,21 @@ enum Trace {

#[derive(Copy, Clone, PartialEq, Count)]
enum SeqError {
AuxMissingBlob,
AuxReadError(#[count(children)] drv_auxflash_api::AuxFlashError),
AuxFlashError(#[count(children)] drv_auxflash_api::AuxFlashError),
AuxChecksumMismatch,
SpiWrite(#[count(children)] drv_spi_api::SpiError),
DoneTimeout,
FpgaError(#[count(children)] drv_spartan7_spi_program::Spartan7Error),
}

impl From<drv_auxflash_api::AuxFlashError> for SeqError {
fn from(v: drv_auxflash_api::AuxFlashError) -> Self {
SeqError::AuxFlashError(v)
}
}

impl From<drv_spartan7_spi_program::Spartan7Error> for SeqError {
fn from(v: drv_spartan7_spi_program::Spartan7Error) -> Self {
SeqError::FpgaError(v)
}
}

counted_ringbuf!(Trace, 128, Trace::None);
Expand Down Expand Up @@ -135,8 +144,8 @@ impl<S: SpiServer + Clone> ServerImpl<S> {
spi: S,
aux: drv_auxflash_api::AuxFlash,
) -> Result<Self, SeqError> {
// Ensure the SP fault pin is configured as an open-drain output, and pull
// it low to make the sequencer restart externally visible.
// Ensure the SP fault pin is configured as an open-drain output, and
// pull it low to make the sequencer restart externally visible.
sys.gpio_configure_output(
FAULT_PIN_L,
sys_api::OutputType::OpenDrain,
Expand All @@ -154,96 +163,29 @@ impl<S: SpiServer + Clone> ServerImpl<S> {
sys_api::Pull::None,
);

// Configure the FPGA_INIT_L and FPGA_CONFIG_DONE lines as inputs
sys.gpio_configure_input(FPGA_INIT_L, sys_api::Pull::None);
sys.gpio_configure_input(FPGA_CONFIG_DONE, sys_api::Pull::None);

// To allow for the possibility that we are restarting, rather than
// starting, we take care during early sequencing to _not turn anything
// off,_ only on. This means if it was _already_ on, the outputs should
// not glitch.

// To program the FPGA, we're using "slave serial" mode.
//
// See "7 Series FPGAs Configuration", UG470 (v1.17) for details,
// as well as "Using a Microprocessor to Configure Xilinx 7 Series FPGAs
// via Slave Serial or Slave SelectMAP Mode Application Note" (XAPP583)

// Configure the PROGRAM_B line to the FPGA
sys.gpio_set(FPGA_PROGRAM_L);
sys.gpio_configure_output(
FPGA_PROGRAM_L,
sys_api::OutputType::OpenDrain,
sys_api::Speed::Low,
sys_api::Pull::None,
);

// Pulse PROGRAM_B low for 1 ms to reset the bitstream
// (T_PROGRAM is 250 ns min, so this is fine)
// https://docs.amd.com/r/en-US/ds189-spartan-7-data-sheet/XADC-Specifications
sys.gpio_reset(FPGA_PROGRAM_L);
hl::sleep_for(1);
sys.gpio_set(FPGA_PROGRAM_L);

// Wait for INIT_B to rise
loop {
let init = sys.gpio_read(FPGA_INIT_L) != 0;
ringbuf_entry!(Trace::FpgaInit(init));
if init {
break;
}

// Do _not_ burn CPU constantly polling, it's rude. We could also
// set up pin-change interrupts but we only do this once per power
// on, so it seems like a lot of work.
hl::sleep_for(2);
}

// Bind to the sequencer device on our SPI port
let seq = spi.device(drv_spi_api::devices::FPGA);

let blob = aux
.get_blob_by_tag(*b"FPGA")
.map_err(|_| SeqError::AuxMissingBlob)?;
let mut scratch_buf = [0u8; 128];
let mut pos = blob.start;
let mut sha = Sha3_256::new();
let mut decompressor = gnarle::Decompressor::default();
while pos < blob.end {
let amount = (blob.end - pos).min(scratch_buf.len() as u32);
let chunk = &mut scratch_buf[0..(amount as usize)];
aux.read_slot_with_offset(blob.slot, pos, chunk)
.map_err(SeqError::AuxReadError)?;
sha.update(&chunk);
pos += amount;

// Reborrow as an immutable chunk, then decompress
let mut chunk = &scratch_buf[0..(amount as usize)];
let mut decompress_buffer = [0; 512];

while !chunk.is_empty() {
let decompressed_chunk = gnarle::decompress(
&mut decompressor,
&mut chunk,
&mut decompress_buffer,
);

// The compressor may have encountered a partial run at the
// end of the `chunk`, in which case `decompressed_chunk`
// will be empty since more data is needed before output is
// generated.
if !decompressed_chunk.is_empty() {
// Write the decompressed bitstream to the FPGA over SPI
seq.write(decompressed_chunk)
.map_err(SeqError::SpiWrite)?;
ringbuf_entry!(Trace::ContinueBitstreamLoad(
decompressed_chunk.len()
));
}
}
}
let pin_cfg = drv_spartan7_spi_program::Config {
program_l: FPGA_PROGRAM_L,
init_l: FPGA_INIT_L,
config_done: FPGA_CONFIG_DONE,
};
ringbuf_entry!(Trace::FpgaInit);
let loader =
drv_spartan7_spi_program::BitstreamLoader::begin_bitstream_load(
sys, &pin_cfg, &seq, true,
)?;

let sha_out = aux.get_compressed_blob_streaming(
*b"FPGA",
|chunk| -> Result<(), SeqError> {
loader.continue_bitstream_load(chunk)?;
ringbuf_entry!(Trace::ContinueBitstreamLoad(chunk.len()));
Ok(())
},
)?;

let sha_out: [u8; 32] = sha.finalize().into();
if sha_out != gen::FPGA_BITSTREAM_CHECKSUM {
// Reset the FPGA to clear the invalid bitstream
sys.gpio_reset(FPGA_PROGRAM_L);
Expand All @@ -253,22 +195,8 @@ impl<S: SpiServer + Clone> ServerImpl<S> {
return Err(SeqError::AuxChecksumMismatch);
}

// Wait for the FPGA to pull DONE high
const DELAY_MS: u64 = 2;
const TIMEOUT_MS: u64 = 250;
let mut wait_time_ms = 0;
while sys.gpio_read(FPGA_CONFIG_DONE) == 0 {
ringbuf_entry!(Trace::WaitForDone);
hl::sleep_for(DELAY_MS);
wait_time_ms += DELAY_MS;
if wait_time_ms > TIMEOUT_MS {
return Err(SeqError::DoneTimeout);
}
}

// Send 64 bonus clocks to complete the startup sequence (see "Clocking
// to End of Startup" in UG470).
seq.write(&[0u8; 8]).map_err(SeqError::SpiWrite)?;
ringbuf_entry!(Trace::WaitForDone);
loader.finish_bitstream_load()?;

ringbuf_entry!(Trace::Programmed);

Expand Down
18 changes: 18 additions & 0 deletions drv/spartan7-spi-program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "drv-spartan7-spi-program"
version = "0.1.0"
edition = "2021"

[dependencies]
drv-spi-api = { path = "../spi-api" }
drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" }
counters = { path = "../../lib/counters" }
userlib = { path = "../../sys/userlib" }

[lib]
test = false
doctest = false
bench = false

[lints]
workspace = true
Loading

0 comments on commit 9c63d4c

Please sign in to comment.