opentitanlib/test_utils/
lc_transition.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::iter;
6use std::time::Duration;
7
8use anyhow::{Context, Result, bail};
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::app::TransportWrapper;
13use crate::chip::boolean::MultiBitBool8;
14use crate::dif::lc_ctrl::{
15    DifLcCtrlState, LcCtrlReg, LcCtrlStatus, LcCtrlTransitionCmd, LcCtrlTransitionCtrl,
16};
17use crate::impl_serializable_error;
18use crate::io::jtag::{Jtag, JtagParams, JtagTap};
19use crate::test_utils::poll;
20
21use top_earlgrey::top_earlgrey;
22
23/// Errors related to performing an LcTransition.
24#[derive(Error, Debug, Deserialize, Serialize)]
25pub enum LcTransitionError {
26    #[error("LC controller not ready to perform an LC transition (status: 0x{0:x}).")]
27    LcCtrlNotReady(LcCtrlStatus),
28    #[error("LC transition mutex was already claimed.")]
29    MutexAlreadyClaimed,
30    #[error("Failed to claim LC transition mutex.")]
31    FailedToClaimMutex,
32    #[error("Volatile raw unlock is not supported on this chip.")]
33    VolatileRawUnlockNotSupported,
34    #[error("Volatile raw unlock is unexpectedly supported on this chip.")]
35    VolatileRawUnlockSupported,
36    #[error("LC transition target programming failed (target state: 0x{0:x}).")]
37    TargetProgrammingFailed(u32),
38    #[error("LC transition failed (status: 0x{0:x}).")]
39    TransitionFailed(LcCtrlStatus),
40    #[error("Bad post transition LC state: 0x{0:x}.")]
41    BadPostTransitionState(u32),
42    #[error("Invalid LC state: {0:x}")]
43    InvalidState(u32),
44    #[error("Generic error {0}")]
45    Generic(String),
46}
47impl_serializable_error!(LcTransitionError);
48
49fn setup_lc_transition(
50    jtag: &mut dyn Jtag,
51    target_lc_state: DifLcCtrlState,
52    token: Option<[u32; 4]>,
53) -> Result<()> {
54    // Check the lc_ctrl is initialized and ready to accept a transition request.
55    let status = jtag.read_lc_ctrl_reg(&LcCtrlReg::Status)?;
56    let status = LcCtrlStatus::from_bits(status).ok_or(LcTransitionError::InvalidState(status))?;
57    if status != LcCtrlStatus::INITIALIZED | LcCtrlStatus::READY {
58        return Err(LcTransitionError::LcCtrlNotReady(status).into());
59    }
60
61    // Check the LC transition mutex has not been claimed yet.
62    if jtag.read_lc_ctrl_reg(&LcCtrlReg::ClaimTransitionIf)? == u8::from(MultiBitBool8::True) as u32
63    {
64        return Err(LcTransitionError::MutexAlreadyClaimed.into());
65    }
66
67    // Attempt to claim the LC transition mutex.
68    jtag.write_lc_ctrl_reg(
69        &LcCtrlReg::ClaimTransitionIf,
70        u8::from(MultiBitBool8::True) as u32,
71    )?;
72
73    // Check the LC transition mutex was claimed.
74    if jtag.read_lc_ctrl_reg(&LcCtrlReg::ClaimTransitionIf)? != u8::from(MultiBitBool8::True) as u32
75    {
76        return Err(LcTransitionError::FailedToClaimMutex.into());
77    }
78
79    // Program the target LC state.
80    jtag.write_lc_ctrl_reg(
81        &LcCtrlReg::TransitionTarget,
82        target_lc_state.redundant_encoding(),
83    )?;
84
85    // Check correct target LC state was programmed.
86    let target_lc_state_programmed = DifLcCtrlState::from_redundant_encoding(
87        jtag.read_lc_ctrl_reg(&LcCtrlReg::TransitionTarget)?,
88    )?;
89    if target_lc_state_programmed != target_lc_state {
90        return Err(
91            LcTransitionError::TargetProgrammingFailed(target_lc_state_programmed.into()).into(),
92        );
93    }
94
95    // If the transition requires a token, write it to the multi-register.
96    if let Some(token_words) = token {
97        let token_regs = [
98            &LcCtrlReg::TransitionToken0,
99            &LcCtrlReg::TransitionToken1,
100            &LcCtrlReg::TransitionToken2,
101            &LcCtrlReg::TransitionToken3,
102        ];
103
104        for (reg, value) in iter::zip(token_regs, token_words) {
105            jtag.write_lc_ctrl_reg(reg, value)?;
106        }
107    }
108
109    Ok(())
110}
111
112/// Perform a lifecycle transition through the JTAG interface to the LC CTRL.
113///
114/// Requires the `jtag` to be already connected to the LC TAP.
115/// The device will be reset into the new lifecycle state.
116/// The `jtag` will be disconnected before resetting the device.
117/// Optionally, the function will setup JTAG straps to the requested interface.
118///
119/// # Examples
120///
121/// ```rust
122/// let init: InitializedTest;
123/// let transport = init.init_target().unwrap();
124///
125/// // Set TAP strapping to the LC controller.
126/// let tap_lc_strapping = transport.pin_strapping("PINMUX_TAP_LC").unwrap();
127/// tap_lc_strapping.apply().expect("failed to apply strapping");
128///
129/// // Reset into the new strapping.
130/// transport.reset_target(init.bootstrap.options.reset_delay, true).unwrap();
131///
132/// // Connect to the LC controller TAP.
133/// let mut jtag = transport
134///     .jtag(jtag_opts)
135///     .unwrap()
136///     .connect(JtagTap::LcTap)
137///     .expect("failed to connect to LC TAP");
138///
139/// let test_exit_token = DifLcCtrlToken::from([0xff; 16]);
140///
141/// lc_transition::trigger_lc_transition(
142///     &transport,
143///     jtag,
144///     DifLcCtrlState::Prod,
145///     Some(test_exit_token.into_register_values()),
146///     true,
147///     init.bootstrap.options.reset_delay,
148///     Some(JtagTap::LcTap),
149/// ).expect("failed to trigger transition to prod");
150///
151/// jtag = transport
152///     .jtag(jtag_opts)
153///     .unwrap()
154///     .connect(JtagTap::LcTap)
155///     .expect("failed to reconnect to LC TAP");
156///
157/// assert_eq!(
158///     jtag.read_lc_ctrl_reg(&LcCtrlReg::LCState).unwrap(),
159///     DifLcCtrlState::Prod.redundant_encoding(),
160/// );
161/// ```
162pub fn trigger_lc_transition(
163    transport: &TransportWrapper,
164    mut jtag: Box<dyn Jtag + '_>,
165    target_lc_state: DifLcCtrlState,
166    token: Option<[u32; 4]>,
167    use_external_clk: bool,
168    reset_delay: Duration,
169    reset_tap_straps: Option<JtagTap>,
170) -> Result<()> {
171    // Wait for the lc_ctrl to become initialized, claim the mutex, and program the target state
172    // and token CSRs.
173    setup_lc_transition(&mut *jtag, target_lc_state, token)?;
174
175    // Configure external clock.
176    if use_external_clk {
177        jtag.write_lc_ctrl_reg(
178            &LcCtrlReg::TransitionCtrl,
179            LcCtrlTransitionCtrl::EXT_CLOCK_EN.bits(),
180        )?;
181    } else {
182        jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl, 0)?;
183    }
184
185    // Initiate LC transition and poll status register until transition is completed.
186    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCmd, LcCtrlTransitionCmd::START.bits())?;
187
188    wait_for_status(
189        &mut *jtag,
190        Duration::from_secs(3),
191        LcCtrlStatus::TRANSITION_SUCCESSFUL,
192    )
193    .context("failed waiting for TRANSITION_SUCCESSFUL status.")?;
194
195    // Check we have entered the post transition state.
196    let post_transition_lc_state = jtag.read_lc_ctrl_reg(&LcCtrlReg::LcState)?;
197    if post_transition_lc_state != DifLcCtrlState::PostTransition.redundant_encoding() {
198        return Err(LcTransitionError::BadPostTransitionState(post_transition_lc_state).into());
199    }
200
201    // Reset the chip, selecting the requested JTAG TAP if necessary
202    jtag.disconnect()?;
203    if let Some(tap) = reset_tap_straps {
204        transport.pin_strapping("PINMUX_TAP_LC")?.remove()?;
205        match tap {
206            JtagTap::LcTap => transport.pin_strapping("PINMUX_TAP_LC")?.apply()?,
207            JtagTap::RiscvTap => transport.pin_strapping("PINMUX_TAP_RISCV")?.apply()?,
208        }
209    }
210    transport.reset_target(reset_delay, true)?;
211
212    Ok(())
213}
214
215/// Perform a volatile raw unlock transition through the LC JTAG interface.
216///
217/// Requires the `jtag` to be already connected to the LC TAP. Requires the pre-hashed token be
218/// provided (a pre-requisite of the volatile operation. The device will NOT be reset into the
219/// new lifecycle state as TAP straps are sampled again on a successfull transition. However,
220/// the TAP can be switched from LC to RISCV on a successfull transition.
221///
222/// If the feature is not present in HW we expect the transition to fail with
223/// a token error since the token is invalid for a real RAW unlock
224/// transition. Use the expect_raw_unlock_supported argument to indicate
225/// whether we expect this transition to succeed or not.
226#[allow(clippy::too_many_arguments)]
227pub fn trigger_volatile_raw_unlock<'t>(
228    transport: &'t TransportWrapper,
229    mut jtag: Box<dyn Jtag + 't>,
230    target_lc_state: DifLcCtrlState,
231    hashed_token: Option<[u32; 4]>,
232    use_external_clk: bool,
233    post_transition_tap: JtagTap,
234    jtag_params: &JtagParams,
235    expect_raw_unlock_supported: bool,
236) -> Result<Box<dyn Jtag + 't>> {
237    // Wait for the lc_ctrl to become initialized, claim the mutex, and program the target state
238    // and token CSRs.
239    setup_lc_transition(&mut *jtag, target_lc_state, hashed_token)?;
240
241    // Configure external clock and set volatile raw unlock bit.
242    let mut ctrl = LcCtrlTransitionCtrl::VOLATILE_RAW_UNLOCK;
243    if use_external_clk {
244        ctrl |= LcCtrlTransitionCtrl::EXT_CLOCK_EN;
245    }
246    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl, ctrl.bits())?;
247
248    // Read back the volatile raw unlock bit to see if the feature is supported in the silicon.
249    let read = jtag.read_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl)?;
250    if read < 2u32 && expect_raw_unlock_supported {
251        return Err(LcTransitionError::VolatileRawUnlockNotSupported.into());
252    } else if read >= 2u32 && !expect_raw_unlock_supported {
253        return Err(LcTransitionError::VolatileRawUnlockSupported.into());
254    }
255
256    // Select the requested JTAG TAP to connect to post-transition.
257    if post_transition_tap == JtagTap::RiscvTap {
258        transport.pin_strapping("PINMUX_TAP_LC")?.remove()?;
259        transport.pin_strapping("PINMUX_TAP_RISCV")?.apply()?;
260    }
261
262    // Initiate LC transition and poll status register until transition is completed.
263    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCmd, LcCtrlTransitionCmd::START.bits())?;
264
265    // Disconnect and reconnect to JTAG if we are switching to the RISCV TAP, as TAP straps are
266    // re-sampled on a successfull transition. We do this before we poll the status register
267    // because a volatile unlock will trigger a TAP strap resampling immediately upon success.
268    if post_transition_tap == JtagTap::RiscvTap {
269        jtag.disconnect()?;
270        jtag = transport.jtag(jtag_params)?.connect(JtagTap::RiscvTap)?;
271    }
272
273    if expect_raw_unlock_supported {
274        wait_for_status(
275            &mut *jtag,
276            Duration::from_secs(3),
277            LcCtrlStatus::TRANSITION_SUCCESSFUL,
278        )
279        .context("failed waiting for TRANSITION_SUCCESSFUL status.")?;
280    } else {
281        let mut status = LcCtrlStatus::INITIALIZED | LcCtrlStatus::TOKEN_ERROR;
282        if use_external_clk {
283            status |= LcCtrlStatus::EXT_CLOCK_SWITCHED;
284        }
285        wait_for_status(&mut *jtag, Duration::from_secs(3), status)
286            .context("failed waiting for TOKEN_ERROR status.")?;
287    }
288    Ok(jtag)
289}
290
291pub fn wait_for_status(jtag: &mut dyn Jtag, timeout: Duration, status: LcCtrlStatus) -> Result<()> {
292    let jtag_tap = jtag.tap();
293
294    // Wait for LC controller to be ready.
295    poll::poll_until(timeout, Duration::from_millis(50), || {
296        let polled_status = match jtag_tap {
297            JtagTap::LcTap => jtag.read_lc_ctrl_reg(&LcCtrlReg::Status).unwrap(),
298            JtagTap::RiscvTap => {
299                let mut status = [0u32];
300                jtag.read_memory32(
301                    top_earlgrey::LC_CTRL_REGS_BASE_ADDR as u32 + LcCtrlReg::Status as u32,
302                    &mut status,
303                )?;
304                status[0]
305            }
306        };
307
308        let polled_status =
309            LcCtrlStatus::from_bits(polled_status).context("status has invalid bits set")?;
310
311        // Check for any error bits set - however, we exclude the status that
312        // we are looking for in this comparison, since otherwise this
313        // function would just bail.
314        if polled_status.intersects(LcCtrlStatus::ERRORS & !status) {
315            bail!("status {polled_status:#b} has error bits set");
316        }
317
318        Ok(polled_status.contains(status))
319    })
320}