use alloc::{
borrow::{Cow, ToOwned},
boxed::Box,
format,
string::String,
sync::Arc,
vec::Vec,
};
use axfs_ng_vfs::{Filesystem, NodeType, VfsError, VfsResult};
use crate::pseudofs::{
DirMaker, DirMapping, NodeOpsMux, SimpleDir, SimpleDirOps, SimpleFile, SimpleFs,
};
const DRM_MAJOR: u32 = 226;
const FB_MAJOR: u32 = 29;
const INPUT_MAJOR: u32 = 13;
const EVDEV_MINOR_BASE: u32 = 64;
const EVDEV_TAGS: &[&str] = &["ID_INPUT", "ID_INPUT_KEYBOARD", "ID_INPUT_MOUSE"];
pub fn new_sysfs() -> Filesystem {
SimpleFs::new_with("sysfs".into(), 0x62656572, builder)
}
fn builder(fs: Arc<SimpleFs>) -> DirMaker {
let mut root = DirMapping::new();
root.add(
"class",
SimpleDir::new_maker(fs.clone(), Arc::new(ClassDir { fs: fs.clone() })),
);
root.add(
"bus",
SimpleDir::new_maker(fs.clone(), Arc::new(BusDir { fs: fs.clone() })),
);
root.add(
"devices",
SimpleDir::new_maker(fs.clone(), Arc::new(DevicesDir { fs: fs.clone() })),
);
root.add(
"dev",
SimpleDir::new_maker(fs.clone(), Arc::new(DevDir { fs: fs.clone() })),
);
SimpleDir::new_maker(fs.clone(), Arc::new(root))
}
struct DevDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for DevDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(["char", "block"].into_iter().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(NodeOpsMux::Dir(match name {
"char" => SimpleDir::new_maker(fs.clone(), Arc::new(DevCharDir { fs })),
"block" => SimpleDir::new_maker(fs.clone(), Arc::new(DevBlockDir)),
_ => return Err(VfsError::NotFound),
}))
}
}
struct DevBlockDir;
impl SimpleDirOps for DevBlockDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(core::iter::empty())
}
fn lookup_child(&self, _name: &str) -> VfsResult<NodeOpsMux> {
Err(VfsError::NotFound)
}
}
struct DevCharDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for DevCharDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let mut v: Vec<Cow<'a, str>> = alloc::vec![
Cow::Owned(format!("{DRM_MAJOR}:0")),
Cow::Owned(format!("{FB_MAJOR}:0")),
];
for i in 0..input_device_count() {
v.push(Cow::Owned(format!(
"{INPUT_MAJOR}:{}",
EVDEV_MINOR_BASE + i
)));
}
Box::new(v.into_iter())
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let (maj, min) = name
.split_once(':')
.and_then(|(a, b)| Some((a.parse::<u32>().ok()?, b.parse::<u32>().ok()?)))
.ok_or(VfsError::NotFound)?;
let target = match (maj, min) {
(DRM_MAJOR, 0) => "../../devices/virtual/drm/card0".to_owned(),
(FB_MAJOR, 0) => "../../devices/virtual/graphics/fb0".to_owned(),
(INPUT_MAJOR, m)
if m >= EVDEV_MINOR_BASE && (m - EVDEV_MINOR_BASE) < input_device_count() =>
{
let n = m - EVDEV_MINOR_BASE;
format!("../../devices/virtual/input/input{n}/event{n}")
}
_ => return Err(VfsError::NotFound),
};
Ok(
SimpleFile::new(self.fs.clone(), NodeType::Symlink, move || {
Ok(target.clone())
})
.into(),
)
}
}
struct ClassDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for ClassDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
#[cfg(feature = "sg2002")]
let names: &'static [&'static str] = &["drm", "graphics", "input", "pwm"];
#[cfg(not(feature = "sg2002"))]
let names: &'static [&'static str] = &["drm", "graphics", "input"];
Box::new(names.iter().copied().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(NodeOpsMux::Dir(match name {
"drm" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ClassSubsystemDir::new(fs, "drm", &["card0"])),
),
"graphics" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ClassSubsystemDir::new(fs, "graphics", &["fb0"])),
),
"input" => SimpleDir::new_maker(fs.clone(), Arc::new(InputClassDir { fs })),
#[cfg(feature = "sg2002")]
"pwm" => crate::pseudofs::dev::pwm::pwm_class_dir_maker(fs),
_ => return Err(VfsError::NotFound),
}))
}
}
struct ClassSubsystemDir {
fs: Arc<SimpleFs>,
subsystem: &'static str,
names: Vec<&'static str>,
}
impl ClassSubsystemDir {
fn new(fs: Arc<SimpleFs>, subsystem: &'static str, names: &[&'static str]) -> Self {
Self {
fs,
subsystem,
names: names.to_vec(),
}
}
}
impl SimpleDirOps for ClassSubsystemDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(self.names.iter().copied().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
if !self.names.contains(&name) {
return Err(VfsError::NotFound);
}
let target = format!("../../devices/virtual/{}/{}", self.subsystem, name);
Ok(
SimpleFile::new(self.fs.clone(), NodeType::Symlink, move || {
Ok(target.clone())
})
.into(),
)
}
}
struct InputClassDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for InputClassDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let names: Vec<_> = (0..input_device_count())
.map(|i| Cow::Owned(format!("event{i}")))
.collect();
Box::new(names.into_iter())
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let n = name
.strip_prefix("event")
.and_then(|s| s.parse::<u32>().ok())
.ok_or(VfsError::NotFound)?;
if n >= input_device_count() {
return Err(VfsError::NotFound);
}
let target = format!("../../devices/virtual/input/input{n}/event{n}");
Ok(
SimpleFile::new(self.fs.clone(), NodeType::Symlink, move || {
Ok(target.clone())
})
.into(),
)
}
}
#[cfg(feature = "input")]
fn input_device_count() -> u32 {
crate::pseudofs::dev::event::input_device_count()
}
#[cfg(not(feature = "input"))]
fn input_device_count() -> u32 {
0
}
struct BusDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for BusDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(["platform"].into_iter().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(NodeOpsMux::Dir(match name {
"platform" => SimpleDir::new_maker(fs.clone(), Arc::new(PlatformBusClassDir)),
_ => return Err(VfsError::NotFound),
}))
}
}
struct PlatformBusClassDir;
impl SimpleDirOps for PlatformBusClassDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(core::iter::empty())
}
fn lookup_child(&self, _name: &str) -> VfsResult<NodeOpsMux> {
Err(VfsError::NotFound)
}
}
struct DevicesDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for DevicesDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(["platform", "virtual"].into_iter().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(NodeOpsMux::Dir(match name {
"platform" => SimpleDir::new_maker(fs.clone(), Arc::new(PlatformBusDir { fs })),
"virtual" => SimpleDir::new_maker(fs.clone(), Arc::new(VirtualDir { fs })),
_ => return Err(VfsError::NotFound),
}))
}
}
struct VirtualDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for VirtualDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(["drm", "graphics", "input"].into_iter().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(NodeOpsMux::Dir(match name {
"drm" => SimpleDir::new_maker(
fs.clone(),
Arc::new(DeviceContainer::new(
fs,
"drm",
&[("card0", (DRM_MAJOR, 0), "dri/card0")],
)),
),
"graphics" => SimpleDir::new_maker(
fs.clone(),
Arc::new(DeviceContainer::new(
fs,
"graphics",
&[("fb0", (FB_MAJOR, 0), "fb0")],
)),
),
"input" => SimpleDir::new_maker(fs.clone(), Arc::new(InputDevicesDir { fs })),
_ => return Err(VfsError::NotFound),
}))
}
}
struct DeviceContainer {
fs: Arc<SimpleFs>,
subsystem: &'static str,
entries: Vec<(&'static str, (u32, u32), &'static str)>,
}
impl DeviceContainer {
fn new(
fs: Arc<SimpleFs>,
subsystem: &'static str,
entries: &[(&'static str, (u32, u32), &'static str)],
) -> Self {
Self {
fs,
subsystem,
entries: entries.to_vec(),
}
}
}
impl SimpleDirOps for DeviceContainer {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(self.entries.iter().map(|(n, ..)| Cow::Borrowed(*n)))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let (_, dev, devname) = *self
.entries
.iter()
.find(|(n, ..)| *n == name)
.ok_or(VfsError::NotFound)?;
Ok(NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(DeviceAttributesDir {
fs: self.fs.clone(),
subsystem: self.subsystem,
name: name.to_owned(),
dev,
devname: devname.to_owned(),
parent_kind: ParentKind::ClassRoot,
}),
)))
}
}
struct InputDevicesDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for InputDevicesDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let names: Vec<_> = (0..input_device_count())
.map(|i| Cow::Owned(format!("input{i}")))
.collect();
Box::new(names.into_iter())
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let n = name
.strip_prefix("input")
.and_then(|s| s.parse::<u32>().ok())
.ok_or(VfsError::NotFound)?;
if n >= input_device_count() {
return Err(VfsError::NotFound);
}
Ok(NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(InputParentDir {
fs: self.fs.clone(),
index: n,
}),
)))
}
}
struct InputParentDir {
fs: Arc<SimpleFs>,
index: u32,
}
impl SimpleDirOps for InputParentDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let event = format!("event{}", self.index);
Box::new(
[
Cow::Borrowed("uevent"),
Cow::Borrowed("name"),
Cow::Borrowed("subsystem"),
Cow::Owned(event),
]
.into_iter(),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
let n = self.index;
Ok(match name {
"uevent" => SimpleFile::new_regular(fs, move || {
let mut body = format!("PRODUCT=0/0/0/0\nNAME=\"starry-input{n}\"\n");
for tag in EVDEV_TAGS {
body.push_str(tag);
body.push_str("=1\n");
}
Ok(body)
})
.into(),
"name" => {
let body = format!("starry-input{n}\n");
SimpleFile::new_regular(fs, move || Ok(body.clone())).into()
}
"subsystem" => SimpleFile::new(fs, NodeType::Symlink, || {
Ok("../../../../class/input".to_owned())
})
.into(),
_ if name == format!("event{n}") => NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(DeviceAttributesDir {
fs: self.fs.clone(),
subsystem: "input",
name: name.to_owned(),
dev: (INPUT_MAJOR, EVDEV_MINOR_BASE + n),
devname: format!("input/event{n}"),
parent_kind: ParentKind::InputInputN,
}),
)),
_ => return Err(VfsError::NotFound),
})
}
}
#[derive(Clone, Copy, Debug)]
enum ParentKind {
ClassRoot,
InputInputN,
}
struct DeviceAttributesDir {
fs: Arc<SimpleFs>,
subsystem: &'static str,
name: String,
dev: (u32, u32),
devname: String,
parent_kind: ParentKind,
}
impl SimpleDirOps for DeviceAttributesDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
["uevent", "dev", "name", "subsystem", "device"]
.into_iter()
.map(Cow::Borrowed),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(match name {
"uevent" => {
let (major, minor) = self.dev;
let devname = self.devname.clone();
let subsystem = self.subsystem;
SimpleFile::new_regular(fs, move || {
let mut buf = format!(
"MAJOR={major}\nMINOR={minor}\nDEVNAME={devname}\nSUBSYSTEM={subsystem}\n"
);
if subsystem == "input" && devname.starts_with("input/event") {
for tag in EVDEV_TAGS {
buf.push_str(tag);
buf.push_str("=1\n");
}
}
Ok(buf)
})
.into()
}
"dev" => {
let (major, minor) = self.dev;
SimpleFile::new_regular(fs, move || Ok(format!("{major}:{minor}\n"))).into()
}
"name" => {
let body = format!("{}\n", self.name);
SimpleFile::new_regular(fs, move || Ok(body.clone())).into()
}
"subsystem" => {
let ups = match self.parent_kind {
ParentKind::ClassRoot => "../../../..",
ParentKind::InputInputN => "../../../../..",
};
let target = format!("{}/class/{}", ups, self.subsystem);
SimpleFile::new(fs, NodeType::Symlink, move || Ok(target.clone())).into()
}
"device" => {
let target = match (self.parent_kind, self.subsystem) {
(ParentKind::ClassRoot, "drm") | (ParentKind::ClassRoot, "graphics") => {
"../../../../devices/platform/virtio-gpu0".to_owned()
}
(ParentKind::ClassRoot, _) => "..".to_owned(),
(ParentKind::InputInputN, _) => "..".to_owned(),
};
SimpleFile::new(fs, NodeType::Symlink, move || Ok(target.clone())).into()
}
_ => return Err(VfsError::NotFound),
})
}
}
struct PlatformBusDir {
fs: Arc<SimpleFs>,
}
impl SimpleDirOps for PlatformBusDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
["virtio-gpu0", "virtio-input"]
.into_iter()
.map(Cow::Borrowed),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let driver = match name {
"virtio-gpu0" => "virtio-gpu",
"virtio-input" => "virtio-input",
_ => return Err(VfsError::NotFound),
};
Ok(NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(PlatformDeviceDir {
fs: self.fs.clone(),
driver,
}),
)))
}
}
struct PlatformDeviceDir {
fs: Arc<SimpleFs>,
driver: &'static str,
}
impl SimpleDirOps for PlatformDeviceDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let mut names: Vec<&'static str> = alloc::vec!["uevent", "subsystem"];
if self.driver == "virtio-gpu" {
names.extend_from_slice(&[
"vendor",
"device",
"subsystem_vendor",
"subsystem_device",
"revision",
"class",
]);
}
Box::new(names.into_iter().map(Cow::Borrowed))
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
Ok(match name {
"uevent" => {
let driver = self.driver.to_owned();
SimpleFile::new_regular(fs, move || {
Ok(format!("DRIVER={driver}\nSUBSYSTEM=platform\n"))
})
.into()
}
"subsystem" => SimpleFile::new(fs, NodeType::Symlink, || {
Ok("../../../bus/platform".to_owned())
})
.into(),
"vendor" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x1af4\n".to_owned())).into()
}
"device" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x1050\n".to_owned())).into()
}
"subsystem_vendor" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x1af4\n".to_owned())).into()
}
"subsystem_device" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x1100\n".to_owned())).into()
}
"revision" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x01\n".to_owned())).into()
}
"class" if self.driver == "virtio-gpu" => {
SimpleFile::new_regular(fs, || Ok("0x030000\n".to_owned())).into()
}
_ => return Err(VfsError::NotFound),
})
}
}