Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 1 | // Copyright 2024 The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! Implements converting file system to FDT blob |
| 16 | |
| 17 | use anyhow::{anyhow, Context, Result}; |
| 18 | use libfdt::Fdt; |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 19 | use std::ffi::{CStr, CString}; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 20 | use std::fs; |
| 21 | use std::os::unix::ffi::OsStrExt; |
| 22 | use std::path::Path; |
| 23 | |
| 24 | /// Trait for Fdt's file system support |
| 25 | pub trait FsFdt<'a> { |
| 26 | /// Creates a Fdt from /proc/device-tree style directory by wrapping a mutable slice |
| 27 | fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Self>; |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 28 | |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 29 | /// Overlay an FDT from /proc/device-tree style directory at the given node path |
| 30 | fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 31 | } |
| 32 | |
| 33 | impl<'a> FsFdt<'a> for Fdt { |
| 34 | fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Fdt> { |
| 35 | let fdt = Fdt::create_empty_tree(fdt_buffer) |
| 36 | .map_err(|e| anyhow!("Failed to create FDT, {e:?}"))?; |
| 37 | |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 38 | fdt.overlay_onto(&CString::new("").unwrap(), fs_path)?; |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 39 | |
| 40 | Ok(fdt) |
| 41 | } |
| 42 | |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 43 | fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()> { |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 44 | // Recursively traverse fs_path with DFS algorithm. |
| 45 | let mut stack = vec![fs_path.to_path_buf()]; |
| 46 | while let Some(dir_path) = stack.pop() { |
| 47 | let relative_path = dir_path |
| 48 | .strip_prefix(fs_path) |
| 49 | .context("Internal error. Path does not have expected prefix")? |
| 50 | .as_os_str(); |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 51 | let fdt_path = CString::from_vec_with_nul( |
| 52 | [fdt_node_path.to_bytes(), b"/", relative_path.as_bytes(), b"\0"].concat(), |
| 53 | ) |
| 54 | .context("Internal error. Path is not a valid Fdt path")?; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 55 | |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 56 | let mut node = self |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 57 | .node_mut(&fdt_path) |
| 58 | .map_err(|e| anyhow!("Failed to write FDT, {e:?}"))? |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 59 | .ok_or_else(|| anyhow!("Failed to find {fdt_path:?} in FDT"))?; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 60 | |
| 61 | let mut subnode_names = vec![]; |
| 62 | let entries = |
| 63 | fs::read_dir(&dir_path).with_context(|| format!("Failed to read {dir_path:?}"))?; |
| 64 | for entry in entries { |
| 65 | let entry = |
| 66 | entry.with_context(|| format!("Failed to get an entry in {dir_path:?}"))?; |
| 67 | let entry_type = |
| 68 | entry.file_type().with_context(|| "Unsupported entry type, {entry:?}")?; |
| 69 | let entry_name = entry.file_name(); // binding to keep name below. |
| 70 | if !entry_name.is_ascii() { |
| 71 | return Err(anyhow!("Unsupported entry name for FDT, {entry:?}")); |
| 72 | } |
| 73 | // Safe to unwrap because validated as an ascii string above. |
| 74 | let name = CString::new(entry_name.as_bytes()).unwrap(); |
| 75 | if entry_type.is_dir() { |
| 76 | stack.push(entry.path()); |
| 77 | subnode_names.push(name); |
| 78 | } else if entry_type.is_file() { |
Chris Wailes | 63b67d7 | 2024-08-19 16:23:21 -0700 | [diff] [blame^] | 79 | let value = fs::read(entry.path())?; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 80 | |
| 81 | node.setprop(&name, &value) |
| 82 | .map_err(|e| anyhow!("Failed to set FDT property, {e:?}"))?; |
| 83 | } else { |
| 84 | return Err(anyhow!( |
| 85 | "Failed to handle {entry:?}. FDT only uses file or directory" |
| 86 | )); |
| 87 | } |
| 88 | } |
| 89 | // Note: sort() is necessary to prevent FdtError::Exists from add_subnodes(). |
| 90 | // FDT library may omit address in node name when comparing their name, so sort to add |
| 91 | // node without address first. |
| 92 | subnode_names.sort(); |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 93 | let subnode_names: Vec<_> = subnode_names |
| 94 | .iter() |
| 95 | .filter_map(|name| { |
| 96 | // Filter out subnode names which are already present in the target parent node! |
| 97 | let name = name.as_c_str(); |
| 98 | let is_present_res = node.as_node().subnode(name); |
| 99 | match is_present_res { |
| 100 | Ok(Some(_)) => None, |
| 101 | Ok(None) => Some(Ok(name)), |
| 102 | Err(e) => Some(Err(e)), |
| 103 | } |
| 104 | }) |
| 105 | .collect::<Result<_, _>>() |
| 106 | .map_err(|e| anyhow!("Failed to filter subnodes, {e:?}"))?; |
| 107 | node.add_subnodes(&subnode_names).map_err(|e| anyhow!("Failed to add node, {e:?}"))?; |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 108 | } |
| 109 | |
Jaewan Kim | 355fcd8 | 2024-01-31 23:36:46 +0900 | [diff] [blame] | 110 | Ok(()) |
Jaewan Kim | 9cf731b | 2024-01-19 14:45:45 +0900 | [diff] [blame] | 111 | } |
| 112 | } |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 113 | |
| 114 | #[cfg(test)] |
| 115 | mod test { |
| 116 | use super::*; |
Jaewan Kim | 0bb7cd7 | 2024-02-17 00:00:38 +0900 | [diff] [blame] | 117 | use dts::Dts; |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 118 | |
| 119 | const TEST_FS_FDT_ROOT_PATH: &str = "testdata/fs"; |
| 120 | const BUF_SIZE_MAX: usize = 1024; |
| 121 | |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 122 | #[test] |
| 123 | fn test_from_fs() { |
| 124 | let fs_path = Path::new(TEST_FS_FDT_ROOT_PATH); |
| 125 | |
| 126 | let mut data = vec![0_u8; BUF_SIZE_MAX]; |
| 127 | let fdt = Fdt::from_fs(fs_path, &mut data).unwrap(); |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 128 | |
Jaewan Kim | 0bb7cd7 | 2024-02-17 00:00:38 +0900 | [diff] [blame] | 129 | let expected = Dts::from_fs(fs_path).unwrap(); |
| 130 | let actual = Dts::from_fdt(fdt).unwrap(); |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 131 | |
| 132 | assert_eq!(&expected, &actual); |
Shikha Panwar | 584f6cb | 2024-02-13 17:07:30 +0000 | [diff] [blame] | 133 | // Again append fdt from TEST_FS_FDT_ROOT_PATH at root & ensure it succeeds when some |
| 134 | // subnode are already present. |
| 135 | fdt.overlay_onto(&CString::new("/").unwrap(), fs_path).unwrap(); |
Jaewan Kim | aea90e5 | 2024-02-05 08:59:51 +0900 | [diff] [blame] | 136 | } |
| 137 | } |