opentitanlib/util/
status.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 utility handles the various aspect of the status_t type used in
6// the device code.
7
8use std::convert::TryFrom;
9use std::ffi::CString;
10use std::path::PathBuf;
11
12use anyhow::{Context, Result, bail};
13use bindgen::status::{ot_status_create_record_t, status_create, status_err, status_extract};
14use object::{Object, ObjectSection};
15use zerocopy::FromBytes;
16
17pub use bindgen::status::absl_status_t as RawStatusCode;
18pub use bindgen::status::status_t as RawStatus;
19
20// FIXME: is there a better way of doing this? bindgen CLI does not
21// support adding custom derive to a type.
22#[derive(Debug, serde::Serialize, serde::Deserialize, strum::FromRepr, PartialEq, Eq)]
23#[repr(u32)]
24pub enum StatusCode {
25    Ok = bindgen::status::absl_status_code_kOk,
26    Cancelled = bindgen::status::absl_status_code_kCancelled,
27    Unknown = bindgen::status::absl_status_code_kUnknown,
28    InvalidArgument = bindgen::status::absl_status_code_kInvalidArgument,
29    DeadlineExceeded = bindgen::status::absl_status_code_kDeadlineExceeded,
30    NotFound = bindgen::status::absl_status_code_kNotFound,
31    AlreadyExists = bindgen::status::absl_status_code_kAlreadyExists,
32    PermissionDenied = bindgen::status::absl_status_code_kPermissionDenied,
33    ResourceExhausted = bindgen::status::absl_status_code_kResourceExhausted,
34    FailedPrecondition = bindgen::status::absl_status_code_kFailedPrecondition,
35    Aborted = bindgen::status::absl_status_code_kAborted,
36    OutOfRange = bindgen::status::absl_status_code_kOutOfRange,
37    Unimplemented = bindgen::status::absl_status_code_kUnimplemented,
38    Internal = bindgen::status::absl_status_code_kInternal,
39    Unavailable = bindgen::status::absl_status_code_kUnavailable,
40    DataLoss = bindgen::status::absl_status_code_kDataLoss,
41    Unauthenticated = bindgen::status::absl_status_code_kUnauthenticated,
42}
43
44// Safe abstraction over status_create
45fn status_create_safe(code: StatusCode, mod_id: u32, file: String, arg: i32) -> RawStatus {
46    // We do not expect an error since it is a valid String.
47    let file = CString::new(file).expect("CString::new failed");
48    // SAFETY: the function expects a valid readonly C-string which is exactly what
49    // CString:as_ptr() provides.
50    unsafe { status_create(code as u32, mod_id, file.as_ptr(), arg) }
51    // Note: file is dropped here so the C-string pointer is valid accross the function call.
52}
53
54// Convert an array of i8 to a string. This function will stop at first 0 (or at the
55// end of the array if it contains no zero).
56fn c_string_to_string(array: &[i8]) -> String {
57    let array = array
58        .iter()
59        .map(|c| *c as u8)
60        .take_while(|c| *c != 0u8)
61        .collect::<Vec<_>>();
62    String::from_utf8_lossy(&array).to_string()
63}
64
65#[derive(Debug, serde::Serialize, serde::Deserialize)]
66/// Hold a completely decoded status.
67pub struct Status {
68    // Module ID if present.
69    pub module_id: String,
70    // Status argument. Note that depending on whether this is an error status or ok
71    // status, the maximum value that can fit is different.
72    pub arg: i32,
73    // Error code of the status.
74    pub code: StatusCode,
75}
76
77// Safe abstraction over status_extract.
78impl Status {
79    pub fn from_raw_status(status: RawStatus) -> Result<Status> {
80        // We do not care about the code string but status_extract expects a non-null pointer.
81        let mut code_str: *const std::os::raw::c_char = std::ptr::null();
82        let mut arg = 0i32;
83        let mut mod_id: [std::os::raw::c_char; 3] = [0; 3];
84        let mod_id_ptr = &mut mod_id as *mut i8;
85        // SAFETY: status_extract expects:
86        // - a non-null pointer to string pointer that will be updated to point
87        //   to the english name of the error code,
88        // - a non-null pointer to an integer (argument),
89        // - a non-null pointer to a char[3] buffer that is filled with the module ID.
90        let is_err_status = unsafe { status_extract(status, &mut code_str, &mut arg, mod_id_ptr) };
91        let code = match is_err_status {
92            false => StatusCode::Ok,
93            true => {
94                // SAFETY: nothing unsafe except that it's an FFI call.
95                let raw_code = unsafe { status_err(status) };
96                StatusCode::from_repr(raw_code)
97                    .with_context(|| format!("invalid status code value {raw_code}"))?
98            }
99        };
100        Ok(Status {
101            module_id: c_string_to_string(&mod_id),
102            arg,
103            code,
104        })
105    }
106
107    pub fn from_u32(status: u32) -> Result<Status> {
108        Self::from_raw_status(RawStatus {
109            value: status as i32,
110        })
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117
118    #[test]
119    fn status_manip_test_good() {
120        // Make sure that we can correctly decode a status. For this, we first need to
121        // create it.
122        const CODE: StatusCode = StatusCode::PermissionDenied;
123        const MOD_ID: &str = "abc";
124        const ARG: i32 = 42;
125        let mod_id_bytes = MOD_ID.as_bytes();
126        assert_eq!(mod_id_bytes.len(), 3);
127        // Unfortunately the input to status_create assumes that the module ID value
128        // is already shifted and we cannot extract the MAKE_MODULE_ID macro from the
129        // headers using bindgen.
130        let mod_id_val = ((mod_id_bytes[0].to_ascii_uppercase() as u32) << 16)
131            | ((mod_id_bytes[1].to_ascii_uppercase() as u32) << 21)
132            | ((mod_id_bytes[2].to_ascii_uppercase() as u32) << 26);
133        let raw_status = status_create_safe(CODE, mod_id_val, "".to_string(), ARG);
134
135        // Now decode status.
136        let decoded_status = Status::from_raw_status(raw_status);
137        assert!(decoded_status.is_ok());
138        let decoded_status = decoded_status.unwrap();
139        assert_eq!(decoded_status.module_id, MOD_ID.to_ascii_uppercase());
140        assert_eq!(decoded_status.code, CODE);
141        assert_eq!(decoded_status.arg, ARG);
142    }
143}
144
145#[derive(Debug, serde::Serialize, serde::Deserialize)]
146/// Hold a status creation record as stored in the special ELF section of an executable.
147pub struct StatusCreateRecord {
148    // Module ID, or None if it is not specified (in which case it computed at runtime
149    // by status_create based on the filename).
150    pub module_id: Option<u32>,
151    // Name of the file that creates this status.
152    pub filename: String,
153}
154
155impl StatusCreateRecord {
156    /// Compute the Module ID actually created by status_create on device, that is either the
157    /// module_id or comes from the filename.
158    pub fn get_module_id(&self) -> Result<String> {
159        // In order to avoid reimplementing the algorithm, we first create a temporary
160        // status with status_create that we then decode using status_extract.
161        // If the value of MODULE_ID was not overriden in the file, then it is defined as
162        //   extern const uint32_t MODULE_ID;
163        // in status.h, and therefore it is not known at compile time. In this case, on the
164        // actual device, the value of MODULE_ID is 0.
165        const DEFAULT_MODULE_ID: u32 = 0;
166
167        let mod_id = self.module_id.map_or(DEFAULT_MODULE_ID, |mod_id| mod_id);
168        // We need to use a non-ok error code, otherwise the module ID is not stored.
169        let status = status_create_safe(StatusCode::Unknown, mod_id, self.filename.clone(), 0);
170        let status = Status::from_raw_status(status)?;
171        Ok(status.module_id)
172    }
173}
174
175// Fields that are unknown at compile time use special value.
176const UNKNOWN_MODULE_ID: u32 = bindgen::status::ot_status_create_record_magic_OT_SCR_UNKNOWN_MOD_ID;
177
178impl TryFrom<ot_status_create_record_t> for StatusCreateRecord {
179    type Error = anyhow::Error;
180
181    fn try_from(record: ot_status_create_record_t) -> Result<StatusCreateRecord> {
182        // A C string is an array of char so bindgen creates an array of i8, so we need to
183        // convert it to an array of u8. Furthermore, since it is a fixed-size array, it contains
184        // a lot of zeroes at the end which CString does not like and that we need to remove.
185        let filename = c_string_to_string(&record.filename);
186
187        Ok(StatusCreateRecord {
188            module_id: Some(record.module_id).filter(|mod_id| mod_id != &UNKNOWN_MODULE_ID),
189            filename,
190        })
191    }
192}
193
194#[derive(Debug, serde::Serialize, serde::Deserialize)]
195/// Hold a collection of status creation records
196pub struct StatusCreateRecords {
197    pub records: Vec<StatusCreateRecord>,
198}
199
200impl StatusCreateRecords {
201    /// Return the name of the file(s) that match a given module ID.
202    pub fn find_module_id(&self, mod_id: &String) -> Vec<String> {
203        let iter = self
204            .records
205            .iter()
206            .filter(|rec| rec.get_module_id().is_ok_and(|id| id == *mod_id))
207            .map(|rec| rec.filename.clone());
208        std::collections::HashSet::<String>::from_iter(iter)
209            .into_iter()
210            .collect()
211    }
212}
213
214pub fn load_elf(elf_file: &PathBuf) -> Result<StatusCreateRecords> {
215    let file_data = std::fs::read(elf_file)
216        .with_context(|| format!("Could not read ELF file {}.", elf_file.display()))?;
217    let file = object::File::parse(&*file_data)
218        .with_context(|| format!("Could not parse ELF file {}", elf_file.display()))?;
219    // Try to find the .ot.status_create_record section.
220    let section = file
221        .section_by_name(".ot.status_create_record")
222        .ok_or_else(|| {
223            anyhow::anyhow!("ELF file should have a .ot.status_create_record section")
224        })?;
225    let status_create_records = section
226        .data()
227        .context("cannot read .ot.status_create_record section data")?;
228    // Make sure that the section size is a multiple of the record size.
229    const RECORD_SIZE: usize = std::mem::size_of::<ot_status_create_record_t>();
230    if status_create_records.len() % RECORD_SIZE != 0 {
231        bail!(
232            ".ot.status_create_record section size ({}) is not a multiple of the ot_status_create_record_t size ({})",
233            status_create_records.len(),
234            RECORD_SIZE
235        );
236    }
237    // Conversion is unsafe but since the structure is packed and contains only POD,
238    // it really is safe.
239    let records = status_create_records
240        .chunks(RECORD_SIZE)
241        .map(|chunk| ot_status_create_record_t::read_from_bytes(chunk).unwrap())
242        .map(StatusCreateRecord::try_from)
243        .collect::<Result<_>>()?;
244    Ok(StatusCreateRecords { records })
245}