1use super::tracing_directory::TraceMarkerFile;
6use starnix_core::perf::TraceEventQueue;
7use starnix_core::task::CurrentTask;
8use starnix_core::vfs::pseudo::dynamic_file::ConstFile;
9use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
10use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps, SimpleFileNode};
11use starnix_core::vfs::{
12 CacheMode, FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions,
13 FsNodeOps, FsStr, OutputBuffer,
14};
15use starnix_core::{fileops_impl_nonseekable, fileops_impl_noop_sync};
16use starnix_logging::track_stub;
17use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
18use starnix_types::PAGE_SIZE;
19use starnix_types::vfs::default_statfs;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::mode;
22use starnix_uapi::{TRACEFS_MAGIC, errno, error, statfs};
23use std::borrow::Cow;
24use std::sync::{Arc, LazyLock};
25
26pub fn trace_fs(
27 locked: &mut Locked<Unlocked>,
28 current_task: &CurrentTask,
29 options: FileSystemOptions,
30) -> Result<FileSystemHandle, Errno> {
31 struct TraceFsHandle(FileSystemHandle);
32
33 let handle = current_task.kernel().expando.get_or_init(|| {
34 TraceFsHandle(
35 TraceFs::new_fs(locked, current_task, options)
36 .expect("tracefs constructed with valid options"),
37 )
38 });
39 Ok(handle.0.clone())
40}
41
42pub struct TraceFs;
43
44impl FileSystemOps for TraceFs {
45 fn statfs(
46 &self,
47 _locked: &mut Locked<FileOpsCore>,
48 _fs: &FileSystem,
49 _current_task: &CurrentTask,
50 ) -> Result<statfs, Errno> {
51 Ok(default_statfs(TRACEFS_MAGIC))
52 }
53
54 fn name(&self) -> &'static FsStr {
55 "tracefs".into()
56 }
57}
58
59impl TraceFs {
60 pub fn new_fs<L>(
61 locked: &mut Locked<L>,
62 current_task: &CurrentTask,
63 options: FileSystemOptions,
64 ) -> Result<FileSystemHandle, Errno>
65 where
66 L: LockEqualOrBefore<FileOpsCore>,
67 {
68 let kernel = current_task.kernel();
69 let trace_event_queue = TraceEventQueue::from(kernel);
71 let fs = FileSystem::new(locked, kernel, CacheMode::Uncached, TraceFs, options)?;
72 let dir = SimpleDirectory::new();
73 dir.edit(&fs, |dir| {
74 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o755));
75 dir.subdir("per_cpu", 0o755, |dir| {
76 static CPU_DIR_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
78 (0..zx::system_get_num_cpus()).map(|cpu| format!("cpu{}", cpu)).collect()
79 });
80 for dir_name in CPU_DIR_NAMES.iter().map(|s| s.as_str()) {
81 dir.subdir(dir_name, 0o755, |dir| {
82 let ops: Box<dyn FsNodeOps> = if dir_name == "cpu0" {
85 Box::new(TraceRawFile::new_node(trace_event_queue.clone()))
86 } else {
87 track_stub!(
88 TODO("https://fxbug.dev/357665908"),
89 "/sys/kernel/tracing/per_cpu/cpuX/trace_pipe_raw"
90 );
91 Box::new(EmptyFile::new_node())
92 };
93 dir.entry("trace_pipe_raw", ops, mode!(IFREG, 0o444));
94 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o444));
95 });
96 }
97 });
98 dir.subdir("events", 0o755, |dir| {
99 dir.entry("header_page", EventsHeaderPage::new_node(), mode!(IFREG, 0o444));
100 dir.subdir("ftrace", 0o755, |dir| {
101 dir.subdir("print", 0o755, |dir| {
102 dir.entry("format", FtracePrintFormatFile::new_node(), mode!(IFREG, 0o444));
103 });
104 if kernel.features.selinux_test_suite {
105 dir.subdir("function", 0o755, |dir| {
108 dir.entry(
109 "id",
110 BytesFile::new_node(b"1\n".to_vec()),
111 mode!(IFREG, 0o444),
112 );
113 });
114 }
115 });
116 dir.entry("enable", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
117 });
118 dir.subdir("options", 0o755, |dir| {
119 dir.entry("overwrite", TraceBytesFile::new_node(), mode!(IFREG, 0o444));
120 });
121 dir.entry(
122 "tracing_on",
123 TracingOnFile::new_node(trace_event_queue.clone()),
124 mode!(IFREG, 0o666),
125 );
126 dir.entry("current_tracer", ConstFile::new_node("nop".into()), mode!(IFREG, 0o755));
127 dir.entry(
128 "trace_marker",
129 TraceMarkerFile::new_node(trace_event_queue.clone()),
130 mode!(IFREG, 0o222),
131 );
132 dir.entry("printk_formats", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
133 dir.entry(
134 "trace_clock",
135 ConstFile::new_node(
136 "[local] global counter uptime perf mono mono_raw boot tai x86-tsc ".into(),
137 ),
138 mode!(IFREG, 0o755),
139 );
140 dir.entry("buffer_size_kb", ConstFile::new_node("7".into()), mode!(IFREG, 0o755));
141 });
142
143 let root_ino = fs.allocate_ino();
144 fs.create_root(root_ino, dir);
145 Ok(fs)
146 }
147}
148
149#[derive(Default)]
150struct TraceBytesFile {
151 data: Mutex<Vec<u8>>,
152}
153
154impl TraceBytesFile {
155 #[track_caller]
156 fn new_node() -> impl FsNodeOps {
157 SimpleFileNode::new(move |_, _| Ok(BytesFile::new(Self::default())))
158 }
159}
160
161impl BytesFileOps for TraceBytesFile {
162 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
163 *self.data.lock() = data;
164 Ok(())
165 }
166 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
167 let data = self.data.lock().clone();
168 Ok(data.into())
169 }
170}
171
172#[derive(Clone)]
173struct TraceRawFile {
174 queue: Arc<TraceEventQueue>,
175}
176
177impl TraceRawFile {
178 pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
179 SimpleFileNode::new(move |_, _| Ok(Self { queue: queue.clone() }))
180 }
181}
182
183impl FileOps for TraceRawFile {
184 fileops_impl_nonseekable!();
185 fileops_impl_noop_sync!();
186
187 fn read(
188 &self,
189 _locked: &mut Locked<FileOpsCore>,
190 _file: &FileObject,
191 _current_task: &CurrentTask,
192 _offset: usize,
193 data: &mut dyn OutputBuffer,
194 ) -> Result<usize, Errno> {
195 assert!(data.available() as u64 == *PAGE_SIZE);
196 self.queue.read(data)
197 }
198
199 fn write(
200 &self,
201 _locked: &mut Locked<FileOpsCore>,
202 _file: &FileObject,
203 _current_task: &CurrentTask,
204 _offset: usize,
205 _data: &mut dyn starnix_core::vfs::InputBuffer,
206 ) -> Result<usize, Errno> {
207 error!(ENOSYS)
208 }
209}
210
211#[derive(Default)]
212struct EmptyFile {}
213
214impl EmptyFile {
215 #[track_caller]
216 fn new_node() -> impl FsNodeOps {
217 SimpleFileNode::new(move |_, _| Ok(BytesFile::new(Self::default())))
218 }
219}
220
221impl BytesFileOps for EmptyFile {
222 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
223 error!(ENOTSUP)
224 }
225 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
226 error!(EAGAIN)
227 }
228}
229
230#[derive(Clone)]
231struct TracingOnFile {
232 queue: Arc<TraceEventQueue>,
233}
234
235impl TracingOnFile {
236 pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
237 BytesFile::new_node(Self { queue })
238 }
239}
240
241impl BytesFileOps for TracingOnFile {
242 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
243 let state_str = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
244 let clean_state_str = state_str.split('\n').next().unwrap_or("");
245 match clean_state_str {
246 "0" => self.queue.disable(),
247 "1" => self.queue.enable(),
248 _ => error!(EINVAL),
249 }
250 }
251
252 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
253 if self.queue.is_enabled() {
254 Ok(Cow::Borrowed(&b"1\n"[..]))
255 } else {
256 Ok(Cow::Borrowed(&b"0\n"[..]))
257 }
258 }
259}
260
261#[derive(Clone)]
262struct EventsHeaderPage;
263impl EventsHeaderPage {
264 fn new_node() -> impl FsNodeOps {
265 ConstFile::new_node(
266 "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
267 \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
268 \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
269 \tfield: char data;\toffset:16;\tsize:4080;\tsigned:0\n"
270 .into(),
271 )
272 }
273}
274
275#[derive(Clone)]
276struct FtracePrintFormatFile;
277impl FtracePrintFormatFile {
278 fn new_node() -> impl FsNodeOps {
279 ConstFile::new_node(
280 "name: print\n\
281 ID: 5\n\
282 format:\n\
283 \tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n\
284 \tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n\
285 \tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n\
286 \tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n\
287 \n\
288 \tfield:unsigned long ip;\toffset:8;\tsize:8;\tsigned:0;\n\
289 \tfield:char buf[];\toffset:16;\tsize:0;\tsigned:0;\n\
290 \n\
291 print fmt: \"%ps: %s\", (void *)REC->ip, REC- >buf\n"
292 .into(),
293 )
294 }
295}
296
297#[derive(Clone)]
298struct TraceFile;
299impl TraceFile {
300 fn new_node() -> impl FsNodeOps {
301 track_stub!(TODO("https://fxbug.dev/357665908"), "/sys/kernel/tracing/trace");
302 ConstFile::new_node("# tracer: nop\n".into())
303 }
304}