blob: 3dfafa02e2d096e3db2dd7a48958914e3c663a1e [file] [log] [blame]
/*
* Copyright 2018 The Fuchsia Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "msg_buf.h"
#include "core.h"
#include "debug.h"
#include "hif.h"
#include "wmi-tlv.h"
// Information about our message types. This doesn't have to be in the same order as the
// ath10k_msg_type enums, but in order for the init algorithm to work properly, a type
// must be defined in the init_data array before it appears in an 'isa' field.
#define STR_NAME(name) #name
#define MSG(type, base, hdr) \
{ type, base, hdr, STR_NAME(type) }
static const struct {
enum ath10k_msg_type type;
enum ath10k_msg_type isa;
size_t hdr_size;
const char* name;
} ath10k_msg_types_init_data[] = {
{ATH10K_MSG_TYPE_BASE, 0, 0, "ATH10K_MSG_TYPE_BASE"},
HTC_MSGS,
// Note that since all of the following use the HTC interface they must follow HTC_MSGS
WMI_MSGS,
WMI_TLV_MSGS,
HTT_MSGS};
#undef MSG
// Table to keep track of the sizes and types of each message. Once initialized, this data
// is constant so we only keep a single copy. This is perhaps a terrible idea, but it does
// allow us to have a fairly compact representation of the message types in
// ath10k_msg_types_init_data (above), which is the structure most likely to require
// ongoing maintenance.
static struct ath10k_msg_type_info {
enum ath10k_msg_type isa;
size_t offset;
size_t hdr_size;
const char* name;
} ath10k_msg_types_info[ATH10K_MSG_TYPE_COUNT];
static mtx_t ath10k_msg_types_lock = MTX_INIT;
static bool ath10k_msg_types_initialized = false;
void ath10k_msg_bufs_init_stats(struct ath10k_msg_buf_state* state) {
list_initialize(&state->bufs_in_use);
}
// The number of buffers to pre-allocate. This is primarily necessary because of ZX-1073:
// if we don't allocate all needed MMIO at startup, we may not be able to allocate it later
// since we need 32b addresses, and the io_buffer_t interface doesn't provide any way to
// ask for it.
#define ATH10K_INITIAL_BUF_COUNT 2560
// One-time initialization of the module
zx_status_t ath10k_msg_bufs_init(struct ath10k* ar) {
static mtx_t init_lock = MTX_INIT;
static bool initialized = false;
mtx_lock(&init_lock);
if (initialized) {
mtx_unlock(&init_lock);
return ZX_OK;
}
initialized = true;
mtx_unlock(&init_lock);
struct ath10k_msg_buf_state* state = &ar->msg_buf_state;
state->ar = ar;
// Clear the buffer pool
mtx_init(&state->lock, mtx_plain);
list_initialize(&state->buf_pool);
#if DEBUG_MSG_BUF
ath10k_msg_bufs_init_stats(state);
#endif
// Organize our msg type information into something more usable (an array indexed by msg
// type, with total size information).
mtx_lock(&ath10k_msg_types_lock);
if (!ath10k_msg_types_initialized) {
for (size_t ndx = 0; ndx < countof(ath10k_msg_types_init_data); ndx++) {
enum ath10k_msg_type type = ath10k_msg_types_init_data[ndx].type;
enum ath10k_msg_type parent_type = ath10k_msg_types_init_data[ndx].isa;
struct ath10k_msg_type_info* type_info = &ath10k_msg_types_info[type];
type_info->isa = parent_type;
type_info->offset = ath10k_msg_types_info[parent_type].offset +
ath10k_msg_types_info[parent_type].hdr_size;
type_info->hdr_size = ath10k_msg_types_init_data[ndx].hdr_size;
type_info->name = ath10k_msg_types_init_data[ndx].name;
}
ath10k_msg_types_initialized = true;
}
mtx_unlock(&ath10k_msg_types_lock);
struct ath10k_msg_buf* msg_buf;
for (unsigned i = 0; i < ATH10K_INITIAL_BUF_COUNT; i++) {
ath10k_msg_buf_alloc_internal(ar, &msg_buf, ATH10K_MSG_TYPE_BASE, 1, true, __FILE__,
__LINE__);
ath10k_msg_buf_free(msg_buf);
}
return ZX_OK;
}
zx_status_t ath10k_msg_buf_alloc_internal(struct ath10k* ar, struct ath10k_msg_buf** msg_buf_ptr,
enum ath10k_msg_type type, size_t extra_bytes,
bool force_new, const char* filename, size_t line_num) {
struct ath10k_msg_buf_state* state = &ar->msg_buf_state;
zx_status_t status;
ZX_DEBUG_ASSERT(type < ATH10K_MSG_TYPE_COUNT);
struct ath10k_msg_buf* msg_buf;
size_t requested_sz =
ath10k_msg_types_info[type].offset + ath10k_msg_types_info[type].hdr_size + extra_bytes;
ZX_DEBUG_ASSERT(requested_sz > 0);
ZX_DEBUG_ASSERT(requested_sz <= PAGE_SIZE);
// First, see if we have any available buffers in our pool
mtx_lock(&state->lock);
if (!list_is_empty(&state->buf_pool) && !force_new) {
msg_buf = list_remove_head_type(&state->buf_pool, struct ath10k_msg_buf, listnode);
ZX_DEBUG_ASSERT(msg_buf->capacity == PAGE_SIZE);
ZX_DEBUG_ASSERT(msg_buf->state == state);
mtx_unlock(&state->lock);
io_buffer_cache_flush_invalidate(&msg_buf->buf, 0, PAGE_SIZE);
} else {
// Allocate a new buffer
mtx_unlock(&state->lock);
msg_buf = calloc(1, sizeof(struct ath10k_msg_buf));
if (!msg_buf) { return ZX_ERR_NO_MEMORY; }
zx_handle_t bti_handle;
status = ath10k_hif_get_bti_handle(ar, &bti_handle);
if (status != ZX_OK) { goto err_free_buf; }
status =
io_buffer_init(&msg_buf->buf, bti_handle, PAGE_SIZE, IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) { goto err_free_buf; }
msg_buf->paddr = io_buffer_phys(&msg_buf->buf);
if (msg_buf->paddr + PAGE_SIZE > 0x100000000) {
status = ZX_ERR_NO_MEMORY;
ath10k_warn(
"attempt to allocate buffer, unable to get mmio with "
"32 bit phys addr (see ZX-1073)\n");
goto err_free_iobuf;
}
msg_buf->vaddr = io_buffer_virt(&msg_buf->buf);
msg_buf->capacity = PAGE_SIZE;
msg_buf->state = state;
}
memset(msg_buf->vaddr, 0, requested_sz);
msg_buf->type = type;
msg_buf->used = requested_sz;
msg_buf->allocated = true;
#if DEBUG_MSG_BUF
msg_buf->alloc_file_name = filename;
msg_buf->alloc_line_num = line_num;
mtx_lock(&state->lock);
list_add_tail(&state->bufs_in_use, &msg_buf->debug_listnode);
mtx_unlock(&state->lock);
#endif
*msg_buf_ptr = msg_buf;
return ZX_OK;
err_free_iobuf:
io_buffer_release(&msg_buf->buf);
err_free_buf:
free(msg_buf);
return status;
}
void* ath10k_msg_buf_get_header(struct ath10k_msg_buf* msg_buf, enum ath10k_msg_type type) {
return (void*)((uint8_t*)msg_buf->vaddr + ath10k_msg_types_info[type].offset);
}
void* ath10k_msg_buf_get_payload(struct ath10k_msg_buf* msg_buf) {
enum ath10k_msg_type type = msg_buf->type;
return (void*)((uint8_t*)msg_buf->vaddr + ath10k_msg_types_info[type].offset +
ath10k_msg_types_info[type].hdr_size);
}
size_t ath10k_msg_buf_get_payload_len(struct ath10k_msg_buf* msg_buf,
enum ath10k_msg_type msg_type) {
return msg_buf->used - ath10k_msg_buf_get_payload_offset(msg_type);
}
size_t ath10k_msg_buf_get_offset(enum ath10k_msg_type type) {
return ath10k_msg_types_info[type].offset;
}
size_t ath10k_msg_buf_get_payload_offset(enum ath10k_msg_type type) {
return ath10k_msg_types_info[type].offset + ath10k_msg_types_info[type].hdr_size;
}
void ath10k_msg_buf_free(struct ath10k_msg_buf* msg_buf) {
struct ath10k_msg_buf_state* state = msg_buf->state;
ZX_DEBUG_ASSERT(msg_buf->capacity == PAGE_SIZE);
#if DEBUG_MSG_BUF
mtx_lock(&state->lock);
list_delete(&msg_buf->debug_listnode);
mtx_unlock(&state->lock);
#endif
// Save in pool for reuse
mtx_lock(&state->lock);
ZX_DEBUG_ASSERT_MSG(msg_buf->allocated, "attempt to free already freed buffer");
msg_buf->allocated = false;
list_add_head(&state->buf_pool, &msg_buf->listnode);
mtx_unlock(&state->lock);
}
#if DEBUG_MSG_BUF
#define MAX_BUFFER_LOCS 16
static void dump_buffer_locs(list_node_t* buf_list) {
struct {
const char* filename;
size_t line_number;
size_t count;
} buffer_origins[MAX_BUFFER_LOCS];
// Initialize
for (size_t ndx = 0; ndx < MAX_BUFFER_LOCS; ndx++) {
buffer_origins[ndx].count = 0;
}
// Count
struct ath10k_msg_buf* next_buf;
list_for_every_entry(buf_list, next_buf, struct ath10k_msg_buf, debug_listnode) {
size_t ndx;
for (ndx = 0; ndx < MAX_BUFFER_LOCS; ndx++) {
if (buffer_origins[ndx].count == 0) {
buffer_origins[ndx].filename = next_buf->alloc_file_name;
buffer_origins[ndx].line_number = next_buf->alloc_line_num;
buffer_origins[ndx].count = 1;
break;
} else if ((buffer_origins[ndx].line_number == next_buf->alloc_line_num) &&
!strcmp(buffer_origins[ndx].filename, next_buf->alloc_file_name)) {
buffer_origins[ndx].count++;
break;
}
}
ZX_DEBUG_ASSERT(ndx < MAX_BUFFER_LOCS);
}
// Report
printf(" Buffer origins:\n");
for (size_t ndx = 0; ndx < MAX_BUFFER_LOCS && buffer_origins[ndx].count != 0; ndx++) {
printf(" %s:%zd... %zd\n", buffer_origins[ndx].filename, buffer_origins[ndx].line_number,
buffer_origins[ndx].count);
}
}
void ath10k_msg_buf_dump_stats(struct ath10k* ar) {
struct ath10k_msg_buf_state* state = &ar->msg_buf_state;
mtx_lock(&state->lock);
printf("msg_buf stats:\n");
printf(" Buffers in use: %d\n", (int)list_length(&state->bufs_in_use));
printf(" Buffers available for reuse: %zd\n", list_length(&state->buf_pool));
dump_buffer_locs(&state->bufs_in_use);
mtx_unlock(&state->lock);
}
#endif // DEBUG_MSG_BUF
void ath10k_msg_buf_dump(struct ath10k_msg_buf* msg_buf, const char* prefix) {
uint8_t* raw_data = msg_buf->vaddr;
ath10k_info("msg_buf (%s): paddr %#x\n", ath10k_msg_types_info[msg_buf->type].name,
(unsigned int)msg_buf->paddr);
unsigned ndx;
for (ndx = 0; msg_buf->used - ndx >= 4; ndx += 4) {
ath10k_info("%s0x%02x 0x%02x 0x%02x 0x%02x\n", prefix, raw_data[ndx], raw_data[ndx + 1],
raw_data[ndx + 2], raw_data[ndx + 3]);
}
if (ndx != msg_buf->used) {
ath10k_err("%sBuffer has %d bytes extra\n", prefix, (int)(msg_buf->used - ndx));
}
}
/* Pop up to |n| buffers from the head of the list and free them.
* |n| equal to -1 indicates clearing the entire list. */
static void pop_and_free_n(list_node_t* msg_buf_list, int n) {
while (n--) {
struct ath10k_msg_buf* msg_buf = list_remove_head_type(
msg_buf_list, struct ath10k_msg_buf, listnode);
if (!msg_buf) {
break;
}
ath10k_msg_buf_free(msg_buf);
}
}
void ath10k_msg_buf_purge(list_node_t* msg_buf_list) {
pop_and_free_n(msg_buf_list, -1);
}
void ath10k_msg_buf_pop_and_free_n(list_node_t* msg_buf_list, int n) {
pop_and_free_n(msg_buf_list, n);
}