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 ot_hal::dif::lc_ctrl::{
13    DifLcCtrlState, LcCtrlReg, LcCtrlStatus, LcCtrlTransitionCmd, LcCtrlTransitionCtrl,
14};
15use ot_hal::top::earlgrey as top_earlgrey;
16use ot_hal::util::multibits::MultiBitBool8;
17
18use crate::app::{TransportWrapper, UartRx};
19use crate::impl_serializable_error;
20use crate::io::jtag::{Jtag, JtagParams, JtagTap};
21use crate::test_utils::poll;
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(UartRx::Clear).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///     Some(JtagTap::LcTap),
148/// ).expect("failed to trigger transition to prod");
149///
150/// jtag = transport
151///     .jtag(jtag_opts)
152///     .unwrap()
153///     .connect(JtagTap::LcTap)
154///     .expect("failed to reconnect to LC TAP");
155///
156/// assert_eq!(
157///     jtag.read_lc_ctrl_reg(&LcCtrlReg::LCState).unwrap(),
158///     DifLcCtrlState::Prod.redundant_encoding(),
159/// );
160/// ```
161pub fn trigger_lc_transition(
162    transport: &TransportWrapper,
163    mut jtag: Box<dyn Jtag + '_>,
164    target_lc_state: DifLcCtrlState,
165    token: Option<[u32; 4]>,
166    use_external_clk: bool,
167    reset_tap_straps: Option<JtagTap>,
168) -> Result<()> {
169    // Wait for the lc_ctrl to become initialized, claim the mutex, and program the target state
170    // and token CSRs.
171    setup_lc_transition(&mut *jtag, target_lc_state, token)?;
172
173    // Configure external clock.
174    if use_external_clk {
175        jtag.write_lc_ctrl_reg(
176            &LcCtrlReg::TransitionCtrl,
177            LcCtrlTransitionCtrl::EXT_CLOCK_EN.bits(),
178        )?;
179    } else {
180        jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl, 0)?;
181    }
182
183    // Initiate LC transition and poll status register until transition is completed.
184    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCmd, LcCtrlTransitionCmd::START.bits())?;
185
186    wait_for_status(
187        &mut *jtag,
188        Duration::from_secs(3),
189        LcCtrlStatus::TRANSITION_SUCCESSFUL,
190    )
191    .context("failed waiting for TRANSITION_SUCCESSFUL status.")?;
192
193    // Check we have entered the post transition state.
194    let post_transition_lc_state = jtag.read_lc_ctrl_reg(&LcCtrlReg::LcState)?;
195    if post_transition_lc_state != DifLcCtrlState::PostTransition.redundant_encoding() {
196        return Err(LcTransitionError::BadPostTransitionState(post_transition_lc_state).into());
197    }
198
199    // Reset the chip, selecting the requested JTAG TAP if necessary
200    jtag.disconnect()?;
201    if let Some(tap) = reset_tap_straps {
202        transport.pin_strapping("PINMUX_TAP_LC")?.remove()?;
203        match tap {
204            JtagTap::LcTap => transport.pin_strapping("PINMUX_TAP_LC")?.apply()?,
205            JtagTap::RiscvTap => transport.pin_strapping("PINMUX_TAP_RISCV")?.apply()?,
206        }
207    }
208    transport.reset(UartRx::Clear)?;
209
210    Ok(())
211}
212
213/// Perform a volatile raw unlock transition through the LC JTAG interface.
214///
215/// Requires the `jtag` to be already connected to the LC TAP. Requires the pre-hashed token be
216/// provided (a pre-requisite of the volatile operation. The device will NOT be reset into the
217/// new lifecycle state as TAP straps are sampled again on a successfull transition. However,
218/// the TAP can be switched from LC to RISCV on a successfull transition.
219///
220/// If the feature is not present in HW we expect the transition to fail with
221/// a token error since the token is invalid for a real RAW unlock
222/// transition. Use the expect_raw_unlock_supported argument to indicate
223/// whether we expect this transition to succeed or not.
224#[allow(clippy::too_many_arguments)]
225pub fn trigger_volatile_raw_unlock<'t>(
226    transport: &'t TransportWrapper,
227    mut jtag: Box<dyn Jtag + 't>,
228    target_lc_state: DifLcCtrlState,
229    hashed_token: Option<[u32; 4]>,
230    use_external_clk: bool,
231    post_transition_tap: JtagTap,
232    jtag_params: &JtagParams,
233    expect_raw_unlock_supported: bool,
234) -> Result<Box<dyn Jtag + 't>> {
235    // Wait for the lc_ctrl to become initialized, claim the mutex, and program the target state
236    // and token CSRs.
237    setup_lc_transition(&mut *jtag, target_lc_state, hashed_token)?;
238
239    // Configure external clock and set volatile raw unlock bit.
240    let mut ctrl = LcCtrlTransitionCtrl::VOLATILE_RAW_UNLOCK;
241    if use_external_clk {
242        ctrl |= LcCtrlTransitionCtrl::EXT_CLOCK_EN;
243    }
244    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl, ctrl.bits())?;
245
246    // Read back the volatile raw unlock bit to see if the feature is supported in the silicon.
247    let read = jtag.read_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl)?;
248    if read < 2u32 && expect_raw_unlock_supported {
249        return Err(LcTransitionError::VolatileRawUnlockNotSupported.into());
250    } else if read >= 2u32 && !expect_raw_unlock_supported {
251        return Err(LcTransitionError::VolatileRawUnlockSupported.into());
252    }
253
254    // Select the requested JTAG TAP to connect to post-transition.
255    if post_transition_tap == JtagTap::RiscvTap {
256        transport.pin_strapping("PINMUX_TAP_LC")?.remove()?;
257        transport.pin_strapping("PINMUX_TAP_RISCV")?.apply()?;
258    }
259
260    // Initiate LC transition and poll status register until transition is completed.
261    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCmd, LcCtrlTransitionCmd::START.bits())?;
262
263    // Disconnect and reconnect to JTAG if we are switching to the RISCV TAP, as TAP straps are
264    // re-sampled on a successfull transition. We do this before we poll the status register
265    // because a volatile unlock will trigger a TAP strap resampling immediately upon success.
266    if post_transition_tap == JtagTap::RiscvTap {
267        jtag.disconnect()?;
268        jtag = transport.jtag(jtag_params)?.connect(JtagTap::RiscvTap)?;
269    }
270
271    if expect_raw_unlock_supported {
272        wait_for_status(
273            &mut *jtag,
274            Duration::from_secs(3),
275            LcCtrlStatus::TRANSITION_SUCCESSFUL,
276        )
277        .context("failed waiting for TRANSITION_SUCCESSFUL status.")?;
278    } else {
279        let mut status = LcCtrlStatus::INITIALIZED | LcCtrlStatus::TOKEN_ERROR;
280        if use_external_clk {
281            status |= LcCtrlStatus::EXT_CLOCK_SWITCHED;
282        }
283        wait_for_status(&mut *jtag, Duration::from_secs(3), status)
284            .context("failed waiting for TOKEN_ERROR status.")?;
285    }
286    Ok(jtag)
287}
288
289pub fn wait_for_status(jtag: &mut dyn Jtag, timeout: Duration, status: LcCtrlStatus) -> Result<()> {
290    let jtag_tap = jtag.tap();
291
292    // Wait for LC controller to be ready.
293    poll::poll_until(timeout, Duration::from_millis(1), || {
294        let polled_status = match jtag_tap {
295            JtagTap::LcTap => jtag.read_lc_ctrl_reg(&LcCtrlReg::Status).unwrap(),
296            JtagTap::RiscvTap => {
297                let mut status = [0u32];
298                jtag.read_memory32(
299                    top_earlgrey::LC_CTRL_REGS_BASE_ADDR as u32 + LcCtrlReg::Status as u32,
300                    &mut status,
301                )?;
302                status[0]
303            }
304        };
305
306        let polled_status =
307            LcCtrlStatus::from_bits(polled_status).context("status has invalid bits set")?;
308
309        // Check for any error bits set - however, we exclude the status that
310        // we are looking for in this comparison, since otherwise this
311        // function would just bail.
312        if polled_status.intersects(LcCtrlStatus::ERRORS & !status) {
313            bail!("status {polled_status:#b} has error bits set");
314        }
315
316        Ok(polled_status.contains(status))
317    })
318}