xref: /openbmc/linux/drivers/net/ethernet/netronome/nfp/nfp_net_ctrl.c (revision 05cf4fe738242183f1237f1b3a28b4479348c0a1)
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 len:%u\n",
45 				NFP_NET_CFG_TLV_LENGTH_INC, 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 len:%d\n",
65 				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\n",
71 					length);
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 		default:
94 			if (!FIELD_GET(NFP_NET_CFG_TLV_HEADER_REQUIRED, hdr))
95 				break;
96 
97 			dev_err(dev, "unknown TLV type:%u offset:%u len:%u\n",
98 				FIELD_GET(NFP_NET_CFG_TLV_HEADER_TYPE, hdr),
99 				offset, length);
100 			return -EINVAL;
101 		}
102 
103 		data += length;
104 		if (data + 4 > end) {
105 			dev_err(dev, "reached end of BAR without END TLV\n");
106 			return -EINVAL;
107 		}
108 	}
109 
110 	/* Not reached */
111 	return -EINVAL;
112 }
113