1use super::block_device::BlockDevice;
11
12const REG_DEVICE_FEATURES: u64 = 0x000;
14const REG_DRIVER_FEATURES: u64 = 0x008;
15const REG_QUEUE_SEL: u64 = 0x010;
16const REG_QUEUE_NUM_MAX: u64 = 0x014;
17const REG_QUEUE_NUM: u64 = 0x018;
18const REG_QUEUE_ALIGN: u64 = 0x01C;
19const REG_QUEUE_PFN: u64 = 0x020;
20const REG_QUEUE_NOTIFY: u64 = 0x024;
21const REG_STATUS: u64 = 0x030;
22const REG_CAPACITY: u64 = 0x100;
23
24const STATUS_ACK: u32 = 0x01;
26const STATUS_DRIVER: u32 = 0x02;
27const STATUS_FEATURES_OK: u32 = 0x08;
28const STATUS_DRIVER_OK: u32 = 0x04;
29const STATUS_FAILED: u32 = 0x80;
30
31const VRING_DESC_F_NEXT: u16 = 0x01;
33const VRING_DESC_F_WRITE: u16 = 0x02;
34
35const REQ_TYPE_IN: u32 = 0;
37
38const BOOT_QUEUE_SIZE: u16 = 8;
41
42#[repr(C)]
44struct BootBlockHeader {
45 request_type: u32,
46 reserved: u32,
47 sector: u64,
48}
49
50#[repr(u8)]
52#[derive(PartialEq, Eq)]
53enum BootBlockStatus {
54 Ok = 0,
55 Error = 1,
56 Unsupported = 2,
57}
58
59#[repr(C)]
61struct VringDesc {
62 addr: u64,
63 len: u32,
64 flags: u16,
65 next: u16,
66}
67
68#[repr(C)]
70struct VringAvail {
71 flags: u16,
72 idx: u16,
73 }
76
77#[repr(C)]
79struct VringUsedElem {
80 id: u32,
81 len: u32,
82}
83
84#[repr(C)]
86struct VringUsed {
87 flags: u16,
88 idx: u16,
89 }
92
93struct BootVirtqueue {
101 vring_phys: u64,
103 vring_virt: u64,
105 queue_size: u16,
107 avail_idx: u16,
109 last_used_idx: u16,
111}
112
113impl BootVirtqueue {
114 unsafe fn init(device_base: u64, queue_size: u16) -> Result<Self, &'static str> {
119 if !queue_size.is_power_of_two() {
120 return Err("Boot virtqueue size must be power of 2");
121 }
122
123 let desc_size = queue_size as usize * core::mem::size_of::<VringDesc>();
124 let avail_size = core::mem::size_of::<VringAvail>() + queue_size as usize * 2;
125 let used_elem_size = core::mem::size_of::<VringUsedElem>();
126 let used_size = core::mem::size_of::<VringUsed>() + queue_size as usize * used_elem_size;
127
128 let page_size: usize = 4096;
129 let avail_offset = (desc_size + page_size - 1) / page_size * page_size;
130 let used_offset = (avail_offset + avail_size + page_size - 1) / page_size * page_size;
131 let total_size = used_offset + used_size;
132 let total_pages = (total_size + page_size - 1) / page_size;
133 let order = total_pages.next_power_of_two().trailing_zeros() as u8;
134
135 let frame = crate::sync::with_irqs_disabled(|token| {
137 crate::memory::allocate_phys_contiguous(token, order)
138 })
139 .map_err(|_| "Failed to allocate boot vring")?;
140
141 let vring_phys = frame.start_address.as_u64();
142 let vring_virt = crate::memory::phys_to_virt(vring_phys);
143
144 core::ptr::write_bytes(vring_virt as *mut u8, 0, total_size);
146
147 core::ptr::write_volatile((device_base + REG_QUEUE_SEL) as *mut u32, 0);
149
150 core::ptr::write_volatile((device_base + REG_QUEUE_NUM) as *mut u32, queue_size as u32);
152
153 core::ptr::write_volatile(
155 (device_base + REG_QUEUE_ALIGN) as *mut u32,
156 page_size as u32,
157 );
158
159 let pfn = (vring_phys >> 12) as u32;
161 core::ptr::write_volatile((device_base + REG_QUEUE_PFN) as *mut u32, pfn);
162
163 Ok(Self {
164 vring_phys,
165 vring_virt,
166 queue_size,
167 avail_idx: 0,
168 last_used_idx: 0,
169 })
170 }
171
172 unsafe fn read_block(
179 &mut self,
180 device_base: u64,
181 lba: u64,
182 bounce_phys: u64,
183 bounce_virt: u64,
184 buf: &mut [u8],
185 ) -> Result<(), ()> {
186 let qs = self.queue_size as usize;
187 let desc_off = 0;
188 let avail_off = ((qs * core::mem::size_of::<VringDesc>()) + 4095) / 4096 * 4096;
189 let used_off =
190 ((avail_off + core::mem::size_of::<VringAvail>() + qs * 2) + 4095) / 4096 * 4096;
191
192 let desc_ptr = (self.vring_virt + desc_off as u64) as *mut VringDesc;
193 let avail_ptr = (self.vring_virt + avail_off as u64) as *mut VringAvail;
194 let used_ptr = (self.vring_virt + used_off as u64) as *mut VringUsed;
195
196 let header_ptr = bounce_virt as *mut BootBlockHeader;
198 core::ptr::write(
199 header_ptr,
200 BootBlockHeader {
201 request_type: REQ_TYPE_IN,
202 reserved: 0,
203 sector: lba,
204 },
205 );
206
207 let data_len = buf.len() as u32;
209 let status_off = data_len as u64; let status_ptr = (bounce_virt + status_off) as *mut u8;
211 core::ptr::write(status_ptr, 0xFF);
212
213 core::ptr::write(
215 desc_ptr,
216 VringDesc {
217 addr: bounce_phys,
218 len: core::mem::size_of::<BootBlockHeader>() as u32,
219 flags: VRING_DESC_F_NEXT,
220 next: 1,
221 },
222 );
223 core::ptr::write(
225 desc_ptr.add(1),
226 VringDesc {
227 addr: bounce_phys + core::mem::size_of::<BootBlockHeader>() as u64,
228 len: data_len,
229 flags: VRING_DESC_F_NEXT | VRING_DESC_F_WRITE,
230 next: 2,
231 },
232 );
233 core::ptr::write(
235 desc_ptr.add(2),
236 VringDesc {
237 addr: bounce_phys + status_off,
238 len: 1,
239 flags: VRING_DESC_F_WRITE,
240 next: 0,
241 },
242 );
243
244 let avail_ring_ptr = (self.vring_virt
246 + avail_off as u64
247 + core::mem::size_of::<VringAvail>() as u64) as *mut u16;
248 let avail_slot = (self.avail_idx as usize) % qs;
249 core::ptr::write(avail_ring_ptr.add(avail_slot), 0u16); core::sync::atomic::fence(core::sync::atomic::Ordering::Release);
253 core::ptr::write(&mut (*avail_ptr).idx, self.avail_idx.wrapping_add(1));
254 self.avail_idx = self.avail_idx.wrapping_add(1);
255
256 core::ptr::write_volatile((device_base + REG_QUEUE_NOTIFY) as *mut u32, 0u32);
258
259 let mut spins = 0u32;
261 loop {
262 let used_idx = core::ptr::read_volatile(&(*used_ptr).idx);
263 if self.last_used_idx != used_idx {
264 let used_ring_ptr =
265 (self.vring_virt + used_off as u64 + core::mem::size_of::<VringUsed>() as u64)
266 as *mut VringUsedElem;
267 let slot = (self.last_used_idx as usize) % qs;
268 let _elem = core::ptr::read_volatile(used_ring_ptr.add(slot));
269 self.last_used_idx = self.last_used_idx.wrapping_add(1);
270 break;
271 }
272 spins = spins.saturating_add(1);
273 if spins >= 5_000_000 {
274 crate::serial_println!("[virtio-blk] boot read timeout lba={:#x}", lba);
275 return Err(());
276 }
277 core::hint::spin_loop();
278 }
279
280 let status = core::ptr::read(status_ptr);
282 if status != BootBlockStatus::Ok as u8 {
283 return Err(());
284 }
285
286 let src = (bounce_virt + core::mem::size_of::<BootBlockHeader>() as u64) as *const u8;
288 core::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), data_len as usize);
289
290 Ok(())
291 }
292}
293
294impl Drop for BootVirtqueue {
295 fn drop(&mut self) {
296 }
299}
300
301pub struct VirtioBlkDevice {
303 base_addr: u64,
305 block_size: u64,
307 features: u32,
309 queue: Option<BootVirtqueue>,
311}
312
313impl VirtioBlkDevice {
314 pub unsafe fn new(base_addr: u64) -> Self {
320 Self {
321 base_addr,
322 block_size: 512, features: 0,
324 queue: None,
325 }
326 }
327
328 pub unsafe fn init(&mut self) -> Result<(), &'static str> {
334 crate::serial_println!("[virtio-blk] Initializing at {:#x}", self.base_addr);
335
336 let status = self.read_status();
338 crate::serial_println!("[virtio-blk] Device status: {:#x}", status);
339 self.write_status(0);
340
341 self.add_status(STATUS_ACK);
343 self.add_status(STATUS_DRIVER);
344
345 self.features = self.read_device_features();
347 crate::serial_println!("[virtio-blk] Features: {:#x}", self.features);
348
349 self.write_driver_features(0);
354
355 self.add_status(STATUS_FEATURES_OK);
357 if self.read_status() & STATUS_FEATURES_OK == 0 {
358 return Err("Device rejected feature negotiation");
359 }
360
361 let capacity_lo = core::ptr::read_volatile((self.base_addr + REG_CAPACITY) as *const u32);
363 let capacity_hi =
364 core::ptr::read_volatile((self.base_addr + REG_CAPACITY + 4) as *const u32);
365 let capacity = ((capacity_hi as u64) << 32) | (capacity_lo as u64);
366 crate::serial_println!("[virtio-blk] Capacity: {} blocks", capacity);
367
368 let queue = BootVirtqueue::init(self.base_addr, BOOT_QUEUE_SIZE)?;
370 self.queue = Some(queue);
371
372 self.add_status(STATUS_DRIVER_OK);
374
375 crate::serial_println!("[virtio-blk] Initialized successfully");
376 Ok(())
377 }
378
379 unsafe fn read_status(&self) -> u32 {
380 core::ptr::read_volatile((self.base_addr + REG_STATUS) as *const u32)
381 }
382
383 unsafe fn write_status(&self, val: u32) {
384 core::ptr::write_volatile((self.base_addr + REG_STATUS) as *mut u32, val);
385 }
386
387 unsafe fn add_status(&self, bit: u32) {
388 let s = self.read_status();
389 self.write_status(s | bit);
390 }
391
392 unsafe fn read_device_features(&self) -> u32 {
393 core::ptr::read_volatile((self.base_addr + REG_DEVICE_FEATURES) as *const u32)
394 }
395
396 unsafe fn write_driver_features(&self, features: u32) {
397 core::ptr::write_volatile((self.base_addr + REG_DRIVER_FEATURES) as *mut u32, features);
398 }
399
400 pub unsafe fn from_dtb(dtb_ptr: u64) -> Option<Self> {
406 #[cfg(target_arch = "x86_64")]
409 let base_addr = 0xfebf0000u64; #[cfg(not(target_arch = "x86_64"))]
412 let base_addr = {
413 crate::serial_println!("[virtio-blk] DTB parsing not implemented for this arch");
414 return None;
415 };
416
417 let mut device = VirtioBlkDevice::new(base_addr);
418 if device.init().is_err() {
419 return None;
420 }
421 Some(device)
422 }
423}
424
425impl BlockDevice for VirtioBlkDevice {
426 fn read_block(&mut self, lba: u64, buf: &mut [u8]) -> Result<(), ()> {
427 let queue = self.queue.as_mut().ok_or(())?;
428 let block_sz = self.block_size as usize;
429
430 let bounce_frame =
432 crate::sync::with_irqs_disabled(|token| crate::memory::allocate_frame(token))
433 .map_err(|_| ())?;
434 let bounce_phys = bounce_frame.start_address.as_u64();
435 let bounce_virt = crate::memory::phys_to_virt(bounce_phys);
436
437 let result =
438 unsafe { queue.read_block(self.base_addr, lba, bounce_phys, bounce_virt, buf) };
439
440 crate::sync::with_irqs_disabled(|token| {
441 crate::memory::free_frame(token, bounce_frame);
442 });
443
444 result
445 }
446
447 fn block_size(&self) -> u64 {
448 self.block_size
449 }
450}