| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use starnix_lock::Mutex; |
| use std::{ |
| collections::BTreeMap, |
| sync::{Arc, Weak}, |
| }; |
| |
| use crate::{ |
| device::DeviceMode, |
| fs::{ |
| buffers::{InputBuffer, OutputBuffer}, |
| fileops_impl_seekable, fs_node_impl_not_dir, |
| sysfs::SysFsDirectory, |
| FileObject, FileOps, FsNode, FsNodeOps, FsStr, FsString, |
| }, |
| task::CurrentTask, |
| types::{error, DeviceType, Errno, OpenFlags}, |
| }; |
| |
| #[derive(Debug, PartialEq, Clone)] |
| pub enum KType { |
| /// Root of the kobject tree. |
| Root, |
| /// A bus which devices can be attached to. |
| Bus, |
| /// A group of devices that have a smilar behavior. |
| Class, |
| /// A virtual/physical device that is attached to a bus. |
| /// |
| /// Contains all information of a device node. |
| Device(DeviceMetadata), |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct DeviceMetadata { |
| /// Name of the device in /dev. |
| pub name: FsString, |
| pub device_type: DeviceType, |
| pub mode: DeviceMode, |
| } |
| |
| impl DeviceMetadata { |
| pub fn new(name: &FsStr, device_type: DeviceType, mode: DeviceMode) -> Self { |
| Self { name: name.to_vec(), device_type, mode } |
| } |
| } |
| |
| /// Attributes that are used to create a KType::Device kobject. |
| #[derive(Clone, Debug)] |
| pub struct KObjectDeviceAttribute { |
| /// Class kobject that the device belongs to. |
| /// |
| /// `None` when it's a Block device. |
| pub class: Option<KObjectHandle>, |
| /// Name in /sys. |
| pub name: FsString, |
| pub device: DeviceMetadata, |
| } |
| |
| impl KObjectDeviceAttribute { |
| pub fn new( |
| class: Option<KObjectHandle>, |
| kobject_name: &FsStr, |
| device_name: &FsStr, |
| device_type: DeviceType, |
| mode: DeviceMode, |
| ) -> Self { |
| assert!( |
| mode != DeviceMode::Block || class.is_none(), |
| "class should be None if it is a Block device" |
| ); |
| Self { |
| class, |
| name: kobject_name.to_vec(), |
| device: DeviceMetadata::new(device_name, device_type, mode), |
| } |
| } |
| } |
| |
| pub struct KObject { |
| /// The name that will appear in sysfs. |
| /// |
| /// It is also used by the parent to find this child. This name will be reflected in the full |
| /// path from the root. |
| name: FsString, |
| |
| /// The weak reference to its parent kobject. |
| parent: Option<Weak<KObject>>, |
| |
| /// A collection of the children of this kobject. |
| /// |
| /// The kobject tree has strong references from parent-to-child and weak |
| /// references from child-to-parent. This will avoid reference cycle. |
| children: Mutex<BTreeMap<FsString, KObjectHandle>>, |
| |
| /// The type of object that embeds a kobject. |
| /// |
| /// It controls what happens to the kobject when being created and destroyed. |
| ktype: KType, |
| |
| /// Function to create the associated `FsNodeOps`. |
| create_fs_node_ops: CreateFsNodeOps, |
| } |
| pub type KObjectHandle = Arc<KObject>; |
| |
| type CreateFsNodeOps = Box<dyn Fn(Weak<KObject>) -> Box<dyn FsNodeOps> + Send + Sync>; |
| |
| impl KObject { |
| pub fn new_root() -> KObjectHandle { |
| Arc::new(Self { |
| name: Default::default(), |
| parent: None, |
| children: Default::default(), |
| ktype: KType::Root, |
| create_fs_node_ops: Box::new(|kobject| Box::new(SysFsDirectory::new(kobject))), |
| }) |
| } |
| |
| fn new<F, N>( |
| name: &FsStr, |
| parent: KObjectHandle, |
| ktype: KType, |
| create_fs_node_ops: F, |
| ) -> KObjectHandle |
| where |
| F: Fn(Weak<KObject>) -> N + Send + Sync + 'static, |
| N: FsNodeOps, |
| { |
| Arc::new(Self { |
| name: name.to_vec(), |
| parent: Some(Arc::downgrade(&parent)), |
| children: Default::default(), |
| ktype, |
| create_fs_node_ops: Box::new(move |kobject| Box::new(create_fs_node_ops(kobject))), |
| }) |
| } |
| |
| /// The name that will appear in sysfs. |
| pub fn name(&self) -> FsString { |
| self.name.clone() |
| } |
| |
| /// The parent kobject. |
| /// |
| /// Returns none if this kobject is the root. |
| pub fn parent(&self) -> Option<KObjectHandle> { |
| self.parent.clone().and_then(|parent| Weak::upgrade(&parent)) |
| } |
| |
| /// The type of object that embeds a kobject. |
| pub fn ktype(&self) -> KType { |
| self.ktype.clone() |
| } |
| |
| /// Returns the associated `FsNodeOps`. |
| /// |
| /// The `create_fs_node_ops` function will be called with a weak pointer to kobject itself. |
| pub fn ops(self: &KObjectHandle) -> Box<dyn FsNodeOps> { |
| self.create_fs_node_ops.as_ref()(Arc::downgrade(self)) |
| } |
| |
| /// Get the full path from the root. |
| pub fn path(self: &KObjectHandle) -> FsString { |
| let mut current = Some(self.clone()); |
| let mut path: Vec<String> = vec![]; |
| while let Some(n) = current { |
| path.push(String::from_utf8(n.name()).unwrap()); |
| current = n.parent(); |
| } |
| |
| path.reverse(); |
| path.join("/").into() |
| } |
| |
| /// Checks if there is any child holding the `name`. |
| pub fn has_child(self: &KObjectHandle, name: &FsStr) -> bool { |
| self.get_child(name).is_some() |
| } |
| |
| /// Get the child based on the name. |
| pub fn get_child(self: &KObjectHandle, name: &FsStr) -> Option<KObjectHandle> { |
| self.children.lock().get(name).cloned() |
| } |
| |
| /// Gets the child if exists, creates a new child if not. |
| pub fn get_or_create_child<F, N>( |
| self: &KObjectHandle, |
| name: &FsStr, |
| ktype: KType, |
| create_fs_node_ops: F, |
| ) -> KObjectHandle |
| where |
| F: Fn(Weak<KObject>) -> N + Send + Sync + 'static, |
| N: FsNodeOps, |
| { |
| let mut children = self.children.lock(); |
| match children.get(name).cloned() { |
| Some(child) => child, |
| None => { |
| let child = KObject::new(name, self.clone(), ktype, create_fs_node_ops); |
| children.insert(name.to_vec(), child.clone()); |
| child |
| } |
| } |
| } |
| |
| /// Collects all children names. |
| pub fn get_children_names(&self) -> Vec<FsString> { |
| self.children.lock().keys().cloned().collect() |
| } |
| } |
| |
| impl std::fmt::Debug for KObject { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| f.debug_struct("KObject") |
| .field("name", &String::from_utf8_lossy(&self.name())) |
| .field("ktype", &self.ktype()) |
| .finish() |
| } |
| } |
| |
| pub struct UEventFsNode { |
| kobject: KObjectHandle, |
| } |
| |
| impl UEventFsNode { |
| pub fn new(kobject: KObjectHandle) -> Self { |
| Self { kobject } |
| } |
| } |
| |
| impl FsNodeOps for UEventFsNode { |
| fs_node_impl_not_dir!(); |
| |
| fn create_file_ops( |
| &self, |
| _node: &FsNode, |
| _current_task: &CurrentTask, |
| _flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| Ok(Box::new(UEventFile::new(self.kobject.clone()))) |
| } |
| } |
| |
| struct UEventFile { |
| kobject: KObjectHandle, |
| } |
| |
| impl UEventFile { |
| pub fn new(kobject: KObjectHandle) -> Self { |
| Self { kobject } |
| } |
| |
| fn parse_commands(data: &[u8]) -> Vec<&[u8]> { |
| data.split(|&c| c == b'\0' || c == b'\n').collect() |
| } |
| } |
| |
| impl FileOps for UEventFile { |
| fileops_impl_seekable!(); |
| |
| fn read( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| match self.kobject.ktype() { |
| KType::Device(device) => { |
| let content = format!( |
| "MAJOR={}\nMINOR={}\nDEVNAME={}\n", |
| device.device_type.major(), |
| device.device_type.minor(), |
| String::from_utf8_lossy(&device.name), |
| ); |
| data.write(content[offset..].as_bytes()) |
| } |
| _ => error!(ENODEV), |
| } |
| } |
| |
| fn write( |
| &self, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| if offset != 0 { |
| return error!(EINVAL); |
| } |
| // TODO(fxb/127713): Support parsing synthetic variables. |
| let content = data.read_all()?; |
| for command in Self::parse_commands(&content) { |
| // Ignore empty lines. |
| if command == b"" { |
| continue; |
| } |
| |
| current_task |
| .kernel() |
| .device_registry |
| .dispatch_uevent(command.try_into()?, self.kobject.clone()); |
| } |
| Ok(content.len()) |
| } |
| } |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] |
| pub enum UEventAction { |
| Add, |
| Remove, |
| Change, |
| Move, |
| Online, |
| Offline, |
| Bind, |
| Unbind, |
| } |
| |
| impl std::fmt::Display for UEventAction { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| UEventAction::Add => write!(f, "add"), |
| UEventAction::Remove => write!(f, "remove"), |
| UEventAction::Change => write!(f, "change"), |
| UEventAction::Move => write!(f, "move"), |
| UEventAction::Online => write!(f, "online"), |
| UEventAction::Offline => write!(f, "offline"), |
| UEventAction::Bind => write!(f, "bind"), |
| UEventAction::Unbind => write!(f, "unbind"), |
| } |
| } |
| } |
| |
| impl TryFrom<&[u8]> for UEventAction { |
| type Error = Errno; |
| |
| fn try_from(action: &[u8]) -> Result<Self, Self::Error> { |
| match action { |
| b"add" => Ok(UEventAction::Add), |
| b"remove" => Ok(UEventAction::Remove), |
| b"change" => Ok(UEventAction::Change), |
| b"move" => Ok(UEventAction::Move), |
| b"online" => Ok(UEventAction::Online), |
| b"offline" => Ok(UEventAction::Offline), |
| b"bind" => Ok(UEventAction::Bind), |
| b"unbind" => Ok(UEventAction::Unbind), |
| _ => error!(EINVAL), |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| pub struct UEventContext { |
| pub seqnum: u64, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::fs::sysfs::DeviceDirectory; |
| |
| #[::fuchsia::test] |
| fn kobject_create_child() { |
| let root = KObject::new_root(); |
| assert!(root.parent().is_none()); |
| |
| assert!(!root.has_child(b"virtual")); |
| root.get_or_create_child(b"virtual", KType::Bus, SysFsDirectory::new); |
| assert!(root.has_child(b"virtual")); |
| } |
| |
| #[::fuchsia::test] |
| fn kobject_path() { |
| let root = KObject::new_root(); |
| let device = root |
| .get_or_create_child(b"virtual", KType::Bus, SysFsDirectory::new) |
| .get_or_create_child(b"mem", KType::Class, SysFsDirectory::new) |
| .get_or_create_child( |
| b"null", |
| KType::Device(DeviceMetadata::new(b"null", DeviceType::NULL, DeviceMode::Char)), |
| DeviceDirectory::new, |
| ); |
| assert_eq!(device.path(), b"/virtual/mem/null".to_vec()); |
| } |
| |
| #[::fuchsia::test] |
| fn kobject_get_children_names() { |
| let root = KObject::new_root(); |
| root.get_or_create_child(b"virtual", KType::Bus, SysFsDirectory::new); |
| root.get_or_create_child(b"cpu", KType::Bus, SysFsDirectory::new); |
| root.get_or_create_child(b"power", KType::Bus, SysFsDirectory::new); |
| |
| let names = root.get_children_names(); |
| assert!(names.iter().any(|name| *name == b"virtual".to_vec())); |
| assert!(names.iter().any(|name| *name == b"cpu".to_vec())); |
| assert!(names.iter().any(|name| *name == b"power".to_vec())); |
| assert!(!names.iter().any(|name| *name == b"system".to_vec())); |
| } |
| } |