blob: 4fa7dcf3e3425b5dfe5febf50c9d7bac336ed9d4 [file] [log] [blame]
// Copyright 2019 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 "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/dma_buffer.h"
#include <zircon/limits.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <utility>
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/debug.h"
namespace wlan {
namespace brcmfmac {
DmaBuffer::DmaBuffer() = default;
DmaBuffer::DmaBuffer(DmaBuffer&& other) { swap(*this, other); }
DmaBuffer& DmaBuffer::operator=(DmaBuffer other) {
swap(*this, other);
return *this;
}
void swap(DmaBuffer& lhs, DmaBuffer& rhs) {
using std::swap;
swap(lhs.vmo_, rhs.vmo_);
swap(lhs.pmt_, rhs.pmt_);
swap(lhs.size_, rhs.size_);
swap(lhs.cache_policy_, rhs.cache_policy_);
swap(lhs.dma_address_, rhs.dma_address_);
swap(lhs.address_, rhs.address_);
}
DmaBuffer::~DmaBuffer() {
if (address_ != 0) {
Unmap();
}
if (pmt_.is_valid()) {
pmt_.unpin();
}
}
// static
zx_status_t DmaBuffer::Create(const zx::bti& bti, uint32_t cache_policy, size_t size,
std::unique_ptr<DmaBuffer>* out_dma_buffer) {
zx_status_t status = ZX_OK;
auto dma_buffer = std::make_unique<DmaBuffer>();
// Create the VMO.
uint32_t bti_pin_options = ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE;
if (cache_policy == ZX_CACHE_POLICY_CACHED) {
// For VMOs with ZX_CACHE_POLICY_CACHED, we can use zx::vmo::create_contiguous(), since
// contiguous VMOs are by default cached.
if ((status = zx::vmo::create_contiguous(bti, size, 0, &dma_buffer->vmo_)) != ZX_OK) {
BRCMF_ERR("Failed to create contiguous VMO, size=%zu: %s", size,
zx_status_get_string(status));
return status;
}
bti_pin_options |= ZX_BTI_CONTIGUOUS;
} else {
// VMOs created with zx::vmo::create_contiguous() cannot have their cache policy set after
// creation, since the creation causes pages to be committed. So we have to use a "plain" VMO,
// and we can only ensure that these are contiguous up to ZX_PAGE_SIZE.
if (size > ZX_PAGE_SIZE) {
BRCMF_ERR(
"Failed to create uncached large VMO, size=%zu (ZX_PAGE_SIZE=%zu), cache_policy=0x%08x",
size, static_cast<size_t>(ZX_PAGE_SIZE), cache_policy);
return ZX_ERR_NO_MEMORY;
}
if ((status = zx::vmo::create(size, 0, &dma_buffer->vmo_)) != ZX_OK) {
BRCMF_ERR("Failed to create VMO, size=%zu: %s", size, zx_status_get_string(status));
return status;
}
if ((status = dma_buffer->vmo_.set_cache_policy(cache_policy)) != ZX_OK) {
BRCMF_ERR("Failed to set cache policy, cache_policy=0x%08x: %s", cache_policy,
zx_status_get_string(status));
return status;
}
}
// Double-check that we got the cache policy we wanted.
zx_info_vmo_t vmo_info = {};
if ((status = dma_buffer->vmo_.get_info(ZX_INFO_VMO, &vmo_info, sizeof(vmo_info), nullptr,
nullptr)) != ZX_OK) {
BRCMF_ERR("Failed to get VMO info: %s", zx_status_get_string(status));
return status;
}
if (vmo_info.cache_policy != cache_policy) {
BRCMF_ERR("Failed to set cache policy, expected=0x%08x actual=0x%08x", cache_policy,
vmo_info.cache_policy);
return ZX_ERR_NO_MEMORY;
}
dma_buffer->cache_policy_ = cache_policy;
uint64_t actual_vmo_size = 0;
if ((status = dma_buffer->vmo_.get_size(&actual_vmo_size)) != ZX_OK) {
BRCMF_ERR("Failed to get VMO size, size=%zu: %s", size, zx_status_get_string(status));
return status;
}
dma_buffer->size_ = static_cast<size_t>(actual_vmo_size);
// Pin it.
if ((status = bti.pin(bti_pin_options, dma_buffer->vmo_, 0, dma_buffer->size_,
&dma_buffer->dma_address_, 1, &dma_buffer->pmt_)) != ZX_OK) {
BRCMF_ERR("Failed to pin VMO: %s", zx_status_get_string(status));
return status;
}
*out_dma_buffer = std::move(dma_buffer);
return ZX_OK;
}
zx_status_t DmaBuffer::Map(uint32_t vmar_options) {
if (address_ != 0) {
BRCMF_ERR("Already mapped, vmar_options=0x%08x, old=0x%08x", vmar_options, vmar_options);
return ZX_ERR_BAD_STATE;
}
// Map it.
return Map(*zx::vmar::root_self(), vmar_options, &address_);
}
zx_status_t DmaBuffer::Unmap() {
zx_status_t status = ZX_OK;
if (address_ == 0) {
BRCMF_ERR("Not mapped");
return ZX_ERR_BAD_STATE;
}
if ((status = zx::vmar::root_self()->unmap(address_, size_)) != ZX_OK) {
BRCMF_ERR("Failed to unmap: %s", zx_status_get_string(status));
return status;
}
address_ = 0;
return ZX_OK;
}
zx_status_t DmaBuffer::Map(const zx::vmar& vmar, uint32_t vmar_options, uintptr_t* out_address) {
zx_status_t status = ZX_OK;
if ((status = vmar.map(0, vmo_, 0, size_, vmar_options, out_address)) != ZX_OK) {
BRCMF_ERR("Failed to map, vmar_options=0x%08x: %s", vmar_options, zx_status_get_string(status));
return status;
}
return ZX_OK;
}
bool DmaBuffer::is_valid() const { return vmo_.is_valid(); }
size_t DmaBuffer::size() const { return size_; }
uint32_t DmaBuffer::cache_policy() const { return cache_policy_; }
zx_paddr_t DmaBuffer::dma_address() const { return dma_address_; }
uintptr_t DmaBuffer::address() const { return address_; }
} // namespace brcmfmac
} // namespace wlan