blob: 075c9809a638468defb75874b3ea0f0b36c62f56 [file] [log] [blame]
// 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 <inttypes.h>
#include <limits.h>
#include <zircon/status.h>
#include <memory>
#include <utility>
#include <ddk/debug.h>
#include <fbl/auto_lock.h>
#define LOCAL_TRACE 0
namespace virtio {
RngDevice::RngDevice(zx_device_t* bus_device, zx::bti bti, std::unique_ptr<Backend> backend)
: virtio::Device(bus_device, std::move(bti), std::move(backend)),
ddk::Device<RngDevice>(bus_device) {}
RngDevice::~RngDevice() {
// TODO: clean up allocated physical memory
}
zx_status_t RngDevice::Init() {
// reset the device
DeviceReset();
// ack and set the driver status bit
DriverStatusAck();
// allocate the main vring
auto err = vring_.Init(kRingIndex, kRingSize);
if (err < 0) {
zxlogf(ERROR, "%s: failed to allocate vring", tag());
return err;
}
// allocate the entropy buffer
static_assert(kBufferSize <= PAGE_SIZE, "");
zx_status_t rc = io_buffer_init(&buf_, bti_.get(), kBufferSize, IO_BUFFER_RO | IO_BUFFER_CONTIG);
if (rc != ZX_OK) {
zxlogf(ERROR, "%s: cannot allocate entropy buffer: %d", tag(), rc);
return rc;
}
zxlogf(TRACE, "%s: allocated entropy buffer at %p, physical address %#" PRIxPTR "", tag(),
io_buffer_virt(&buf_), io_buffer_phys(&buf_));
// start the interrupt thread
StartIrqThread();
// set DRIVER_OK
DriverStatusOk();
zx_status_t status = DdkAdd("virtio-rng");
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to add device: %s", tag(), zx_status_get_string(status));
return status;
}
device_ = zxdev();
// TODO(fxbug.dev/24760): 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_);
zxlogf(INFO, "%s: initialization succeeded", tag());
return ZX_OK;
}
void RngDevice::IrqRingUpdate() {
zxlogf(DEBUG, "%s: Got irq ring update", tag());
// 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) {
zxlogf(ERROR, "%s: entropy response with unexpected buffer", tag());
} else {
zxlogf(TRACE, "%s: received entropy; adding to kernel pool", tag());
zx_status_t rc = zx_cprng_add_entropy(io_buffer_virt(&buf_), kBufferSize);
if (rc != ZX_OK) {
zxlogf(ERROR, "%s: add_entropy failed (%d)", tag(), 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() { zxlogf(DEBUG, "%s: Got irq config change (ignoring)", tag()); }
int RngDevice::SeedThreadEntry(void* arg) {
RngDevice* d = static_cast<RngDevice*>(arg);
for (;;) {
zx_status_t rc = d->Request();
zxlogf(TRACE, "virtio-rng-seed-thread: RngDevice::Request() returned %d", rc);
zx_nanosleep(zx_deadline_after(ZX_SEC(300)));
}
}
zx_status_t RngDevice::Request() {
zxlogf(DEBUG, "%s: sending entropy request", tag());
fbl::AutoLock lock(&lock_);
uint16_t i;
vring_desc* desc = vring_.AllocDescChain(1, &i);
if (!desc) {
zxlogf(ERROR, "%s: failed to allocate descriptor chain of length 1", tag());
return ZX_ERR_NO_RESOURCES;
}
desc->addr = io_buffer_phys(&buf_);
desc->len = kBufferSize;
desc->flags = VRING_DESC_F_WRITE;
zxlogf(TRACE, "%s: allocated descriptor chain desc %p, i %u", tag(), desc, i);
if (zxlog_level_enabled(TRACE)) {
virtio_dump_desc(desc);
}
vring_.SubmitChain(i);
vring_.Kick();
zxlogf(TRACE, "%s: kicked off entropy request", tag());
return ZX_OK;
}
} // namespace virtio