blob: 4741c9321b672bb4bf558c876e0ce1fdd380bb88 [file] [log] [blame] [edit]
// Copyright 2025 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 <zircon/time.h>
#include <arch/arm64/periphmap.h>
#include <dev/hw_rng.h>
#include <dev/hw_rng/qcom_rng/init.h>
#include <explicit-memory/bytes.h>
#include <hwreg/bitfields.h>
#include <hwreg/mmio.h>
#include <kernel/mutex.h>
#include <kernel/thread.h>
namespace {
DECLARE_SINGLETON_MUTEX(QcomRngLock);
class StatusRegister : public hwreg::RegisterBase<StatusRegister, uint32_t> {
public:
DEF_BIT(0, data_ready);
static auto Get() { return hwreg::RegisterAddr<StatusRegister>(0x4); }
};
class DataRegister : public hwreg::RegisterBase<DataRegister, uint32_t> {
public:
DEF_FIELD(31, 0, data);
static auto Get() { return hwreg::RegisterAddr<DataRegister>(0x0); }
};
constexpr zx_duration_t kRetryDelay = ZX_USEC(440);
constexpr int kMaxRetries = 5;
static vaddr_t mmio_base = 0;
size_t GetEntropy(void* buf, size_t len) {
Guard<Mutex> guard(QcomRngLock::Get());
ktl::span<ktl::byte> buffer = {reinterpret_cast<ktl::byte*>(buf), len};
hwreg::RegisterMmio mmio(reinterpret_cast<void*>(mmio_base));
uint32_t data = 0;
// RNG data that exists in the current stack frame must be scrubbed before returning, preventing
// any sort of "leaks". `explicit_memory` ensures none of these cleanup operations are elided by
// the compiler.
explicit_memory::ZeroDtor scrubber(&data, 1);
int retries = 0;
while (!buffer.empty()) {
auto status_register = StatusRegister::Get().ReadFrom(&mmio);
// When the HW RNG unit runs out of data, it must produce more. This bit is set when the unit
// has produced data, and it can be read from the `DataRegister`.
if (!status_register.data_ready()) {
if (retries++ == kMaxRetries) {
// We failed to generate enough random data, scrub everything and bail out.
mandatory_memset(buf, 0, len);
return 0;
}
Thread::Current::SleepRelative(kRetryDelay);
continue;
}
data = DataRegister::Get().ReadFrom(&mmio).data();
size_t copy_size = ktl::min(buffer.size(), sizeof(data));
memcpy(buffer.data(), &data, copy_size);
buffer = buffer.subspan(copy_size);
}
// Return the number of bytes provided.
return len - buffer.size();
}
const struct hw_rng_ops kOps = {
.hw_rng_get_entropy = &GetEntropy,
};
} // namespace
void QcomRngInit(const zbi_dcfg_qcom_rng_t& config) {
// Check that the HWRNG unit has been handed off ready for use, otherwise complain and move on.
if ((config.flags & ZBI_QCOM_RNG_FLAGS_ENABLED) == 0) {
printf("QCOM HWRNG: Handed off disabled.\n");
return;
}
mmio_base = periph_paddr_to_vaddr(config.mmio_phys);
hw_rng_register(&kOps);
printf("QCOM HWRNG: Registered {mmio=%zu,flags=%zu}.\n", config.mmio_phys, config.mmio_phys);
}