opentitanlib/test_utils/
otp_ctrl.rs1use 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
26pub struct OtpParam;
28impl OtpParam {
29 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 pub fn write_param(jtag: &mut dyn Jtag, param: DaiParam, data: &[u32]) -> OtpDaiResult<()> {
72 let OtpParamMmap { byte_addr, size } = param.mmap();
73
74 if !SECRET_PARTITIONS.contains(¶m.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
118pub struct OtpPartition;
120impl OtpPartition {
121 pub fn lock(jtag: &mut dyn Jtag, partition: Partition) -> OtpDaiResult<()> {
123 OtpDai::lock(jtag, partition.byte_addr)
124 }
125
126 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
137struct OtpDai;
139impl OtpDai {
140 const OTP_CTRL_BASE_ADDR: u32 = top_earlgrey::OTP_CTRL_CORE_BASE_ADDR as u32;
142
143 const POLL_TIMEOUT: Duration = Duration::from_millis(500);
145 const POLL_DELAY: Duration = Duration::from_millis(5);
146
147 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 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 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 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 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 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 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 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 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 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#[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}