1use crate::device::DeviceMode;
6use crate::task::CurrentTask;
7use crate::vfs::buffers::{InputBuffer, OutputBuffer};
8use crate::vfs::pseudo::simple_directory::SimpleDirectory;
9use crate::vfs::{
10 FileObject, FileOps, FsNode, FsNodeOps, FsString, PathBuilder, fileops_impl_noop_sync,
11 fileops_impl_seekable, fs_node_impl_not_dir,
12};
13use starnix_logging::track_stub;
14use starnix_rcu::{RcuHashMap, RcuReadScope};
15use starnix_uapi::device_id::DeviceId;
16use starnix_uapi::errors::Errno;
17use starnix_uapi::open_flags::OpenFlags;
18use starnix_uapi::{errno, error};
19use std::sync::Arc;
20
21#[derive(Clone)]
25pub struct Class {
26 pub name: FsString,
27 pub dir: Arc<SimpleDirectory>,
28 pub bus: Bus,
30 pub collection: Arc<SimpleDirectory>,
31}
32
33impl Class {
34 pub fn new(
35 name: FsString,
36 dir: Arc<SimpleDirectory>,
37 bus: Bus,
38 collection: Arc<SimpleDirectory>,
39 ) -> Self {
40 Self { name, dir, bus, collection }
41 }
42}
43
44impl std::fmt::Debug for Class {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("Class").field("name", &self.name).field("bus", &self.bus).finish()
47 }
48}
49
50#[derive(Clone)]
52pub struct Bus {
53 pub name: FsString,
54 pub dir: Arc<SimpleDirectory>,
55 pub collection: Option<Arc<SimpleDirectory>>,
56}
57
58impl Bus {
59 pub fn new(
60 name: FsString,
61 dir: Arc<SimpleDirectory>,
62 collection: Option<Arc<SimpleDirectory>>,
63 ) -> Self {
64 Self { name, dir, collection }
65 }
66}
67
68impl std::fmt::Debug for Bus {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("Bus").field("name", &self.name).finish()
71 }
72}
73
74pub type UEventProperties = Vec<(FsString, FsString)>;
75
76#[derive(Clone, Debug)]
77pub struct Device {
78 pub name: FsString,
79 pub class: Class,
80 pub metadata: Option<DeviceMetadata>,
81}
82
83impl Device {
84 pub fn new(name: FsString, class: Class, metadata: Option<DeviceMetadata>) -> Self {
85 Self { name, class, metadata }
86 }
87
88 pub fn path_from_depth(&self, depth: usize) -> FsString {
90 let mut builder = PathBuilder::new();
91 builder.prepend_element(self.name.as_ref());
92 builder.prepend_element(self.class.name.as_ref());
93 builder.prepend_element(self.class.bus.name.as_ref());
94 builder.prepend_element(b"devices".into());
95 for _ in 0..depth {
96 builder.prepend_element(b"..".into());
97 }
98 builder.build_relative()
99 }
100
101 pub fn uevent_properties(&self, separator: char) -> FsString {
102 let props = self.get_uevent_properties_list();
103 flatten_uevent_properties(props, separator)
104 }
105
106 pub fn get_uevent_properties_list(&self) -> UEventProperties {
107 let mut props = vec![];
108
109 let path = self.path_from_depth(0);
112
113 let mut devpath = vec![b'/'];
114 devpath.extend_from_slice(path.as_ref());
115
116 props.push((b"DEVPATH".into(), devpath.into()));
117 props.push((b"SUBSYSTEM".into(), self.class.name.clone()));
118
119 if let Some(metadata) = &self.metadata {
120 props.push((b"DEVNAME".into(), metadata.devname.clone()));
121 props.push((b"SYNTH_UUID".into(), b"0".into()));
122 props.push((b"MAJOR".into(), metadata.devt.major().to_string().into()));
123 props.push((b"MINOR".into(), metadata.devt.minor().to_string().into()));
124 let scope = RcuReadScope::new();
125 for (key, value) in metadata.properties.iter(&scope) {
126 props.push((key.clone(), value.clone()));
127 }
128 }
129
130 props
131 }
132}
133
134pub fn flatten_uevent_properties(props: UEventProperties, separator: char) -> FsString {
135 let mut result = vec![];
136 let sep = separator as u8;
137 for (key, value) in props {
138 result.extend_from_slice(key.as_ref());
139 result.push(b'=');
140 result.extend_from_slice(value.as_ref());
141 result.push(sep);
142 }
143 result.into()
144}
145
146#[derive(Clone, Debug)]
147pub struct DeviceMetadata {
148 pub devname: FsString,
152 pub devt: DeviceId,
153 pub mode: DeviceMode,
154 pub properties: Arc<RcuHashMap<FsString, FsString>>,
155}
156
157impl DeviceMetadata {
158 pub fn new(devname: FsString, devt: DeviceId, mode: DeviceMode) -> Self {
159 Self { devname, devt, mode, properties: Arc::new(RcuHashMap::default()) }
160 }
161
162 pub fn with_devtype(self, devtype: impl Into<FsString>) -> Self {
163 self.properties.insert(b"DEVTYPE".into(), devtype.into());
164 self
165 }
166}
167
168pub struct UEventFsNode {
169 device: Device,
170}
171
172impl UEventFsNode {
173 pub fn new(device: Device) -> Self {
174 Self { device }
175 }
176}
177
178impl FsNodeOps for UEventFsNode {
179 fs_node_impl_not_dir!();
180
181 fn create_file_ops(
182 &self,
183 _node: &FsNode,
184 _current_task: &CurrentTask,
185 _flags: OpenFlags,
186 ) -> Result<Box<dyn FileOps>, Errno> {
187 Ok(Box::new(UEventFile::new(self.device.clone())))
188 }
189}
190
191struct UEventFile {
192 device: Device,
193}
194
195impl UEventFile {
196 pub fn new(device: Device) -> Self {
197 Self { device }
198 }
199
200 fn parse_commands(data: &[u8]) -> Vec<&[u8]> {
201 data.split(|&c| c == b'\0' || c == b'\n').collect()
202 }
203}
204
205impl FileOps for UEventFile {
206 fileops_impl_seekable!();
207 fileops_impl_noop_sync!();
208
209 fn read(
210 &self,
211 _file: &FileObject,
212 _current_task: &CurrentTask,
213 offset: usize,
214 data: &mut dyn OutputBuffer,
215 ) -> Result<usize, Errno> {
216 let content = self.device.uevent_properties('\n');
217 let content_bytes: &[u8] = content.as_ref();
218 data.write(content_bytes.get(offset..).ok_or_else(|| errno!(EINVAL))?)
219 }
220
221 fn write(
222 &self,
223 _file: &FileObject,
224 current_task: &CurrentTask,
225 offset: usize,
226 data: &mut dyn InputBuffer,
227 ) -> Result<usize, Errno> {
228 if offset != 0 {
229 return error!(EINVAL);
230 }
231 let content = data.read_all()?;
232 for command in Self::parse_commands(&content) {
233 if command == b"" {
235 continue;
236 }
237
238 match UEventAction::try_from(command) {
239 Ok(c) => {
240 current_task.kernel().device_registry.dispatch_uevent(c, self.device.clone())
241 }
242 Err(e) => {
243 track_stub!(TODO("https://fxbug.dev/297435061"), "synthetic uevent variables");
244 return Err(e);
245 }
246 }
247 }
248 Ok(content.len())
249 }
250}
251
252#[derive(Copy, Clone, Eq, PartialEq, Debug)]
253pub enum UEventAction {
254 Add,
255 Remove,
256 Change,
257 Move,
258 Online,
259 Offline,
260 Bind,
261 Unbind,
262}
263
264impl std::fmt::Display for UEventAction {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 match self {
267 UEventAction::Add => write!(f, "add"),
268 UEventAction::Remove => write!(f, "remove"),
269 UEventAction::Change => write!(f, "change"),
270 UEventAction::Move => write!(f, "move"),
271 UEventAction::Online => write!(f, "online"),
272 UEventAction::Offline => write!(f, "offline"),
273 UEventAction::Bind => write!(f, "bind"),
274 UEventAction::Unbind => write!(f, "unbind"),
275 }
276 }
277}
278
279impl TryFrom<&[u8]> for UEventAction {
280 type Error = Errno;
281
282 fn try_from(action: &[u8]) -> Result<Self, Self::Error> {
283 match action {
284 b"add" => Ok(UEventAction::Add),
285 b"remove" => Ok(UEventAction::Remove),
286 b"change" => Ok(UEventAction::Change),
287 b"move" => Ok(UEventAction::Move),
288 b"online" => Ok(UEventAction::Online),
289 b"offline" => Ok(UEventAction::Offline),
290 b"bind" => Ok(UEventAction::Bind),
291 b"unbind" => Ok(UEventAction::Unbind),
292 _ => error!(EINVAL),
293 }
294 }
295}
296
297#[derive(Copy, Clone)]
298pub struct UEventContext {
299 pub seqnum: u64,
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use crate::vfs::pseudo::simple_directory::SimpleDirectory;
306 use starnix_uapi::device_id::DeviceId;
307
308 #[test]
309 fn test_uevent_properties() {
310 let dir = SimpleDirectory::new();
311 let collection = SimpleDirectory::new();
312 let bus = Bus::new("bus".into(), dir.clone(), Some(collection.clone()));
313 let class = Class::new("class".into(), dir.clone(), bus, collection);
314 let device = Device::new(
315 "device".into(),
316 class,
317 Some(
318 DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)
319 .with_devtype("disk"),
320 ),
321 );
322
323 assert_eq!(
324 device.uevent_properties('\n'),
325 b"DEVPATH=/devices/bus/class/device\n\
326 SUBSYSTEM=class\n\
327 DEVNAME=devname\n\
328 SYNTH_UUID=0\n\
329 MAJOR=1\n\
330 MINOR=2\n\
331 DEVTYPE=disk\n"
332 );
333 }
334
335 #[test]
336 fn test_uevent_properties_no_devtype() {
337 let dir = SimpleDirectory::new();
338 let collection = SimpleDirectory::new();
339 let bus = Bus::new("bus".into(), dir.clone(), Some(collection.clone()));
340 let class = Class::new("class".into(), dir.clone(), bus, collection);
341 let device = Device::new(
342 "device".into(),
343 class,
344 Some(DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)),
345 );
346
347 assert_eq!(
348 device.uevent_properties('\n'),
349 b"DEVPATH=/devices/bus/class/device\n\
350 SUBSYSTEM=class\n\
351 DEVNAME=devname\n\
352 SYNTH_UUID=0\n\
353 MAJOR=1\n\
354 MINOR=2\n"
355 );
356 }
357
358 #[::fuchsia::test]
359 fn test_get_uevent_properties_list() {
360 let bus = Bus::new("virtual".into(), SimpleDirectory::new(), None);
361 let class =
362 Class::new("android_usb".into(), SimpleDirectory::new(), bus, SimpleDirectory::new());
363 let metadata =
364 DeviceMetadata::new("android0".into(), DeviceId::new(1, 2), DeviceMode::Char);
365 let device = Device::new("android0".into(), class, Some(metadata));
366
367 let props = device.get_uevent_properties_list();
368
369 assert_eq!(props.len(), 6);
373 assert_eq!(props[0], ("DEVPATH".into(), "/devices/virtual/android_usb/android0".into()));
374 assert_eq!(props[1], ("SUBSYSTEM".into(), "android_usb".into()));
375
376 let properties = &device.metadata.as_ref().unwrap().properties;
377 properties.insert("USB_STATE".into(), "CONNECTED".into());
378 properties.insert("ABC".into(), "XYZ".into());
379 properties.insert("FOO".into(), "BAR".into());
380
381 let mut props = device.get_uevent_properties_list();
382
383 assert_eq!(props.len(), 9);
384 props[6..].sort();
387 assert_eq!(props[6], ("ABC".into(), "XYZ".into()));
388 assert_eq!(props[7], ("FOO".into(), "BAR".into()));
389 assert_eq!(props[8], ("USB_STATE".into(), "CONNECTED".into()));
390 }
391}