opentitanlib/test_utils/
otp_ctrl.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
5//! This module contains code for programming and reading OTP parameters
6//! through the Direct Access Interface.
7//!
8//! Beware that OTP parameters can only be written once and can be corrupted if
9//! written again.
10
11use std::mem;
12use std::time::Duration;
13
14use anyhow::{Context, bail};
15use thiserror::Error;
16
17use top_earlgrey::top_earlgrey;
18
19use crate::dif::otp_ctrl::{
20    DaiParam, DirectAccessCmd, Granularity, OtpCtrlReg, OtpCtrlStatus, OtpParamMmap, Partition,
21    SECRET_PARTITIONS,
22};
23use crate::io::jtag::Jtag;
24use crate::test_utils::poll;
25
26/// Controls for reading and writing OTP parameters.
27pub struct OtpParam;
28impl OtpParam {
29    /// Read a parameter from OTP into an output buffer.
30    pub fn read_param(
31        jtag: &mut dyn Jtag,
32        param: DaiParam,
33        out_buf: &mut [u32],
34    ) -> OtpDaiResult<()> {
35        let OtpParamMmap { byte_addr, size } = param.mmap();
36
37        if mem::size_of_val(out_buf) < size as usize {
38            let err = OtpDaiError::BufSize {
39                buf_size: mem::size_of_val(out_buf),
40                param_size: size,
41            };
42            return Err(err);
43        }
44
45        let Partition { access_granule, .. } = param.partition();
46
47        match access_granule {
48            Granularity::B32 => {
49                let out_iter = out_buf.iter_mut().take(size as usize);
50                for (idx, out_word) in out_iter.enumerate() {
51                    let addr = byte_addr + (idx * mem::size_of::<u32>()) as u32;
52                    let [lower, _] = OtpDai::read(jtag, addr, access_granule)?;
53                    *out_word = lower;
54                }
55            }
56            Granularity::B64 => {
57                let out_iter = out_buf.chunks_mut(2).take(size as usize / 2);
58                for (idx, out_words) in out_iter.enumerate() {
59                    let addr = byte_addr + (idx * mem::size_of::<u64>()) as u32;
60                    let otp_words = OtpDai::read(jtag, addr, access_granule)?;
61                    out_words[0] = otp_words[0];
62                    out_words[1] = otp_words[1];
63                }
64            }
65        }
66
67        Ok(())
68    }
69
70    /// Write a value from a buffer to an OTP parameter.
71    pub fn write_param(jtag: &mut dyn Jtag, param: DaiParam, data: &[u32]) -> OtpDaiResult<()> {
72        let OtpParamMmap { byte_addr, size } = param.mmap();
73
74        // Check if the word has been written already.
75        // Note: we can only check non-secret partitions, since secret partitions are scrambled,
76        // when they are backdoor loaded with 0s they will be garbage when read out.
77        if !SECRET_PARTITIONS.contains(&param.partition()) {
78            let mut curr_value = [0u32].repeat((size / 4).try_into().unwrap());
79            Self::read_param(jtag, param, curr_value.as_mut_slice())?;
80            for word in curr_value.iter() {
81                if *word != 0u32 {
82                    return Err(OtpDaiError::WriteErrorAlreadyWritten { value: *word });
83                }
84            }
85        }
86
87        if mem::size_of_val(data) > size as usize {
88            let err = OtpDaiError::BufSize {
89                buf_size: mem::size_of_val(data),
90                param_size: size,
91            };
92            return Err(err);
93        }
94
95        let Partition { access_granule, .. } = param.partition();
96
97        match access_granule {
98            Granularity::B32 => {
99                let data_iter = data.iter().take(size as usize);
100                for (idx, data_word) in data_iter.enumerate() {
101                    let addr = byte_addr + (idx * mem::size_of::<u32>()) as u32;
102                    OtpDai::write(jtag, addr, access_granule, [*data_word, 0x00])?;
103                }
104            }
105            Granularity::B64 => {
106                let data_iter = data.chunks(2).take(size as usize / 2);
107                for (idx, data_words) in data_iter.enumerate() {
108                    let addr = byte_addr + (idx * mem::size_of::<u64>()) as u32;
109                    OtpDai::write(jtag, addr, access_granule, [data_words[0], data_words[1]])?;
110                }
111            }
112        }
113
114        Ok(())
115    }
116}
117
118/// Commands for operating on OTP partitions.
119pub struct OtpPartition;
120impl OtpPartition {
121    /// Lock the given partition by calculating its digest.
122    pub fn lock(jtag: &mut dyn Jtag, partition: Partition) -> OtpDaiResult<()> {
123        OtpDai::lock(jtag, partition.byte_addr)
124    }
125
126    /// Read back this partition's digest from the OTP.
127    ///
128    /// This goes via the DAI, but partitions are also exposed via CSRs after reset.
129    pub fn read_digest(jtag: &mut dyn Jtag, partition: Partition) -> OtpDaiResult<[u32; 2]> {
130        let OtpParamMmap { byte_addr, size } = partition.digest;
131        assert_eq!(size, 8, "OTP partition digests should be 2 words in size");
132
133        OtpDai::read(jtag, byte_addr, Granularity::B64)
134    }
135}
136
137/// Direct Access Interface to the OTP.
138struct OtpDai;
139impl OtpDai {
140    /// Base address of the OTP controller in memory.
141    const OTP_CTRL_BASE_ADDR: u32 = top_earlgrey::OTP_CTRL_CORE_BASE_ADDR as u32;
142
143    // Polling timeout and delay while waiting on statuses.
144    const POLL_TIMEOUT: Duration = Duration::from_millis(500);
145    const POLL_DELAY: Duration = Duration::from_millis(5);
146
147    /// Perform a single read over the Direct Access Interface.
148    ///
149    /// On success, returns an array of words `[lower, upper]` where `upper` is non-zero
150    /// for 64-bit granularity reads.
151    pub fn read(
152        jtag: &mut dyn Jtag,
153        byte_addr: u32,
154        granule: Granularity,
155    ) -> OtpDaiResult<[u32; 2]> {
156        Self::wait_for_idle(jtag)?;
157
158        // Set the DAI address to read from.
159        let dai_address_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessAddress as u32;
160        jtag.write_memory32(dai_address_addr, &[byte_addr])
161            .map_err(|source| OtpDaiError::Jtag { source })?;
162
163        // Trigger a read command.
164        let dai_cmd_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessCmd as u32;
165        jtag.write_memory32(dai_cmd_addr, &[DirectAccessCmd::RD.bits()])
166            .map_err(|source| OtpDaiError::Jtag { source })?;
167
168        Self::wait_for_idle(jtag)?;
169
170        let dai_rdata0_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessRdata0 as u32;
171        let dai_rdata1_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessRdata1 as u32;
172
173        let mut rdata = [0x00; 2];
174
175        jtag.read_memory32(dai_rdata0_addr, &mut rdata[0..=0])
176            .map_err(|source| OtpDaiError::Jtag { source })?;
177
178        if granule == Granularity::B64 {
179            jtag.read_memory32(dai_rdata1_addr, &mut rdata[1..=1])
180                .map_err(|source| OtpDaiError::Jtag { source })?;
181        }
182
183        Ok(rdata)
184    }
185
186    /// Perform a single write over the Direct Access Interface.
187    pub fn write(
188        jtag: &mut dyn Jtag,
189        byte_addr: u32,
190        granule: Granularity,
191        values: [u32; 2],
192    ) -> OtpDaiResult<()> {
193        Self::wait_for_idle(jtag)?;
194
195        // Set the DAI address to write to.
196        let dai_address_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessAddress as u32;
197        jtag.write_memory32(dai_address_addr, &[byte_addr])
198            .map_err(|source| OtpDaiError::Jtag { source })?;
199
200        let dai_wdata0_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessWdata0 as u32;
201        let dai_wdata1_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessWdata1 as u32;
202
203        jtag.write_memory32(dai_wdata0_addr, &[values[0]])
204            .map_err(|source| OtpDaiError::Jtag { source })?;
205
206        if granule == Granularity::B64 {
207            jtag.write_memory32(dai_wdata1_addr, &[values[1]])
208                .map_err(|source| OtpDaiError::Jtag { source })?;
209        }
210
211        // Trigger a write command.
212        let dai_cmd_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessCmd as u32;
213        jtag.write_memory32(dai_cmd_addr, &[DirectAccessCmd::WR.bits()])
214            .map_err(|source| OtpDaiError::Jtag { source })?;
215
216        Self::wait_for_idle(jtag)?;
217
218        Ok(())
219    }
220
221    /// Lock the partition starting at offset `byte_addr`.
222    pub fn lock(jtag: &mut dyn Jtag, byte_addr: u32) -> OtpDaiResult<()> {
223        if byte_addr == Partition::CREATOR_SW_CFG.byte_addr
224            || byte_addr == Partition::OWNER_SW_CFG.byte_addr
225        {
226            return Err(OtpDaiError::NotSupported);
227        }
228        Self::wait_for_idle(jtag)?;
229
230        // Set the DAI address to the start of the partition.
231        let dai_address_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessAddress as u32;
232        jtag.write_memory32(dai_address_addr, &[byte_addr])
233            .map_err(|source| OtpDaiError::Jtag { source })?;
234
235        // Trigger a digest calculation command.
236        let dai_cmd_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::DirectAccessCmd as u32;
237        jtag.write_memory32(dai_cmd_addr, &[DirectAccessCmd::DIGEST.bits()])
238            .map_err(|source| OtpDaiError::Jtag { source })?;
239
240        Self::wait_for_idle(jtag)?;
241
242        Ok(())
243    }
244
245    /// Wait for the OTP controller's status to read `DAI_IDLE`, showing it's
246    /// ready to accept commands.
247    pub fn wait_for_idle(jtag: &mut dyn Jtag) -> Result<(), OtpDaiError> {
248        let otp_status_addr = Self::OTP_CTRL_BASE_ADDR + OtpCtrlReg::Status as u32;
249
250        poll::poll_until(Self::POLL_TIMEOUT, Self::POLL_DELAY, || {
251            let mut status = [0];
252            jtag.read_memory32(otp_status_addr, &mut status)
253                .map_err(|source| OtpDaiError::Jtag { source })?;
254
255            let status =
256                OtpCtrlStatus::from_bits(status[0]).context("status has invalid bits set")?;
257
258            if status.intersects(OtpCtrlStatus::ERRORS) {
259                bail!("status {status:#b} has error bits set");
260            }
261
262            Ok(status.contains(OtpCtrlStatus::DAI_IDLE))
263        })
264        .map_err(|source| OtpDaiError::WaitForIdle { source })
265    }
266}
267
268pub type OtpDaiResult<T> = Result<T, OtpDaiError>;
269
270/// Failures to write OTP parameters through the Direct Access Interface.
271#[derive(Debug, Error)]
272pub enum OtpDaiError {
273    #[error("provided buffer has invalid size {buf_size} for parameter of size {param_size}")]
274    BufSize { buf_size: usize, param_size: u32 },
275
276    #[error("feature not supported for current partition")]
277    NotSupported,
278
279    #[error("failed to communicate over JTAG")]
280    Jtag { source: anyhow::Error },
281
282    #[error("failed to wait for otp_ctrl DAI to be idle")]
283    WaitForIdle { source: anyhow::Error },
284
285    #[error("writing to otp_ctrl direct access registers is disabled")]
286    WriteDisabled,
287
288    #[error(
289        "writing to OTP failed since field has already been written (with value: 0x{value:08X})"
290    )]
291    WriteErrorAlreadyWritten { value: u32 },
292}