-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Virtio Block Driver #108
Conversation
exts/virtio_blk/Cargo.toml
Outdated
[package] | ||
name = "virtio_blk" | ||
version = "0.0.1" | ||
authors = ["The Kerla Authors"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it's mostly your authorship at the moment, I think it's perfectly legal to claim your name here. There is no legal entity behind Kerla at the moment other than individual contributors and definitely one under such name don't exist.
exts/virtio_blk/lib.rs
Outdated
|
||
|
||
pub struct VirtioBlk { | ||
virtio: Virtio, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please confirm it always have to claim ownership of underlying Virtio
instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean by claim ownership of "it".
tools/run-qemu.py
Outdated
@@ -21,6 +22,9 @@ | |||
"-device", "virtio-net,netdev=net0,disable-legacy=on,disable-modern=off", | |||
"-netdev", "user,id=net0,hostfwd=tcp:127.0.0.1:20022-:22,hostfwd=tcp:127.0.0.1:20080-:80", | |||
"-object", "filter-dump,id=fiter0,netdev=net0,file=virtio-net.pcap", | |||
|
|||
"-device", "virtio-blk-pci,drive=drive0,id=virtblk0,disable-legacy=on,disable-modern=off", | |||
"-drive", "file=vdisk,if=none,id=drive0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip: QEMU has a virtual FAT formatted disk and you may find it convenient
https://en.wikibooks.org/wiki/QEMU/Devices/Storage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey @nuta. I'm confused as to what exact qemu argument I should pass. The latest commit to run-qemu.py
exits to status 0. I've also tried the following:
"-drive", "file=vdisk,if=none,format=raw,id=hd",
"-device", "virtio-blk-pci,drive=hd",
The strings above compile however the virtioblock::new()
method isn't being instantiated. Help would be much appreciated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
disable-legacy=on,disable-modern=off
is essential because the current virtio implementation supports the virtio modern spec only.
"-drive", "file=vdisk,if=none,format=raw,id=hd",
"-device", "virtio-blk-pci,drive=hd,disable-legacy=on,disable-modern=off",
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see thanks, in regards to the qemu failure status I think it's likely the request_to_device()
method that isn't well fleshed out. I'll be looking over the virtio spec some more and hopefully have the driver ready to merge by the end of the weekend.
exts/virtio_block/lib.rs
Outdated
let chain = &[VirtqDescBuffer::ReadOnlyFromDevice { | ||
addr: addr.as_paddr(), | ||
len: request_len + frame.len() | ||
}]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you need to add 3 descriptors instead of a single ReadOnlyFromDevice
one:
- Descriptor 1:
type
,reserved
, andsector
(ReadOnlyFromDevice). - Descriptor 2: The memory buffer to be read (ReadOnlyFromDevice) or written (ReadOnlyFromDevice) from the device for
RequestType::Write
andRequestType:: RequestType::Read
respectively. - Descriptor 3:
status
as defined in 5.2.6 Device Operation in the virtio spec (WritableFromDevice).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I've tried to do that, but I am still getting an exit with failure status. Perhaps the memory and status buffers are wrong. Am I supposed to allocate pages for descriptor 2 and 3 or will an array buffer suffice? Right now I have:
fn request_to_device(&mut self, request_type: RequestType, sector: u64, frame: &[u8]) {
let request_addr = self.request_buffer.add(MAX_BLK_SIZE);
let request_len = size_of::<VirtioBlockRequest>();
let read_addr = unsafe {PAddr::new(*frame.as_ptr() as usize)};
let read_len = frame.len();
let status_buffer = [0];
let status_addr =unsafe { PAddr::new(*status_buffer.as_ptr() as usize)};
let status_len = status_buffer.len();
// Fill block request
let block_request = unsafe { &mut *request_addr.as_mut_ptr::<VirtioBlockRequest>()};
block_request.type_ = request_type as u32;
block_request.sector = sector;
block_request.reserved = 0;
// Copy data into buffers
unsafe {
request_addr.as_mut_ptr::<u8>()
.add(request_len)
.copy_from_nonoverlapping(frame.as_ptr(), frame.len());
}
// Chain Descriptor
let chain = &[VirtqDescBuffer::ReadOnlyFromDevice {
addr: request_addr.as_paddr(),
len: request_len + frame.len()
},
VirtqDescBuffer::ReadOnlyFromDevice {
addr: read_addr,
len: read_len
},
VirtqDescBuffer::WritableFromDevice {
addr: status_addr,
len: status_len
}
];
// enqueue to data to send
let request_virtq = self.virtio.virtq_mut(VIRTIO_REQUEST_QUEUE);
request_virtq.enqueue(chain);
request_virtq.notify();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I've tried to do that, but I am still getting an exit with failure status.
What do you mean by "exit with failure status"? Does QEMU stop without a kernel panic message?
One thing I noticed is that read_addr
and status_addr
point to possibly the stack area (frame
and status_buffer
respectively). Since the virtio-blk device may process the request asynchronously, after returning from this method, in other words after status_buffer
is freed, the device could modify other variables in the stack. If it modies the return address, it could be the cause of your mysterious crash.
FYI, virtio-net device driver allocates memory buffers for network packets beforehand and copy into/from them (just like self.request_buffer
in your virtio-blk driver) not to use the stack. It incurs additional memory copies, but it just works for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "exit with failure status"? Does QEMU stop without a kernel panic message?
Yes, as of the latest commit to the PR, this is the output I get when running the kernel unsure whether it's my qemu settings for the block driver or something wrong with my implementation somewhere.
Booting from ROM..
cmdline: /tmp/tmp64lfyxuw
cmdline: unsupported option, ignoring: ''
available RAM: base=669000, size=505MiB
Booting Kerla...
initramfs: loaded 73 files and directories (1MiB)
kext: Loading virtio_net...
kext: Loading virtio_block...
virtio-net: MAC address is 52:54:00:12:34:56
Block size is 0
run-qemu.py: qemu exited with failure status (status=0)
make: *** [Makefile:113: run] Error 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JFYI: From my experience, if the kernel stops without showing a panic message, I suspect memory corruption by newly added code. For example, use-after-free.
Can you push the latest changes? Let me try later 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply. I just pushed the latest commit and addressed merge conflicts. I am still getting kernel panics without messages. Haven't been able to figure out why yet.
kernel/main.rs
Outdated
@@ -136,6 +136,10 @@ impl KernelOps for ApiOps { | |||
register_ethernet_driver(driver) | |||
} | |||
|
|||
fn register_block_driver(&self, driver: Box<dyn kerla_api::driver::block::BlockDriver>) { | |||
register_block_driver(driver) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes an infinite recursion. I think this is the root cause of the kernel panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since Kerla still doesn't have the block I/O layer, let's leave this method empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! Would've took a while to catch that. All that's seems to be left now is interrupt handling and reading the block size properly.
It reads a byte from the device's configuration space. For virtio-blk, you'll see the structure you've already defined as My naive idea is to allocate a memory page ( |
While attempting to attach irq kernel panics with the following message:
|
|
hey @nuta what specific action do you take to make sure the driver works properly? |
You should just try using the driver: write a sector into the disk and see if the attached image file has changed. |
Not sure how to write a sector within the context of the kernel. Looked through vfat but didn't manage to get it running. Would be helpful to show a demonstration. I apologize for all the questions. |
attach_irq(mmio_device.irq, move || { | ||
device.lock().handle_irq(); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I were you, I would add the following line here:
device.write_to_disk(0 /* the first sector */, [0x5a; 256].as_slice());
Then, run it on QEMU and see if the first sector has been modified:
$ hexdump -C vdisk.img | head
I applied some changes but am not sure whether my buffer allocation is correct. pub fn new(transport: Arc<dyn VirtioTransport>) -> Result<VirtioBlock, VirtioAttachError> {
let mut virtio = Virtio::new(transport);
virtio.initialize(VIRTIO_BLK_F_SIZE, 1)?;
// Read the block size
let block_size =
virtio.read_device_config64(offset_of!(VirtioBlockConfig, capacity) as u16);
info!(
"The disk capacity is {}",
ByteSize::new(block_size as usize * 512)
);
let ring_len = virtio.virtq(VIRTIO_REQUEST_QUEUE).num_descs() as usize;
let request_buffer = alloc_pages(
(align_up(MAX_BLK_SIZE * ring_len, PAGE_SIZE)) / PAGE_SIZE,
AllocPageFlags::KERNEL,
)
.unwrap()
.as_vaddr();
let status_buffer = alloc_pages(
(align_up((MAX_BLK_SIZE * ring_len) + 1, PAGE_SIZE)) / PAGE_SIZE,
AllocPageFlags::KERNEL,
)
.unwrap()
.as_vaddr();
let read_buffer = alloc_pages(
(align_up((MAX_BLK_SIZE * ring_len) + 2, PAGE_SIZE)) / PAGE_SIZE,
AllocPageFlags::KERNEL,
)
.unwrap()
.as_vaddr();
Ok(VirtioBlock {
virtio: virtio,
request_buffer: request_buffer,
status_buffer: status_buffer,
read_buffer: read_buffer,
request_buffer_index: 0,
ring_len: ring_len,
})
}
fn write_to_disk(&mut self, sector: u64, buf: &[u8]) {
let i = self.request_buffer_index % self.ring_len;
let request_addr = self.request_buffer.add(MAX_BLK_SIZE * i);
let status_addr = self.status_buffer.add((MAX_BLK_SIZE * i) + 1);
let read_addr = self.read_buffer.add((MAX_BLK_SIZE * i) + 2);
let request_header_len = size_of::<VirtioBlockRequest>();
// Fill block request
let block_request = unsafe { &mut *request_addr.as_mut_ptr::<VirtioBlockRequest>() };
block_request.type_ = RequestType::Write as u32;
block_request.sector = sector;
block_request.reserved = 0;
// Chain Descriptor
let chain = &[
VirtqDescBuffer::ReadOnlyFromDevice {
addr: request_addr.as_paddr(),
len: request_header_len,
},
VirtqDescBuffer::ReadOnlyFromDevice {
addr: read_addr.as_paddr(),
len: buf.len(),
},
VirtqDescBuffer::WritableFromDevice {
addr: status_addr.as_paddr(),
len: 1,
},
];
// enqueue to data to send
let request_virtq = self.virtio.virtq_mut(VIRTIO_REQUEST_QUEUE);
request_virtq.enqueue(chain);
request_virtq.notify();
} |
Which specific part are you unsure about? Perhaps appropriate length? |
More specifically, I'm not sure whether I allotted the correct number of pages required for each buffer. Also is my offsetting correct in the add buffer methods? |
I think you should read the virtio spec again to understand how |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, how did you tested this code?
len: request_header_len, | ||
}, | ||
VirtqDescBuffer::ReadOnlyFromDevice { | ||
addr: read_addr.as_paddr(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who initializes read_addr
?
In order to test the driver I do the following it still does not write though ///attach_irq(pci_device.config().interrupt_line(), move || {
// device.lock().handle_irq();
//});
device.clone().lock().write_to_disk(0, [0x5a].as_slice())
} |
I'm not sure what you mean I allocated memory and created an address just as I did for the header and the status |
OK let me rephrase my question. What's the purpose of |
I see what you mean it's a misnomer. It is a virtqueue request for the buffer parameter. As you have previously mentioned there are 3 buffers to be made: request header, request buffer, and status. I will be changing the name. |
How did you test this code? |
By attempting to write to vdisk.img. Also printing the values of each buffer making sure they're appropriate. |
I have tried to write to disk multiple times in a plethora of ways to no avail. If anyone else is intent on continuing this work or writing their own virtio block driver for the kernel you are free to do so. Will be closing this pr. |
This PR works towards resolving #74.
Checklist Towards Completion