blob: a3331d92848e1f02a915908aa6c543d63ee7c8a5 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <reg.h>
#include <zircon/boot/driver-config.h>
#include <arch/arm64/periphmap.h>
#include <dev/hw_rng.h>
#include <dev/hw_rng/amlogic_rng/init.h>
#include <explicit-memory/bytes.h>
#include <fbl/algorithm.h>
#include <kernel/thread.h>
#include <ktl/algorithm.h>
#include <ktl/enforce.h>
// Mask for the bit indicating RNG status.
#define AML_RNG_READY 1
namespace {
// Register for RNG data
static vaddr_t rng_data = 0;
// Register whose 1st bit indicates RNG status: 1->ready, 0->not ready.
static vaddr_t rng_status = 0;
// Hardware RNG refresh time in microsecond.
static uint64_t rng_refresh_interval_usec = 0;
// Size of each RNG draw.
constexpr size_t kRngDrawSize = 4;
// Max number of retry
constexpr size_t kMaxRetry = 10000;
size_t amlogic_hw_rng_get_entropy(void* buf, size_t len) {
if (buf == nullptr) {
return 0;
}
char* dest = static_cast<char*>(buf);
size_t total_read = 0;
size_t retry = 0;
while (len > 0) {
// Retry until RNG is ready.
while ((readl(rng_status) & AML_RNG_READY) != 1) {
if (retry > kMaxRetry) {
mandatory_memset(&buf, 0, len);
return 0;
}
Thread::Current::SleepRelative(ZX_USEC(1));
retry++;
}
uint32_t read_buf = readl(rng_data);
static_assert(sizeof(read_buf) == kRngDrawSize);
size_t read_size = ktl::min(len, kRngDrawSize);
memcpy(dest + total_read, &read_buf, read_size);
mandatory_memset(&read_buf, 0, sizeof(read_buf));
total_read += read_size;
len -= read_size;
// Hardware RNG expected to be ready after an interval.
Thread::Current::SleepRelative(ZX_USEC(rng_refresh_interval_usec));
}
return total_read;
}
static struct hw_rng_ops ops = {
.hw_rng_get_entropy = amlogic_hw_rng_get_entropy,
};
} // namespace
void AmlogicRngInit(const dcfg_amlogic_rng_driver_t& config) {
ASSERT(config.rng_data_phys);
ASSERT(config.rng_status_phys);
rng_data = periph_paddr_to_vaddr(config.rng_data_phys);
rng_status = periph_paddr_to_vaddr(config.rng_status_phys);
rng_refresh_interval_usec = config.rng_refresh_interval_usec;
ASSERT(rng_data);
ASSERT(rng_status);
ASSERT(rng_refresh_interval_usec > 0);
hw_rng_register(&ops);
}