blob: be55659e46d3a295505f43788c79e02f60977305 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2008-2015 Travis Geiselbrecht
//
// 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 <assert.h>
#include <debug.h>
#include <lib/cbuf.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <kernel/auto_lock.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/event.h>
#include <kernel/spinlock.h>
#include <ktl/bit.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
// This should only be called once to initialize the Cbuf, and so thread safety analysis is
// disabled.
void Cbuf::Initialize(size_t len, void* buf) TA_NO_THREAD_SAFETY_ANALYSIS {
DEBUG_ASSERT(len > 0);
DEBUG_ASSERT(ktl::has_single_bit(len));
len_pow2_ = static_cast<uint32_t>(log2_ulong_floor(len));
buf_ = static_cast<char*>(buf);
LTRACEF("len %zu, len_pow2 %u\n", len, len_pow2_);
}
// TODO(https://fxbug.dev/42125783): We want to revisit the Cbuf API. It's intended to be used from
// interrupt context, at which time clients can rely on being the only accessor. For now, we disable
// thread safety analysis on this function.
bool Cbuf::Full() const TA_NO_THREAD_SAFETY_ANALYSIS {
uint32_t consumed = modpow2(head_ - tail_, len_pow2_);
size_t avail = valpow2(len_pow2_) - consumed - 1;
return avail == 0;
}
size_t Cbuf::WriteChar(char c) {
{
AutoSpinLock guard(&lock_);
if (Full()) {
return 0;
}
buf_[head_] = c;
IncPointer(&head_, 1);
}
// By signaling after dropping the lock, we avoid lock thrashing (though it doesn't matter much
// since this lock is a spinlock).
//
// Note: by the time we signal, the buffer may have already been drained, but that's OK. It just
// means a reader may be woken when the buffer is empty.
event_.Signal();
return 1;
}
zx::result<Cbuf::ReadContext> Cbuf::ReadCharWithContext(bool block) {
while (true) {
{
AutoSpinLock guard(&lock_);
if (!Empty()) {
ReadContext res = {
.c = buf_[tail_],
.transitioned_from_full = Full(),
};
IncPointer(&tail_, 1);
if (Empty()) {
event_.Unsignal();
}
return zx::ok(res);
}
// Because the signal state does not 100% match the buffer state, it is critical that the
// event is unsignaled when the buffer is found to be empty (not just when it *transitions* to
// empty).
event_.Unsignal();
}
if (!block) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
zx_status_t status = event_.Wait(Deadline::infinite());
if (status != ZX_OK) {
return zx::error(status);
}
}
}
bool Cbuf::Empty() const { return (tail_ == head_); }