/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Forwarding Information Base (Routing Tables)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
* Mostly copied from the IPv4 routing code
*
*
* Changes:
*
*/
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
#include <linux/route.h> /* RTF_xxx */
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/fib_rules.h>
#include <net/dn.h>
#include <net/dn_route.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
struct dn_zone
{
struct dn_zone *dz_next;
struct dn_fib_node **dz_hash;
int dz_nent;
int dz_divisor;
u32 dz_hashmask;
#define DZ_HASHMASK(dz) ((dz)->dz_hashmask)
int dz_order;
__le16 dz_mask;
#define DZ_MASK(dz) ((dz)->dz_mask)
};
struct dn_hash
{
struct dn_zone *dh_zones[17];
struct dn_zone *dh_zone_list;
};
#define dz_key_0(key) ((key).datum = 0)
#define dz_prefix(key,dz) ((key).datum)
#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define endfor_nexthops(fi) }
#define DN_MAX_DIVISOR 1024
#define DN_S_ZOMBIE 1
#define DN_S_ACCESSED 2
#define DN_FIB_SCAN(f, fp) \
for( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next)
#define DN_FIB_SCAN_KEY(f, fp, key) \
for( ; ((f) = *(fp)) != NULL && dn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next)
#define RT_TABLE_MIN 1
#define DN_FIB_TABLE_HASHSZ 256
static struct hlist_head dn_fib_table_hash[DN_FIB_TABLE_HASHSZ];
static DEFINE_RWLOCK(dn_fib_tables_lock);
static struct kmem_cache *dn_hash_kmem __read_mostly;
static int dn_fib_hash_zombies;
static inline dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz)
{
u16 h = dn_ntohs(key.datum)>>(16 - dz->dz_order);
h ^= (h >> 10);
h ^= (h >> 6);
h &= DZ_HASHMASK(dz);
return *(dn_fib_idx_t *)&h;
}
static inline dn_fib_key_t dz_key(__le16 dst, struct dn_zone *dz)
{
dn_fib_key_t k;
k.datum = dst & DZ_MASK(dz);
return k;
}
static inline struct dn_fib_node **dn_chain_p(dn_fib_key_t key, struct dn_zone *dz)
{
return &dz->dz_hash[dn_hash(key, dz).datum];
}
static inline struct dn_fib_node *dz_chain(dn_fib_key_t key, struct dn_zone *dz)
{
return dz->dz_hash[dn_hash(key, dz).datum];
}
static inline int dn_key_eq(dn_fib_key_t a, dn_fib_key_t b)
{
return a.datum == b.datum;
}
static inline int dn_key_leq(dn_fib_key_t a, dn_fib_key_t b)
{
return a.datum <= b.datum;
}
static inline void dn_rebuild_zone(struct dn_zone *dz,
struct dn_fib_node **old_ht,
int old_divisor)
{
int i;
struct dn_fib_node *f, **fp, *next;
for(i = 0; i < old_divisor; i++) {
for(f = old_ht[i]; f; f = f->fn_next) {
next = f->fn_next;
for(fp = dn_chain_p(f->fn_key, dz);
*fp && dn_key_leq((*fp)->fn_key, f->fn_key);
fp = &(*fp)->fn_next)
/* NOTHING */;
f->fn_next = *fp;
*fp = f;
}
}
}
static void dn_rehash_zone(struct dn_zone *dz)
{
struct dn_fib_node **ht, **old_ht;
int old_divisor, new_divisor;
u32 new_hashmask;
old_divisor = dz->dz_divisor;
switch(old_divisor) {
case 16:
new_divisor = 256;
new_hashmask = 0xFF;
break;
default:
printk(KERN_DEBUG "DECnet: dn_rehash_zone: BUG! %d\n", old_divisor);
case 256:
new_divisor = 1024;
new_hashmask = 0x3FF;
break;
}
ht = kcalloc(new_divisor, sizeof(struct dn_fib_node*), GFP_KERNEL);
if (ht == NULL)
return;
write_lock_bh(&dn_fib_tables_lock);
old_ht = dz->dz_hash;
dz->dz_hash = ht;
dz->dz_hashmask = new_hashmask;
dz->dz_divisor = new_divisor;
dn_rebuild_zone(dz, old_ht, old_divisor);
write_unlock_bh(&dn_fib_tables_lock);
kfree(old_ht);
}
static void dn_free_node(struct dn_fib_node *f)
{
dn_fib_release_info(DN_FIB_INFO(f));
kmem_cache_free(dn_hash_kmem, f);
}
static struct dn_zone *dn_new_zone(struct dn_hash *table, int z)
{
int i;
struct dn_zone