[virtio][entropy] Basic virtio-rng driver

The driver draws entropy from the host system on qemu. Since there
isn't a mechanism for the kernel to pull entropy from the driver or to
control the driver's rate of activity, for now the driver just launches
its own thread to push entropy to the kernel periodically.

Change-Id: I950518db4a5776f041856b05ad0277b970aa6bc5
diff --git a/system/dev/bus/virtio/rng.cpp b/system/dev/bus/virtio/rng.cpp
new file mode 100644
index 0000000..040ea50
--- /dev/null
+++ b/system/dev/bus/virtio/rng.cpp
@@ -0,0 +1,140 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "rng.h"
+
+#include <ddk/debug.h>
+#include <inttypes.h>
+#include <fbl/auto_lock.h>
+
+namespace virtio {
+
+RngDevice::RngDevice(mx_device_t* bus_device)
+    : Device(bus_device) {
+}
+
+RngDevice::~RngDevice() {
+    // TODO: clean up allocated physical memory
+}
+
+mx_status_t RngDevice::Init() {
+    // reset the device
+    Reset();
+
+    // ack and set the driver status bit
+    StatusAcknowledgeDriver();
+
+    // allocate the main vring
+    auto err = vring_.Init(kRingIndex, kRingSize);
+    if (err < 0) {
+        dprintf(ERROR, "virtio-rng: failed to allocate vring\n");
+        return err;
+    }
+
+    // allocate the entropy buffer
+    mx_status_t rc = io_buffer_init(&buf_, kBufferSize, IO_BUFFER_RO);
+    if (rc != MX_OK) {
+        dprintf(ERROR, "virtio-rng: cannot allocate entropy buffer: %d\n", rc);
+        return rc;
+    }
+
+    dprintf(SPEW, "virtio-rng: allocated entropy buffer at %p, physical address %#" PRIxPTR "\n",
+            io_buffer_virt(&buf_), io_buffer_phys(&buf_));
+
+    // start the interrupt thread
+    StartIrqThread();
+
+    // set DRIVER_OK
+    StatusDriverOK();
+
+    device_add_args_t args = {};
+    args.version = DEVICE_ADD_ARGS_VERSION;
+    args.name = "virtio-rng";
+    args.ctx = nullptr;
+    args.ops = &device_ops_;
+
+    auto status = device_add(bus_device_, &args, &device_);
+    if (status < 0) {
+        dprintf(ERROR, "virtio-rng: device_add failed %d\n", status);
+        device_ = nullptr;
+        return status;
+    }
+
+    // TODO(SEC-29): The kernel should trigger entropy requests, instead of relying on this
+    // userspace thread to push entropy whenever it wants to. As a temporary hack, this thread
+    // pushes entropy to the kernel every 300 seconds instead.
+    thrd_create_with_name(&seed_thread_, RngDevice::SeedThreadEntry, this,
+                          "virtio-rng-seed-thread");
+    thrd_detach(seed_thread_);
+
+    dprintf(INFO, "virtio-rng: initialization succeeded\n");
+
+    return MX_OK;
+}
+
+void RngDevice::IrqRingUpdate() {
+    dprintf(TRACE, "virtio-rng: Got irq ring update\n");
+
+    // parse our descriptor chain, add back to the free queue
+    auto free_chain = [this](vring_used_elem* used_elem) {
+        uint32_t i = (uint16_t)used_elem->id;
+        struct vring_desc* desc = vring_.DescFromIndex((uint16_t)i);
+
+        if (desc->addr != io_buffer_phys(&buf_) || desc->len != kBufferSize) {
+            dprintf(ERROR, "virtio-rng: entropy response with unexpected buffer\n");
+        } else {
+            dprintf(SPEW, "virtio-rng: received entropy; adding to kernel pool\n");
+            mx_status_t rc = mx_cprng_add_entropy(io_buffer_virt(&buf_), kBufferSize);
+            if (rc != MX_OK) {
+                dprintf(ERROR, "virtio-rng: add_entropy failed (%d)\n", rc);
+            }
+        }
+
+        vring_.FreeDesc((uint16_t)i);
+    };
+
+    // tell the ring to find free chains and hand it back to our lambda
+    vring_.IrqRingUpdate(free_chain);
+}
+
+void RngDevice::IrqConfigChange() {
+    dprintf(TRACE, "virtio-rng: Got irq config change (ignoring)\n");
+}
+
+int RngDevice::SeedThreadEntry(void* arg) {
+    RngDevice* d = static_cast<RngDevice*>(arg);
+    for(;;) {
+        mx_status_t rc = d->Request();
+        dprintf(SPEW, "virtio-rng-seed-thread: RngDevice::Request() returned %d\n", rc);
+        mx_nanosleep(mx_deadline_after(MX_SEC(300)));
+    }
+}
+
+mx_status_t RngDevice::Request() {
+    dprintf(TRACE, "virtio-rng: sending entropy request\n");
+    fbl::AutoLock lock(&lock_);
+    uint16_t i;
+    vring_desc* desc = vring_.AllocDescChain(1, &i);
+    if (!desc) {
+        dprintf(ERROR, "virtio-rng: failed to allocate descriptor chain of length 1\n");
+        return MX_ERR_NO_RESOURCES;
+    }
+
+    desc->addr = io_buffer_phys(&buf_);
+    desc->len = kBufferSize;
+    desc->flags = VRING_DESC_F_WRITE;
+    dprintf(SPEW, "virtio-rng: allocated descriptor chain desc %p, i %u\n", desc, i);
+    if (driver_get_log_flags() & DDK_LOG_SPEW) {
+        virtio_dump_desc(desc);
+    }
+
+    vring_.SubmitChain(i);
+    vring_.Kick();
+
+    dprintf(SPEW, "virtio-rng: kicked off entropy request\n");
+
+    return MX_OK;
+}
+
+} // namespace virtio
diff --git a/system/dev/bus/virtio/rng.h b/system/dev/bus/virtio/rng.h
new file mode 100644
index 0000000..60ddd41
--- /dev/null
+++ b/system/dev/bus/virtio/rng.h
@@ -0,0 +1,53 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#pragma once
+
+#include "device.h"
+#include "ring.h"
+
+#include <ddk/io-buffer.h>
+#include <magenta/compiler.h>
+#include <stdlib.h>
+
+namespace virtio {
+
+class Ring;
+
+class RngDevice : public Device {
+public:
+    RngDevice(mx_device_t* device);
+    virtual ~RngDevice();
+
+    virtual mx_status_t Init();
+
+    virtual void IrqRingUpdate();
+    virtual void IrqConfigChange();
+
+
+private:
+    // TODO(SEC-29): The kernel should trigger entropy requests, instead of relying on this
+    // userspace thread to push entropy whenever it wants to. As a temporary hack, this thread
+    // pushes entropy to the kernel every 300 seconds instead.
+
+    // the entry point for the entropy seeding thread
+    static int SeedThreadEntry(void* arg);
+
+    // the method called by SeedThreadEntry() to actually launch a request
+    mx_status_t Request();
+
+    // the thread that seeds the system CPRNG periodically
+    thrd_t seed_thread_;
+
+    // the virtio ring
+    static constexpr uint16_t kRingIndex = 0;
+    static constexpr uint16_t kRingSize = 1;
+    Ring vring_ = {this};
+
+    // the buffer used to receive entropy
+    static constexpr size_t kBufferSize = MX_CPRNG_ADD_ENTROPY_MAX_LEN;
+    io_buffer_t buf_;
+
+};
+
+} // namespace virtio
diff --git a/system/dev/bus/virtio/rules.mk b/system/dev/bus/virtio/rules.mk
index 9dfd28b..8d26a44 100644
--- a/system/dev/bus/virtio/rules.mk
+++ b/system/dev/bus/virtio/rules.mk
@@ -14,6 +14,7 @@
     $(LOCAL_DIR)/ethernet.cpp \
     $(LOCAL_DIR)/gpu.cpp \
     $(LOCAL_DIR)/ring.cpp \
+    $(LOCAL_DIR)/rng.cpp \
     $(LOCAL_DIR)/utils.cpp \
     $(LOCAL_DIR)/virtio_c.c \
     $(LOCAL_DIR)/virtio_driver.cpp \
diff --git a/system/dev/bus/virtio/virtio_c.c b/system/dev/bus/virtio/virtio_c.c
index f1d9551..2dff13d 100644
--- a/system/dev/bus/virtio/virtio_c.c
+++ b/system/dev/bus/virtio/virtio_c.c
@@ -20,12 +20,14 @@
     .bind = virtio_bind,
 };
 
-MAGENTA_DRIVER_BEGIN(virtio, virtio_driver_ops, "magenta", "0.1", 7)
+MAGENTA_DRIVER_BEGIN(virtio, virtio_driver_ops, "magenta", "0.1", 9)
     BI_ABORT_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_PCI),
     BI_ABORT_IF(NE, BIND_PCI_VID, 0x1af4),
     BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1000), // Network device (transitional)
     BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1001), // Block device (transitional)
     BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1042), // Block device
+    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1005), // RNG device (transitional)
+    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1044), // RNG device
     BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1050), // GPU device
     BI_ABORT(),
 MAGENTA_DRIVER_END(virtio)
diff --git a/system/dev/bus/virtio/virtio_driver.cpp b/system/dev/bus/virtio/virtio_driver.cpp
index 0bb4bb7..5baedee 100644
--- a/system/dev/bus/virtio/virtio_driver.cpp
+++ b/system/dev/bus/virtio/virtio_driver.cpp
@@ -19,6 +19,7 @@
 
 #include "block.h"
 #include "device.h"
+#include "rng.h"
 #include "ethernet.h"
 #include "gpu.h"
 #include "trace.h"
@@ -67,6 +68,11 @@
         LTRACEF("found gpu device\n");
         vd.reset(new virtio::GpuDevice(device));
         break;
+    case 0x1005:
+    case 0x1044:
+        LTRACEF("found entropy device\n");
+        vd.reset(new virtio::RngDevice(device));
+        break;
     default:
         printf("unhandled device id, how did this happen?\n");
         return -1;