From 66948f343436fa89e4f53edb38f537c1ef72f116 Mon Sep 17 00:00:00 2001 From: Dor Laor <dor@cloudius-systems.com> Date: Tue, 5 Feb 2013 10:34:50 +0200 Subject: [PATCH] Add a block worker thread that awakes on irq notifications from virtio and process pops requests from the ring. In addition, implement the vfs device callbacks and wire them into virtio-blk Need to test the later. --- drivers/virtio-blk.cc | 219 +++++++++++++++++++++++++++++++----------- drivers/virtio-blk.hh | 15 ++- 2 files changed, 176 insertions(+), 58 deletions(-) diff --git a/drivers/virtio-blk.cc b/drivers/virtio-blk.cc index 884abe07e..02ce039a3 100644 --- a/drivers/virtio-blk.cc +++ b/drivers/virtio-blk.cc @@ -14,20 +14,53 @@ #include <string> #include <string.h> #include <map> +#include <errno.h> #include "debug.hh" #include "sched.hh" #include "drivers/clock.hh" #include "drivers/clockevent.hh" +#include <osv/device.h> +#include <osv/bio.h> using namespace memory; using sched::thread; + namespace virtio { int virtio_blk::_instance = 0; + +struct virtio_blk_priv { + virtio_blk* drv; +}; + +static void +virtio_blk_strategy(struct bio *bio) +{ + struct virtio_blk_priv *prv = reinterpret_cast<struct virtio_blk_priv*>(bio->bio_dev->private_data); + + prv->drv->make_virtio_request(bio); +} + +static struct devops virtio_blk_devops = { + .open = no_open, + .close = no_close, + .read = no_read, + .write = no_write, + .ioctl = no_ioctl, + .devctl = no_devctl, + .strategy = virtio_blk_strategy, +}; + +struct driver virtio_blk_driver = { + .name = "virtio_blk", + .devops = &virtio_blk_devops, + .devsz = sizeof(struct virtio_blk_priv), +}; + virtio_blk::virtio_blk(unsigned dev_idx) : virtio_driver(VIRTIO_BLK_DEVICE_ID, dev_idx) { @@ -37,6 +70,7 @@ int virtio_blk::_instance = 0; _driver_name = ss.str(); debug(fmt("VIRTIO BLK INSTANCE %d") % dev_idx); _id = _instance++; + _wake_response = false; } virtio_blk::~virtio_blk() @@ -44,11 +78,6 @@ int virtio_blk::_instance = 0; //TODO: In theory maintain the list of free instances and gc it } - void virtio_blk::virtio_blk_isr() - { - debug(fmt("GOT ISR WORKING, %d") % _driver_name); - } - bool virtio_blk::load(void) { virtio_driver::load(); @@ -58,24 +87,25 @@ int virtio_blk::_instance = 0; sizeof(_config.capacity)); debug(fmt("capacity of the device is %x") % (u64)_config.capacity); - debug("Setup msix for virtio-blk"); - - msix_isr_list* isrs = new msix_isr_list; - void* stk1 = malloc(10000); - thread* isr = new thread([this] { this->virtio_blk_isr(); } , {stk1, 10000}); - - isrs->insert(std::make_pair(0, isr)); - - interrupt_manager::instance()->easy_register(_dev, *isrs); - - + thread* worker = new thread([this] { this->response_worker(); } , {stk1, 10000}); + worker->wake(); _dev->add_dev_status(VIRTIO_CONFIG_S_DRIVER_OK); + _dev->register_callback([this] { this->blk_callback();}); + // Perform test if this isn't the boot image (test is destructive if (_id > 0) { debug(fmt("virtio blk: testing instance %d") % _id); + + struct virtio_blk_priv* prv; + struct device *dev; + + dev = device_create(&virtio_blk_driver, "vblk0", D_BLK); + prv = reinterpret_cast<struct virtio_blk_priv*>(dev->private_data); + prv->drv = this; + test(); } @@ -87,6 +117,7 @@ int virtio_blk::_instance = 0; return (true); } + // to be removed soon once we move the test from here to the vfs layer virtio_blk::virtio_blk_req* virtio_blk::make_virtio_req(u64 sector, virtio_blk_request_type type, int val) { sglist* sg = new sglist(); @@ -94,7 +125,6 @@ int virtio_blk::_instance = 0; memset(buf, val, page_size); sg->add(mmu::virt_to_phys(buf), page_size); - virtio_blk_req* req = new virtio_blk_req; virtio_blk_outhdr* hdr = new virtio_blk_outhdr; hdr->type = type; hdr->ioprio = 0; @@ -107,10 +137,7 @@ int virtio_blk::_instance = 0; res->status = 0; sg->add(mmu::virt_to_phys(res), sizeof (struct virtio_blk_res)); - req->status = res; - req->req_header = hdr; - req->payload = sg; - + virtio_blk_req* req = new virtio_blk_req(hdr, sg, res); return req; } @@ -133,56 +160,134 @@ int virtio_blk::_instance = 0; debug(fmt(" Let the host know about the %d requests") % i); queue->kick(); - debug(" Wait for the irq to be injected by sleeping 1 sec"); - timespec ts = {}; - ts.tv_sec = 1; - nanosleep(&ts, nullptr); - - debug(" Collect the block write responses"); - i = 0; - while((req = reinterpret_cast<virtio_blk_req*>(queue->get_buf())) != nullptr) { - debug(fmt("\t got response:%d = %d ") % i++ % (int)req->status->status); - - delete req->status; - delete reinterpret_cast<virtio_blk_outhdr*>(req->req_header); - delete req->payload; - } - debug(" read several requests"); for (i=0;i<iterations;i++) { req = make_virtio_req(i*8, VIRTIO_BLK_T_IN,0); if (!queue->add_buf(req->payload,1,2,req)) { break; } + queue->kick(); // should be out of the loop but I like plenty of irqs for the test + } debug(fmt(" Let the host know about the %d requests") % i); queue->kick(); - debug(" Wait for the irq to be injected by sleeping 1 sec"); - ts.tv_sec = 1; - nanosleep(&ts, nullptr); - - debug(" Collect the block read responses"); - i = 0; - while((req = reinterpret_cast<virtio_blk_req*>(queue->get_buf())) != nullptr) { - debug(fmt("\t got response:%d = %d ") % i % (int)req->status->status); - - debug(fmt("\t verify that sector %d contains data %d") % (i*8) % i); - i++; - auto ii = req->payload->_nodes.begin(); - ii++; - char*buf = reinterpret_cast<char*>(mmu::phys_to_virt(ii->_paddr)); - debug(fmt("\t value = %d len=%d") % (int)(*buf) % ii->_len); - - delete req->status; - delete reinterpret_cast<virtio_blk_outhdr*>(req->req_header); - delete req->payload; + debug("test virtio blk end"); + } + + void virtio_blk::blk_callback() { + _wake_response = true; + debug("virtio blk callback executed"); + } + + void virtio_blk::response_worker() { + vring* queue = _dev->get_virt_queue(0); + virtio_blk_req* req; + + while (1) { + + debug("\t ----> virtio_blk: response worker"); + + thread::wait_until([&] { return _wake_response; }); + _dev->enable_callback(); + _wake_response = false; + debug("\t ----> debug - blk thread awken"); + + int i = 0; + + while((req = reinterpret_cast<virtio_blk_req*>(queue->get_buf())) != nullptr) { + debug(fmt("\t got response:%d = %d ") % i++ % (int)req->status->status); + + /* This is debug code to verify the read content, to be remove later on + if (reinterpret_cast<virtio_blk_outhdr*>(req->req_header)->type == VIRTIO_BLK_T_IN) { + debug(fmt("\t verify that sector %d contains data %d") % (i*8) % i); + i++; + auto ii = req->payload->_nodes.begin(); + ii++; + char*buf = reinterpret_cast<char*>(mmu::phys_to_virt(ii->_paddr)); + debug(fmt("\t value = %d len=%d") % (int)(*buf) % ii->_len); + + }*/ + if (req->bio != nullptr) { + biodone(req->bio); + req->bio = nullptr; + } + + delete req; + } + } + } - debug("test virtio blk end"); + virtio_blk::virtio_blk_req::~virtio_blk_req() + { + if (req_header) delete reinterpret_cast<virtio_blk_outhdr*>(req_header); + if (payload) delete payload; + if (status) delete status; + if (bio) delete bio; } -} + //todo: get it from the host + int virtio_blk::size() { + return 1024 * 1024 * 1024; + } + + static const int page_size = 4096; + static const int sector_size = 512; + + int virtio_blk::make_virtio_request(struct bio* bio) + { + if (!bio) return EIO; + + if ((bio->bio_cmd & (BIO_READ | BIO_WRITE)) == 0) + return ENOTBLK; + + virtio_blk_request_type type = (bio->bio_cmd == BIO_READ)? VIRTIO_BLK_T_IN:VIRTIO_BLK_T_OUT; + sglist* sg = new sglist(); + + // need to break a contiguous buffers that > 4k into several physical page mapping + // even if the virtual space is contiguous. + int len = 0; + int offset = bio->bio_offset; + while (len != bio->bio_bcount) { + int size = std::min((int)bio->bio_bcount - len, page_size); + if (offset + size > page_size) + size = page_size - offset; + len += size; + sg->add(mmu::virt_to_phys(bio->bio_data + offset), size); + offset += size; + } + + virtio_blk_outhdr* hdr = new virtio_blk_outhdr; + hdr->type = type; + hdr->ioprio = 0; + hdr->sector = (int)bio->bio_offset / sector_size; //wait, isn't offset starts on page addr?? + + //push 'output' buffers to the beginning of the sg list + sg->add(mmu::virt_to_phys(hdr), sizeof(struct virtio_blk_outhdr), true); + + virtio_blk_res* res = reinterpret_cast<virtio_blk_res*>(malloc(sizeof(virtio_blk_res))); + res->status = 0; + sg->add(mmu::virt_to_phys(res), sizeof (struct virtio_blk_res)); + + virtio_blk_req* req = new virtio_blk_req(hdr, sg, res, bio); + vring* queue = _dev->get_virt_queue(0); + int in = 1 , out = 1; + if (bio->bio_cmd == BIO_READ) + in += len/page_size + 1; + else + out += len/page_size + 1; + if (!queue->add_buf(req->payload,out,in,req)) { + //todo: free req; + return EBUSY; + } + + queue->kick(); // should be out of the loop but I like plenty of irqs for the test + + return 0; + } + +} diff --git a/drivers/virtio-blk.hh b/drivers/virtio-blk.hh index 036b49eff..dc57ac45f 100644 --- a/drivers/virtio-blk.hh +++ b/drivers/virtio-blk.hh @@ -30,6 +30,8 @@ #include "drivers/virtio.hh" #include "drivers/pci-device.hh" +#include <osv/bio.h> + namespace virtio { @@ -139,9 +141,13 @@ namespace virtio { }; struct virtio_blk_req { + virtio_blk_req(void* req = nullptr, sglist* sg = nullptr, virtio_blk_res* res = nullptr, struct bio* b=nullptr) + :req_header(req), payload(sg), status(res), bio(b) {}; + ~virtio_blk_req(); void* req_header; sglist* payload; virtio_blk_res* status; + struct bio* bio; }; virtio_blk(unsigned dev_idx=0); @@ -154,10 +160,16 @@ namespace virtio { virtual u32 get_driver_features(void) { return ((1 << VIRTIO_BLK_F_SIZE_MAX)); } virtio_blk_req* make_virtio_req(u64 sector, virtio_blk_request_type type, int val); + int make_virtio_request(struct bio*); - void virtio_blk_isr(); void test(); + void response_worker(); + void blk_callback(); + + int size(); + + private: std::string _driver_name; @@ -166,6 +178,7 @@ namespace virtio { //maintains the virtio instance number for multiple drives static int _instance; int _id; + bool _wake_response; }; } -- GitLab