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
nfp_net_tlv_caps_reset(struct nfp_net_tlv_caps * caps)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
nfp_net_tlv_caps_parse(struct device * dev,u8 __iomem * ctrl_mem,struct nfp_net_tlv_caps * caps)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 case NFP_NET_CFG_TLV_TYPE_MBOX_CMSG_TYPES:
103 if (length >= 4)
104 caps->mbox_cmsg_types = readl(data);
105 break;
106 case NFP_NET_CFG_TLV_TYPE_CRYPTO_OPS:
107 if (length < 32) {
108 dev_err(dev,
109 "CRYPTO OPS TLV should be at least 32B, is %dB offset:%u\n",
110 length, offset);
111 return -EINVAL;
112 }
113
114 caps->crypto_ops = readl(data);
115 caps->crypto_enable_off = data - ctrl_mem + 16;
116 break;
117 default:
118 if (!FIELD_GET(NFP_NET_CFG_TLV_HEADER_REQUIRED, hdr))
119 break;
120
121 dev_err(dev, "unknown TLV type:%u offset:%u len:%u\n",
122 FIELD_GET(NFP_NET_CFG_TLV_HEADER_TYPE, hdr),
123 offset, length);
124 return -EINVAL;
125 }
126
127 data += length;
128 if (data + 4 > end) {
129 dev_err(dev, "reached end of BAR without END TLV\n");
130 return -EINVAL;
131 }
132 }
133
134 /* Not reached */
135 return -EINVAL;
136 }
137