Commit | Line | Data |
---|---|---|
e3cf00d0 UM |
1 | /* |
2 | * Copyright (c) 2013, Cisco Systems, Inc. All rights reserved. | |
3 | * | |
4 | * This program is free software; you may redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; version 2 of the License. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
9 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
10 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
11 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
12 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
14 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
15 | * SOFTWARE. | |
16 | * | |
17 | */ | |
18 | #include <linux/netdevice.h> | |
19 | #include <linux/pci.h> | |
20 | ||
21 | #include "enic_api.h" | |
22 | #include "usnic_common_pkt_hdr.h" | |
23 | #include "usnic_fwd.h" | |
24 | #include "usnic_log.h" | |
25 | ||
2183b990 UM |
26 | static int usnic_fwd_devcmd_locked(struct usnic_fwd_dev *ufdev, int vnic_idx, |
27 | enum vnic_devcmd_cmd cmd, u64 *a0, | |
28 | u64 *a1) | |
29 | { | |
30 | int status; | |
31 | struct net_device *netdev = ufdev->netdev; | |
32 | ||
33 | lockdep_assert_held(&ufdev->lock); | |
34 | ||
35 | status = enic_api_devcmd_proxy_by_index(netdev, | |
36 | vnic_idx, | |
37 | cmd, | |
38 | a0, a1, | |
39 | 1000); | |
40 | if (status) { | |
41 | if (status == ERR_EINVAL && cmd == CMD_DEL_FILTER) { | |
42 | usnic_dbg("Dev %s vnic idx %u cmd %u already deleted", | |
43 | ufdev->name, vnic_idx, cmd); | |
44 | } else { | |
45 | usnic_err("Dev %s vnic idx %u cmd %u failed with status %d\n", | |
46 | ufdev->name, vnic_idx, cmd, | |
47 | status); | |
48 | } | |
49 | } else { | |
50 | usnic_dbg("Dev %s vnic idx %u cmd %u success", | |
51 | ufdev->name, vnic_idx, cmd); | |
52 | } | |
53 | ||
54 | return status; | |
55 | } | |
56 | ||
57 | static int usnic_fwd_devcmd(struct usnic_fwd_dev *ufdev, int vnic_idx, | |
58 | enum vnic_devcmd_cmd cmd, u64 *a0, u64 *a1) | |
59 | { | |
60 | int status; | |
61 | ||
62 | spin_lock(&ufdev->lock); | |
63 | status = usnic_fwd_devcmd_locked(ufdev, vnic_idx, cmd, a0, a1); | |
64 | spin_unlock(&ufdev->lock); | |
65 | ||
66 | return status; | |
67 | } | |
68 | ||
e3cf00d0 UM |
69 | struct usnic_fwd_dev *usnic_fwd_dev_alloc(struct pci_dev *pdev) |
70 | { | |
71 | struct usnic_fwd_dev *ufdev; | |
72 | ||
73 | ufdev = kzalloc(sizeof(*ufdev), GFP_KERNEL); | |
74 | if (!ufdev) | |
75 | return NULL; | |
76 | ||
77 | ufdev->pdev = pdev; | |
78 | ufdev->netdev = pci_get_drvdata(pdev); | |
79 | spin_lock_init(&ufdev->lock); | |
2183b990 UM |
80 | strncpy(ufdev->name, netdev_name(ufdev->netdev), |
81 | sizeof(ufdev->name) - 1); | |
e3cf00d0 UM |
82 | |
83 | return ufdev; | |
84 | } | |
85 | ||
86 | void usnic_fwd_dev_free(struct usnic_fwd_dev *ufdev) | |
87 | { | |
88 | kfree(ufdev); | |
89 | } | |
90 | ||
2183b990 | 91 | void usnic_fwd_set_mac(struct usnic_fwd_dev *ufdev, char mac[ETH_ALEN]) |
e3cf00d0 | 92 | { |
2183b990 UM |
93 | spin_lock(&ufdev->lock); |
94 | memcpy(&ufdev->mac, mac, sizeof(ufdev->mac)); | |
95 | spin_unlock(&ufdev->lock); | |
96 | } | |
e3cf00d0 | 97 | |
3f92bed3 UM |
98 | int usnic_fwd_add_ipaddr(struct usnic_fwd_dev *ufdev, __be32 inaddr) |
99 | { | |
100 | int status; | |
101 | ||
102 | spin_lock(&ufdev->lock); | |
103 | if (ufdev->inaddr == 0) { | |
104 | ufdev->inaddr = inaddr; | |
105 | status = 0; | |
106 | } else { | |
107 | status = -EFAULT; | |
108 | } | |
109 | spin_unlock(&ufdev->lock); | |
110 | ||
111 | return status; | |
112 | } | |
113 | ||
114 | void usnic_fwd_del_ipaddr(struct usnic_fwd_dev *ufdev) | |
115 | { | |
116 | spin_lock(&ufdev->lock); | |
117 | ufdev->inaddr = 0; | |
118 | spin_unlock(&ufdev->lock); | |
119 | } | |
120 | ||
2183b990 UM |
121 | void usnic_fwd_carrier_up(struct usnic_fwd_dev *ufdev) |
122 | { | |
e3cf00d0 | 123 | spin_lock(&ufdev->lock); |
2183b990 | 124 | ufdev->link_up = 1; |
e3cf00d0 | 125 | spin_unlock(&ufdev->lock); |
2183b990 | 126 | } |
e3cf00d0 | 127 | |
2183b990 UM |
128 | void usnic_fwd_carrier_down(struct usnic_fwd_dev *ufdev) |
129 | { | |
130 | spin_lock(&ufdev->lock); | |
131 | ufdev->link_up = 0; | |
132 | spin_unlock(&ufdev->lock); | |
133 | } | |
134 | ||
135 | void usnic_fwd_set_mtu(struct usnic_fwd_dev *ufdev, unsigned int mtu) | |
136 | { | |
137 | spin_lock(&ufdev->lock); | |
138 | ufdev->mtu = mtu; | |
139 | spin_unlock(&ufdev->lock); | |
140 | } | |
141 | ||
142 | static int usnic_fwd_dev_ready_locked(struct usnic_fwd_dev *ufdev) | |
143 | { | |
144 | lockdep_assert_held(&ufdev->lock); | |
145 | ||
146 | if (!ufdev->link_up) | |
147 | return -EPERM; | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
3f92bed3 UM |
152 | static int validate_filter_locked(struct usnic_fwd_dev *ufdev, |
153 | struct filter *filter) | |
154 | { | |
155 | ||
156 | lockdep_assert_held(&ufdev->lock); | |
157 | ||
158 | if (filter->type == FILTER_IPV4_5TUPLE) { | |
159 | if (!(filter->u.ipv4.flags & FILTER_FIELD_5TUP_DST_AD)) | |
160 | return -EACCES; | |
161 | if (!(filter->u.ipv4.flags & FILTER_FIELD_5TUP_DST_PT)) | |
162 | return -EBUSY; | |
163 | else if (ufdev->inaddr == 0) | |
164 | return -EINVAL; | |
165 | else if (filter->u.ipv4.dst_port == 0) | |
166 | return -ERANGE; | |
167 | else if (ntohl(ufdev->inaddr) != filter->u.ipv4.dst_addr) | |
168 | return -EFAULT; | |
169 | else | |
170 | return 0; | |
171 | } | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
2183b990 UM |
176 | static void fill_tlv(struct filter_tlv *tlv, struct filter *filter, |
177 | struct filter_action *action) | |
178 | { | |
179 | tlv->type = CLSF_TLV_FILTER; | |
180 | tlv->length = sizeof(struct filter); | |
181 | *((struct filter *)&tlv->val) = *filter; | |
182 | ||
183 | tlv = (struct filter_tlv *)((char *)tlv + sizeof(struct filter_tlv) + | |
184 | sizeof(struct filter)); | |
185 | tlv->type = CLSF_TLV_ACTION; | |
186 | tlv->length = sizeof(struct filter_action); | |
187 | *((struct filter_action *)&tlv->val) = *action; | |
e3cf00d0 UM |
188 | } |
189 | ||
2183b990 UM |
190 | struct usnic_fwd_flow* |
191 | usnic_fwd_alloc_flow(struct usnic_fwd_dev *ufdev, struct filter *filter, | |
192 | struct usnic_filter_action *uaction) | |
e3cf00d0 | 193 | { |
2183b990 | 194 | struct filter_tlv *tlv; |
e3cf00d0 | 195 | struct pci_dev *pdev; |
2183b990 UM |
196 | struct usnic_fwd_flow *flow; |
197 | uint64_t a0, a1; | |
198 | uint64_t tlv_size; | |
e3cf00d0 | 199 | dma_addr_t tlv_pa; |
2183b990 | 200 | int status; |
e3cf00d0 UM |
201 | |
202 | pdev = ufdev->pdev; | |
2183b990 UM |
203 | tlv_size = (2*sizeof(struct filter_tlv) + sizeof(struct filter) + |
204 | sizeof(struct filter_action)); | |
205 | ||
206 | flow = kzalloc(sizeof(*flow), GFP_ATOMIC); | |
207 | if (!flow) | |
208 | return ERR_PTR(-ENOMEM); | |
209 | ||
e3cf00d0 UM |
210 | tlv = pci_alloc_consistent(pdev, tlv_size, &tlv_pa); |
211 | if (!tlv) { | |
212 | usnic_err("Failed to allocate memory\n"); | |
2183b990 UM |
213 | status = -ENOMEM; |
214 | goto out_free_flow; | |
e3cf00d0 UM |
215 | } |
216 | ||
2183b990 UM |
217 | fill_tlv(tlv, filter, &uaction->action); |
218 | ||
219 | spin_lock(&ufdev->lock); | |
220 | status = usnic_fwd_dev_ready_locked(ufdev); | |
221 | if (status) { | |
222 | usnic_err("Forwarding dev %s not ready with status %d\n", | |
223 | ufdev->name, status); | |
224 | goto out_free_tlv; | |
e3cf00d0 UM |
225 | } |
226 | ||
3f92bed3 UM |
227 | status = validate_filter_locked(ufdev, filter); |
228 | if (status) { | |
229 | usnic_err("Failed to validate filter with status %d\n", | |
230 | status); | |
231 | goto out_free_tlv; | |
232 | } | |
233 | ||
2183b990 | 234 | /* Issue Devcmd */ |
e3cf00d0 UM |
235 | a0 = tlv_pa; |
236 | a1 = tlv_size; | |
2183b990 UM |
237 | status = usnic_fwd_devcmd_locked(ufdev, uaction->vnic_idx, |
238 | CMD_ADD_FILTER, &a0, &a1); | |
e3cf00d0 UM |
239 | if (status) { |
240 | usnic_err("VF %s Filter add failed with status:%d", | |
2183b990 UM |
241 | ufdev->name, status); |
242 | status = -EFAULT; | |
243 | goto out_free_tlv; | |
e3cf00d0 | 244 | } else { |
2183b990 | 245 | usnic_dbg("VF %s FILTER ID:%llu", ufdev->name, a0); |
e3cf00d0 UM |
246 | } |
247 | ||
2183b990 UM |
248 | flow->flow_id = (uint32_t) a0; |
249 | flow->vnic_idx = uaction->vnic_idx; | |
250 | flow->ufdev = ufdev; | |
e3cf00d0 | 251 | |
2183b990 UM |
252 | out_free_tlv: |
253 | spin_unlock(&ufdev->lock); | |
254 | pci_free_consistent(pdev, tlv_size, tlv, tlv_pa); | |
255 | if (!status) | |
256 | return flow; | |
257 | out_free_flow: | |
258 | kfree(flow); | |
259 | return ERR_PTR(status); | |
e3cf00d0 UM |
260 | } |
261 | ||
2183b990 | 262 | int usnic_fwd_dealloc_flow(struct usnic_fwd_flow *flow) |
e3cf00d0 UM |
263 | { |
264 | int status; | |
265 | u64 a0, a1; | |
e3cf00d0 | 266 | |
2183b990 | 267 | a0 = flow->flow_id; |
e3cf00d0 | 268 | |
2183b990 | 269 | status = usnic_fwd_devcmd(flow->ufdev, flow->vnic_idx, |
e3cf00d0 UM |
270 | CMD_DEL_FILTER, &a0, &a1); |
271 | if (status) { | |
272 | if (status == ERR_EINVAL) { | |
273 | usnic_dbg("Filter %u already deleted for VF Idx %u pf: %s status: %d", | |
2183b990 UM |
274 | flow->flow_id, flow->vnic_idx, |
275 | flow->ufdev->name, status); | |
e3cf00d0 UM |
276 | } else { |
277 | usnic_err("PF %s VF Idx %u Filter: %u FILTER DELETE failed with status %d", | |
2183b990 UM |
278 | flow->ufdev->name, flow->vnic_idx, |
279 | flow->flow_id, status); | |
e3cf00d0 | 280 | } |
2183b990 UM |
281 | status = 0; |
282 | /* | |
283 | * Log the error and fake success to the caller because if | |
284 | * a flow fails to be deleted in the firmware, it is an | |
285 | * unrecoverable error. | |
286 | */ | |
e3cf00d0 UM |
287 | } else { |
288 | usnic_dbg("PF %s VF Idx %u Filter: %u FILTER DELETED", | |
2183b990 UM |
289 | flow->ufdev->name, flow->vnic_idx, |
290 | flow->flow_id); | |
e3cf00d0 UM |
291 | } |
292 | ||
2183b990 | 293 | kfree(flow); |
e3cf00d0 UM |
294 | return status; |
295 | } | |
296 | ||
2183b990 | 297 | int usnic_fwd_enable_qp(struct usnic_fwd_dev *ufdev, int vnic_idx, int qp_idx) |
e3cf00d0 UM |
298 | { |
299 | int status; | |
300 | struct net_device *pf_netdev; | |
301 | u64 a0, a1; | |
302 | ||
303 | pf_netdev = ufdev->netdev; | |
2183b990 | 304 | a0 = qp_idx; |
e3cf00d0 UM |
305 | a1 = CMD_QP_RQWQ; |
306 | ||
2183b990 UM |
307 | status = usnic_fwd_devcmd(ufdev, vnic_idx, CMD_QP_ENABLE, |
308 | &a0, &a1); | |
e3cf00d0 UM |
309 | if (status) { |
310 | usnic_err("PF %s VNIC Index %u RQ Index: %u ENABLE Failed with status %d", | |
311 | netdev_name(pf_netdev), | |
312 | vnic_idx, | |
2183b990 | 313 | qp_idx, |
e3cf00d0 UM |
314 | status); |
315 | } else { | |
316 | usnic_dbg("PF %s VNIC Index %u RQ Index: %u ENABLED", | |
317 | netdev_name(pf_netdev), | |
2183b990 | 318 | vnic_idx, qp_idx); |
e3cf00d0 UM |
319 | } |
320 | ||
321 | return status; | |
322 | } | |
323 | ||
2183b990 | 324 | int usnic_fwd_disable_qp(struct usnic_fwd_dev *ufdev, int vnic_idx, int qp_idx) |
e3cf00d0 UM |
325 | { |
326 | int status; | |
327 | u64 a0, a1; | |
328 | struct net_device *pf_netdev; | |
329 | ||
330 | pf_netdev = ufdev->netdev; | |
2183b990 | 331 | a0 = qp_idx; |
e3cf00d0 UM |
332 | a1 = CMD_QP_RQWQ; |
333 | ||
2183b990 UM |
334 | status = usnic_fwd_devcmd(ufdev, vnic_idx, CMD_QP_DISABLE, |
335 | &a0, &a1); | |
e3cf00d0 UM |
336 | if (status) { |
337 | usnic_err("PF %s VNIC Index %u RQ Index: %u DISABLE Failed with status %d", | |
338 | netdev_name(pf_netdev), | |
339 | vnic_idx, | |
2183b990 | 340 | qp_idx, |
e3cf00d0 UM |
341 | status); |
342 | } else { | |
343 | usnic_dbg("PF %s VNIC Index %u RQ Index: %u DISABLED", | |
344 | netdev_name(pf_netdev), | |
345 | vnic_idx, | |
2183b990 | 346 | qp_idx); |
e3cf00d0 UM |
347 | } |
348 | ||
349 | return status; | |
350 | } |