1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 /* Copyright (C) 2018 Netronome Systems, Inc. */ 3 4 #include <linux/bitfield.h> 5 #include <linux/device.h> 6 #include <linux/kernel.h> 7 #include <linux/types.h> 8 9 #include "nfp_net_ctrl.h" 10 #include "nfp_net.h" 11 12 static void nfp_net_tlv_caps_reset(struct nfp_net_tlv_caps *caps) 13 { 14 memset(caps, 0, sizeof(*caps)); 15 caps->me_freq_mhz = 1200; 16 caps->mbox_off = NFP_NET_CFG_MBOX_BASE; 17 caps->mbox_len = NFP_NET_CFG_MBOX_VAL_MAX_SZ; 18 } 19 20 int nfp_net_tlv_caps_parse(struct device *dev, u8 __iomem *ctrl_mem, 21 struct nfp_net_tlv_caps *caps) 22 { 23 u8 __iomem *data = ctrl_mem + NFP_NET_CFG_TLV_BASE; 24 u8 __iomem *end = ctrl_mem + NFP_NET_CFG_BAR_SZ; 25 u32 hdr; 26 27 nfp_net_tlv_caps_reset(caps); 28 29 hdr = readl(data); 30 if (!hdr) 31 return 0; 32 33 while (true) { 34 unsigned int length, offset; 35 u32 hdr = readl(data); 36 37 length = FIELD_GET(NFP_NET_CFG_TLV_HEADER_LENGTH, hdr); 38 offset = data - ctrl_mem; 39 40 /* Advance past the header */ 41 data += 4; 42 43 if (length % NFP_NET_CFG_TLV_LENGTH_INC) { 44 dev_err(dev, "TLV size not multiple of %u offset:%u len:%u\n", 45 NFP_NET_CFG_TLV_LENGTH_INC, offset, length); 46 return -EINVAL; 47 } 48 if (data + length > end) { 49 dev_err(dev, "oversized TLV offset:%u len:%u\n", 50 offset, length); 51 return -EINVAL; 52 } 53 54 switch (FIELD_GET(NFP_NET_CFG_TLV_HEADER_TYPE, hdr)) { 55 case NFP_NET_CFG_TLV_TYPE_UNKNOWN: 56 dev_err(dev, "NULL TLV at offset:%u\n", offset); 57 return -EINVAL; 58 case NFP_NET_CFG_TLV_TYPE_RESERVED: 59 break; 60 case NFP_NET_CFG_TLV_TYPE_END: 61 if (!length) 62 return 0; 63 64 dev_err(dev, "END TLV should be empty, has offset:%u len:%d\n", 65 offset, length); 66 return -EINVAL; 67 case NFP_NET_CFG_TLV_TYPE_ME_FREQ: 68 if (length != 4) { 69 dev_err(dev, 70 "ME FREQ TLV should be 4B, is %dB offset:%u\n", 71 length, offset); 72 return -EINVAL; 73 } 74 75 caps->me_freq_mhz = readl(data); 76 break; 77 case NFP_NET_CFG_TLV_TYPE_MBOX: 78 if (!length) { 79 caps->mbox_off = 0; 80 caps->mbox_len = 0; 81 } else { 82 caps->mbox_off = data - ctrl_mem; 83 caps->mbox_len = length; 84 } 85 break; 86 case NFP_NET_CFG_TLV_TYPE_EXPERIMENTAL0: 87 case NFP_NET_CFG_TLV_TYPE_EXPERIMENTAL1: 88 dev_warn(dev, 89 "experimental TLV type:%u offset:%u len:%u\n", 90 FIELD_GET(NFP_NET_CFG_TLV_HEADER_TYPE, hdr), 91 offset, length); 92 break; 93 case NFP_NET_CFG_TLV_TYPE_REPR_CAP: 94 if (length < 4) { 95 dev_err(dev, "REPR CAP TLV short %dB < 4B offset:%u\n", 96 length, offset); 97 return -EINVAL; 98 } 99 100 caps->repr_cap = readl(data); 101 break; 102 default: 103 if (!FIELD_GET(NFP_NET_CFG_TLV_HEADER_REQUIRED, hdr)) 104 break; 105 106 dev_err(dev, "unknown TLV type:%u offset:%u len:%u\n", 107 FIELD_GET(NFP_NET_CFG_TLV_HEADER_TYPE, hdr), 108 offset, length); 109 return -EINVAL; 110 } 111 112 data += length; 113 if (data + 4 > end) { 114 dev_err(dev, "reached end of BAR without END TLV\n"); 115 return -EINVAL; 116 } 117 } 118 119 /* Not reached */ 120 return -EINVAL; 121 } 122