/* * Driver O/S-independent utility routines * * Copyright (C) 2021, Broadcom. * * 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. * * * <> */ #include #include #include #ifdef BCMDRIVER #include #else /* !BCMDRIVER */ #include #include #include #ifndef ASSERT #define ASSERT(exp) #endif #endif /* !BCMDRIVER */ #include #include #include int BCMPOSTTRAPFN(bcm_xtlv_hdr_size)(bcm_xtlv_opts_t opts) { int len = (int)OFFSETOF(bcm_xtlv_t, data); /* nominal */ if (opts & BCM_XTLV_OPTION_LENU8) --len; if (opts & BCM_XTLV_OPTION_IDU8) --len; return len; } bool bcm_valid_xtlv(const bcm_xtlv_t *elt, int buf_len, bcm_xtlv_opts_t opts) { return elt != NULL && buf_len >= bcm_xtlv_hdr_size(opts) && buf_len >= bcm_xtlv_size(elt, opts); } int BCMPOSTTRAPFN(bcm_xtlv_size_for_data)(int dlen, bcm_xtlv_opts_t opts) { int hsz; hsz = bcm_xtlv_hdr_size(opts); return ((opts & BCM_XTLV_OPTION_ALIGN32) ? ALIGN_SIZE(dlen + hsz, 4) : (dlen + hsz)); } int bcm_xtlv_size(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) { int size; /* size including header, data, and any pad */ int len; /* length wthout padding */ len = BCM_XTLV_LEN_EX(elt, opts); size = bcm_xtlv_size_for_data(len, opts); return size; } int bcm_xtlv_len(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) { const uint8 *lenp; int len; lenp = (const uint8 *)elt + OFFSETOF(bcm_xtlv_t, len); /* nominal */ if (opts & BCM_XTLV_OPTION_IDU8) { --lenp; } if (opts & BCM_XTLV_OPTION_LENU8) { len = *lenp; } else if (opts & BCM_XTLV_OPTION_LENBE) { len = (uint32)hton16(elt->len); } else { len = ltoh16_ua(lenp); } return len; } int bcm_xtlv_id(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) { int id = 0; if (opts & BCM_XTLV_OPTION_IDU8) { id = *(const uint8 *)elt; } else if (opts & BCM_XTLV_OPTION_IDBE) { id = (uint32)hton16(elt->id); } else { id = ltoh16_ua((const uint8 *)elt); } return id; } bcm_xtlv_t * bcm_next_xtlv(const bcm_xtlv_t *elt, int *buflen, bcm_xtlv_opts_t opts) { uint sz; COV_TAINTED_DATA_SINK(buflen); COV_NEG_SINK(buflen); /* validate current elt */ if (!bcm_valid_xtlv(elt, *buflen, opts)) return NULL; /* advance to next elt */ sz = BCM_XTLV_SIZE_EX(elt, opts); elt = (const bcm_xtlv_t*)((const uint8 *)elt + sz); #if defined(__COVERITY__) /* The 'sz' value is tainted in Coverity because it is read from the tainted data pointed * to by 'elt'. However, bcm_valid_xtlv() verifies that the elt pointer is a valid element, * so its size, sz = BCM_XTLV_SIZE_EX(elt, opts), is in the bounds of the buffer. * Clearing the tainted attribute of 'sz' for Coverity. */ __coverity_tainted_data_sanitize__(sz); if (sz > *buflen) { return NULL; } #endif /* __COVERITY__ */ *buflen -= sz; /* validate next elt */ if (!bcm_valid_xtlv(elt, *buflen, opts)) return NULL; COV_TAINTED_DATA_ARG(elt); GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST() return (bcm_xtlv_t *)(elt); GCC_DIAGNOSTIC_POP() } int bcm_xtlv_buf_init(bcm_xtlvbuf_t *tlv_buf, uint8 *buf, uint16 len, bcm_xtlv_opts_t opts) { if (!tlv_buf || !buf || !len) return BCME_BADARG; tlv_buf->opts = opts; tlv_buf->size = len; tlv_buf->head = buf; tlv_buf->buf = buf; return BCME_OK; } uint16 bcm_xtlv_buf_len(bcm_xtlvbuf_t *tbuf) { uint16 len; if (tbuf) len = (uint16)(tbuf->buf - tbuf->head); else len = 0; return len; } uint16 bcm_xtlv_buf_rlen(bcm_xtlvbuf_t *tbuf) { uint16 rlen; if (tbuf) rlen = tbuf->size - bcm_xtlv_buf_len(tbuf); else rlen = 0; return rlen; } uint8 * bcm_xtlv_buf(bcm_xtlvbuf_t *tbuf) { return tbuf ? tbuf->buf : NULL; } uint8 * bcm_xtlv_head(bcm_xtlvbuf_t *tbuf) { return tbuf ? tbuf->head : NULL; } void BCMPOSTTRAPFN(bcm_xtlv_pack_xtlv)(bcm_xtlv_t *xtlv, uint16 type, uint16 len, const uint8 *data, bcm_xtlv_opts_t opts) { uint8 *data_buf; bcm_xtlv_opts_t mask = BCM_XTLV_OPTION_IDU8 | BCM_XTLV_OPTION_LENU8; if (!(opts & mask)) { /* default */ uint8 *idp = (uint8 *)xtlv; uint8 *lenp = idp + sizeof(xtlv->id); htol16_ua_store(type, idp); htol16_ua_store(len, lenp); data_buf = lenp + sizeof(uint16); } else if ((opts & mask) == mask) { /* u8 id and u8 len */ uint8 *idp = (uint8 *)xtlv; uint8 *lenp = idp + 1; *idp = (uint8)type; *lenp = (uint8)len; data_buf = lenp + sizeof(uint8); } else if (opts & BCM_XTLV_OPTION_IDU8) { /* u8 id, u16 len */ uint8 *idp = (uint8 *)xtlv; uint8 *lenp = idp + 1; *idp = (uint8)type; htol16_ua_store(len, lenp); data_buf = lenp + sizeof(uint16); } else if (opts & BCM_XTLV_OPTION_LENU8) { /* u16 id, u8 len */ uint8 *idp = (uint8 *)xtlv; uint8 *lenp = idp + sizeof(uint16); htol16_ua_store(type, idp); *lenp = (uint8)len; data_buf = lenp + sizeof(uint8); } else { ASSERT(!"Unexpected xtlv option"); return; } if (opts & BCM_XTLV_OPTION_LENU8) { ASSERT(len <= 0x00ff); len &= 0xff; } if (data != NULL) { memcpy(data_buf, data, len); } } /* xtlv header is always packed in LE order */ void bcm_xtlv_unpack_xtlv(const bcm_xtlv_t *xtlv, uint16 *type, uint16 *len, const uint8 **data, bcm_xtlv_opts_t opts) { if (type) *type = (uint16)bcm_xtlv_id(xtlv, opts); if (len) *len = (uint16)bcm_xtlv_len(xtlv, opts); if (data) *data = (const uint8 *)xtlv + BCM_XTLV_HDR_SIZE_EX(opts); } int bcm_xtlv_put_data(bcm_xtlvbuf_t *tbuf, uint16 type, const uint8 *data, int n) { bcm_xtlv_t *xtlv; int size; if (tbuf == NULL) return BCME_BADARG; size = bcm_xtlv_size_for_data(n, tbuf->opts); if (bcm_xtlv_buf_rlen(tbuf) < size) return BCME_NOMEM; xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); bcm_xtlv_pack_xtlv(xtlv, type, (uint16)n, data, tbuf->opts); tbuf->buf += size; /* note: data may be NULL, reserves space */ return BCME_OK; } static int bcm_xtlv_put_int(bcm_xtlvbuf_t *tbuf, uint16 type, const uint8 *data, int n, int int_sz) { bcm_xtlv_t *xtlv; int xtlv_len; uint8 *xtlv_data; int err = BCME_OK; if (tbuf == NULL) { err = BCME_BADARG; goto done; } xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); /* put type and length in xtlv and reserve data space */ xtlv_len = n * int_sz; err = bcm_xtlv_put_data(tbuf, type, NULL, xtlv_len); if (err != BCME_OK) goto done; xtlv_data = (uint8 *)xtlv + bcm_xtlv_hdr_size(tbuf->opts); /* write data w/ little-endianness into buffer - single loop, aligned access */ for (; n != 0; --n, xtlv_data += int_sz, data += int_sz) { switch (int_sz) { case sizeof(uint8): break; case sizeof(uint16): { uint16 v = load16_ua(data); htol16_ua_store(v, xtlv_data); break; } case sizeof(uint32): { uint32 v = load32_ua(data); htol32_ua_store(v, xtlv_data); break; } case sizeof(uint64): { uint64 v = load64_ua(data); htol64_ua_store(v, xtlv_data); break; } default: err = BCME_UNSUPPORTED; goto done; } } done: return err; } int bcm_xtlv_put16(bcm_xtlvbuf_t *tbuf, uint16 type, const uint16 *data, int n) { return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint16)); } int bcm_xtlv_put32(bcm_xtlvbuf_t *tbuf, uint16 type, const uint32 *data, int n) { return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint32)); } int bcm_xtlv_put64(bcm_xtlvbuf_t *tbuf, uint16 type, const uint64 *data, int n) { return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint64)); } /* * upacks xtlv record from buf checks the type * copies data to callers buffer * advances tlv pointer to next record * caller's resposible for dst space check */ int bcm_unpack_xtlv_entry(const uint8 **tlv_buf, uint16 xpct_type, uint16 xpct_len, uint8 *dst_data, bcm_xtlv_opts_t opts) { const bcm_xtlv_t *ptlv = (const bcm_xtlv_t *)*tlv_buf; uint16 len; uint16 type; const uint8 *data; ASSERT(ptlv); bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); if (len) { if ((type != xpct_type) || (len > xpct_len)) return BCME_BADARG; if (dst_data && data) memcpy(dst_data, data, len); /* copy data to dst */ } *tlv_buf += BCM_XTLV_SIZE_EX(ptlv, opts); return BCME_OK; } /* * packs user data into tlv record and advances tlv pointer to next xtlv slot * buflen is used for tlv_buf space check */ int bcm_pack_xtlv_entry(uint8 **tlv_buf, uint16 *buflen, uint16 type, uint16 len, const uint8 *src_data, bcm_xtlv_opts_t opts) { bcm_xtlv_t *ptlv = (bcm_xtlv_t *)*tlv_buf; int size; ASSERT(ptlv); size = bcm_xtlv_size_for_data(len, opts); /* copy data from tlv buffer to dst provided by user */ if (size > *buflen) { return BCME_BADLEN; } bcm_xtlv_pack_xtlv(ptlv, type, len, src_data, opts); /* advance callers pointer to tlv buff */ *tlv_buf = (uint8*)(*tlv_buf) + size; /* decrement the len */ *buflen -= (uint16)size; return BCME_OK; } /* * unpack all xtlv records from the issue a callback * to set function one call per found tlv record */ int bcm_unpack_xtlv_buf(void *ctx, const uint8 *tlv_buf, uint16 buflen, bcm_xtlv_opts_t opts, bcm_xtlv_unpack_cbfn_t *cbfn) { uint16 len; uint16 type; int res = BCME_OK; int size; const bcm_xtlv_t *ptlv; int sbuflen = buflen; const uint8 *data; int hdr_size; ASSERT(!buflen || tlv_buf); ASSERT(!buflen || cbfn); hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); while (sbuflen >= hdr_size) { ptlv = (const bcm_xtlv_t *)tlv_buf; bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); size = bcm_xtlv_size_for_data(len, opts); sbuflen -= size; /* check for buffer overrun */ if (sbuflen < 0) { break; } if ((res = cbfn(ctx, data, type, len)) != BCME_OK) { break; } tlv_buf += size; } return res; } int bcm_pack_xtlv_buf(void *ctx, uint8 *tlv_buf, uint16 buflen, bcm_xtlv_opts_t opts, bcm_pack_xtlv_next_info_cbfn_t get_next, bcm_pack_xtlv_pack_next_cbfn_t pack_next, int *outlen) { int res = BCME_OK; uint16 tlv_id; uint16 tlv_len; uint8 *startp; uint8 *endp; uint8 *buf; bool more; int size; int hdr_size; ASSERT(get_next && pack_next); buf = tlv_buf; startp = buf; endp = (uint8 *)buf + buflen; more = TRUE; hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); while (more && (buf < endp)) { more = get_next(ctx, &tlv_id, &tlv_len); size = bcm_xtlv_size_for_data(tlv_len, opts); if ((buf + size) > endp) { res = BCME_BUFTOOSHORT; goto done; } bcm_xtlv_pack_xtlv((bcm_xtlv_t *)buf, tlv_id, tlv_len, NULL, opts); pack_next(ctx, tlv_id, tlv_len, buf + hdr_size); buf += size; } if (more) res = BCME_BUFTOOSHORT; done: if (outlen) { *outlen = (int)(buf - startp); } return res; } /* * pack xtlv buffer from memory according to xtlv_desc_t */ int bcm_pack_xtlv_buf_from_mem(uint8 **tlv_buf, uint16 *buflen, const xtlv_desc_t *items, bcm_xtlv_opts_t opts) { int res = BCME_OK; uint8 *ptlv = *tlv_buf; while (items->type != 0) { if (items->len && items->ptr) { res = bcm_pack_xtlv_entry(&ptlv, buflen, items->type, items->len, items->ptr, opts); if (res != BCME_OK) break; } items++; } *tlv_buf = ptlv; /* update the external pointer */ return res; } /* * unpack xtlv buffer to memory according to xtlv_desc_t * */ int bcm_unpack_xtlv_buf_to_mem(const uint8 *tlv_buf, int *buflen, xtlv_desc_t *items, bcm_xtlv_opts_t opts) { int res = BCME_OK; const bcm_xtlv_t *elt; elt = bcm_valid_xtlv((const bcm_xtlv_t *)tlv_buf, *buflen, opts) ? (const bcm_xtlv_t *)tlv_buf : NULL; if (!elt || !items) { res = BCME_BADARG; return res; } for (; elt != NULL && res == BCME_OK; elt = bcm_next_xtlv(elt, buflen, opts)) { /* find matches in desc_t items */ xtlv_desc_t *dst_desc = items; uint16 len, type; const uint8 *data; bcm_xtlv_unpack_xtlv(elt, &type, &len, &data, opts); while (dst_desc->type != 0) { if (type == dst_desc->type) { if (len != dst_desc->len) { res = BCME_BADLEN; } else { memcpy(dst_desc->ptr, data, len); } break; } dst_desc++; } } if (res == BCME_OK && *buflen != 0) /* this does not look right */ res = BCME_BUFTOOSHORT; return res; } /* * return data pointer of a given ID from xtlv buffer. * If the specified xTLV ID is found, on return *datalen will contain * the the data length of the xTLV ID. */ const uint8* bcm_get_data_from_xtlv_buf(const uint8 *tlv_buf, uint16 buflen, uint16 id, uint16 *datalen, bcm_xtlv_opts_t opts) { const uint8 *retptr = NULL; uint16 type, len; int size; const bcm_xtlv_t *ptlv; int sbuflen = buflen; const uint8 *data; int hdr_size; COV_TAINTED_DATA_SINK(buflen); COV_NEG_SINK(buflen); hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); /* Init the datalength */ if (datalen) { *datalen = 0; } while (sbuflen >= hdr_size) { ptlv = (const bcm_xtlv_t *)tlv_buf; bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); size = bcm_xtlv_size_for_data(len, opts); sbuflen -= size; if (sbuflen < 0) /* buffer overrun? */ break; if (id == type) { retptr = data; if (datalen) *datalen = len; break; } tlv_buf += size; } COV_TAINTED_DATA_ARG(retptr); return retptr; } bcm_xtlv_t* bcm_xtlv_bcopy(const bcm_xtlv_t *src, bcm_xtlv_t *dst, int src_buf_len, int dst_buf_len, bcm_xtlv_opts_t opts) { bcm_xtlv_t *dst_next = NULL; src = (src && bcm_valid_xtlv(src, src_buf_len, opts)) ? src : NULL; if (src && dst) { uint16 type; uint16 len; const uint8 *data; int size; bcm_xtlv_unpack_xtlv(src, &type, &len, &data, opts); size = bcm_xtlv_size_for_data(len, opts); if (size <= dst_buf_len) { bcm_xtlv_pack_xtlv(dst, type, len, data, opts); dst_next = (bcm_xtlv_t *)((uint8 *)dst + size); } } return dst_next; }