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