|
| 1 | +use crate::{ |
| 2 | + device::{self, Device}, |
| 3 | + memory::IovecIter, |
| 4 | + posix::errno::{EResult, Errno}, |
| 5 | + sched::Scheduler, |
| 6 | + util::{event::Event, mutex::spin::SpinMutex, once::Once}, |
| 7 | + vfs::{ |
| 8 | + File, |
| 9 | + file::{FileOps, OpenFlags, PollEventSet, PollFlags}, |
| 10 | + fs::devtmpfs::DEVTMPFS_STAGE, |
| 11 | + inode::Mode, |
| 12 | + }, |
| 13 | +}; |
| 14 | +use alloc::{ |
| 15 | + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, |
| 16 | + sync::Arc, |
| 17 | + vec::Vec, |
| 18 | +}; |
| 19 | +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; |
| 20 | + |
| 21 | +/// Maximum number of buffered lines per reader before the oldest is dropped. |
| 22 | +const QUEUE_CAPACITY: usize = 256; |
| 23 | +/// Maximum number of lines buffered before any reader has connected. |
| 24 | +const BACKLOG_CAPACITY: usize = 256; |
| 25 | + |
| 26 | +static READER_COUNTER: AtomicU32 = AtomicU32::new(0); |
| 27 | +static DEVCTL: Once<Arc<DevCtl>> = Once::new(); |
| 28 | +static DEVCTL_READY: AtomicBool = AtomicBool::new(false); |
| 29 | + |
| 30 | +/// The global `/dev/devctl` device. |
| 31 | +struct DevCtl { |
| 32 | + readers: SpinMutex<BTreeMap<u32, Arc<SpinMutex<VecDeque<Vec<u8>>>>>>, |
| 33 | + /// Lines emitted before any reader connects (so boot-time nodes aren't lost). |
| 34 | + backlog: SpinMutex<VecDeque<Vec<u8>>>, |
| 35 | + had_reader: AtomicBool, |
| 36 | + rd_event: Event, |
| 37 | +} |
| 38 | + |
| 39 | +/// A single open handle on `/dev/devctl`. |
| 40 | +struct DevCtlFile { |
| 41 | + device: Arc<DevCtl>, |
| 42 | + reader_id: u32, |
| 43 | + queue: Arc<SpinMutex<VecDeque<Vec<u8>>>>, |
| 44 | +} |
| 45 | + |
| 46 | +impl DevCtl { |
| 47 | + fn new() -> Self { |
| 48 | + Self { |
| 49 | + readers: SpinMutex::new(BTreeMap::new()), |
| 50 | + backlog: SpinMutex::new(VecDeque::new()), |
| 51 | + had_reader: AtomicBool::new(false), |
| 52 | + rd_event: Event::new(), |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + /// Enqueue a single notification line and wake any blocked readers. |
| 57 | + fn enqueue(&self, line: Vec<u8>) { |
| 58 | + let readers = self.readers.lock(); |
| 59 | + if readers.is_empty() { |
| 60 | + drop(readers); |
| 61 | + // No reader yet: buffer until the first one connects, unless one has |
| 62 | + // already come and gone (then drop, like FreeBSD without devd). |
| 63 | + if !self.had_reader.load(Ordering::Acquire) { |
| 64 | + let mut backlog = self.backlog.lock(); |
| 65 | + if backlog.len() >= BACKLOG_CAPACITY { |
| 66 | + backlog.pop_front(); |
| 67 | + } |
| 68 | + backlog.push_back(line); |
| 69 | + } |
| 70 | + return; |
| 71 | + } |
| 72 | + |
| 73 | + for buf in readers.values() { |
| 74 | + let mut buf = buf.lock(); |
| 75 | + if buf.len() >= QUEUE_CAPACITY { |
| 76 | + buf.pop_front(); |
| 77 | + } |
| 78 | + buf.push_back(line.clone()); |
| 79 | + } |
| 80 | + drop(readers); |
| 81 | + self.rd_event.wake_all(); |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +impl Device for DevCtl { |
| 86 | + fn open(self: Arc<Self>, _flags: OpenFlags) -> EResult<Arc<dyn FileOps>> { |
| 87 | + let reader_id = READER_COUNTER.fetch_add(1, Ordering::Relaxed); |
| 88 | + let queue = Arc::try_new(SpinMutex::new(VecDeque::new()))?; |
| 89 | + |
| 90 | + // On the first ever open, drain the boot backlog into this reader. |
| 91 | + if !self.had_reader.swap(true, Ordering::AcqRel) { |
| 92 | + let mut backlog = self.backlog.lock(); |
| 93 | + let mut q = queue.lock(); |
| 94 | + q.extend(backlog.drain(..)); |
| 95 | + drop(q); |
| 96 | + drop(backlog); |
| 97 | + } |
| 98 | + |
| 99 | + self.readers.lock().insert(reader_id, queue.clone()); |
| 100 | + |
| 101 | + Ok(Arc::try_new(DevCtlFile { |
| 102 | + device: self, |
| 103 | + reader_id, |
| 104 | + queue, |
| 105 | + })?) |
| 106 | + } |
| 107 | + |
| 108 | + fn major(&self) -> u32 { |
| 109 | + 5 |
| 110 | + } |
| 111 | + |
| 112 | + fn minor(&self) -> u32 { |
| 113 | + 3 |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +impl Drop for DevCtlFile { |
| 118 | + fn drop(&mut self) { |
| 119 | + self.device.readers.lock().remove(&self.reader_id); |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +impl FileOps for DevCtlFile { |
| 124 | + fn read(&self, file: &File, buffer: &mut IovecIter, _offset: u64) -> EResult<isize> { |
| 125 | + let non_blocking = file.flags.lock().contains(OpenFlags::NonBlocking); |
| 126 | + |
| 127 | + loop { |
| 128 | + let guard = self.device.rd_event.guard(); |
| 129 | + { |
| 130 | + let mut queue = self.queue.lock(); |
| 131 | + if !queue.is_empty() { |
| 132 | + return Self::drain_lines(&mut queue, buffer); |
| 133 | + } |
| 134 | + } |
| 135 | + if non_blocking { |
| 136 | + return Err(Errno::EAGAIN); |
| 137 | + } |
| 138 | + if Scheduler::get_current().has_pending_signals() { |
| 139 | + return Err(Errno::EINTR); |
| 140 | + } |
| 141 | + guard.wait(); |
| 142 | + if Scheduler::get_current().has_pending_signals() { |
| 143 | + return Err(Errno::EINTR); |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + fn write(&self, _file: &File, _buffer: &mut IovecIter, _offset: u64) -> EResult<isize> { |
| 149 | + Err(Errno::EBADF) |
| 150 | + } |
| 151 | + |
| 152 | + fn poll(&self, _file: &File, mask: PollFlags) -> EResult<PollFlags> { |
| 153 | + let mut revents = PollFlags::empty(); |
| 154 | + if mask.contains(PollFlags::In) && !self.queue.lock().is_empty() { |
| 155 | + revents |= PollFlags::In; |
| 156 | + } |
| 157 | + Ok(revents) |
| 158 | + } |
| 159 | + |
| 160 | + fn poll_events(&self, _file: &File, mask: PollFlags) -> PollEventSet<'_> { |
| 161 | + if mask.intersects(PollFlags::Read) { |
| 162 | + PollEventSet::one(&self.device.rd_event) |
| 163 | + } else { |
| 164 | + PollEventSet::new() |
| 165 | + } |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +impl DevCtlFile { |
| 170 | + /// Copy as many whole lines as fit into the user buffer. Returns bytes copied. |
| 171 | + fn drain_lines(queue: &mut VecDeque<Vec<u8>>, buffer: &mut IovecIter) -> EResult<isize> { |
| 172 | + let mut total = 0isize; |
| 173 | + while let Some(line) = queue.front() { |
| 174 | + if line.len() > buffer.len() { |
| 175 | + // A single line larger than the whole buffer can never be read. |
| 176 | + if total == 0 { |
| 177 | + return Err(Errno::EINVAL); |
| 178 | + } |
| 179 | + break; |
| 180 | + } |
| 181 | + let line = queue.pop_front().unwrap(); |
| 182 | + total += buffer.copy_from_slice(&line)?; |
| 183 | + } |
| 184 | + Ok(total) |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +fn format_line(typ: &str, relpath: &[u8]) -> Vec<u8> { |
| 189 | + let mut line = Vec::new(); |
| 190 | + line.extend_from_slice(b"!system=DEVFS subsystem=CDEV type="); |
| 191 | + line.extend_from_slice(typ.as_bytes()); |
| 192 | + line.extend_from_slice(b" cdev="); |
| 193 | + line.extend_from_slice(relpath); |
| 194 | + line.push(b'\n'); |
| 195 | + line |
| 196 | +} |
| 197 | + |
| 198 | +pub fn notify_create(relpath: &[u8]) { |
| 199 | + if !DEVCTL_READY.load(Ordering::Acquire) { |
| 200 | + return; |
| 201 | + } |
| 202 | + DEVCTL.get().enqueue(format_line("CREATE", relpath)); |
| 203 | +} |
| 204 | + |
| 205 | +pub fn notify_destroy(relpath: &[u8]) { |
| 206 | + if !DEVCTL_READY.load(Ordering::Acquire) { |
| 207 | + return; |
| 208 | + } |
| 209 | + DEVCTL.get().enqueue(format_line("DESTROY", relpath)); |
| 210 | +} |
| 211 | + |
| 212 | +#[initgraph::task( |
| 213 | + name = "generic.device.devctl", |
| 214 | + depends = [DEVTMPFS_STAGE], |
| 215 | +)] |
| 216 | +pub fn DEVCTL_STAGE() { |
| 217 | + let dev = Arc::new(DevCtl::new()); |
| 218 | + unsafe { DEVCTL.init(dev.clone()) }; |
| 219 | + DEVCTL_READY.store(true, Ordering::Release); |
| 220 | + |
| 221 | + device::register_char_node(b"devctl", dev, Mode::from_bits_truncate(0o600)) |
| 222 | + .expect("Unable to create /dev/devctl"); |
| 223 | +} |
0 commit comments