@@ -84,6 +84,7 @@ struct netdev_flow_api {
struct dpif_flow_stats *);
/* Get the number of flows offloaded to netdev.
+ * 'n_flows' is an array of counters, one per offload thread.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_get_n_flows)(struct netdev *, uint64_t *n_flows);
@@ -60,6 +60,12 @@ VLOG_DEFINE_THIS_MODULE(netdev_offload);
static bool netdev_flow_api_enabled = false;
+#define DEFAULT_OFFLOAD_THREAD_NB 1
+#define MAX_OFFLOAD_THREAD_NB 10
+
+static unsigned int offload_thread_nb = DEFAULT_OFFLOAD_THREAD_NB;
+DEFINE_EXTERN_PER_THREAD_DATA(netdev_offload_thread_id, OVSTHREAD_ID_UNSET);
+
/* Protects 'netdev_flow_apis'. */
static struct ovs_mutex netdev_flow_api_provider_mutex = OVS_MUTEX_INITIALIZER;
@@ -448,6 +454,64 @@ netdev_is_flow_api_enabled(void)
return netdev_flow_api_enabled;
}
+unsigned int
+netdev_offload_thread_nb(void)
+{
+ return offload_thread_nb;
+}
+
+unsigned int
+netdev_offload_ufid_to_thread_id(const ovs_u128 ufid)
+{
+ uint32_t ufid_hash;
+
+ if (netdev_offload_thread_nb() == 1) {
+ return 0;
+ }
+
+ ufid_hash = hash_words64_inline(
+ (const uint64_t [2]){ ufid.u64.lo,
+ ufid.u64.hi }, 2, 1);
+ return ufid_hash % netdev_offload_thread_nb();
+}
+
+unsigned int
+netdev_offload_thread_init(void)
+{
+ static atomic_count next_id = ATOMIC_COUNT_INIT(0);
+ bool thread_is_hw_offload;
+ bool thread_is_rcu;
+
+ thread_is_hw_offload = !strncmp(get_subprogram_name(),
+ "hw_offload", strlen("hw_offload"));
+ thread_is_rcu = !strncmp(get_subprogram_name(), "urcu", strlen("urcu"));
+
+ /* Panic if any other thread besides offload and RCU tries
+ * to initialize their thread ID. */
+ ovs_assert(thread_is_hw_offload || thread_is_rcu);
+
+ if (*netdev_offload_thread_id_get() == OVSTHREAD_ID_UNSET) {
+ unsigned int id;
+
+ if (thread_is_rcu) {
+ /* RCU will compete with other threads for shared object access.
+ * Reclamation functions using a thread ID must be thread-safe.
+ * For that end, and because RCU must consider all potential shared
+ * objects anyway, its thread-id can be whichever, so return 0.
+ */
+ id = 0;
+ } else {
+ /* Only the actual offload threads have their own ID. */
+ id = atomic_count_inc(&next_id);
+ }
+ /* Panic if any offload thread is getting a spurious ID. */
+ ovs_assert(id < netdev_offload_thread_nb());
+ return *netdev_offload_thread_id_get() = id;
+ } else {
+ return *netdev_offload_thread_id_get();
+ }
+}
+
void
netdev_ports_flow_flush(const char *dpif_type)
{
@@ -660,7 +724,16 @@ netdev_ports_get_n_flows(const char *dpif_type, odp_port_t port_no,
ovs_rwlock_rdlock(&netdev_hmap_rwlock);
data = netdev_ports_lookup(port_no, dpif_type);
if (data) {
- ret = netdev_flow_get_n_flows(data->netdev, n_flows);
+ uint64_t thread_n_flows[MAX_OFFLOAD_THREAD_NB] = {0};
+ unsigned int tid;
+
+ ret = netdev_flow_get_n_flows(data->netdev, thread_n_flows);
+ *n_flows = 0;
+ if (!ret) {
+ for (tid = 0; tid < netdev_offload_thread_nb(); tid++) {
+ *n_flows += thread_n_flows[tid];
+ }
+ }
}
ovs_rwlock_unlock(&netdev_hmap_rwlock);
return ret;
@@ -713,7 +786,18 @@ netdev_set_flow_api_enabled(const struct smap *ovs_other_config)
if (ovsthread_once_start(&once)) {
netdev_flow_api_enabled = true;
- VLOG_INFO("netdev: Flow API Enabled");
+ offload_thread_nb = smap_get_ullong(ovs_other_config,
+ "n-offload-threads",
+ DEFAULT_OFFLOAD_THREAD_NB);
+ if (offload_thread_nb > MAX_OFFLOAD_THREAD_NB) {
+ VLOG_WARN("netdev: Invalid number of threads requested: %u",
+ offload_thread_nb);
+ offload_thread_nb = DEFAULT_OFFLOAD_THREAD_NB;
+ }
+
+ VLOG_INFO("netdev: Flow API Enabled, using %u thread%s",
+ offload_thread_nb,
+ offload_thread_nb > 1 ? "s" : "");
#ifdef __linux__
tc_set_policy(smap_get_def(ovs_other_config, "tc-policy",
@@ -21,6 +21,7 @@
#include "openvswitch/netdev.h"
#include "openvswitch/types.h"
#include "ovs-rcu.h"
+#include "ovs-thread.h"
#include "packets.h"
#include "flow.h"
@@ -81,6 +82,24 @@ struct offload_info {
odp_port_t orig_in_port; /* Originating in_port for tnl flows. */
};
+DECLARE_EXTERN_PER_THREAD_DATA(unsigned int, netdev_offload_thread_id);
+
+unsigned int netdev_offload_thread_nb(void);
+unsigned int netdev_offload_thread_init(void);
+unsigned int netdev_offload_ufid_to_thread_id(const ovs_u128 ufid);
+
+static inline unsigned int
+netdev_offload_thread_id(void)
+{
+ unsigned int id = *netdev_offload_thread_id_get();
+
+ if (OVS_UNLIKELY(id == OVSTHREAD_ID_UNSET)) {
+ id = netdev_offload_thread_init();
+ }
+
+ return id;
+}
+
int netdev_flow_flush(struct netdev *);
int netdev_flow_dump_create(struct netdev *, struct netdev_flow_dump **dump,
bool terse);
@@ -247,6 +247,22 @@
</p>
</column>
+ <column name="other_config" key="n-offload-threads"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 10}'>
+ <p>
+ Set this value to the number of threads created to manage hardware
+ offloads.
+ </p>
+ <p>
+ The default value is <code>1</code>. Changing this value requires
+ restarting the daemon.
+ </p>
+ <p>
+ This is only relevant if
+ <ref column="other_config" key="hw-offload"/> is enabled.
+ </p>
+ </column>
+
<column name="other_config" key="tc-policy"
type='{"type": "string",
"enum": ["set", ["none", "skip_sw", "skip_hw"]]}'>