X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=datapath%2Fvport.c;h=0dcecd048916cae92a883987b25c8f9ec2b980e3;hb=e2f3178f0582eda302bdc5629189b6a56d9fbcdd;hp=172261aa58985b010142d17d6310bc142380d9c2;hpb=e0edde6fee279cdbbf3c179f5f50adaf0c7c7f1e;p=sliver-openvswitch.git diff --git a/datapath/vport.c b/datapath/vport.c index 172261aa5..0dcecd048 100644 --- a/datapath/vport.c +++ b/datapath/vport.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -34,90 +35,48 @@ #include "vport.h" #include "vport-internal_dev.h" +static void ovs_vport_record_error(struct vport *, + enum vport_err_type err_type); + /* List of statically compiled vport implementations. Don't forget to also * add yours to the list at the bottom of vport.h. */ -static const struct vport_ops *base_vport_ops_list[] = { +static const struct vport_ops *vport_ops_list[] = { &ovs_netdev_vport_ops, &ovs_internal_vport_ops, - &ovs_patch_vport_ops, +#if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX) &ovs_gre_vport_ops, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) - &ovs_capwap_vport_ops, + &ovs_gre64_vport_ops, #endif + &ovs_vxlan_vport_ops, + &ovs_lisp_vport_ops, }; -static const struct vport_ops **vport_ops_list; -static int n_vport_types; - -/* Protected by RCU read lock for reading, RTNL lock for writing. */ +/* Protected by RCU read lock for reading, ovs_mutex for writing. */ static struct hlist_head *dev_table; #define VPORT_HASH_BUCKETS 1024 /** * ovs_vport_init - initialize vport subsystem * - * Called at module load time to initialize the vport subsystem and any - * compiled in vport types. + * Called at module load time to initialize the vport subsystem. */ int ovs_vport_init(void) { - int err; - int i; - dev_table = kzalloc(VPORT_HASH_BUCKETS * sizeof(struct hlist_head), GFP_KERNEL); - if (!dev_table) { - err = -ENOMEM; - goto error; - } - - vport_ops_list = kmalloc(ARRAY_SIZE(base_vport_ops_list) * - sizeof(struct vport_ops *), GFP_KERNEL); - if (!vport_ops_list) { - err = -ENOMEM; - goto error_dev_table; - } - - for (i = 0; i < ARRAY_SIZE(base_vport_ops_list); i++) { - const struct vport_ops *new_ops = base_vport_ops_list[i]; - - if (new_ops->init) - err = new_ops->init(); - else - err = 0; - - if (!err) - vport_ops_list[n_vport_types++] = new_ops; - else if (new_ops->flags & VPORT_F_REQUIRED) { - ovs_vport_exit(); - goto error; - } - } + if (!dev_table) + return -ENOMEM; return 0; - -error_dev_table: - kfree(dev_table); -error: - return err; } /** * ovs_vport_exit - shutdown vport subsystem * - * Called at module exit time to shutdown the vport subsystem and any - * initialized vport types. + * Called at module exit time to shutdown the vport subsystem. */ void ovs_vport_exit(void) { - int i; - - for (i = 0; i < n_vport_types; i++) { - if (vport_ops_list[i]->exit) - vport_ops_list[i]->exit(); - } - - kfree(vport_ops_list); kfree(dev_table); } @@ -132,15 +91,14 @@ static struct hlist_head *hash_bucket(struct net *net, const char *name) * * @name: name of port to find * - * Must be called with RTNL or RCU read lock. + * Must be called with ovs or RCU read lock. */ struct vport *ovs_vport_locate(struct net *net, const char *name) { struct hlist_head *bucket = hash_bucket(net, name); struct vport *vport; - struct hlist_node *node; - hlist_for_each_entry_rcu(vport, node, bucket, hash_node) + hlist_for_each_entry_rcu(vport, bucket, hash_node) if (!strcmp(name, vport->ops->get_name(vport)) && net_eq(ovs_dp_get_net(vport->dp), net)) return vport; @@ -148,19 +106,6 @@ struct vport *ovs_vport_locate(struct net *net, const char *name) return NULL; } -static void release_vport(struct kobject *kobj) -{ - struct vport *p = container_of(kobj, struct vport, kobj); - kfree(p); -} - -static struct kobj_type brport_ktype = { -#ifdef CONFIG_SYSFS - .sysfs_ops = &ovs_brport_sysfs_ops, -#endif - .release = release_vport -}; - /** * ovs_vport_alloc - allocate and initialize new vport * @@ -177,6 +122,7 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, { struct vport *vport; size_t alloc_size; + int i; alloc_size = sizeof(struct vport); if (priv_size) { @@ -190,21 +136,24 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, vport->dp = parms->dp; vport->port_no = parms->port_no; - vport->upcall_pid = parms->upcall_pid; vport->ops = ops; INIT_HLIST_NODE(&vport->dp_hash_node); - /* Initialize kobject for bridge. This will be added as - * /sys/class/net//brport later, if sysfs is enabled. */ - vport->kobj.kset = NULL; - kobject_init(&vport->kobj, &brport_ktype); + if (ovs_vport_set_upcall_portids(vport, parms->upcall_portids)) + return ERR_PTR(-EINVAL); - vport->percpu_stats = alloc_percpu(struct vport_percpu_stats); + vport->percpu_stats = alloc_percpu(struct pcpu_sw_netstats); if (!vport->percpu_stats) { kfree(vport); return ERR_PTR(-ENOMEM); } + for_each_possible_cpu(i) { + struct pcpu_sw_netstats *vport_stats; + vport_stats = per_cpu_ptr(vport->percpu_stats, i); + u64_stats_init(&vport_stats->syncp); + } + spin_lock_init(&vport->stats_lock); return vport; @@ -222,9 +171,9 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, */ void ovs_vport_free(struct vport *vport) { + kfree((struct vport_portids __force *)vport->upcall_portids); free_percpu(vport->percpu_stats); - - kobject_put(&vport->kobj); + kfree(vport); } /** @@ -233,7 +182,7 @@ void ovs_vport_free(struct vport *vport) * @parms: Information about new vport. * * Creates a new vport with the specified configuration (which is dependent on - * device type). RTNL lock must be held. + * device type). ovs_mutex must be held. */ struct vport *ovs_vport_add(const struct vport_parms *parms) { @@ -241,9 +190,7 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) int err = 0; int i; - ASSERT_RTNL(); - - for (i = 0; i < n_vport_types; i++) { + for (i = 0; i < ARRAY_SIZE(vport_ops_list); i++) { if (vport_ops_list[i]->type == parms->type) { struct hlist_head *bucket; @@ -270,15 +217,13 @@ out: * ovs_vport_set_options - modify existing vport device (for kernel callers) * * @vport: vport to modify. - * @port: New configuration. + * @options: New configuration. * * Modifies an existing device with the specified configuration (which is - * dependent on device type). RTNL lock must be held. + * dependent on device type). ovs_mutex must be held. */ int ovs_vport_set_options(struct vport *vport, struct nlattr *options) { - ASSERT_RTNL(); - if (!vport->ops->set_options) return -EOPNOTSUPP; return vport->ops->set_options(vport, options); @@ -290,40 +235,16 @@ int ovs_vport_set_options(struct vport *vport, struct nlattr *options) * @vport: vport to delete. * * Detaches @vport from its datapath and destroys it. It is possible to fail - * for reasons such as lack of memory. RTNL lock must be held. + * for reasons such as lack of memory. ovs_mutex must be held. */ void ovs_vport_del(struct vport *vport) { - ASSERT_RTNL(); + ASSERT_OVSL(); hlist_del_rcu(&vport->hash_node); - vport->ops->destroy(vport); } -/** - * ovs_vport_set_addr - set device Ethernet address (for kernel callers) - * - * @vport: vport on which to set Ethernet address. - * @addr: New address. - * - * Sets the Ethernet address of the given device. Some devices may not support - * setting the Ethernet address, in which case the result will always be - * -EOPNOTSUPP. RTNL lock must be held. - */ -int ovs_vport_set_addr(struct vport *vport, const unsigned char *addr) -{ - ASSERT_RTNL(); - - if (!is_valid_ether_addr(addr)) - return -EADDRNOTAVAIL; - - if (vport->ops->set_addr) - return vport->ops->set_addr(vport, addr); - else - return -EOPNOTSUPP; -} - /** * ovs_vport_set_stats - sets offset device stats * @@ -331,16 +252,14 @@ int ovs_vport_set_addr(struct vport *vport, const unsigned char *addr) * @stats: stats to set * * Provides a set of transmit, receive, and error stats to be added as an - * offset to the collect data when stats are retreived. Some devices may not + * offset to the collected data when stats are retrieved. Some devices may not * support setting the stats, in which case the result will always be * -EOPNOTSUPP. * - * Must be called with RTNL lock. + * Must be called with ovs_mutex. */ void ovs_vport_set_stats(struct vport *vport, struct ovs_vport_stats *stats) { - ASSERT_RTNL(); - spin_lock_bh(&vport->stats_lock); vport->offset_stats = *stats; spin_unlock_bh(&vport->stats_lock); @@ -354,7 +273,7 @@ void ovs_vport_set_stats(struct vport *vport, struct ovs_vport_stats *stats) * * Retrieves transmit, receive, and error stats for the given device. * - * Must be called with RTNL lock or rcu_read_lock. + * Must be called with ovs_mutex or rcu_read_lock. */ void ovs_vport_get_stats(struct vport *vport, struct ovs_vport_stats *stats) { @@ -383,16 +302,16 @@ void ovs_vport_get_stats(struct vport *vport, struct ovs_vport_stats *stats) spin_unlock_bh(&vport->stats_lock); for_each_possible_cpu(i) { - const struct vport_percpu_stats *percpu_stats; - struct vport_percpu_stats local_stats; + const struct pcpu_sw_netstats *percpu_stats; + struct pcpu_sw_netstats local_stats; unsigned int start; percpu_stats = per_cpu_ptr(vport->percpu_stats, i); do { - start = u64_stats_fetch_begin_bh(&percpu_stats->sync); + start = u64_stats_fetch_begin_bh(&percpu_stats->syncp); local_stats = *percpu_stats; - } while (u64_stats_fetch_retry_bh(&percpu_stats->sync, start)); + } while (u64_stats_fetch_retry_bh(&percpu_stats->syncp, start)); stats->rx_bytes += local_stats.rx_bytes; stats->rx_packets += local_stats.rx_packets; @@ -415,55 +334,150 @@ void ovs_vport_get_stats(struct vport *vport, struct ovs_vport_stats *stats) * negative error code if a real error occurred. If an error occurs, @skb is * left unmodified. * - * Must be called with RTNL lock or rcu_read_lock. + * Must be called with ovs_mutex or rcu_read_lock. */ int ovs_vport_get_options(const struct vport *vport, struct sk_buff *skb) { struct nlattr *nla; + int err; + + if (!vport->ops->get_options) + return 0; nla = nla_nest_start(skb, OVS_VPORT_ATTR_OPTIONS); if (!nla) return -EMSGSIZE; - if (vport->ops->get_options) { - int err = vport->ops->get_options(vport, skb); - if (err) { - nla_nest_cancel(skb, nla); - return err; - } + err = vport->ops->get_options(vport, skb); + if (err) { + nla_nest_cancel(skb, nla); + return err; } nla_nest_end(skb, nla); return 0; } +static void vport_portids_destroy_rcu_cb(struct rcu_head *rcu) +{ + struct vport_portids *ids = container_of(rcu, struct vport_portids, + rcu); + + kfree(ids); +} + +/** + * ovs_vport_set_upcall_portids - set upcall portids of @vport. + * + * @vport: vport to modify. + * @ids: new configuration, an array of port ids. + * + * Sets the vport's upcall_portids to @ids. + * + * Returns 0 if successful, -EINVAL if @ids is zero length or cannot be parsed + * as an array of U32. + * + * Must be called with ovs_mutex. + */ +int ovs_vport_set_upcall_portids(struct vport *vport, struct nlattr *ids) +{ + struct vport_portids *old, *vport_portids; + + if (!nla_len(ids) || nla_len(ids) % sizeof(u32)) + return -EINVAL; + + old = ovsl_dereference(vport->upcall_portids); + + vport_portids = kmalloc(sizeof *vport_portids + nla_len(ids), + GFP_KERNEL); + vport_portids->n_ids = nla_len(ids) / sizeof(u32); + vport_portids->rn_ids = reciprocal_value(vport_portids->n_ids); + nla_memcpy(vport_portids->ids, ids, nla_len(ids)); + + rcu_assign_pointer(vport->upcall_portids, vport_portids); + + if (old) + call_rcu(&old->rcu, vport_portids_destroy_rcu_cb); + + return 0; +} + +/** + * ovs_vport_get_upcall_portids - get the upcall_portids of @vport. + * + * @vport: vport from which to retrieve the portids. + * @skb: sk_buff where portids should be appended. + * + * Retrieves the configuration of the given vport, appending the + * %OVS_VPORT_ATTR_UPCALL_PID attribute which is the array of upcall + * portids to @skb. + * + * Returns 0 if successful, -EMSGSIZE if @skb has insufficient room. + * If an error occurs, @skb is left unmodified. Must be called with + * ovs_mutex or rcu_read_lock. + */ +int ovs_vport_get_upcall_portids(const struct vport *vport, + struct sk_buff *skb) +{ + struct vport_portids *ids; + + ids = rcu_dereference_ovsl(vport->upcall_portids); + + if (vport->dp->user_features & OVS_DP_F_VPORT_PIDS) + return nla_put(skb, OVS_VPORT_ATTR_UPCALL_PID, + ids->n_ids * sizeof(u32), (void *) ids->ids); + else + return nla_put_u32(skb, OVS_VPORT_ATTR_UPCALL_PID, ids->ids[0]); +} + +/** + * ovs_vport_find_upcall_portid - find the upcall portid to send upcall. + * + * @vport: vport from which the missed packet is received. + * @skb: skb that the missed packet was received. + * + * Uses the skb_get_hash() to select the upcall portid to send the + * upcall. + * + * Returns the portid of the target socket. Must be called with rcu_read_lock. + */ +u32 ovs_vport_find_upcall_portid(const struct vport *p, struct sk_buff *skb) +{ + struct vport_portids *ids; + u32 hash; + + ids = rcu_dereference(p->upcall_portids); + + if (ids->n_ids == 1 && ids->ids[0] == 0) + return 0; + + hash = skb_get_hash(skb); + return ids->ids[hash - ids->n_ids * reciprocal_divide(hash, ids->rn_ids)]; +} + /** * ovs_vport_receive - pass up received packet to the datapath for processing * * @vport: vport that received the packet * @skb: skb that was received + * @tun_key: tunnel (if any) that carried packet * * Must be called with rcu_read_lock. The packet cannot be shared and * skb->data should point to the Ethernet header. The caller must have already * called compute_ip_summed() to initialize the checksumming fields. */ -void ovs_vport_receive(struct vport *vport, struct sk_buff *skb) +void ovs_vport_receive(struct vport *vport, struct sk_buff *skb, + struct ovs_key_ipv4_tunnel *tun_key) { - struct vport_percpu_stats *stats; - - stats = per_cpu_ptr(vport->percpu_stats, smp_processor_id()); + struct pcpu_sw_netstats *stats; - u64_stats_update_begin(&stats->sync); + stats = this_cpu_ptr(vport->percpu_stats); + u64_stats_update_begin(&stats->syncp); stats->rx_packets++; stats->rx_bytes += skb->len; - u64_stats_update_end(&stats->sync); - - if (!(vport->ops->flags & VPORT_F_FLOW)) - OVS_CB(skb)->flow = NULL; - - if (!(vport->ops->flags & VPORT_F_TUN_ID)) - OVS_CB(skb)->tun_id = 0; + u64_stats_update_end(&stats->syncp); + OVS_CB(skb)->tun_key = tun_key; ovs_dp_process_received_packet(vport, skb); } @@ -473,23 +487,28 @@ void ovs_vport_receive(struct vport *vport, struct sk_buff *skb) * @vport: vport on which to send the packet * @skb: skb to send * - * Sends the given packet and returns the length of data sent. Either RTNL + * Sends the given packet and returns the length of data sent. Either ovs * lock or rcu_read_lock must be held. */ int ovs_vport_send(struct vport *vport, struct sk_buff *skb) { int sent = vport->ops->send(vport, skb); - if (likely(sent)) { - struct vport_percpu_stats *stats; + if (likely(sent > 0)) { + struct pcpu_sw_netstats *stats; - stats = per_cpu_ptr(vport->percpu_stats, smp_processor_id()); + stats = this_cpu_ptr(vport->percpu_stats); - u64_stats_update_begin(&stats->sync); + u64_stats_update_begin(&stats->syncp); stats->tx_packets++; stats->tx_bytes += sent; - u64_stats_update_end(&stats->sync); - } + u64_stats_update_end(&stats->syncp); + } else if (sent < 0) { + ovs_vport_record_error(vport, VPORT_E_TX_ERROR); + kfree_skb(skb); + } else + ovs_vport_record_error(vport, VPORT_E_TX_DROPPED); + return sent; } @@ -500,9 +519,10 @@ int ovs_vport_send(struct vport *vport, struct sk_buff *skb) * @err_type: one of enum vport_err_type types to indicate the error type * * If using the vport generic stats layer indicate that an error of the given - * type has occured. + * type has occurred. */ -void ovs_vport_record_error(struct vport *vport, enum vport_err_type err_type) +static void ovs_vport_record_error(struct vport *vport, + enum vport_err_type err_type) { spin_lock(&vport->stats_lock); @@ -522,7 +542,22 @@ void ovs_vport_record_error(struct vport *vport, enum vport_err_type err_type) case VPORT_E_TX_ERROR: vport->err_stats.tx_errors++; break; - }; + } spin_unlock(&vport->stats_lock); } + +static void free_vport_rcu(struct rcu_head *rcu) +{ + struct vport *vport = container_of(rcu, struct vport, rcu); + + ovs_vport_free(vport); +} + +void ovs_vport_deferred_free(struct vport *vport) +{ + if (!vport) + return; + + call_rcu(&vport->rcu, free_vport_rcu); +}