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        }
222    }
223    transport.reset(UartRx::Clear)?;
224
225    Ok(())
226}
227
228/// Perform a volatile raw unlock transition through the LC JTAG interface.
229///
230/// Requires the `jtag` to be already connected to the LC TAP. Requires the pre-hashed token be
231/// provided (a pre-requisite of the volatile operation. The device will NOT be reset into the
232/// new lifecycle state as TAP straps are sampled again on a successfull transition. However,
233/// the TAP can be switched from LC to RISCV on a successfull transition.
234///
235/// If the feature is not present in HW we expect the transition to fail with
236/// a token error since the token is invalid for a real RAW unlock
237/// transition. Use the expect_raw_unlock_supported argument to indicate
238/// whether we expect this transition to succeed or not.
239#[allow(clippy::too_many_arguments)]
240pub fn trigger_volatile_raw_unlock<'t>(
241    transport: &'t TransportWrapper,
242    mut jtag: Box<dyn Jtag + 't>,
243    target_lc_state: DifLcCtrlState,
244    hashed_token: Option<[u32; 4]>,
245    use_external_clk: bool,
246    post_transition_tap: JtagTap,
247    jtag_params: &JtagParams,
248    expect_raw_unlock_supported: bool,
249) -> Result<Box<dyn Jtag + 't>> {
250    // Wait for the lc_ctrl to become initialized, claim the mutex, and program the target state
251    // and token CSRs.
252    setup_lc_transition(&mut *jtag, target_lc_state, hashed_token)?;
253
254    // Configure external clock and set volatile raw unlock bit.
255    let mut ctrl = LcCtrlTransitionCtrl::VOLATILE_RAW_UNLOCK;
256    if use_external_clk {
257        ctrl |= LcCtrlTransitionCtrl::EXT_CLOCK_EN;
258    }
259    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl, ctrl.bits())?;
260
261    // Read back the volatile raw unlock bit to see if the feature is supported in the silicon.
262    let read = jtag.read_lc_ctrl_reg(&LcCtrlReg::TransitionCtrl)?;
263    if read < 2u32 && expect_raw_unlock_supported {
264        return Err(LcTransitionError::VolatileRawUnlockNotSupported.into());
265    } else if read >= 2u32 && !expect_raw_unlock_supported {
266        return Err(LcTransitionError::VolatileRawUnlockSupported.into());
267    }
268
269    // Select the requested JTAG TAP to connect to post-transition.
270    if post_transition_tap == JtagTap::RiscvTap {
271        transport.pin_strapping("PINMUX_TAP_LC")?.remove()?;
272        transport.pin_strapping("PINMUX_TAP_RISCV")?.apply()?;
273    }
274
275    // Initiate LC transition and poll status register until transition is completed.
276    jtag.write_lc_ctrl_reg(&LcCtrlReg::TransitionCmd, LcCtrlTransitionCmd::START.bits())?;
277
278    // Disconnect and reconnect to JTAG if we are switching to the RISCV TAP, as TAP straps are
279    // re-sampled on a successfull transition. We do this before we poll the status register
280    // because a volatile unlock will trigger a TAP strap resampling immediately upon success.
281    if post_transition_tap == JtagTap::RiscvTap {
282        jtag.disconnect()?;
283        jtag = transport.jtag(jtag_params)?.connect(JtagTap::RiscvTap)?;
284    }
285
286    if expect_raw_unlock_supported {
287        wait_for_status(
288            &mut *jtag,
289            Duration::from_secs(3),
290            LcCtrlStatus::TRANSITION_SUCCESSFUL,
291        )
292        .context("failed waiting for TRANSITION_SUCCESSFUL status.")?;
293    } else {
294        let mut status = LcCtrlStatus::INITIALIZED | LcCtrlStatus::TOKEN_ERROR;
295        if use_external_clk {
296            status |= LcCtrlStatus::EXT_CLOCK_SWITCHED;
297        }
298        wait_for_status(&mut *jtag, Duration::from_secs(3), status)
299            .context("failed waiting for TOKEN_ERROR status.")?;
300    }
301    Ok(jtag)
302}
303
304pub fn wait_for_status(jtag: &mut dyn Jtag, timeout: Duration, status: LcCtrlStatus) -> Result<()> {
305    let jtag_tap = jtag.tap();
306
307    // Wait for LC controller to be ready.
308    poll::poll_until(timeout, Duration::from_millis(1), || {
309        let polled_status = match jtag_tap {
310            JtagTap::LcTap => jtag.read_lc_ctrl_reg(&LcCtrlReg::Status).unwrap(),
311            JtagTap::RiscvTap => {
312                let mut status = [0u32];
313                jtag.read_memory32(
314                    top_earlgrey::LC_CTRL_REGS_BASE_ADDR as u32 + LcCtrlReg::Status as u32,
315                    &mut status,
316                )?;
317                status[0]
318            }
319        };
320
321        let polled_status =
322            LcCtrlStatus::from_bits(polled_status).context("status has invalid bits set")?;
323
324        // Check for any error bits set - however, we exclude the status that
325        // we are looking for in this comparison, since otherwise this
326        // function would just bail.
327        if polled_status.intersects(LcCtrlStatus::ERRORS & !status) {
328            bail!("status {polled_status:#b} has error bits set");
329        }
330
331        Ok(polled_status.contains(status))
332    })
333}