blob: a1b8638f398dc7227f508d3689ff5a9848579a63 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#include <bcmos_system.h>
#include <bcmolt_msg.h>
#include <bcm_dev_log.h>
#include <bcm_api_cli_helpers.h>
#include "bcm_api_dev_log.h"
/* maximum number of tasks that can use the API/logger integration simultaneously */
#define API_LOG_NUM_BLKS 16
/* string that denotes that this entry is not complete - there is more data to come */
#define API_LOG_CONT_STR "..."
/* number of characters reserved at the end of each log entry (continuation string + newline) */
#define API_LOG_ENTRY_OVERHEAD sizeof(API_LOG_CONT_STR)
typedef struct
{
dev_log_id log_id;
const bcmolt_msg *msg;
char *buf;
uint32_t used;
} api_log_rec;
static bcmos_blk_pool api_log_blk_pool;
static bcmos_errno api_log_dump_data_type(
api_log_rec *rec,
const bcmcli_type_descr *td,
const void *data,
const char *name,
uint32_t num_entries,
uint32_t entry_size);
static bcmos_bool api_log_is_singleton(bcmolt_mgt_group group)
{
switch (group)
{
case BCMOLT_MGT_GROUP_CFG:
case BCMOLT_MGT_GROUP_STAT:
case BCMOLT_MGT_GROUP_STAT_CFG:
case BCMOLT_MGT_GROUP_AUTO_CFG:
return BCMOS_TRUE;
default:
return BCMOS_FALSE;
}
}
static inline uint32_t api_log_buf_remaining(const api_log_rec *rec)
{
return (MAX_DEV_LOG_STRING_NET_SIZE - API_LOG_ENTRY_OVERHEAD) - rec->used;
}
static void api_log_flush(api_log_rec *rec, const char *end_of_line)
{
if (rec->used != 0)
{
strncat(rec->buf, end_of_line, MAX_DEV_LOG_STRING_NET_SIZE - rec->used);
bcm_dev_log_log(rec->log_id, DEV_LOG_LEVEL_INFO, BCM_LOG_FLAG_CALLER_FMT, "%s", rec->buf);
rec->used = 0;
}
}
static void api_log_print(api_log_rec *rec, const char *format, ...)
{
va_list args;
int ret;
va_start(args, format);
ret = vsnprintf(rec->buf + rec->used, api_log_buf_remaining(rec), format, args);
va_end(args);
if (ret < 0 || ret > api_log_buf_remaining(rec))
{
/* this print doesn't fit in the buffer - send the current buffer and create a new continuation log entry */
rec->buf[rec->used] = '\0';
api_log_flush(rec, API_LOG_CONT_STR "\n");
/* the continuation entry can skip the header */
api_log_print(rec, API_LOG_CONT_STR);
/* try again, this time against the continuation entry */
va_start(args, format);
ret = vsnprintf(rec->buf + rec->used, api_log_buf_remaining(rec), format, args);
va_end(args);
if (ret < 0 || ret > api_log_buf_remaining(rec))
{
BCM_LOG(ERROR, rec->log_id, "API message log overflow on msg #%u\n", rec->msg->corr_tag);
}
else
{
rec->used += ret;
}
}
else
{
rec->used += ret;
}
}
static bcmos_errno api_log_read_snum(const bcmcli_type_descr *td, const void *data, int64_t *n)
{
switch (td->size)
{
case 1:
{
int8_t n1 = *(const int8_t *)data;
*n = n1;
break;
}
case 2:
{
int16_t n2 = *(const int16_t *)data;
*n = n2;
break;
}
case 4:
{
int32_t n4 = *(const int32_t *)data;
*n = n4;
break;
}
case 8:
{
memcpy(n, data, sizeof(*n));
break;
}
default:
return BCM_ERR_NOT_SUPPORTED;
}
return BCM_ERR_OK;
}
static bcmos_errno api_log_read_unum(const bcmcli_type_descr *td, const void *data, uint64_t *n)
{
switch (td->size)
{
case 1:
{
uint8_t n1 = *(const uint8_t *)data;
*n = n1;
break;
}
case 2:
{
uint16_t n2 = *(const uint16_t *)data;
*n = n2;
break;
}
case 4:
{
uint32_t n4 = *(const uint32_t *)data;
*n = n4;
break;
}
case 8:
{
memcpy(n, data, sizeof(*n));
break;
}
default:
return BCM_ERR_NOT_SUPPORTED;
}
return BCM_ERR_OK;
}
/* break a string up into smaller chunks (preferably at \n boundaries) so we don't overflow the size of a log entry */
static void api_log_print_long_line(api_log_rec *rec, const char *line, uint32_t len)
{
uint32_t i;
uint32_t handled = 0;
for (i = 0; i < len; i++)
{
if (line[i] == '\n')
{
if (i > handled)
{
api_log_print(rec, "%.*s", i - handled, &line[handled]);
api_log_flush(rec, API_LOG_CONT_STR "\n");
api_log_print(rec, API_LOG_CONT_STR);
}
handled = i + 1;
}
if (i > handled && i - handled == 32)
{
api_log_print(rec, "%.*s", 32, &line[handled]);
handled = i;
}
}
if (handled < len)
{
api_log_print(rec, "%.*s", len - handled, &line[handled]);
}
}
static bcmos_errno api_log_dump_simple_data_type(
api_log_rec *rec,
const bcmcli_type_descr *td,
const void *data,
const char *name)
{
bcmos_errno err = BCM_ERR_OK;
switch (td->base_type)
{
case BCMOLT_BASE_TYPE_ID_SNUM: /* signed number */
{
int64_t n = 0;
err = api_log_read_snum(td, data, &n);
api_log_print(rec, "%lld", (long long)n);
break;
}
case BCMOLT_BASE_TYPE_ID_UNUM: /* unsigned number */
{
uint64_t n = 0;
err = api_log_read_unum(td, data, &n);
api_log_print(rec, "%llu", (unsigned long long)n);
break;
}
case BCMOLT_BASE_TYPE_ID_UNUM_HEX: /* unsigned number printed in hex */
{
uint64_t n = 0;
err = api_log_read_unum(td, data, &n);
api_log_print(rec, "0x%llx", (unsigned long long)n);
break;
}
case BCMOLT_BASE_TYPE_ID_FLOAT: /* floating-point number */
{
if (td->size == sizeof(float))
{
api_log_print(rec, "%f", *(const float *)data);
}
else if (td->size == sizeof(double))
{
api_log_print(rec, "%f", *(const double *)data);
}
else
{
err = BCM_ERR_NOT_SUPPORTED;
}
break;
}
case BCMOLT_BASE_TYPE_ID_BOOL:
{
uint64_t n = 0;
err = api_log_read_unum(td, data, &n);
api_log_print(rec, "%s", n == 0 ? "no" : "yes");
break;
}
case BCMOLT_BASE_TYPE_ID_STRING: /* string */
{
uint32_t size = td->size;
if (size == 0)
{
size = strlen((const char *)data);
}
api_log_print_long_line(rec, (const char *)data, size);
break;
}
case BCMOLT_BASE_TYPE_ID_IPV4: /* IPv4 address */
{
uint32_t ip;
memcpy(&ip, data, sizeof(ip));
api_log_print(rec, "%d.%d.%d.%d", (ip>>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, ip & 0xff);
break;
}
case BCMOLT_BASE_TYPE_ID_MAC: /* MAC address */
{
bcmos_mac_address mac;
memcpy(mac.u8, data, sizeof(mac.u8));
api_log_print(rec, "%02x:%02x:%02x:%02x:%02x:%02x",
mac.u8[0], mac.u8[1], mac.u8[2], mac.u8[3], mac.u8[4], mac.u8[5]);
break;
}
case BCMOLT_BASE_TYPE_ID_ENUM: /* enum */
{
uint64_t n = 0;
err = api_log_read_unum(td, data, &n);
BUG_ON(td->x.e == NULL);
api_log_print(rec, "%s", bcmcli_enum_stringval(td->x.e, n));
break;
}
case BCMOLT_BASE_TYPE_ID_ENUM_MASK:
{
uint64_t n = 0;
bcmcli_enum_val *value = td->x.e;
bcmos_bool first = BCMOS_TRUE;
BUG_ON(value == NULL);
err = api_log_read_unum(td, data, &n);
while (value->name != NULL && n != 0)
{
if ((value->val & n) != 0)
{
api_log_print(rec, "%s%s", first ? "" : "+", value->name);
first = BCMOS_FALSE;
}
n -= value->val;
++value;
}
if (first)
{
api_log_print(rec, "-");
}
break;
}
default:
err = BCM_ERR_NOT_SUPPORTED;
break;
}
return err;
}
static uint16_t api_log_get_num_enum_vals(const bcmcli_enum_val *vals)
{
const bcmcli_enum_val *v = vals;
while (v != NULL && v->name != NULL)
{
++v;
}
return (uint16_t)(v - vals);
}
static bcmos_errno api_log_dump_array(
api_log_rec *rec,
const bcmcli_type_descr *td,
const void *data,
uint32_t size,
const char *name)
{
bcmos_errno err = BCM_ERR_OK;
/* Print as buffer or element by element ? */
if ((td->base_type == BCMOLT_BASE_TYPE_ID_UNUM || td->base_type == BCMOLT_BASE_TYPE_ID_UNUM_HEX) && td->size == 1)
{
uint32_t i;
api_log_print(rec, "%s=", name);
if (size == 0)
{
api_log_print(rec, "-");
}
else
{
for (i = 0; i < size; i++)
{
api_log_print(rec, "%02X", ((const uint8_t *)data)[i]);
}
}
api_log_print(rec, " ");
}
else
{
err = api_log_dump_data_type(rec, td, data, name, size, td->size);
}
return err;
}
static bcmos_errno api_log_dump_data_type(
api_log_rec *rec,
const bcmcli_type_descr *td,
const void *data,
const char *name,
uint32_t num_entries,
uint32_t entry_size)
{
bcmos_errno err = BCM_ERR_OK;
switch (td->base_type)
{
case BCMOLT_BASE_TYPE_ID_STRUCT:
{
uint16_t f;
char full_name[APICLI_MAX_PARM_NAME_LENGTH];
if (td->x.s.num_fields == 0)
{
return 0;
}
BUG_ON(td->x.s.fields == NULL);
for (f = 0; f < td->x.s.num_fields; f++)
{
const bcmcli_field_descr *fld = &td->x.s.fields[f];
void *fdata = (void *)((long)data + fld->offset);
bcmcli_strncpy(full_name, name, sizeof(full_name));
bcmcli_strncat(full_name, ".", sizeof(full_name));
bcmcli_strncat(full_name, fld->name, sizeof(full_name));
err = api_log_dump_data_type(rec, fld->type, fdata, full_name, num_entries, entry_size);
}
break;
}
case BCMOLT_BASE_TYPE_ID_UNION:
{
/* Print fields up to selector, then selector, then selected sub-structure */
uint16_t f;
char full_name[APICLI_MAX_PARM_NAME_LENGTH];
const bcmcli_field_descr *fld;
void *fdata;
int64_t selector_val = 0;
uint16_t num_union_vals;
if (td->x.u.num_common_fields == 0)
{
return 0;
}
BUG_ON(td->x.u.common_fields == NULL);
/* Common fields, including selector */
for (f = 0; f <= td->x.u.classifier_idx && err == BCM_ERR_OK; f++)
{
fld = &td->x.u.common_fields[f];
fdata = (void *)((long)data + fld->offset);
bcmcli_strncpy(full_name, name, sizeof(full_name));
if (fld->name != NULL && strlen(fld->name) != 0)
{
bcmcli_strncat(full_name, ".", sizeof(full_name));
bcmcli_strncat(full_name, fld->name, sizeof(full_name));
}
err = api_log_dump_data_type(rec, fld->type, fdata, full_name, num_entries, entry_size);
if (err == BCM_ERR_OK && f == td->x.u.classifier_idx)
{
err = api_log_read_snum(fld->type, fdata, &selector_val);
}
}
if (err != BCM_ERR_OK)
{
return err;
}
num_union_vals = api_log_get_num_enum_vals(td->x.u.common_fields[td->x.u.classifier_idx].type->x.e);
if ((unsigned)selector_val >= num_union_vals)
{
return BCM_ERR_INTERNAL;
}
/* Selected field */
fld = &td->x.u.union_fields[selector_val];
if (fld->type == NULL)
{
return BCM_ERR_OK;
}
fdata = (void *)((long)data + fld->offset);
bcmcli_strncpy(full_name, name, sizeof(full_name));
if (fld->name != NULL && strlen(fld->name) != 0)
{
bcmcli_strncat(full_name, ".", sizeof(full_name));
bcmcli_strncat(full_name, fld->name, sizeof(full_name));
}
err = api_log_dump_data_type(rec, fld->type, fdata, full_name, num_entries, entry_size);
if (err != BCM_ERR_OK)
{
return err;
}
/* Common fields following selector */
for (; f < td->x.u.num_common_fields; f++)
{
fld = &td->x.u.common_fields[f];
fdata = (void *)((long)data + fld->offset);
bcmcli_strncpy(full_name, name, sizeof(full_name));
if (fld->name != NULL && strlen(fld->name) != 0)
{
bcmcli_strncat(full_name, ".", sizeof(full_name));
bcmcli_strncat(full_name, fld->name, sizeof(full_name));
}
err = api_log_dump_data_type(rec, fld->type, fdata, full_name, num_entries, entry_size);
}
break;
}
case BCMOLT_BASE_TYPE_ID_ARR_FIXED:
{
err = api_log_dump_array(rec, td->x.arr_fixed.elem_type, data, td->x.arr_fixed.size, name);
break;
}
case BCMOLT_BASE_TYPE_ID_ARR_DYN:
{
/* Read length */
uint32_t array_size;
long base_ptr;
switch (td->x.arr_dyn.len_size)
{
case 1: array_size = *(const uint8_t *)data; break;
case 2: array_size = *(const uint16_t *)data; break;
case 4: array_size = *(const uint32_t *)data; break;
default:
return BCM_ERR_NOT_SUPPORTED;
}
base_ptr = BCMOS_ROUND_UP((long)data + td->x.arr_dyn.len_size, sizeof(void *));
BUG_ON(base_ptr == 0);
data = *(void **)base_ptr;
err = api_log_dump_array(rec, td->x.arr_dyn.elem_type, data, array_size, name);
break;
}
default:
{
int n;
/* Finally! Simple type that maps to a single name=value pair */
if (name != NULL)
{
api_log_print(rec, "%s=", name);
}
/* Dump simple value or array of simple values */
if (num_entries == 0)
{
api_log_print(rec, "-");
}
for (n = 0; n < num_entries; n++)
{
if (n != 0)
{
api_log_print(rec, ",");
}
err = api_log_dump_simple_data_type(rec, td, data, name);
if (err != BCM_ERR_OK)
{
return err;
}
data = (const void *)((long)data + entry_size);
}
api_log_print(rec, " ");
break;
}
}
return err;
}
static bcmos_errno api_log_dump_prop(api_log_rec *rec, const bcmcli_prop_descr *pd, const void *prop_data)
{
return api_log_dump_data_type(rec, pd->type, prop_data, pd->name, 1, 0);
}
/* Calculate property pointer given the group data pointer and property description */
static inline const void *api_log_prop_data_ptr(const void *group_ptr, const bcmcli_prop_descr *pd)
{
return (const void *)((long)group_ptr + pd->offset);
}
static bcmos_errno api_log_append_key(api_log_rec *rec, const void *key, uint32_t key_size)
{
uint16_t prop;
bcmos_errno err = BCM_ERR_OK;
const bcmcli_prop_descr *pd;
for (prop = 0;
api_cli_object_property(rec->msg->obj_type, BCMOLT_MGT_GROUP_KEY, 0, prop, &pd) == BCM_ERR_OK;
++prop)
{
const void *prop_data = api_log_prop_data_ptr(key, pd);
if (prop_data == NULL)
{
continue;
}
BUG_ON(pd->offset > key_size);
err = api_log_dump_prop(rec, pd, prop_data);
if (err != BCM_ERR_OK)
{
break;
}
}
return err;
}
static bcmos_errno api_log_append_data(api_log_rec *rec, const void *data, uint32_t data_size)
{
uint16_t prop;
bcmos_errno err = BCM_ERR_OK;
const bcmcli_prop_descr *pd;
for (prop = 0;
api_cli_object_property(rec->msg->obj_type, rec->msg->group, rec->msg->subgroup, prop, &pd) == BCM_ERR_OK;
++prop)
{
const void *prop_data = api_log_prop_data_ptr(data, pd);
if (((rec->msg->presence_mask & (1LL << prop)) == 0) || (prop_data == NULL))
{
continue;
}
BUG_ON(pd->offset > data_size);
err = api_log_dump_prop(rec, pd, prop_data);
if (err != BCM_ERR_OK)
{
break;
}
}
return err;
}
static bcmos_errno api_log_append_hdr(api_log_rec *rec)
{
bcmos_errno err;
const char *name;
const char *descr;
err = api_cli_object_name(rec->msg->obj_type, &name, &descr);
if (err != BCM_ERR_OK)
{
return err;
}
api_log_print(rec, apicli_mgt_group_to_str(rec->msg->group));
if (api_log_is_singleton(rec->msg->group))
{
api_log_print(rec, (rec->msg->type & BCMOLT_MSG_TYPE_SET) != 0 ? "_set" : "_get");
if ((rec->msg->type & BCMOLT_MSG_TYPE_MULTI) != 0)
{
api_log_print(rec, "_multi");
}
}
if (rec->msg->group != BCMOLT_MGT_GROUP_AUTO && rec->msg->group != BCMOLT_MGT_GROUP_PROXY_RX)
{
if (rec->msg->dir == BCMOLT_MSG_DIR_REQUEST)
{
api_log_print(rec, " request");
}
else
{
api_log_print(rec, " response[%d]", rec->msg->err);
}
}
api_log_print(rec, "> %s", name);
if (!api_log_is_singleton(rec->msg->group))
{
const char *sub_name;
const char *sub_d;
err = api_cli_object_subgroup_name(rec->msg->obj_type, rec->msg->group, rec->msg->subgroup, &sub_name, &sub_d);
if (err != BCM_ERR_OK)
{
return err;
}
api_log_print(rec, ".%s", sub_name);
}
api_log_print(rec, ": ");
return BCM_ERR_OK;
}
static bcmos_errno api_log_append_body(api_log_rec *rec)
{
bcmos_errno err;
uint32_t key_size;
uint32_t key_offset;
uint32_t data_size;
uint32_t data_offset;
err = api_cli_object_struct_size(
rec->msg->obj_type,
rec->msg->group,
rec->msg->subgroup,
&key_size,
&key_offset,
&data_size,
&data_offset);
if (err != BCM_ERR_OK)
{
return err;
}
if ((rec->msg->type & BCMOLT_MSG_TYPE_MULTI) != 0)
{
/* message set printing for multi-object cfg not implemented */
api_log_print(rec, "(data omitted)");
}
else
{
if ((rec->msg->group != BCMOLT_MGT_GROUP_AUTO_CFG) && (key_size != 0))
{
const void *key = (const void *)((long)rec->msg + key_offset);
err = api_log_append_key(rec, key, key_size);
if (err != BCM_ERR_OK)
{
return err;
}
}
if ((data_size != 0) &&
(((rec->msg->dir == BCMOLT_MSG_DIR_REQUEST) && (rec->msg->type & BCMOLT_MSG_TYPE_SET)) ||
((rec->msg->dir == BCMOLT_MSG_DIR_RESPONSE) && (rec->msg->type & BCMOLT_MSG_TYPE_GET)) ||
(rec->msg->group == BCMOLT_MGT_GROUP_AUTO) ||
(rec->msg->group == BCMOLT_MGT_GROUP_PROXY_RX)))
{
const void *data = (const void *)((long)rec->msg + data_offset);
err = api_log_append_data(rec, data, data_size);
if (err != BCM_ERR_OK)
{
return err;
}
}
}
return BCM_ERR_OK;
}
bcmos_errno bcmolt_msg_log(dev_log_id log_id, const bcmolt_msg *msg)
{
bcmos_errno err = BCM_ERR_OK;
api_log_rec rec = { .log_id = log_id, .msg = msg, .used = 0 };
rec.buf = bcmos_blk_pool_alloc(&api_log_blk_pool);
if (rec.buf == NULL)
{
err = BCM_ERR_NOMEM;
}
err = (err != BCM_ERR_OK) ? err : api_log_append_hdr(&rec);
err = (err != BCM_ERR_OK) ? err : api_log_append_body(&rec);
api_log_flush(&rec, "\n");
if (rec.buf != NULL)
{
bcmos_blk_pool_free(rec.buf);
}
if (err != BCM_ERR_OK)
{
BCM_LOG(ERROR, log_id, "API message log error: %s (%d)\n", bcmos_strerror(err), err);
}
return err;
}
void bcmolt_msg_log_init(void)
{
bcmos_errno err;
bcmos_blk_pool_parm blk_pool_parm =
{
.name = "api_dev_log",
.blk_size = MAX_DEV_LOG_STRING_NET_SIZE,
.num_blks = API_LOG_NUM_BLKS,
};
err = bcmos_blk_pool_create(&api_log_blk_pool, &blk_pool_parm);
BUG_ON(err != BCM_ERR_OK);
}