mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 09:58:01 +00:00
241 lines
5.6 KiB
C
241 lines
5.6 KiB
C
#include "flow.h"
|
|
#include "datapath.h"
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/jhash.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/highmem.h>
|
|
#include <asm/pgtable.h>
|
|
|
|
static void free_table(struct sw_flow ***flows, unsigned int n_buckets,
|
|
int free_flows)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < n_buckets >> DP_L1_BITS; i++) {
|
|
struct sw_flow **l2 = flows[i];
|
|
if (free_flows) {
|
|
unsigned int j;
|
|
for (j = 0; j < DP_L1_SIZE; j++) {
|
|
if (l2[j])
|
|
flow_free(l2[j]);
|
|
}
|
|
}
|
|
free_page((unsigned long)l2);
|
|
}
|
|
kfree(flows);
|
|
}
|
|
|
|
static struct sw_flow ***alloc_table(unsigned int n_buckets)
|
|
{
|
|
struct sw_flow ***flows;
|
|
unsigned int i;
|
|
|
|
flows = kmalloc((n_buckets >> DP_L1_BITS) * sizeof(struct sw_flow**),
|
|
GFP_KERNEL);
|
|
if (!flows)
|
|
return NULL;
|
|
for (i = 0; i < n_buckets >> DP_L1_BITS; i++) {
|
|
flows[i] = (struct sw_flow **)get_zeroed_page(GFP_KERNEL);
|
|
if (!flows[i]) {
|
|
free_table(flows, i << DP_L1_BITS, 0);
|
|
return NULL;
|
|
}
|
|
}
|
|
return flows;
|
|
}
|
|
|
|
struct dp_table *dp_table_create(unsigned int n_buckets)
|
|
{
|
|
struct dp_table *table;
|
|
|
|
table = kzalloc(sizeof *table, GFP_KERNEL);
|
|
if (!table)
|
|
goto err;
|
|
|
|
table->n_buckets = n_buckets;
|
|
table->flows[0] = alloc_table(n_buckets);
|
|
if (!table[0].flows)
|
|
goto err_free_tables;
|
|
|
|
table->flows[1] = alloc_table(n_buckets);
|
|
if (!table->flows[1])
|
|
goto err_free_flows0;
|
|
|
|
return table;
|
|
|
|
err_free_flows0:
|
|
free_table(table->flows[0], table->n_buckets, 0);
|
|
err_free_tables:
|
|
kfree(table);
|
|
err:
|
|
return NULL;
|
|
}
|
|
|
|
void dp_table_destroy(struct dp_table *table, int free_flows)
|
|
{
|
|
int i;
|
|
for (i = 0; i < 2; i++)
|
|
free_table(table->flows[i], table->n_buckets, free_flows);
|
|
kfree(table);
|
|
}
|
|
|
|
static struct sw_flow **find_bucket(struct dp_table *table,
|
|
struct sw_flow ***flows, u32 hash)
|
|
{
|
|
unsigned int l1 = (hash & (table->n_buckets - 1)) >> DP_L1_SHIFT;
|
|
unsigned int l2 = hash & ((1 << DP_L2_BITS) - 1);
|
|
return &flows[l1][l2];
|
|
}
|
|
|
|
static struct sw_flow *lookup_table(struct dp_table *table,
|
|
struct sw_flow ***flows, u32 hash,
|
|
const struct odp_flow_key *key)
|
|
{
|
|
struct sw_flow **bucket = find_bucket(table, flows, hash);
|
|
struct sw_flow *flow = rcu_dereference(*bucket);
|
|
if (flow && !memcmp(&flow->key, key, sizeof(struct odp_flow_key)))
|
|
return flow;
|
|
return NULL;
|
|
}
|
|
|
|
static u32 flow_hash0(const struct odp_flow_key *key)
|
|
{
|
|
return jhash2((u32*)key, sizeof *key / sizeof(u32), 0xaaaaaaaa);
|
|
}
|
|
|
|
static u32 flow_hash1(const struct odp_flow_key *key)
|
|
{
|
|
return jhash2((u32*)key, sizeof *key / sizeof(u32), 0x55555555);
|
|
}
|
|
|
|
static void find_buckets(struct dp_table *table,
|
|
const struct odp_flow_key *key,
|
|
struct sw_flow **buckets[2])
|
|
{
|
|
buckets[0] = find_bucket(table, table->flows[0], flow_hash0(key));
|
|
buckets[1] = find_bucket(table, table->flows[1], flow_hash1(key));
|
|
}
|
|
|
|
struct sw_flow *dp_table_lookup(struct dp_table *table,
|
|
const struct odp_flow_key *key)
|
|
{
|
|
struct sw_flow *flow;
|
|
flow = lookup_table(table, table->flows[0], flow_hash0(key), key);
|
|
if (!flow)
|
|
flow = lookup_table(table, table->flows[1],
|
|
flow_hash1(key), key);
|
|
return flow;
|
|
}
|
|
|
|
int dp_table_foreach(struct dp_table *table,
|
|
int (*callback)(struct sw_flow *flow, void *aux),
|
|
void *aux)
|
|
{
|
|
unsigned int i, j, k;
|
|
for (i = 0; i < 2; i++) {
|
|
for (j = 0; j < table->n_buckets >> DP_L1_BITS; j++) {
|
|
struct sw_flow **l2 = table->flows[i][j];
|
|
for (k = 0; k < DP_L1_SIZE; k++) {
|
|
struct sw_flow *flow = rcu_dereference(l2[k]);
|
|
if (flow) {
|
|
int error = callback(flow, aux);
|
|
if (error)
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int insert_flow(struct sw_flow *flow, void *new_table_)
|
|
{
|
|
struct dp_table *new_table = new_table_;
|
|
struct sw_flow **buckets[2];
|
|
int i;
|
|
|
|
find_buckets(new_table, &flow->key, buckets);
|
|
for (i = 0; i < 2; i++) {
|
|
if (!*buckets[i]) {
|
|
rcu_assign_pointer(*buckets[i], flow);
|
|
return 0;
|
|
}
|
|
}
|
|
WARN_ON_ONCE(1);
|
|
return 0;
|
|
}
|
|
|
|
static void dp_free_table_rcu(struct rcu_head *rcu)
|
|
{
|
|
struct dp_table *table = container_of(rcu, struct dp_table, rcu);
|
|
dp_table_destroy(table, 0);
|
|
}
|
|
|
|
int dp_table_expand(struct datapath *dp)
|
|
{
|
|
struct dp_table *old_table = rcu_dereference(dp->table);
|
|
struct dp_table *new_table = dp_table_create(old_table->n_buckets * 2);
|
|
if (!new_table)
|
|
return -ENOMEM;
|
|
dp_table_foreach(old_table, insert_flow, new_table);
|
|
rcu_assign_pointer(dp->table, new_table);
|
|
call_rcu(&old_table->rcu, dp_free_table_rcu);
|
|
return 0;
|
|
}
|
|
|
|
static void dp_free_table_and_flows_rcu(struct rcu_head *rcu)
|
|
{
|
|
struct dp_table *table = container_of(rcu, struct dp_table, rcu);
|
|
dp_table_destroy(table, 1);
|
|
}
|
|
|
|
int dp_table_flush(struct datapath *dp)
|
|
{
|
|
struct dp_table *old_table = rcu_dereference(dp->table);
|
|
struct dp_table *new_table = dp_table_create(DP_L1_SIZE);
|
|
if (!new_table)
|
|
return -ENOMEM;
|
|
rcu_assign_pointer(dp->table, new_table);
|
|
call_rcu(&old_table->rcu, dp_free_table_and_flows_rcu);
|
|
return 0;
|
|
}
|
|
|
|
struct sw_flow **
|
|
dp_table_lookup_for_insert(struct dp_table *table,
|
|
const struct odp_flow_key *target)
|
|
{
|
|
struct sw_flow **buckets[2];
|
|
struct sw_flow **empty_bucket = NULL;
|
|
int i;
|
|
|
|
find_buckets(table, target, buckets);
|
|
for (i = 0; i < 2; i++) {
|
|
struct sw_flow *f = rcu_dereference(*buckets[i]);
|
|
if (f) {
|
|
if (!memcmp(&f->key, target, sizeof(struct odp_flow_key)))
|
|
return buckets[i];
|
|
} else if (!empty_bucket)
|
|
empty_bucket = buckets[i];
|
|
}
|
|
return empty_bucket;
|
|
}
|
|
|
|
int dp_table_delete(struct dp_table *table, struct sw_flow *target)
|
|
{
|
|
struct sw_flow **buckets[2];
|
|
int i;
|
|
|
|
find_buckets(table, &target->key, buckets);
|
|
for (i = 0; i < 2; i++) {
|
|
struct sw_flow *flow = rcu_dereference(*buckets[i]);
|
|
if (flow == target) {
|
|
rcu_assign_pointer(*buckets[i], NULL);
|
|
return 0;
|
|
}
|
|
}
|
|
return -ENOENT;
|
|
}
|