#include "datapath.h"
 #include "meter.h"
 
-#define METER_HASH_BUCKETS 1024
-
 static const struct nla_policy meter_policy[OVS_METER_ATTR_MAX + 1] = {
        [OVS_METER_ATTR_ID] = { .type = NLA_U32, },
        [OVS_METER_ATTR_KBPS] = { .type = NLA_FLAG },
        [OVS_BAND_ATTR_STATS] = { .len = sizeof(struct ovs_flow_stats) },
 };
 
+static u32 meter_hash(struct dp_meter_instance *ti, u32 id)
+{
+       return id % ti->n_meters;
+}
+
 static void ovs_meter_free(struct dp_meter *meter)
 {
        if (!meter)
        kfree_rcu(meter, rcu);
 }
 
-static struct hlist_head *meter_hash_bucket(const struct datapath *dp,
-                                           u32 meter_id)
-{
-       return &dp->meters[meter_id & (METER_HASH_BUCKETS - 1)];
-}
-
 /* Call with ovs_mutex or RCU read lock. */
-static struct dp_meter *lookup_meter(const struct datapath *dp,
+static struct dp_meter *lookup_meter(const struct dp_meter_table *tbl,
                                     u32 meter_id)
 {
+       struct dp_meter_instance *ti = rcu_dereference_ovsl(tbl->ti);
+       u32 hash = meter_hash(ti, meter_id);
        struct dp_meter *meter;
-       struct hlist_head *head;
 
-       head = meter_hash_bucket(dp, meter_id);
-       hlist_for_each_entry_rcu(meter, head, dp_hash_node,
-                               lockdep_ovsl_is_held()) {
-               if (meter->id == meter_id)
-                       return meter;
-       }
+       meter = rcu_dereference_ovsl(ti->dp_meters[hash]);
+       if (meter && likely(meter->id == meter_id))
+               return meter;
+
        return NULL;
 }
 
-static void attach_meter(struct datapath *dp, struct dp_meter *meter)
+static struct dp_meter_instance *dp_meter_instance_alloc(const u32 size)
+{
+       struct dp_meter_instance *ti;
+
+       ti = kvzalloc(sizeof(*ti) +
+                     sizeof(struct dp_meter *) * size,
+                     GFP_KERNEL);
+       if (!ti)
+               return NULL;
+
+       ti->n_meters = size;
+
+       return ti;
+}
+
+static void dp_meter_instance_free(struct dp_meter_instance *ti)
+{
+       kvfree(ti);
+}
+
+static void dp_meter_instance_free_rcu(struct rcu_head *rcu)
+{
+       struct dp_meter_instance *ti;
+
+       ti = container_of(rcu, struct dp_meter_instance, rcu);
+       kvfree(ti);
+}
+
+static int
+dp_meter_instance_realloc(struct dp_meter_table *tbl, u32 size)
+{
+       struct dp_meter_instance *ti = rcu_dereference_ovsl(tbl->ti);
+       int n_meters = min(size, ti->n_meters);
+       struct dp_meter_instance *new_ti;
+       int i;
+
+       new_ti = dp_meter_instance_alloc(size);
+       if (!new_ti)
+               return -ENOMEM;
+
+       for (i = 0; i < n_meters; i++)
+               new_ti->dp_meters[i] =
+                       rcu_dereference_ovsl(ti->dp_meters[i]);
+
+       rcu_assign_pointer(tbl->ti, new_ti);
+       call_rcu(&ti->rcu, dp_meter_instance_free_rcu);
+
+       return 0;
+}
+
+static void dp_meter_instance_insert(struct dp_meter_instance *ti,
+                                    struct dp_meter *meter)
+{
+       u32 hash;
+
+       hash = meter_hash(ti, meter->id);
+       rcu_assign_pointer(ti->dp_meters[hash], meter);
+}
+
+static void dp_meter_instance_remove(struct dp_meter_instance *ti,
+                                    struct dp_meter *meter)
 {
-       struct hlist_head *head = meter_hash_bucket(dp, meter->id);
+       u32 hash;
 
-       hlist_add_head_rcu(&meter->dp_hash_node, head);
+       hash = meter_hash(ti, meter->id);
+       RCU_INIT_POINTER(ti->dp_meters[hash], NULL);
 }
 
-static void detach_meter(struct dp_meter *meter)
+static int attach_meter(struct dp_meter_table *tbl, struct dp_meter *meter)
 {
+       struct dp_meter_instance *ti = rcu_dereference_ovsl(tbl->ti);
+       u32 hash = meter_hash(ti, meter->id);
+
+       /* In generally, slots selected should be empty, because
+        * OvS uses id-pool to fetch a available id.
+        */
+       if (unlikely(rcu_dereference_ovsl(ti->dp_meters[hash])))
+               return -EBUSY;
+
+       dp_meter_instance_insert(ti, meter);
+
+       /* That function is thread-safe. */
+       if (++tbl->count >= ti->n_meters)
+               if (dp_meter_instance_realloc(tbl, ti->n_meters * 2))
+                       goto expand_err;
+
+       return 0;
+
+expand_err:
+       dp_meter_instance_remove(ti, meter);
+       tbl->count--;
+       return -ENOMEM;
+}
+
+static int detach_meter(struct dp_meter_table *tbl, struct dp_meter *meter)
+{
+       struct dp_meter_instance *ti;
+
        ASSERT_OVSL();
-       if (meter)
-               hlist_del_rcu(&meter->dp_hash_node);
+       if (!meter)
+               return 0;
+
+       ti = rcu_dereference_ovsl(tbl->ti);
+       dp_meter_instance_remove(ti, meter);
+
+       tbl->count--;
+
+       /* Shrink the meter array if necessary. */
+       if (ti->n_meters > DP_METER_ARRAY_SIZE_MIN &&
+           tbl->count <= (ti->n_meters / 4)) {
+               int half_size = ti->n_meters / 2;
+               int i;
+
+               /* Avoid hash collision, don't move slots to other place.
+                * Make sure there are no references of meters in array
+                * which will be released.
+                */
+               for (i = half_size; i < ti->n_meters; i++)
+                       if (rcu_dereference_ovsl(ti->dp_meters[i]))
+                               goto out;
+
+               if (dp_meter_instance_realloc(tbl, half_size))
+                       goto shrink_err;
+       }
+
+out:
+       return 0;
+
+shrink_err:
+       dp_meter_instance_insert(ti, meter);
+       tbl->count++;
+       return -ENOMEM;
 }
 
 static struct sk_buff *
        struct sk_buff *reply;
        struct ovs_header *ovs_reply_header;
        struct ovs_header *ovs_header = info->userhdr;
+       struct dp_meter_table *meter_tbl;
        struct datapath *dp;
        int err;
        u32 meter_id;
                goto exit_unlock;
        }
 
+       meter_tbl = &dp->meter_tbl;
        meter_id = nla_get_u32(a[OVS_METER_ATTR_ID]);
 
-       /* Cannot fail after this. */
-       old_meter = lookup_meter(dp, meter_id);
-       detach_meter(old_meter);
-       attach_meter(dp, meter);
+       old_meter = lookup_meter(meter_tbl, meter_id);
+       err = detach_meter(meter_tbl, old_meter);
+       if (err)
+               goto exit_unlock;
+
+       err = attach_meter(meter_tbl, meter);
+       if (err)
+               goto exit_unlock;
+
        ovs_unlock();
 
        /* Build response with the meter_id and stats from
 
 static int ovs_meter_cmd_get(struct sk_buff *skb, struct genl_info *info)
 {
-       struct nlattr **a = info->attrs;
-       u32 meter_id;
        struct ovs_header *ovs_header = info->userhdr;
        struct ovs_header *ovs_reply_header;
+       struct nlattr **a = info->attrs;
+       struct dp_meter *meter;
+       struct sk_buff *reply;
        struct datapath *dp;
+       u32 meter_id;
        int err;
-       struct sk_buff *reply;
-       struct dp_meter *meter;
 
        if (!a[OVS_METER_ATTR_ID])
                return -EINVAL;
        }
 
        /* Locate meter, copy stats. */
-       meter = lookup_meter(dp, meter_id);
+       meter = lookup_meter(&dp->meter_tbl, meter_id);
        if (!meter) {
                err = -ENOENT;
                goto exit_unlock;
 
 static int ovs_meter_cmd_del(struct sk_buff *skb, struct genl_info *info)
 {
-       struct nlattr **a = info->attrs;
-       u32 meter_id;
        struct ovs_header *ovs_header = info->userhdr;
        struct ovs_header *ovs_reply_header;
+       struct nlattr **a = info->attrs;
+       struct dp_meter *old_meter;
+       struct sk_buff *reply;
        struct datapath *dp;
+       u32 meter_id;
        int err;
-       struct sk_buff *reply;
-       struct dp_meter *old_meter;
 
        if (!a[OVS_METER_ATTR_ID])
                return -EINVAL;
-       meter_id = nla_get_u32(a[OVS_METER_ATTR_ID]);
 
        reply = ovs_meter_cmd_reply_start(info, OVS_METER_CMD_DEL,
                                          &ovs_reply_header);
                goto exit_unlock;
        }
 
-       old_meter = lookup_meter(dp, meter_id);
+       meter_id = nla_get_u32(a[OVS_METER_ATTR_ID]);
+       old_meter = lookup_meter(&dp->meter_tbl, meter_id);
        if (old_meter) {
                spin_lock_bh(&old_meter->lock);
                err = ovs_meter_cmd_reply_stats(reply, meter_id, old_meter);
                WARN_ON(err);
                spin_unlock_bh(&old_meter->lock);
-               detach_meter(old_meter);
+
+               err = detach_meter(&dp->meter_tbl, old_meter);
+               if (err)
+                       goto exit_unlock;
        }
+
        ovs_unlock();
        ovs_meter_free(old_meter);
        genlmsg_end(reply, ovs_reply_header);
 bool ovs_meter_execute(struct datapath *dp, struct sk_buff *skb,
                       struct sw_flow_key *key, u32 meter_id)
 {
-       struct dp_meter *meter;
-       struct dp_meter_band *band;
        long long int now_ms = div_u64(ktime_get_ns(), 1000 * 1000);
        long long int long_delta_ms;
-       u32 delta_ms;
-       u32 cost;
+       struct dp_meter_band *band;
+       struct dp_meter *meter;
        int i, band_exceeded_max = -1;
        u32 band_exceeded_rate = 0;
+       u32 delta_ms;
+       u32 cost;
 
-       meter = lookup_meter(dp, meter_id);
+       meter = lookup_meter(&dp->meter_tbl, meter_id);
        /* Do not drop the packet when there is no meter. */
        if (!meter)
                return false;
 
 int ovs_meters_init(struct datapath *dp)
 {
-       int i;
+       struct dp_meter_table *tbl = &dp->meter_tbl;
+       struct dp_meter_instance *ti;
 
-       dp->meters = kmalloc_array(METER_HASH_BUCKETS,
-                                  sizeof(struct hlist_head), GFP_KERNEL);
-
-       if (!dp->meters)
+       ti = dp_meter_instance_alloc(DP_METER_ARRAY_SIZE_MIN);
+       if (!ti)
                return -ENOMEM;
 
-       for (i = 0; i < METER_HASH_BUCKETS; i++)
-               INIT_HLIST_HEAD(&dp->meters[i]);
+       rcu_assign_pointer(tbl->ti, ti);
+       tbl->count = 0;
 
        return 0;
 }
 
 void ovs_meters_exit(struct datapath *dp)
 {
+       struct dp_meter_table *tbl = &dp->meter_tbl;
+       struct dp_meter_instance *ti = rcu_dereference_raw(tbl->ti);
        int i;
 
-       for (i = 0; i < METER_HASH_BUCKETS; i++) {
-               struct hlist_head *head = &dp->meters[i];
-               struct dp_meter *meter;
-               struct hlist_node *n;
-
-               hlist_for_each_entry_safe(meter, n, head, dp_hash_node)
-                       kfree(meter);
-       }
+       for (i = 0; i < ti->n_meters; i++)
+               ovs_meter_free(ti->dp_meters[i]);
 
-       kfree(dp->meters);
+       dp_meter_instance_free(ti);
 }