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