1use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
2use strat9_syscall::{
3 call,
4 data::{
5 DT_DIR, DT_REG, IpcMessage, PCI_MATCH_DEVICE_ID, PCI_MATCH_VENDOR_ID, PciAddress,
6 PciDeviceInfo, PciProbeCriteria,
7 },
8 error::{EBADF, EINVAL, ENOENT, ENOSYS, ENOTDIR},
9};
10
11use crate::BusDriver;
12
13const OPCODE_OPEN: u32 = 0x01;
14const OPCODE_READ: u32 = 0x02;
15const OPCODE_WRITE: u32 = 0x03;
16const OPCODE_CLOSE: u32 = 0x04;
17const OPCODE_READDIR: u32 = 0x08;
18const REPLY_MSG_TYPE: u32 = 0x80;
19const STATUS_OK: u32 = 0;
20const FILEFLAG_DIRECTORY: u32 = 1;
21
22struct OpenHandle {
23 path: String,
24}
25
26pub struct BusSchemeServer<D: BusDriver> {
27 driver: D,
28 port_handle: u64,
29 handles: BTreeMap<u64, OpenHandle>,
30 next_id: u64,
31 pci_cache: Vec<PciDeviceInfo>,
32}
33
34impl<D: BusDriver> BusSchemeServer<D> {
35 pub fn new(driver: D, port_handle: u64) -> Self {
37 Self {
38 driver,
39 port_handle,
40 handles: BTreeMap::new(),
41 next_id: 1,
42 pci_cache: Vec::new(),
43 }
44 }
45
46 pub fn with_pci_cache(mut self, cache: Vec<PciDeviceInfo>) -> Self {
48 self.pci_cache = cache;
49 self
50 }
51
52 fn ok_reply(sender: u64) -> IpcMessage {
54 let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
55 reply.sender = sender;
56 reply.payload[0..4].copy_from_slice(&STATUS_OK.to_le_bytes());
57 reply
58 }
59
60 fn err_reply(sender: u64, code: usize) -> IpcMessage {
62 let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
63 reply.sender = sender;
64 reply.payload[0..4].copy_from_slice(&(code as u32).to_le_bytes());
65 reply
66 }
67
68 fn parse_hex_u8(s: &str) -> Option<u8> {
70 u8::from_str_radix(s.trim_start_matches("0x"), 16).ok()
71 }
72
73 fn parse_hex_u16(s: &str) -> Option<u16> {
75 u16::from_str_radix(s.trim_start_matches("0x"), 16).ok()
76 }
77
78 fn parse_pci_bdf(s: &str) -> Option<PciAddress> {
80 let (bus_s, rest) = s.split_once(':')?;
81 let (dev_s, fun_s) = rest.split_once('.')?;
82 let bus = Self::parse_hex_u8(bus_s)?;
83 let device = Self::parse_hex_u8(dev_s)?;
84 let function = Self::parse_hex_u8(fun_s)?;
85 if device > 31 || function > 7 {
86 return None;
87 }
88 Some(PciAddress {
89 bus,
90 device,
91 function,
92 _reserved: 0,
93 })
94 }
95
96 fn parse_cfg_path(path: &str) -> Option<(PciAddress, u8, u8)> {
98 let mut parts = path.strip_prefix("pci/cfg/")?.split('/');
99 let bdf = parts.next()?;
100 let off = parts.next()?;
101 let width = parts.next()?;
102 if parts.next().is_some() {
103 return None;
104 }
105 let addr = Self::parse_pci_bdf(bdf)?;
106 let offset = Self::parse_hex_u8(off)?;
107 let width = width.parse::<u8>().ok()?;
108 if !matches!(width, 1 | 2 | 4) {
109 return None;
110 }
111 Some((addr, offset, width))
112 }
113
114 fn parse_find_path(path: &str) -> Option<(u16, u16)> {
116 let mut parts = path.strip_prefix("pci/find/")?.split('/');
117 let ven = Self::parse_hex_u16(parts.next()?)?;
118 let dev = Self::parse_hex_u16(parts.next()?)?;
119 if parts.next().is_some() {
120 return None;
121 }
122 Some((ven, dev))
123 }
124
125 pub fn refresh_pci_cache(&mut self) {
127 let criteria = PciProbeCriteria {
128 match_flags: 0,
129 vendor_id: 0,
130 device_id: 0,
131 class_code: 0,
132 subclass: 0,
133 prog_if: 0,
134 _reserved: 0,
135 };
136 let mut buf = alloc::vec![PciDeviceInfo {
137 address: PciAddress {
138 bus: 0,
139 device: 0,
140 function: 0,
141 _reserved: 0,
142 },
143 vendor_id: 0,
144 device_id: 0,
145 class_code: 0,
146 subclass: 0,
147 prog_if: 0,
148 revision: 0,
149 header_type: 0,
150 interrupt_line: 0,
151 interrupt_pin: 0,
152 _reserved: 0,
153 }; 256];
154 if let Ok(n) = call::pci_enum(&criteria, &mut buf) {
155 self.pci_cache.clear();
156 self.pci_cache.extend_from_slice(&buf[..n.min(buf.len())]);
157 }
158 }
159
160 fn render_inventory(&self) -> Vec<u8> {
162 let mut out = alloc::vec::Vec::new();
163 out.extend_from_slice(b"bus:dev.fn vendor:device class:sub prog_if rev irq\n");
164 for d in &self.pci_cache {
165 let line = format!(
166 "{:02x}:{:02x}.{} {:04x}:{:04x} {:02x}:{:02x} {:02x} {:02x} {}\n",
167 d.address.bus,
168 d.address.device,
169 d.address.function,
170 d.vendor_id,
171 d.device_id,
172 d.class_code,
173 d.subclass,
174 d.prog_if,
175 d.revision,
176 d.interrupt_line
177 );
178 out.extend_from_slice(line.as_bytes());
179 }
180 out
181 }
182
183 fn handle_open(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
185 let path_len = u16::from_le_bytes([payload[4], payload[5]]) as usize;
186 if path_len > 42 {
187 return Self::err_reply(sender, EINVAL);
188 }
189 let path_bytes = &payload[6..6 + path_len];
190 let raw_path = match core::str::from_utf8(path_bytes) {
191 Ok(s) => s,
192 Err(_) => return Self::err_reply(sender, EINVAL),
193 };
194 let path = Self::normalize_path(raw_path);
195 if !self.path_exists(&path) {
196 return Self::err_reply(sender, ENOENT);
197 }
198
199 let file_id = self.next_id;
200 self.next_id = self.next_id.wrapping_add(1).max(1);
201 self.handles
202 .insert(file_id, OpenHandle { path: path.clone() });
203
204 let mut reply = Self::ok_reply(sender);
205 reply.payload[4..12].copy_from_slice(&file_id.to_le_bytes());
206 reply.payload[12..20].copy_from_slice(&0u64.to_le_bytes());
207 let flags = if path.is_empty() || path == "pci" || path == "pci/find" || path == "pci/cfg" {
208 FILEFLAG_DIRECTORY
209 } else {
210 0
211 };
212 reply.payload[20..24].copy_from_slice(&flags.to_le_bytes());
213 reply
214 }
215
216 fn handle_read(&self, sender: u64, payload: &[u8]) -> IpcMessage {
218 let file_id = u64::from_le_bytes([
219 payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
220 payload[7],
221 ]);
222 let offset = u64::from_le_bytes([
223 payload[8],
224 payload[9],
225 payload[10],
226 payload[11],
227 payload[12],
228 payload[13],
229 payload[14],
230 payload[15],
231 ]);
232
233 let handle = match self.handles.get(&file_id) {
234 Some(h) => h,
235 None => return Self::err_reply(sender, EBADF),
236 };
237
238 let content = self.generate_read_content(&handle.path, offset as usize);
239 let n = content.len().min(40);
240
241 let mut reply = Self::ok_reply(sender);
242 reply.payload[4..8].copy_from_slice(&(n as u32).to_le_bytes());
243 reply.payload[8..8 + n].copy_from_slice(&content[..n]);
244 reply
245 }
246
247 fn generate_read_content(&self, path: &str, offset: usize) -> Vec<u8> {
249 if let Some(child) = self.driver.children().into_iter().find(|c| c.name == path) {
250 let text = format!(
251 "name: {}\nbase: 0x{:x}\nsize: {}\n",
252 child.name, child.base_addr, child.size
253 );
254 let bytes = text.into_bytes();
255 return if offset >= bytes.len() {
256 Vec::new()
257 } else {
258 bytes[offset..].to_vec()
259 };
260 }
261
262 let data = match path {
263 "" | "/" => {
264 let mut s = format!("driver: {}\n", self.driver.name());
265 for c in self.driver.compatible() {
266 s.push_str(&format!("compatible: {}\n", c));
267 }
268 s.push_str(&format!("errors: {}\n", self.driver.error_count()));
269 s.into_bytes()
270 }
271 "status" => format!(
272 "driver: {}\nerrors: {}\n",
273 self.driver.name(),
274 self.driver.error_count()
275 )
276 .into_bytes(),
277 "error_count" => format!("{}\n", self.driver.error_count()).into_bytes(),
278 "pci" => b"inventory\ncount\nrescan\nfind\ncfg\n".to_vec(),
279 "pci/find" => b"usage: /bus/pci/find/<vendor>/<device>\n".to_vec(),
280 "pci/cfg" => b"usage: /bus/pci/cfg/<bb:dd.f>/<offset>/<width>\n".to_vec(),
281 "pci/inventory" => self.render_inventory(),
282 "pci/count" => format!("{}\n", self.pci_cache.len()).into_bytes(),
283 path if path.starts_with("pci/find/") => {
284 let Some((vendor_id, device_id)) = Self::parse_find_path(path) else {
285 return b"invalid path\n".to_vec();
286 };
287 let criteria = PciProbeCriteria {
288 match_flags: PCI_MATCH_VENDOR_ID | PCI_MATCH_DEVICE_ID,
289 vendor_id,
290 device_id,
291 class_code: 0,
292 subclass: 0,
293 prog_if: 0,
294 _reserved: 0,
295 };
296 let mut matches = alloc::vec![PciDeviceInfo {
297 address: PciAddress {
298 bus: 0,
299 device: 0,
300 function: 0,
301 _reserved: 0,
302 },
303 vendor_id: 0,
304 device_id: 0,
305 class_code: 0,
306 subclass: 0,
307 prog_if: 0,
308 revision: 0,
309 header_type: 0,
310 interrupt_line: 0,
311 interrupt_pin: 0,
312 _reserved: 0,
313 }; 64];
314 match call::pci_enum(&criteria, &mut matches) {
315 Ok(n) => {
316 let mut out = alloc::vec::Vec::new();
317 for d in matches.into_iter().take(n) {
318 let line = format!(
319 "{:02x}:{:02x}.{} {:04x}:{:04x}\n",
320 d.address.bus,
321 d.address.device,
322 d.address.function,
323 d.vendor_id,
324 d.device_id
325 );
326 out.extend_from_slice(line.as_bytes());
327 }
328 if out.is_empty() {
329 b"none\n".to_vec()
330 } else {
331 out
332 }
333 }
334 Err(_) => b"error\n".to_vec(),
335 }
336 }
337 path if path.starts_with("pci/cfg/") => {
338 let Some((addr, reg, width)) = Self::parse_cfg_path(path) else {
339 return b"invalid path\n".to_vec();
340 };
341 match call::pci_cfg_read(&addr, reg, width) {
342 Ok(v) => format!("0x{:08x}\n", v as u32).into_bytes(),
343 Err(_) => b"error\n".to_vec(),
344 }
345 }
346 _ => {
347 if let Some(reg_str) = path.strip_prefix("reg/") {
348 if let Ok(reg_offset) =
349 usize::from_str_radix(reg_str.trim_start_matches("0x"), 16)
350 {
351 match self.driver.read_reg(reg_offset) {
352 Ok(val) => format!("0x{:08x}\n", val).into_bytes(),
353 Err(_) => b"error\n".to_vec(),
354 }
355 } else {
356 b"invalid register\n".to_vec()
357 }
358 } else {
359 b"unknown path\n".to_vec()
360 }
361 }
362 };
363
364 if offset >= data.len() {
365 Vec::new()
366 } else {
367 data[offset..].to_vec()
368 }
369 }
370
371 fn normalize_path(path: &str) -> String {
373 if path.is_empty() || path == "/" {
374 return String::new();
375 }
376 let trimmed = path.trim_matches('/');
377 String::from(trimmed)
378 }
379
380 fn parse_reg_offset(path: &str) -> Option<usize> {
382 let reg_str = path.strip_prefix("reg/")?;
383 if reg_str.is_empty() {
384 return None;
385 }
386 usize::from_str_radix(reg_str.trim_start_matches("0x"), 16).ok()
387 }
388
389 fn path_exists(&self, path: &str) -> bool {
391 if path.is_empty()
392 || path == "status"
393 || path == "error_count"
394 || path == "pci"
395 || path == "pci/inventory"
396 || path == "pci/count"
397 || path == "pci/rescan"
398 || path == "pci/find"
399 || path == "pci/cfg"
400 {
401 return true;
402 }
403 if path.starts_with("pci/find/") {
404 return Self::parse_find_path(path).is_some();
405 }
406 if path.starts_with("pci/cfg/") {
407 return Self::parse_cfg_path(path).is_some();
408 }
409 if Self::parse_reg_offset(path).is_some() {
410 return true;
411 }
412 self.driver.children().iter().any(|c| c.name == path)
413 }
414
415 fn handle_write(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
417 let file_id = u64::from_le_bytes([
418 payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
419 payload[7],
420 ]);
421 let len = u16::from_le_bytes([payload[16], payload[17]]) as usize;
422
423 if !self.handles.contains_key(&file_id) {
424 return Self::err_reply(sender, EBADF);
425 }
426
427 if len > 30 {
428 return Self::err_reply(sender, EINVAL);
429 }
430
431 let handle = match self.handles.get(&file_id) {
432 Some(h) => h,
433 None => return Self::err_reply(sender, EBADF),
434 };
435
436 if handle.path == "pci/rescan" {
437 self.refresh_pci_cache();
438 } else if let Some((addr, reg, width)) = Self::parse_cfg_path(&handle.path) {
439 if len < 4 {
440 return Self::err_reply(sender, EINVAL);
441 }
442 let val = u32::from_le_bytes([payload[18], payload[19], payload[20], payload[21]]);
443 if call::pci_cfg_write(&addr, reg, width, val).is_err() {
444 return Self::err_reply(sender, EINVAL);
445 }
446 } else {
447 let reg_str = match handle.path.strip_prefix("reg/") {
448 Some(s) => s,
449 None => return Self::err_reply(sender, ENOSYS),
450 };
451 let reg_offset = match usize::from_str_radix(reg_str.trim_start_matches("0x"), 16) {
452 Ok(v) => v,
453 Err(_) => return Self::err_reply(sender, EINVAL),
454 };
455 if len < 4 {
456 return Self::err_reply(sender, EINVAL);
457 }
458 let val = u32::from_le_bytes([payload[18], payload[19], payload[20], payload[21]]);
459 if self.driver.write_reg(reg_offset, val).is_err() {
460 return Self::err_reply(sender, EINVAL);
461 }
462 }
463
464 let mut reply = Self::ok_reply(sender);
465 reply.payload[4..8].copy_from_slice(&(len as u32).to_le_bytes());
466 reply
467 }
468
469 fn handle_close(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
471 let file_id = u64::from_le_bytes([
472 payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
473 payload[7],
474 ]);
475 if self.handles.remove(&file_id).is_some() {
476 Self::ok_reply(sender)
477 } else {
478 Self::err_reply(sender, EBADF)
479 }
480 }
481
482 fn handle_readdir(&self, sender: u64, payload: &[u8]) -> IpcMessage {
484 let file_id = u64::from_le_bytes([
485 payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
486 payload[7],
487 ]);
488 let handle = match self.handles.get(&file_id) {
489 Some(h) => h,
490 None => return Self::err_reply(sender, EBADF),
491 };
492
493 let entries: Vec<(u64, u8, String)> = if handle.path.is_empty() {
494 let mut e = alloc::vec![
495 (1u64, DT_REG, String::from("status")),
496 (2u64, DT_REG, String::from("error_count")),
497 (3u64, DT_DIR, String::from("pci")),
498 ];
499 for c in self.driver.children() {
500 e.push((c.base_addr, DT_REG, c.name));
501 }
502 e
503 } else if handle.path == "pci" {
504 alloc::vec![
505 (4u64, DT_REG, String::from("inventory")),
506 (5u64, DT_REG, String::from("count")),
507 (6u64, DT_REG, String::from("rescan")),
508 (7u64, DT_DIR, String::from("find")),
509 (8u64, DT_DIR, String::from("cfg")),
510 ]
511 } else if handle.path == "pci/find" || handle.path == "pci/cfg" {
512 alloc::vec![]
513 } else {
514 return Self::err_reply(sender, ENOTDIR);
515 };
516
517 let mut reply = Self::ok_reply(sender);
518 let cursor = u16::from_le_bytes([payload[8], payload[9]]) as usize;
519 if cursor >= entries.len() && !entries.is_empty() {
520 reply.payload[4..6].copy_from_slice(&u16::MAX.to_le_bytes());
521 reply.payload[6] = 0;
522 reply.payload[7] = 0;
523 return reply;
524 }
525
526 let mut offset = 8usize;
527 let mut count = 0u8;
528 let mut next_cursor = u16::MAX;
529 let mut index = cursor;
530
531 for (ino, file_type, name) in &entries[cursor..] {
532 let name_bytes = name.as_bytes();
533 let entry_size = 10 + name_bytes.len();
534 if offset + entry_size > 48 {
535 let candidate = index.min(u16::MAX as usize) as u16;
536 next_cursor = candidate;
537 break;
538 }
539 reply.payload[offset..offset + 8].copy_from_slice(&ino.to_le_bytes());
540 reply.payload[offset + 8] = *file_type;
541 reply.payload[offset + 9] = name_bytes.len() as u8;
542 let end = offset + 10 + name_bytes.len();
543 reply.payload[offset + 10..end].copy_from_slice(name_bytes);
544 offset = end;
545 count += 1;
546 index += 1;
547 }
548
549 reply.payload[4..6].copy_from_slice(&next_cursor.to_le_bytes());
550 reply.payload[6] = count;
551 reply.payload[7] = (offset - 8) as u8;
552 reply
553 }
554
555 pub fn serve(&mut self) -> ! {
557 loop {
558 let mut msg = IpcMessage::new(0);
559 if call::ipc_recv(self.port_handle as usize, &mut msg).is_err() {
560 let _ = call::sched_yield();
561 continue;
562 }
563
564 let reply = match msg.msg_type {
565 OPCODE_OPEN => self.handle_open(msg.sender, &msg.payload),
566 OPCODE_READ => self.handle_read(msg.sender, &msg.payload),
567 OPCODE_WRITE => self.handle_write(msg.sender, &msg.payload),
568 OPCODE_CLOSE => self.handle_close(msg.sender, &msg.payload),
569 OPCODE_READDIR => self.handle_readdir(msg.sender, &msg.payload),
570 _ => Self::err_reply(msg.sender, ENOSYS),
571 };
572 let _ = call::ipc_reply(&reply);
573 }
574 }
575}