/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2002 Cluster File Systems, Inc. * * This file is part of Portals * http://sourceforge.net/projects/sandiaportals/ * * Portals is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * Portals is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Portals; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "router.h" LIST_HEAD(kpr_routes); LIST_HEAD(kpr_gateways); LIST_HEAD(kpr_nals); unsigned int kpr_routes_generation; unsigned long long kpr_fwd_bytes; unsigned long kpr_fwd_packets; unsigned long kpr_fwd_errors; atomic_t kpr_queue_depth; /* Mostly the tables are read-only (thread and interrupt context) * * Once in a blue moon we register/deregister NALs and add/remove routing * entries (thread context only)... */ rwlock_t kpr_rwlock = RW_LOCK_UNLOCKED; kpr_router_interface_t kpr_router_interface = { kprri_register: kpr_register_nal, kprri_lookup: kpr_lookup_target, kprri_fwd_start: kpr_forward_packet, kprri_fwd_done: kpr_complete_packet, kprri_notify: kpr_nal_notify, kprri_shutdown: kpr_shutdown_nal, kprri_deregister: kpr_deregister_nal, }; int kpr_register_nal (kpr_nal_interface_t *nalif, void **argp) { unsigned long flags; struct list_head *e; kpr_nal_entry_t *ne; CDEBUG (D_NET, "Registering NAL %x\n", nalif->kprni_nalid); PORTAL_ALLOC (ne, sizeof (*ne)); if (ne == NULL) return (-ENOMEM); memset (ne, 0, sizeof (*ne)); memcpy ((void *)&ne->kpne_interface, (void *)nalif, sizeof (*nalif)); LASSERT (!in_interrupt()); write_lock_irqsave (&kpr_rwlock, flags); for (e = kpr_nals.next; e != &kpr_nals; e = e->next) { kpr_nal_entry_t *ne2 = list_entry (e, kpr_nal_entry_t, kpne_list); if (ne2->kpne_interface.kprni_nalid == ne->kpne_interface.kprni_nalid) { write_unlock_irqrestore (&kpr_rwlock, flags); CERROR ("Attempt to register same NAL %x twice\n", ne->kpne_interface.kprni_nalid); PORTAL_FREE (ne, sizeof (*ne)); return (-EEXIST); } } list_add (&ne->kpne_list, &kpr_nals); write_unlock_irqrestore (&kpr_rwlock, flags); *argp = ne; PORTAL_MODULE_USE; return (0); } void kpr_do_upcall (void *arg) { kpr_upcall_t *u = (kpr_upcall_t *)arg; char nalstr[10]; char nidstr[36]; char whenstr[36]; char *argv[] = { NULL, "ROUTER_NOTIFY", nalstr, nidstr, u->kpru_alive ? "up" : "down", whenstr, NULL}; snprintf (nalstr, sizeof(nalstr), "%d", u->kpru_nal_id); snprintf (nidstr, sizeof(nidstr), LPX64, u->kpru_nid); snprintf (whenstr, sizeof(whenstr), "%ld", u->kpru_when); portals_run_upcall (argv); kfree (u); } void kpr_upcall (int gw_nalid, ptl_nid_t gw_nid, int alive, time_t when) { char str[PTL_NALFMT_SIZE]; /* May be in arbitrary context */ kpr_upcall_t *u = kmalloc (sizeof (kpr_upcall_t), GFP_ATOMIC); if (u == NULL) { CERROR ("Upcall out of memory: nal %x nid "LPX64" (%s) %s\n", gw_nalid, gw_nid, portals_nid2str(gw_nalid, gw_nid, str), alive ? "up" : "down"); return; } u->kpru_nal_id = gw_nalid; u->kpru_nid = gw_nid; u->kpru_alive = alive; u->kpru_when = when; prepare_work (&u->kpru_tq, kpr_do_upcall, u); schedule_work (&u->kpru_tq); } int kpr_do_notify (int byNal, int gateway_nalid, ptl_nid_t gateway_nid, int alive, time_t when) { unsigned long flags; int found; kpr_nal_entry_t *ne = NULL; kpr_gateway_entry_t *ge = NULL; struct timeval now; struct list_head *e; struct list_head *n; char str[PTL_NALFMT_SIZE]; CDEBUG (D_NET, "%s notifying [%x] "LPX64": %s\n", byNal ? "NAL" : "userspace", gateway_nalid, gateway_nid, alive ? "up" : "down"); /* can't do predictions... */ do_gettimeofday (&now); if (when > now.tv_sec) { CWARN ("Ignoring prediction from %s of [%x] "LPX64" %s " "%ld seconds in the future\n", byNal ? "NAL" : "userspace", gateway_nalid, gateway_nid, alive ? "up" : "down", when - now.tv_sec); return (EINVAL); } LASSERT (when <= now.tv_sec); /* Serialise with lookups (i.e. write lock) */ write_lock_irqsave(&kpr_rwlock, flags); found = 0; list_for_each_safe (e, n, &kpr_gateways) { ge = list_entry(e, kpr_gateway_entry_t, kpge_list); if ((gateway_nalid != 0 && ge->kpge_nalid != gateway_nalid) || ge->kpge_nid != gateway_nid) continue; found = 1; break; } if (!found) { /* gateway not found */ write_unlock_irqrestore(&kpr_rwlock, flags); CDEBUG (D_NET, "Gateway not found\n"); return (0); } if (when < ge->kpge_timestamp) { /* out of date information */ write_unlock_irqrestore (&kpr_rwlock, flags); CDEBUG (D_NET, "Out of date\n"); return (0); } /* update timestamp */ ge->kpge_timestamp = when; if ((!ge->kpge_alive) == (!alive)) { /* new date for old news */ write_unlock_irqrestore (&kpr_rwlock, flags); CDEBUG (D_NET, "Old news\n"); return (0); } ge->kpge_alive = alive; CDEBUG(D_NET, "set "LPX64" [%p] %d\n", gateway_nid, ge, alive); if (alive) { /* Reset all gateway weights so the newly-enabled gateway * doesn't have to play catch-up */ list_for_each_safe (e, n, &kpr_gateways) { kpr_gateway_entry_t *ge = list_entry(e, kpr_gateway_entry_t, kpge_list); atomic_set (&ge->kpge_weight, 0); } } found = 0; if (!byNal) { /* userland notified me: notify NAL? */ ne = kpr_find_nal_entry_locked (ge->kpge_nalid); if (ne != NULL) { if (!ne->kpne_shutdown && ne->kpne_interface.kprni_notify != NULL) { /* take a ref on this NAL until notifying * it has completed... */ atomic_inc (&ne->kpne_refcount); found = 1; } } } write_unlock_irqrestore(&kpr_rwlock, flags); if (found) { ne->kpne_interface.kprni_notify (ne->kpne_interface.kprni_arg, gateway_nid, alive); /* 'ne' can disappear now... */ atomic_dec (&ne->kpne_refcount); } if (byNal) { /* It wasn't userland that notified me... */ CWARN ("Upcall: NAL %x NID "LPX64" (%s) is %s\n", gateway_nalid, gateway_nid, portals_nid2str(gateway_nalid, gateway_nid, str), alive ? "alive" : "dead"); kpr_upcall (gateway_nalid, gateway_nid, alive, when); } else { CDEBUG (D_NET, " NOT Doing upcall\n"); } return (0); } void kpr_nal_notify (void *arg, ptl_nid_t peer, int alive, time_t when) { kpr_nal_entry_t *ne = (kpr_nal_entry_t *)arg; kpr_do_notify (1, ne->kpne_interface.kprni_nalid, peer, alive, when); } void kpr_shutdown_nal (void *arg) { unsigned long flags; kpr_nal_entry_t *ne = (kpr_nal_entry_t *)arg; CDEBUG (D_NET, "Shutting down NAL %x\n", ne->kpne_interface.kprni_nalid); LASSERT (!ne->kpne_shutdown); LASSERT (!in_interrupt()); write_lock_irqsave (&kpr_rwlock, flags); ne->kpne_shutdown = 1; write_unlock_irqrestore (&kpr_rwlock, flags); } void kpr_deregister_nal (void *arg) { unsigned long flags; kpr_nal_entry_t *ne = (kpr_nal_entry_t *)arg; CDEBUG (D_NET, "Deregister NAL %x\n", ne->kpne_interface.kprni_nalid); LASSERT (ne->kpne_shutdown); /* caller must have issued shutdown already */ LASSERT (!in_interrupt()); write_lock_irqsave (&kpr_rwlock, flags); list_del (&ne->kpne_list); write_unlock_irqrestore (&kpr_rwlock, flags); /* Wait until all outstanding messages/notifications have completed */ while (atomic_read (&ne->kpne_refcount) != 0) { CDEBUG (D_NET, "Waiting for refcount on NAL %x to reach zero (%d)\n", ne->kpne_interface.kprni_nalid, atomic_read (&ne->kpne_refcount)); set_current_state (TASK_UNINTERRUPTIBLE); schedule_timeout (HZ); } PORTAL_FREE (ne, sizeof (*ne)); PORTAL_MODULE_UNUSE; } int kpr_ge_isbetter (kpr_gateway_entry_t *ge1, kpr_gateway_entry_t *ge2) { const int significant_bits = 0x00ffffff; /* We use atomic_t to record/compare route weights for * load-balancing. Here we limit ourselves to only using * 'significant_bits' when we do an 'after' comparison */ int diff = (atomic_read (&ge1->kpge_weight) - atomic_read (&ge2->kpge_weight)) & significant_bits; int rc = (diff > (significant_bits >> 1)); CDEBUG(D_INFO, "[%p]"LPX64"=%d %s [%p]"LPX64"=%d\n", ge1, ge1->kpge_nid, atomic_read (&ge1->kpge_weight), rc ? ">" : "<", ge2, ge2->kpge_nid, atomic_read (&ge2->kpge_weight)); return (rc); } void kpr_update_weight (kpr_gateway_entry_t *ge, int nob) { int weight = 1 + (nob + sizeof (ptl_hdr_t)/2)/sizeof (ptl_hdr_t); /* We've chosen this route entry (i.e. gateway) to forward payload * of length 'nob'; update the route's weight to make it less * favoured. Note that the weight is 1 plus the payload size * rounded and scaled to the portals header size, so we get better * use of the significant bits in kpge_weight. */ CDEBUG(D_INFO, "gateway [%p]"LPX64" += %d\n", ge, ge->kpge_nid, weight); atomic_add (weight, &ge->kpge_weight); } int kpr_lookup_target (void *arg, ptl_nid_t target_nid, int nob, ptl_nid_t *gateway_nidp) { kpr_nal_entry_t *ne = (kpr_nal_entry_t *)arg; struct list_head *e; kpr_route_entry_t *re; kpr_gateway_entry_t *ge = NULL; int rc = -ENOENT; /* Caller wants to know if 'target_nid' can be reached via a gateway * ON HER OWN NETWORK */ CDEBUG (D_INFO, "lookup "LPX64" from NAL %x\n", target_nid, ne->kpne_interface.kprni_nalid); LASSERT (!in_interrupt()); read_lock (&kpr_rwlock); if (ne->kpne_shutdown) { /* caller is shutting down */ read_unlock (&kpr_rwlock); return (-ENOENT); } /* Search routes for one that has a gateway to target_nid on the callers network */ list_for_each (e, &kpr_routes) { re = list_entry (e, kpr_route_entry_t, kpre_list); if (re->kpre_lo_nid > target_nid || re->kpre_hi_nid < target_nid) continue; /* found table entry */ if (re->kpre_gateway->kpge_nalid != ne->kpne_interface.kprni_nalid || !re->kpre_gateway->kpge_alive) { /* different NAL or gateway down */ rc = -EHOSTUNREACH; continue; } if (ge == NULL || kpr_ge_isbetter (re->kpre_gateway, ge)) ge = re->kpre_gateway; } if (ge != NULL) { kpr_update_weight (ge, nob); *gateway_nidp = ge->kpge_nid; rc = 0; } read_unlock (&kpr_rwlock); /* NB can't deref 're' now; it might have been removed! */ CDEBUG (D_NET, "lookup "LPX64" from NAL %x: %d ("LPX64")\n", target_nid, ne->kpne_interface.kprni_nalid, rc, (rc == 0) ? *gateway_nidp : (ptl_nid_t)0); return (rc); } kpr_nal_entry_t * kpr_find_nal_entry_locked (int nal_id) { struct list_head *e; /* Called with kpr_rwlock held */ list_for_each (e, &kpr_nals) { kpr_nal_entry_t *ne = list_entry (e, kpr_nal_entry_t, kpne_list); if (nal_id != ne->kpne_interface.kprni_nalid) /* no match */ continue; return (ne); } return (NULL); } void kpr_forward_packet (void *arg, kpr_fwd_desc_t *fwd) { kpr_nal_entry_t *src_ne = (kpr_nal_entry_t *)arg; ptl_nid_t target_nid = fwd->kprfd_target_nid; int nob = fwd->kprfd_nob; kpr_gateway_entry_t *ge = NULL; kpr_nal_entry_t *dst_ne = NULL; struct list_head *e; kpr_route_entry_t *re; kpr_nal_entry_t *tmp_ne; int rc; CDEBUG (D_NET, "forward [%p] "LPX64" from NAL %x\n", fwd, target_nid, src_ne->kpne_interface.kprni_nalid); LASSERT (nob == lib_kiov_nob (fwd->kprfd_niov, fwd->kprfd_kiov)); LASSERT (!in_interrupt()); read_lock (&kpr_rwlock); kpr_fwd_packets++; /* (loose) stats accounting */ kpr_fwd_bytes += nob + sizeof(ptl_hdr_t); if (src_ne->kpne_shutdown) { /* caller is shutting down */ rc = -ESHUTDOWN; goto out; } fwd->kprfd_router_arg = src_ne; /* stash caller's nal entry */ /* Search routes for one that has a gateway to target_nid NOT on the caller's network */ list_for_each (e, &kpr_routes) { re = list_entry (e, kpr_route_entry_t, kpre_list); if (re->kpre_lo_nid > target_nid || /* no match */ re->kpre_hi_nid < target_nid) continue; if (re->kpre_gateway->kpge_nalid == src_ne->kpne_interface.kprni_nalid) continue; /* don't route to same NAL */ if (!re->kpre_gateway->kpge_alive) continue; /* gateway is dead */ tmp_ne = kpr_find_nal_entry_locked (re->kpre_gateway->kpge_nalid); if (tmp_ne == NULL || tmp_ne->kpne_shutdown) { /* NAL must be registered and not shutting down */ continue; } if (ge == NULL || kpr_ge_isbetter (re->kpre_gateway, ge)) { ge = re->kpre_gateway; dst_ne = tmp_ne; } } if (ge != NULL) { LASSERT (dst_ne != NULL); kpr_update_weight (ge, nob); fwd->kprfd_gateway_nid = ge->kpge_nid; atomic_inc (&src_ne->kpne_refcount); /* source and dest nals are */ atomic_inc (&dst_ne->kpne_refcount); /* busy until fwd completes */ atomic_inc (&kpr_queue_depth); read_unlock (&kpr_rwlock); CDEBUG (D_NET, "forward [%p] "LPX64" from NAL %x: " "to "LPX64" on NAL %x\n", fwd, target_nid, src_ne->kpne_interface.kprni_nalid, fwd->kprfd_gateway_nid, dst_ne->kpne_interface.kprni_nalid); dst_ne->kpne_interface.kprni_fwd (dst_ne->kpne_interface.kprni_arg, fwd); return; } rc = -EHOSTUNREACH; out: kpr_fwd_errors++; CDEBUG (D_NET, "Failed to forward [%p] "LPX64" from NAL %x: %d\n", fwd, target_nid, src_ne->kpne_interface.kprni_nalid, rc); (fwd->kprfd_callback)(fwd->kprfd_callback_arg, rc); read_unlock (&kpr_rwlock); } void kpr_complete_packet (void *arg, kpr_fwd_desc_t *fwd, int error) { kpr_nal_entry_t *dst_ne = (kpr_nal_entry_t *)arg; kpr_nal_entry_t *src_ne = (kpr_nal_entry_t *)fwd->kprfd_router_arg; CDEBUG (D_NET, "complete(1) [%p] from NAL %x to NAL %x: %d\n", fwd, src_ne->kpne_interface.kprni_nalid, dst_ne->kpne_interface.kprni_nalid, error); atomic_dec (&dst_ne->kpne_refcount); /* CAVEAT EMPTOR dst_ne can disappear now!!! */ (fwd->kprfd_callback)(fwd->kprfd_callback_arg, error); CDEBUG (D_NET, "complete(2) [%p] from NAL %x: %d\n", fwd, src_ne->kpne_interface.kprni_nalid, error); atomic_dec (&kpr_queue_depth); atomic_dec (&src_ne->kpne_refcount); /* CAVEAT EMPTOR src_ne can disappear now!!! */ } int kpr_add_route (int gateway_nalid, ptl_nid_t gateway_nid, ptl_nid_t lo_nid, ptl_nid_t hi_nid) { unsigned long flags; struct list_head *e; kpr_route_entry_t *re; kpr_gateway_entry_t *ge; int dup = 0; CDEBUG(D_NET, "Add route: %x "LPX64" : "LPX64" - "LPX64"\n", gateway_nalid, gateway_nid, lo_nid, hi_nid); if (gateway_nalid == PTL_NID_ANY || lo_nid == PTL_NID_ANY || hi_nid == PTL_NID_ANY || lo_nid > hi_nid) return (-EINVAL); PORTAL_ALLOC (ge, sizeof (*ge)); if (ge == NULL) return (-ENOMEM); ge->kpge_nalid = gateway_nalid; ge->kpge_nid = gateway_nid; ge->kpge_alive = 1; ge->kpge_timestamp = 0; ge->kpge_refcount = 0; atomic_set (&ge->kpge_weight, 0); PORTAL_ALLOC (re, sizeof (*re)); if (re == NULL) { PORTAL_FREE (ge, sizeof (*ge)); return (-ENOMEM); } re->kpre_lo_nid = lo_nid; re->kpre_hi_nid = hi_nid; LASSERT(!in_interrupt()); write_lock_irqsave (&kpr_rwlock, flags); list_for_each (e, &kpr_gateways) { kpr_gateway_entry_t *ge2 = list_entry(e, kpr_gateway_entry_t, kpge_list); if (ge2->kpge_nalid == gateway_nalid && ge2->kpge_nid == gateway_nid) { PORTAL_FREE (ge, sizeof (*ge)); ge = ge2; dup = 1; break; } } if (!dup) { /* Adding a new gateway... */ list_add (&ge->kpge_list, &kpr_gateways); /* ...zero all gateway weights so this one doesn't have to * play catch-up */ list_for_each (e, &kpr_gateways) { kpr_gateway_entry_t *ge2 = list_entry(e, kpr_gateway_entry_t, kpge_list); atomic_set (&ge2->kpge_weight, 0); } } re->kpre_gateway = ge; ge->kpge_refcount++; list_add (&re->kpre_list, &kpr_routes); kpr_routes_generation++; write_unlock_irqrestore (&kpr_rwlock, flags); return (0); } int kpr_sys_notify (int gateway_nalid, ptl_nid_t gateway_nid, int alive, time_t when) { return (kpr_do_notify (0, gateway_nalid, gateway_nid, alive, when)); } int kpr_del_route (int gw_nalid, ptl_nid_t gw_nid, ptl_nid_t lo, ptl_nid_t hi) { int specific = (lo != PTL_NID_ANY); unsigned long flags; int rc = -ENOENT; struct list_head *e; struct list_head *n; CDEBUG(D_NET, "Del route [%x] "LPX64" : "LPX64" - "LPX64"\n", gw_nalid, gw_nid, lo, hi); LASSERT(!in_interrupt()); /* NB Caller may specify either all routes via the given gateway * (lo/hi == PTL_NID_ANY) or a specific route entry (lo/hi are * actual NIDs) */ if (specific ? (hi == PTL_NID_ANY || hi < lo) : (hi != PTL_NID_ANY)) return (-EINVAL); write_lock_irqsave(&kpr_rwlock, flags); list_for_each_safe (e, n, &kpr_routes) { kpr_route_entry_t *re = list_entry(e, kpr_route_entry_t, kpre_list); kpr_gateway_entry_t *ge = re->kpre_gateway; if (ge->kpge_nalid != gw_nalid || ge->kpge_nid != gw_nid || (specific && (lo != re->kpre_lo_nid || hi != re->kpre_hi_nid))) continue; rc = 0; if (--ge->kpge_refcount == 0) { list_del (&ge->kpge_list); PORTAL_FREE (ge, sizeof (*ge)); } list_del (&re->kpre_list); PORTAL_FREE(re, sizeof (*re)); if (specific) break; } kpr_routes_generation++; write_unlock_irqrestore(&kpr_rwlock, flags); return (rc); } int kpr_get_route (int idx, __u32 *gateway_nalid, ptl_nid_t *gateway_nid, ptl_nid_t *lo_nid, ptl_nid_t *hi_nid, __u32 *alive) { struct list_head *e; LASSERT (!in_interrupt()); read_lock(&kpr_rwlock); for (e = kpr_routes.next; e != &kpr_routes; e = e->next) { kpr_route_entry_t *re = list_entry(e, kpr_route_entry_t, kpre_list); kpr_gateway_entry_t *ge = re->kpre_gateway; if (idx-- == 0) { *gateway_nalid = ge->kpge_nalid; *gateway_nid = ge->kpge_nid; *alive = ge->kpge_alive; *lo_nid = re->kpre_lo_nid; *hi_nid = re->kpre_hi_nid; read_unlock(&kpr_rwlock); return (0); } } read_unlock (&kpr_rwlock); return (-ENOENT); } static int kpr_nal_cmd(struct portals_cfg *pcfg, void * private) { int err = -EINVAL; ENTRY; switch(pcfg->pcfg_command) { default: CDEBUG(D_IOCTL, "Inappropriate cmd: %d\n", pcfg->pcfg_command); break; case NAL_CMD_ADD_ROUTE: CDEBUG(D_IOCTL, "Adding route: [%x] "LPU64" : "LPU64" - "LPU64"\n", pcfg->pcfg_nal, pcfg->pcfg_nid, pcfg->pcfg_nid2, pcfg->pcfg_nid3); err = kpr_add_route(pcfg->pcfg_gw_nal, pcfg->pcfg_nid, pcfg->pcfg_nid2, pcfg->pcfg_nid3); break; case NAL_CMD_DEL_ROUTE: CDEBUG (D_IOCTL, "Removing routes via [%x] "LPU64" : "LPU64" - "LPU64"\n", pcfg->pcfg_gw_nal, pcfg->pcfg_nid, pcfg->pcfg_nid2, pcfg->pcfg_nid3); err = kpr_del_route (pcfg->pcfg_gw_nal, pcfg->pcfg_nid, pcfg->pcfg_nid2, pcfg->pcfg_nid3); break; case NAL_CMD_NOTIFY_ROUTER: { CDEBUG (D_IOCTL, "Notifying peer [%x] "LPU64" %s @ %ld\n", pcfg->pcfg_gw_nal, pcfg->pcfg_nid, pcfg->pcfg_flags ? "Enabling" : "Disabling", (time_t)pcfg->pcfg_nid3); err = kpr_sys_notify (pcfg->pcfg_gw_nal, pcfg->pcfg_nid, pcfg->pcfg_flags, (time_t)pcfg->pcfg_nid3); break; } case NAL_CMD_GET_ROUTE: CDEBUG (D_IOCTL, "Getting route [%d]\n", pcfg->pcfg_count); err = kpr_get_route(pcfg->pcfg_count, &pcfg->pcfg_gw_nal, &pcfg->pcfg_nid, &pcfg->pcfg_nid2, &pcfg->pcfg_nid3, &pcfg->pcfg_flags); break; } RETURN(err); } static void /*__exit*/ kpr_finalise (void) { LASSERT (list_empty (&kpr_nals)); libcfs_nal_cmd_unregister(ROUTER); PORTAL_SYMBOL_UNREGISTER(kpr_router_interface); kpr_proc_fini(); while (!list_empty (&kpr_routes)) { kpr_route_entry_t *re = list_entry(kpr_routes.next, kpr_route_entry_t, kpre_list); list_del(&re->kpre_list); PORTAL_FREE(re, sizeof (*re)); } CDEBUG(D_MALLOC, "kpr_finalise: kmem back to %d\n", atomic_read(&portal_kmemory)); } static int __init kpr_initialise (void) { int rc; CDEBUG(D_MALLOC, "kpr_initialise: kmem %d\n", atomic_read(&portal_kmemory)); kpr_routes_generation = 0; kpr_proc_init(); rc = libcfs_nal_cmd_register(ROUTER, kpr_nal_cmd, NULL); if (rc != 0) { CERROR("Can't register nal cmd handler\n"); return (rc); } PORTAL_SYMBOL_REGISTER(kpr_router_interface); return (0); } MODULE_AUTHOR("Eric Barton"); MODULE_DESCRIPTION("Kernel Portals Router v0.01"); MODULE_LICENSE("GPL"); module_init (kpr_initialise); module_exit (kpr_finalise); EXPORT_SYMBOL (kpr_router_interface);