blob: 68b443b2f115838543ca2edcf72c608d8cdd2d5b [file] [log] [blame]
// 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 crate::device::DeviceMode;
use crate::task::CurrentTask;
use crate::vfs::buffers::{InputBuffer, OutputBuffer};
use crate::vfs::pseudo::simple_directory::SimpleDirectory;
use crate::vfs::{
FileObject, FileOps, FsNode, FsNodeOps, FsString, PathBuilder, fileops_impl_noop_sync,
fileops_impl_seekable, fs_node_impl_not_dir,
};
use starnix_logging::track_stub;
use starnix_sync::{FileOpsCore, Locked};
use starnix_uapi::device_type::DeviceType;
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::{errno, error};
use std::sync::Arc;
/// A Class is a higher-level view of a device.
///
/// It groups devices based on what they do, rather than how they are connected.
#[derive(Clone)]
pub struct Class {
pub name: FsString,
pub dir: Arc<SimpleDirectory>,
/// Physical bus that the devices belong to.
pub bus: Bus,
pub collection: Arc<SimpleDirectory>,
}
impl Class {
pub fn new(
name: FsString,
dir: Arc<SimpleDirectory>,
bus: Bus,
collection: Arc<SimpleDirectory>,
) -> Self {
Self { name, dir, bus, collection }
}
}
impl std::fmt::Debug for Class {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Class").field("name", &self.name).field("bus", &self.bus).finish()
}
}
/// A Bus identifies how the devices are connected to the processor.
#[derive(Clone)]
pub struct Bus {
pub name: FsString,
pub dir: Arc<SimpleDirectory>,
pub collection: Option<Arc<SimpleDirectory>>,
}
impl Bus {
pub fn new(
name: FsString,
dir: Arc<SimpleDirectory>,
collection: Option<Arc<SimpleDirectory>>,
) -> Self {
Self { name, dir, collection }
}
}
impl std::fmt::Debug for Bus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bus").field("name", &self.name).finish()
}
}
#[derive(Clone, Debug)]
pub struct Device {
pub name: FsString,
pub class: Class,
pub metadata: Option<DeviceMetadata>,
}
impl Device {
pub fn new(name: FsString, class: Class, metadata: Option<DeviceMetadata>) -> Self {
Self { name, class, metadata }
}
/// Returns a path to the device, relative to the sysfs root, going up `depth` directories.
pub fn path_from_depth(&self, depth: usize) -> FsString {
let mut builder = PathBuilder::new();
builder.prepend_element(self.name.as_ref());
builder.prepend_element(self.class.name.as_ref());
builder.prepend_element(self.class.bus.name.as_ref());
builder.prepend_element(b"devices".into());
for _ in 0..depth {
builder.prepend_element(b"..".into());
}
builder.build_relative()
}
pub fn uevent_properties(&self, separator: char) -> String {
let Some(metadata) = &self.metadata else {
return String::new();
};
// TODO(https://fxbug.dev/42078277): Pass the synthetic UUID when available.
// Otherwise, default as "0".
let path = self.path_from_depth(0);
format!(
"DEVPATH=/{path}{separator}\
DEVNAME={name}{separator}\
SUBSYSTEM={subsystem}{separator}\
SYNTH_UUID=0{separator}\
MAJOR={major}{separator}\
MINOR={minor}{separator}",
name = metadata.devname,
subsystem = self.class.name,
major = metadata.device_type.major(),
minor = metadata.device_type.minor(),
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DeviceMetadata {
/// Name of the device in /dev.
///
/// Also appears in sysfs via uevent.
pub devname: FsString,
pub device_type: DeviceType,
pub mode: DeviceMode,
}
impl DeviceMetadata {
pub fn new(devname: FsString, device_type: DeviceType, mode: DeviceMode) -> Self {
Self { devname, device_type, mode }
}
}
pub struct UEventFsNode {
device: Device,
}
impl UEventFsNode {
pub fn new(device: Device) -> Self {
Self { device }
}
}
impl FsNodeOps for UEventFsNode {
fs_node_impl_not_dir!();
fn create_file_ops(
&self,
_locked: &mut Locked<FileOpsCore>,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(Box::new(UEventFile::new(self.device.clone())))
}
}
struct UEventFile {
device: Device,
}
impl UEventFile {
pub fn new(device: Device) -> Self {
Self { device }
}
fn parse_commands(data: &[u8]) -> Vec<&[u8]> {
data.split(|&c| c == b'\0' || c == b'\n').collect()
}
}
impl FileOps for UEventFile {
fileops_impl_seekable!();
fileops_impl_noop_sync!();
fn read(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
let content = self.device.uevent_properties('\n');
data.write(content.get(offset..).ok_or_else(|| errno!(EINVAL))?.as_bytes())
}
fn write(
&self,
locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
current_task: &CurrentTask,
offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
if offset != 0 {
return error!(EINVAL);
}
let content = data.read_all()?;
for command in Self::parse_commands(&content) {
// Ignore empty lines.
if command == b"" {
continue;
}
match UEventAction::try_from(command) {
Ok(c) => current_task.kernel().device_registry.dispatch_uevent(
locked,
c,
self.device.clone(),
),
Err(e) => {
track_stub!(TODO("https://fxbug.dev/297435061"), "synthetic uevent variables");
return Err(e);
}
}
}
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,
}