Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next
[linux-2.6-microblaze.git] / drivers / net / ethernet / netronome / nfp / nfp_net_ctrl.c
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 }