diff options
Diffstat (limited to 'net/openvswitch/vport.c')
-rw-r--r-- | net/openvswitch/vport.c | 101 |
1 files changed, 100 insertions, 1 deletions
diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 42c0f4a0b78c..702fb21bfe15 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c | |||
@@ -134,10 +134,12 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, | |||
134 | 134 | ||
135 | vport->dp = parms->dp; | 135 | vport->dp = parms->dp; |
136 | vport->port_no = parms->port_no; | 136 | vport->port_no = parms->port_no; |
137 | vport->upcall_portid = parms->upcall_portid; | ||
138 | vport->ops = ops; | 137 | vport->ops = ops; |
139 | INIT_HLIST_NODE(&vport->dp_hash_node); | 138 | INIT_HLIST_NODE(&vport->dp_hash_node); |
140 | 139 | ||
140 | if (ovs_vport_set_upcall_portids(vport, parms->upcall_portids)) | ||
141 | return ERR_PTR(-EINVAL); | ||
142 | |||
141 | vport->percpu_stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); | 143 | vport->percpu_stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); |
142 | if (!vport->percpu_stats) { | 144 | if (!vport->percpu_stats) { |
143 | kfree(vport); | 145 | kfree(vport); |
@@ -161,6 +163,10 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, | |||
161 | */ | 163 | */ |
162 | void ovs_vport_free(struct vport *vport) | 164 | void ovs_vport_free(struct vport *vport) |
163 | { | 165 | { |
166 | /* vport is freed from RCU callback or error path, Therefore | ||
167 | * it is safe to use raw dereference. | ||
168 | */ | ||
169 | kfree(rcu_dereference_raw(vport->upcall_portids)); | ||
164 | free_percpu(vport->percpu_stats); | 170 | free_percpu(vport->percpu_stats); |
165 | kfree(vport); | 171 | kfree(vport); |
166 | } | 172 | } |
@@ -327,6 +333,99 @@ int ovs_vport_get_options(const struct vport *vport, struct sk_buff *skb) | |||
327 | } | 333 | } |
328 | 334 | ||
329 | /** | 335 | /** |
336 | * ovs_vport_set_upcall_portids - set upcall portids of @vport. | ||
337 | * | ||
338 | * @vport: vport to modify. | ||
339 | * @ids: new configuration, an array of port ids. | ||
340 | * | ||
341 | * Sets the vport's upcall_portids to @ids. | ||
342 | * | ||
343 | * Returns 0 if successful, -EINVAL if @ids is zero length or cannot be parsed | ||
344 | * as an array of U32. | ||
345 | * | ||
346 | * Must be called with ovs_mutex. | ||
347 | */ | ||
348 | int ovs_vport_set_upcall_portids(struct vport *vport, struct nlattr *ids) | ||
349 | { | ||
350 | struct vport_portids *old, *vport_portids; | ||
351 | |||
352 | if (!nla_len(ids) || nla_len(ids) % sizeof(u32)) | ||
353 | return -EINVAL; | ||
354 | |||
355 | old = ovsl_dereference(vport->upcall_portids); | ||
356 | |||
357 | vport_portids = kmalloc(sizeof(*vport_portids) + nla_len(ids), | ||
358 | GFP_KERNEL); | ||
359 | if (!vport_portids) | ||
360 | return -ENOMEM; | ||
361 | |||
362 | vport_portids->n_ids = nla_len(ids) / sizeof(u32); | ||
363 | vport_portids->rn_ids = reciprocal_value(vport_portids->n_ids); | ||
364 | nla_memcpy(vport_portids->ids, ids, nla_len(ids)); | ||
365 | |||
366 | rcu_assign_pointer(vport->upcall_portids, vport_portids); | ||
367 | |||
368 | if (old) | ||
369 | kfree_rcu(old, rcu); | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | /** | ||
374 | * ovs_vport_get_upcall_portids - get the upcall_portids of @vport. | ||
375 | * | ||
376 | * @vport: vport from which to retrieve the portids. | ||
377 | * @skb: sk_buff where portids should be appended. | ||
378 | * | ||
379 | * Retrieves the configuration of the given vport, appending the | ||
380 | * %OVS_VPORT_ATTR_UPCALL_PID attribute which is the array of upcall | ||
381 | * portids to @skb. | ||
382 | * | ||
383 | * Returns 0 if successful, -EMSGSIZE if @skb has insufficient room. | ||
384 | * If an error occurs, @skb is left unmodified. Must be called with | ||
385 | * ovs_mutex or rcu_read_lock. | ||
386 | */ | ||
387 | int ovs_vport_get_upcall_portids(const struct vport *vport, | ||
388 | struct sk_buff *skb) | ||
389 | { | ||
390 | struct vport_portids *ids; | ||
391 | |||
392 | ids = rcu_dereference_ovsl(vport->upcall_portids); | ||
393 | |||
394 | if (vport->dp->user_features & OVS_DP_F_VPORT_PIDS) | ||
395 | return nla_put(skb, OVS_VPORT_ATTR_UPCALL_PID, | ||
396 | ids->n_ids * sizeof(u32), (void *)ids->ids); | ||
397 | else | ||
398 | return nla_put_u32(skb, OVS_VPORT_ATTR_UPCALL_PID, ids->ids[0]); | ||
399 | } | ||
400 | |||
401 | /** | ||
402 | * ovs_vport_find_upcall_portid - find the upcall portid to send upcall. | ||
403 | * | ||
404 | * @vport: vport from which the missed packet is received. | ||
405 | * @skb: skb that the missed packet was received. | ||
406 | * | ||
407 | * Uses the skb_get_hash() to select the upcall portid to send the | ||
408 | * upcall. | ||
409 | * | ||
410 | * Returns the portid of the target socket. Must be called with rcu_read_lock. | ||
411 | */ | ||
412 | u32 ovs_vport_find_upcall_portid(const struct vport *p, struct sk_buff *skb) | ||
413 | { | ||
414 | struct vport_portids *ids; | ||
415 | u32 ids_index; | ||
416 | u32 hash; | ||
417 | |||
418 | ids = rcu_dereference(p->upcall_portids); | ||
419 | |||
420 | if (ids->n_ids == 1 && ids->ids[0] == 0) | ||
421 | return 0; | ||
422 | |||
423 | hash = skb_get_hash(skb); | ||
424 | ids_index = hash - ids->n_ids * reciprocal_divide(hash, ids->rn_ids); | ||
425 | return ids->ids[ids_index]; | ||
426 | } | ||
427 | |||
428 | /** | ||
330 | * ovs_vport_receive - pass up received packet to the datapath for processing | 429 | * ovs_vport_receive - pass up received packet to the datapath for processing |
331 | * | 430 | * |
332 | * @vport: vport that received the packet | 431 | * @vport: vport that received the packet |