      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133

    use super::BlockDevice;
    use crate::mm::{
        frame_alloc, frame_dealloc, kernel_token, FrameTracker, PageTable, PhysAddr, PhysPageNum,
        StepByOne, VirtAddr,
    };
    use crate::sync::{Condvar, UPIntrFreeCell};
    use crate::task::schedule;
    use crate::DEV_NON_BLOCKING_ACCESS;
    use alloc::collections::BTreeMap;
    use alloc::vec::Vec;
    use lazy_static::*;
    use virtio_drivers::{BlkResp, RespStatus, VirtIOBlk, VirtIOHeader};

    #[allow(unused)]
    const VIRTIO0: usize = 0x10001000;

    pub struct VirtIOBlock {
        virtio_blk: UPIntrFreeCell<VirtIOBlk<'static>>,
        condvars: BTreeMap<u16, Condvar>,
    }

    lazy_static! {
        static ref QUEUE_FRAMES: UPIntrFreeCell<Vec<FrameTracker>> = {
            kprintln!("[KERN] drivers::block::virtio_blk::lazy_static!QUEUE_FRAMES begin");
            unsafe { UPIntrFreeCell::new(Vec::new()) }
        };
    }

    impl BlockDevice for VirtIOBlock {
        fn read_block(&self, block_id: usize, buf: &mut [u8]) {
            let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access();
            if nb {
                let mut resp = BlkResp::default();
                let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| {
                    let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() };
                    self.condvars.get(&token).unwrap().wait_no_sched()
                });
                schedule(task_cx_ptr);
                assert_eq!(
                    resp.status(),
                    RespStatus::Ok,
                    "Error when reading VirtIOBlk"
                );
            } else {
                self.virtio_blk
                    .exclusive_access()
                    .read_block(block_id, buf)
                    .expect("Error when reading VirtIOBlk");
            }
        }
        fn write_block(&self, block_id: usize, buf: &[u8]) {
            let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access();
            if nb {
                let mut resp = BlkResp::default();
                let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| {
                    let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() };
                    self.condvars.get(&token).unwrap().wait_no_sched()
                });
                schedule(task_cx_ptr);
                assert_eq!(
                    resp.status(),
                    RespStatus::Ok,
                    "Error when writing VirtIOBlk"
                );
            } else {
                self.virtio_blk
                    .exclusive_access()
                    .write_block(block_id, buf)
                    .expect("Error when writing VirtIOBlk");
            }
        }
        fn handle_irq(&self) {
            self.virtio_blk.exclusive_session(|blk| {
                while let Ok(token) = blk.pop_used() {
                    self.condvars.get(&token).unwrap().signal();
                }
            });
        }
    }

    impl VirtIOBlock {
        pub fn new() -> Self {
            let virtio_blk = unsafe {
                UPIntrFreeCell::new(VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap())
            };
            let mut condvars = BTreeMap::new();
            let channels = virtio_blk.exclusive_access().virt_queue_size();
            for i in 0..channels {
                let condvar = Condvar::new();
                condvars.insert(i, condvar);
            }
            Self {
                virtio_blk,
                condvars,
            }
        }
    }

    #[no_mangle]
    pub extern "C" fn virtio_dma_alloc(pages: usize) -> PhysAddr {
        let mut ppn_base = PhysPageNum(0);
        for i in 0..pages {
            let frame = frame_alloc().unwrap();
            if i == 0 {
                ppn_base = frame.ppn;
            }
            assert_eq!(frame.ppn.0, ppn_base.0 + i);
            QUEUE_FRAMES.exclusive_access().push(frame);
        }
        ppn_base.into()
    }

    #[no_mangle]
    pub extern "C" fn virtio_dma_dealloc(pa: PhysAddr, pages: usize) -> i32 {
        let mut ppn_base: PhysPageNum = pa.into();
        for _ in 0..pages {
            frame_dealloc(ppn_base);
            ppn_base.step();
        }
        0
    }

    #[no_mangle]
    pub extern "C" fn virtio_phys_to_virt(paddr: PhysAddr) -> VirtAddr {
        VirtAddr(paddr.0)
    }

    #[no_mangle]
    pub extern "C" fn virtio_virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
        PageTable::from_token(kernel_token())
            .translate_va(vaddr)
            .unwrap()
    }