2000-10-09 13:18:17 +00:00
|
|
|
/*
|
2002-03-17 09:33:15 +00:00
|
|
|
* Copyright (c) 1999, 2002 Hellmuth Michaelis. All rights reserved.
|
2000-10-09 13:18:17 +00:00
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
*---------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* i4b_ing.c - isdn4bsd B-channel to netgraph driver
|
|
|
|
* -------------------------------------------------
|
|
|
|
*
|
|
|
|
* $FreeBSD$
|
|
|
|
*
|
2002-03-17 09:33:15 +00:00
|
|
|
* last edit-date: [Sat Mar 9 14:09:53 2002]
|
2000-10-09 13:18:17 +00:00
|
|
|
*
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#include "i4bing.h"
|
|
|
|
|
|
|
|
#if NI4BING > 0
|
|
|
|
|
|
|
|
#include "opt_i4b.h"
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/kernel.h>
|
|
|
|
#include <sys/mbuf.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/errno.h>
|
|
|
|
#include <sys/malloc.h>
|
|
|
|
|
|
|
|
#include <net/if.h>
|
|
|
|
|
|
|
|
#include <netgraph/ng_message.h>
|
|
|
|
#include <netgraph/ng_parse.h>
|
|
|
|
#include <netgraph/netgraph.h>
|
|
|
|
|
|
|
|
#include <machine/i4b_ioctl.h>
|
|
|
|
#include <machine/i4b_debug.h>
|
|
|
|
|
|
|
|
#include <i4b/include/i4b_global.h>
|
|
|
|
#include <i4b/include/i4b_l3l4.h>
|
|
|
|
|
|
|
|
#include <i4b/layer4/i4b_l4.h>
|
|
|
|
|
|
|
|
#define I4BINGACCT 1 /* enable accounting messages */
|
|
|
|
#define I4BINGACCTINTVL 2 /* accounting msg interval in secs */
|
|
|
|
|
|
|
|
#define I4BINGMAXQLEN 50 /* max queue length */
|
|
|
|
|
|
|
|
/* initialized by L4 */
|
|
|
|
|
|
|
|
static drvr_link_t ing_drvr_linktab[NI4BING];
|
|
|
|
static isdn_link_t *isdn_linktab[NI4BING];
|
|
|
|
|
|
|
|
struct ing_softc {
|
|
|
|
int sc_unit; /* unit number */
|
|
|
|
int sc_state; /* state of the interface */
|
|
|
|
call_desc_t *sc_cdp; /* ptr to call descriptor */
|
|
|
|
int sc_updown; /* soft state of interface */
|
|
|
|
struct ifqueue sc_fastq; /* interactive traffic */
|
|
|
|
int sc_dialresp; /* dialresponse */
|
|
|
|
int sc_lastdialresp;/* last dialresponse */
|
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
struct callout_handle sc_callout;
|
|
|
|
int sc_iinb; /* isdn driver # of inbytes */
|
|
|
|
int sc_ioutb; /* isdn driver # of outbytes */
|
|
|
|
int sc_inb; /* # of bytes rx'd */
|
|
|
|
int sc_outb; /* # of bytes tx'd */
|
|
|
|
int sc_linb; /* last # of bytes rx'd */
|
|
|
|
int sc_loutb; /* last # of bytes tx'd */
|
|
|
|
int sc_fn; /* flag, first null acct */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int sc_inpkt; /* incoming packets */
|
|
|
|
int sc_outpkt; /* outgoing packets */
|
|
|
|
|
|
|
|
struct ifqueue xmitq_hipri; /* hi-priority transmit queue */
|
|
|
|
struct ifqueue xmitq; /* transmit queue */
|
|
|
|
|
|
|
|
node_p node; /* back pointer to node */
|
|
|
|
char nodename[NG_NODELEN + 1]; /* store our node name */
|
|
|
|
hook_p debughook;
|
|
|
|
hook_p hook;
|
|
|
|
|
|
|
|
u_int packets_in; /* packets in from downstream */
|
|
|
|
u_int packets_out; /* packets out towards downstream */
|
|
|
|
u_int32_t flags;
|
|
|
|
|
|
|
|
} ing_softc[NI4BING];
|
|
|
|
|
|
|
|
enum ing_states {
|
|
|
|
ST_IDLE, /* initialized, ready, idle */
|
|
|
|
ST_DIALING, /* dialling out to remote */
|
|
|
|
ST_CONNECTED /* connected to remote */
|
|
|
|
};
|
|
|
|
|
|
|
|
static void i4bingattach(void *);
|
|
|
|
|
|
|
|
PSEUDO_SET(i4bingattach, i4b_ing);
|
|
|
|
|
|
|
|
static void ing_init_linktab(int unit);
|
|
|
|
static void ing_tx_queue_empty(int unit);
|
|
|
|
|
|
|
|
/* ========= NETGRAPH ============= */
|
|
|
|
|
|
|
|
#define NG_ING_NODE_TYPE "i4bing" /* node type name */
|
|
|
|
#define NGM_ING_COOKIE 947513046 /* node type cookie */
|
|
|
|
|
|
|
|
/* Hook names */
|
|
|
|
#define NG_ING_HOOK_DEBUG "debug"
|
|
|
|
#define NG_ING_HOOK_RAW "rawdata"
|
|
|
|
|
|
|
|
/* Netgraph commands understood by this node type */
|
|
|
|
enum {
|
|
|
|
NGM_ING_SET_FLAG = 1,
|
|
|
|
NGM_ING_GET_STATUS,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* This structure is returned by the NGM_ING_GET_STATUS command */
|
|
|
|
struct ngingstat {
|
|
|
|
u_int packets_in; /* packets in from downstream */
|
|
|
|
u_int packets_out; /* packets out towards downstream */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is used to define the 'parse type' for a struct ngingstat, which
|
|
|
|
* is bascially a description of how to convert a binary struct ngingstat
|
|
|
|
* to an ASCII string and back. See ng_parse.h for more info.
|
|
|
|
*
|
|
|
|
* This needs to be kept in sync with the above structure definition
|
|
|
|
*/
|
|
|
|
#define NG_ING_STATS_TYPE_INFO { \
|
|
|
|
{ \
|
|
|
|
{ "packets_in", &ng_parse_int32_type }, \
|
|
|
|
{ "packets_out", &ng_parse_int32_type }, \
|
|
|
|
{ NULL }, \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This section contains the netgraph method declarations for the
|
|
|
|
* sample node. These methods define the netgraph 'type'.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static ng_constructor_t ng_ing_constructor;
|
|
|
|
static ng_rcvmsg_t ng_ing_rcvmsg;
|
2001-01-06 00:46:47 +00:00
|
|
|
static ng_shutdown_t ng_ing_shutdown;
|
2000-10-09 13:18:17 +00:00
|
|
|
static ng_newhook_t ng_ing_newhook;
|
|
|
|
static ng_connect_t ng_ing_connect;
|
|
|
|
static ng_rcvdata_t ng_ing_rcvdata;
|
|
|
|
static ng_disconnect_t ng_ing_disconnect;
|
|
|
|
|
|
|
|
/* Parse type for struct ngingstat */
|
|
|
|
static const struct
|
|
|
|
ng_parse_struct_info ng_ing_stat_type_info = NG_ING_STATS_TYPE_INFO;
|
|
|
|
|
|
|
|
static const struct ng_parse_type ng_ing_stat_type = {
|
|
|
|
&ng_parse_struct_type,
|
|
|
|
&ng_ing_stat_type_info
|
|
|
|
};
|
|
|
|
|
|
|
|
/* List of commands and how to convert arguments to/from ASCII */
|
|
|
|
|
|
|
|
static const struct ng_cmdlist ng_ing_cmdlist[] = {
|
|
|
|
{
|
|
|
|
NGM_ING_COOKIE,
|
|
|
|
NGM_ING_GET_STATUS,
|
|
|
|
"getstatus",
|
|
|
|
NULL,
|
|
|
|
&ng_ing_stat_type,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
NGM_ING_COOKIE,
|
|
|
|
NGM_ING_SET_FLAG,
|
|
|
|
"setflag",
|
|
|
|
&ng_parse_int32_type,
|
|
|
|
NULL
|
|
|
|
},
|
|
|
|
{ 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Netgraph node type descriptor */
|
|
|
|
static struct ng_type typestruct = {
|
2000-12-18 20:03:32 +00:00
|
|
|
NG_ABI_VERSION,
|
2000-10-09 13:18:17 +00:00
|
|
|
NG_ING_NODE_TYPE,
|
|
|
|
NULL,
|
|
|
|
ng_ing_constructor,
|
|
|
|
ng_ing_rcvmsg,
|
2001-01-06 00:46:47 +00:00
|
|
|
ng_ing_shutdown,
|
2000-10-09 13:18:17 +00:00
|
|
|
ng_ing_newhook,
|
|
|
|
NULL,
|
|
|
|
ng_ing_connect,
|
|
|
|
ng_ing_rcvdata,
|
|
|
|
ng_ing_disconnect,
|
|
|
|
ng_ing_cmdlist
|
|
|
|
};
|
|
|
|
|
2000-11-09 12:27:31 +00:00
|
|
|
NETGRAPH_INIT_ORDERED(ing, &typestruct, SI_SUB_DRIVERS, SI_ORDER_ANY);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* DEVICE DRIVER ROUTINES
|
|
|
|
*===========================================================================*/
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* interface attach routine at kernel boot time
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
i4bingattach(void *dummy)
|
|
|
|
{
|
|
|
|
struct ing_softc *sc = ing_softc;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
printf("i4bing: %d i4b NetGraph ISDN B-channel device(s) attached\n", NI4BING);
|
|
|
|
|
|
|
|
for(i=0; i < NI4BING; sc++, i++)
|
|
|
|
{
|
|
|
|
sc->sc_unit = i;
|
|
|
|
|
|
|
|
ing_init_linktab(i);
|
|
|
|
|
|
|
|
NDBGL4(L4_DIALST, "setting dial state to ST_IDLE");
|
|
|
|
|
|
|
|
sc->sc_state = ST_IDLE;
|
|
|
|
|
|
|
|
sc->sc_fastq.ifq_maxlen = I4BINGMAXQLEN;
|
2001-12-30 19:37:41 +00:00
|
|
|
if(!mtx_initialized(&sc->sc_fastq.ifq_mtx))
|
2002-04-04 21:03:38 +00:00
|
|
|
mtx_init(&sc->sc_fastq.ifq_mtx, "i4b_ing_fastq", NULL, MTX_DEF);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
callout_handle_init(&sc->sc_callout);
|
|
|
|
sc->sc_iinb = 0;
|
|
|
|
sc->sc_ioutb = 0;
|
|
|
|
sc->sc_inb = 0;
|
|
|
|
sc->sc_outb = 0;
|
|
|
|
sc->sc_linb = 0;
|
|
|
|
sc->sc_loutb = 0;
|
|
|
|
sc->sc_fn = 1;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
sc->sc_inpkt = 0;
|
|
|
|
sc->sc_outpkt = 0;
|
|
|
|
|
|
|
|
sc->sc_updown = SOFT_ENA; /* soft enabled */
|
|
|
|
|
|
|
|
sc->sc_dialresp = DSTAT_NONE; /* no response */
|
|
|
|
sc->sc_lastdialresp = DSTAT_NONE;
|
|
|
|
|
|
|
|
/* setup a netgraph node */
|
|
|
|
|
|
|
|
if ((ret = ng_make_node_common(&typestruct, &sc->node)))
|
|
|
|
{
|
|
|
|
printf("ing: ng_make_node_common, ret = %d\n!", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* name the netgraph node */
|
|
|
|
|
|
|
|
sprintf(sc->nodename, "%s%d", NG_ING_NODE_TYPE, sc->sc_unit);
|
2001-01-06 00:46:47 +00:00
|
|
|
if((ret = ng_name_node(sc->node, sc->nodename)))
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
2001-01-06 00:46:47 +00:00
|
|
|
printf("ing: ng_name node, ret = %d\n!", ret);
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_NODE_UNREF(sc->node);
|
2001-01-06 00:46:47 +00:00
|
|
|
break;
|
2000-10-09 13:18:17 +00:00
|
|
|
}
|
2001-01-06 00:46:47 +00:00
|
|
|
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_NODE_SET_PRIVATE(sc->node, sc);
|
2001-01-06 00:46:47 +00:00
|
|
|
|
|
|
|
sc->xmitq.ifq_maxlen = IFQ_MAXLEN;
|
|
|
|
sc->xmitq_hipri.ifq_maxlen = IFQ_MAXLEN;
|
2001-12-30 09:27:28 +00:00
|
|
|
if(!mtx_initialized(&sc->xmitq.ifq_mtx))
|
2002-04-04 21:03:38 +00:00
|
|
|
mtx_init(&sc->xmitq.ifq_mtx, "i4b_ing_xmitq", NULL, MTX_DEF);
|
2001-12-30 09:27:28 +00:00
|
|
|
if(!mtx_initialized(&sc->xmitq_hipri.ifq_mtx))
|
2002-04-04 21:03:38 +00:00
|
|
|
mtx_init(&sc->xmitq_hipri.ifq_mtx, "i4b_ing_hipri", NULL, MTX_DEF);
|
2000-10-09 13:18:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef I4BINGACCT
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* accounting timeout routine
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_timeout(struct ing_softc *sc)
|
|
|
|
{
|
|
|
|
bchan_statistics_t bs;
|
|
|
|
int unit = sc->sc_unit;
|
|
|
|
|
|
|
|
/* get # of bytes in and out from the HSCX driver */
|
|
|
|
|
|
|
|
(*isdn_linktab[unit]->bch_stat)
|
|
|
|
(isdn_linktab[unit]->unit, isdn_linktab[unit]->channel, &bs);
|
|
|
|
|
|
|
|
sc->sc_ioutb += bs.outbytes;
|
|
|
|
sc->sc_iinb += bs.inbytes;
|
|
|
|
|
|
|
|
if((sc->sc_iinb != sc->sc_linb) || (sc->sc_ioutb != sc->sc_loutb) || sc->sc_fn)
|
|
|
|
{
|
|
|
|
int ri = (sc->sc_iinb - sc->sc_linb)/I4BINGACCTINTVL;
|
|
|
|
int ro = (sc->sc_ioutb - sc->sc_loutb)/I4BINGACCTINTVL;
|
|
|
|
|
|
|
|
if((sc->sc_iinb == sc->sc_linb) && (sc->sc_ioutb == sc->sc_loutb))
|
|
|
|
sc->sc_fn = 0;
|
|
|
|
else
|
|
|
|
sc->sc_fn = 1;
|
|
|
|
|
|
|
|
sc->sc_linb = sc->sc_iinb;
|
|
|
|
sc->sc_loutb = sc->sc_ioutb;
|
|
|
|
|
|
|
|
i4b_l4_accounting(BDRV_ING, unit, ACCT_DURING,
|
|
|
|
sc->sc_ioutb, sc->sc_iinb, ro, ri, sc->sc_ioutb, sc->sc_iinb);
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->sc_callout = timeout((TIMEOUT_FUNC_T)ing_timeout,
|
|
|
|
(void *)sc, I4BINGACCTINTVL*hz);
|
|
|
|
}
|
|
|
|
#endif /* I4BINGACCT */
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* clear the interface's send queues
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ingclearqueue(struct ifqueue *iq)
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
|
Lock down the network interface queues. The queue mutex must be obtained
before adding/removing packets from the queue. Also, the if_obytes and
if_omcasts fields should only be manipulated under protection of the mutex.
IF_ENQUEUE, IF_PREPEND, and IF_DEQUEUE perform all necessary locking on
the queue. An IF_LOCK macro is provided, as well as the old (mutex-less)
versions of the macros in the form _IF_ENQUEUE, _IF_QFULL, for code which
needs them, but their use is discouraged.
Two new macros are introduced: IF_DRAIN() to drain a queue, and IF_HANDOFF,
which takes care of locking/enqueue, and also statistics updating/start
if necessary.
2000-11-25 07:35:38 +00:00
|
|
|
x = splimp();
|
|
|
|
IF_DRAIN(iq);
|
|
|
|
splx(x);
|
|
|
|
}
|
2000-10-09 13:18:17 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* ISDN INTERFACE ROUTINES
|
|
|
|
*===========================================================================*/
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is called from L4 handler at connect time
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_connect(int unit, void *cdp)
|
|
|
|
{
|
|
|
|
struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
int s;
|
|
|
|
|
|
|
|
sc->sc_cdp = (call_desc_t *)cdp;
|
|
|
|
|
|
|
|
s = SPLI4B();
|
|
|
|
|
|
|
|
NDBGL4(L4_DIALST, "ing%d: setting dial state to ST_CONNECTED", unit);
|
|
|
|
|
|
|
|
sc->sc_dialresp = DSTAT_NONE;
|
|
|
|
sc->sc_lastdialresp = DSTAT_NONE;
|
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
sc->sc_iinb = 0;
|
|
|
|
sc->sc_ioutb = 0;
|
|
|
|
sc->sc_inb = 0;
|
|
|
|
sc->sc_outb = 0;
|
|
|
|
sc->sc_linb = 0;
|
|
|
|
sc->sc_loutb = 0;
|
|
|
|
sc->sc_callout = timeout((TIMEOUT_FUNC_T)ing_timeout,
|
|
|
|
(void *)sc, I4BINGACCTINTVL*hz);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
sc->sc_state = ST_CONNECTED;
|
|
|
|
|
|
|
|
splx(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is called from L4 handler at disconnect time
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_disconnect(int unit, void *cdp)
|
|
|
|
{
|
|
|
|
call_desc_t *cd = (call_desc_t *)cdp;
|
|
|
|
struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
|
|
|
|
/* new stuff to check that the active channel is being closed */
|
|
|
|
|
|
|
|
if (cd != sc->sc_cdp)
|
|
|
|
{
|
|
|
|
NDBGL4(L4_INGDBG, "ing%d: channel %d not active",
|
|
|
|
cd->driver_unit, cd->channelid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
untimeout((TIMEOUT_FUNC_T)ing_timeout,
|
|
|
|
(void *)sc, sc->sc_callout);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
i4b_l4_accounting(BDRV_ING, cd->driver_unit, ACCT_FINAL,
|
|
|
|
sc->sc_ioutb, sc->sc_iinb, 0, 0, sc->sc_outb, sc->sc_inb);
|
|
|
|
|
|
|
|
sc->sc_cdp = (call_desc_t *)0;
|
|
|
|
|
|
|
|
NDBGL4(L4_DIALST, "setting dial state to ST_IDLE");
|
|
|
|
|
|
|
|
sc->sc_dialresp = DSTAT_NONE;
|
|
|
|
sc->sc_lastdialresp = DSTAT_NONE;
|
|
|
|
|
|
|
|
sc->sc_state = ST_IDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is used to give a feedback from userland daemon
|
|
|
|
* in case of dial problems
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_dialresponse(int unit, int status, cause_t cause)
|
|
|
|
{
|
|
|
|
struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
sc->sc_dialresp = status;
|
|
|
|
|
|
|
|
NDBGL4(L4_INGDBG, "ing%d: last=%d, this=%d",
|
|
|
|
unit, sc->sc_lastdialresp, sc->sc_dialresp);
|
|
|
|
|
|
|
|
if(status != DSTAT_NONE)
|
|
|
|
{
|
|
|
|
NDBGL4(L4_INGDBG, "ing%d: clearing queues", unit);
|
|
|
|
/* ingclearqueues(sc); */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* interface soft up/down
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_updown(int unit, int updown)
|
|
|
|
{
|
|
|
|
struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
sc->sc_updown = updown;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is called from the HSCX interrupt handler
|
|
|
|
* when a new frame (mbuf) has been received and was put on
|
|
|
|
* the rx queue. It is assumed that this routines runs at
|
|
|
|
* pri level splimp() ! Keep it short !
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_rx_data_rdy(int unit)
|
|
|
|
{
|
|
|
|
register struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
register struct mbuf *m;
|
2000-12-12 18:52:14 +00:00
|
|
|
int error;
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
if((m = *isdn_linktab[unit]->rx_mbuf) == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
sc->sc_inb += m->m_pkthdr.len;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
m->m_pkthdr.rcvif = NULL;
|
|
|
|
|
|
|
|
sc->sc_inpkt++;
|
|
|
|
|
2000-12-12 18:52:14 +00:00
|
|
|
NG_SEND_DATA_ONLY(error, sc->hook, m);
|
2000-10-09 13:18:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is called from the HSCX interrupt handler
|
|
|
|
* when the last frame has been sent out and there is no
|
|
|
|
* further frame (mbuf) in the tx queue.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_tx_queue_empty(int unit)
|
|
|
|
{
|
|
|
|
register struct ing_softc *sc = &ing_softc[unit];
|
|
|
|
register struct mbuf *m;
|
|
|
|
int x = 0;
|
|
|
|
|
|
|
|
if(sc->sc_state != ST_CONNECTED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
IF_DEQUEUE(&sc->xmitq_hipri, m);
|
|
|
|
|
|
|
|
if(m == NULL)
|
|
|
|
{
|
|
|
|
IF_DEQUEUE(&sc->xmitq, m);
|
|
|
|
if(m == NULL)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if I4BINGACCT
|
|
|
|
sc->sc_outb += m->m_pkthdr.len;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
x = 1;
|
|
|
|
|
Lock down the network interface queues. The queue mutex must be obtained
before adding/removing packets from the queue. Also, the if_obytes and
if_omcasts fields should only be manipulated under protection of the mutex.
IF_ENQUEUE, IF_PREPEND, and IF_DEQUEUE perform all necessary locking on
the queue. An IF_LOCK macro is provided, as well as the old (mutex-less)
versions of the macros in the form _IF_ENQUEUE, _IF_QFULL, for code which
needs them, but their use is discouraged.
Two new macros are introduced: IF_DRAIN() to drain a queue, and IF_HANDOFF,
which takes care of locking/enqueue, and also statistics updating/start
if necessary.
2000-11-25 07:35:38 +00:00
|
|
|
if(! IF_HANDOFF(isdn_linktab[unit]->tx_queue, m, NULL))
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
|
|
|
NDBGL4(L4_INGDBG, "ing%d: tx queue full!", unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(x)
|
|
|
|
(*isdn_linktab[unit]->bch_tx_start)(isdn_linktab[unit]->unit, isdn_linktab[unit]->channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* this routine is called from the HSCX interrupt handler
|
|
|
|
* each time a packet is received or transmitted. It should
|
|
|
|
* be used to implement an activity timeout mechanism.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_activity(int unit, int rxtx)
|
|
|
|
{
|
|
|
|
ing_softc[unit].sc_cdp->last_active_time = SECOND;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* return this drivers linktab address
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
drvr_link_t *
|
|
|
|
ing_ret_linktab(int unit)
|
|
|
|
{
|
|
|
|
return(&ing_drvr_linktab[unit]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* setup the isdn_linktab for this driver
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
ing_set_linktab(int unit, isdn_link_t *ilt)
|
|
|
|
{
|
|
|
|
isdn_linktab[unit] = ilt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* initialize this drivers linktab
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
ing_init_linktab(int unit)
|
|
|
|
{
|
|
|
|
ing_drvr_linktab[unit].unit = unit;
|
|
|
|
ing_drvr_linktab[unit].bch_rx_data_ready = ing_rx_data_rdy;
|
|
|
|
ing_drvr_linktab[unit].bch_tx_queue_empty = ing_tx_queue_empty;
|
|
|
|
ing_drvr_linktab[unit].bch_activity = ing_activity;
|
|
|
|
ing_drvr_linktab[unit].line_connected = ing_connect;
|
|
|
|
ing_drvr_linktab[unit].line_disconnected = ing_disconnect;
|
|
|
|
ing_drvr_linktab[unit].dial_response = ing_dialresponse;
|
|
|
|
ing_drvr_linktab[unit].updown_ind = ing_updown;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* NETGRAPH INTERFACE ROUTINES
|
|
|
|
*===========================================================================*/
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* It is not possible or allowable to create a node of this type.
|
|
|
|
* If the hardware exists, it will already have created it.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
2001-01-06 00:46:47 +00:00
|
|
|
ng_ing_constructor(node_p node)
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
|
|
|
return(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* Give our ok for a hook to be added...
|
|
|
|
* Add the hook's private info to the hook structure.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
|
|
|
ng_ing_newhook(node_p node, hook_p hook, const char *name)
|
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
struct ing_softc *sc = NG_NODE_PRIVATE(node);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* check if it's our friend the debug hook
|
|
|
|
*/
|
|
|
|
if(strcmp(name, NG_ING_HOOK_DEBUG) == 0)
|
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_HOOK_SET_PRIVATE(hook, NULL); /* paranoid */
|
2000-10-09 13:18:17 +00:00
|
|
|
sc->debughook = hook;
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Check for raw mode hook.
|
|
|
|
*/
|
|
|
|
if(strcmp(name, NG_ING_HOOK_RAW) == 0)
|
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_HOOK_SET_PRIVATE(hook, sc);
|
2000-10-09 13:18:17 +00:00
|
|
|
sc->hook = hook;
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* Get a netgraph control message.
|
|
|
|
* Check it is one we understand. If needed, send a response.
|
|
|
|
* We could save the address for an async action later, but don't here.
|
|
|
|
* Always free the message.
|
|
|
|
* The response should be in a malloc'd region that the caller can 'free'.
|
|
|
|
* A response is not required.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
2001-01-06 00:46:47 +00:00
|
|
|
ng_ing_rcvmsg(node_p node, item_p item, hook_p lasthook)
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
struct ing_softc *sc = NG_NODE_PRIVATE(node);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
struct ng_mesg *resp = NULL;
|
|
|
|
int error = 0;
|
2001-01-06 00:46:47 +00:00
|
|
|
struct ng_mesg *msg;
|
2000-10-09 13:18:17 +00:00
|
|
|
|
2001-01-06 00:46:47 +00:00
|
|
|
NGI_GET_MSG(item, msg);
|
2002-03-17 09:33:15 +00:00
|
|
|
|
2000-10-09 13:18:17 +00:00
|
|
|
if(msg->header.typecookie == NGM_GENERIC_COOKIE)
|
|
|
|
{
|
|
|
|
switch(msg->header.cmd)
|
|
|
|
{
|
|
|
|
case NGM_TEXT_STATUS:
|
|
|
|
{
|
|
|
|
char *arg;
|
|
|
|
char *p;
|
|
|
|
int pos = 0;
|
|
|
|
|
|
|
|
NG_MKRESPONSE(resp, msg, sizeof(struct ng_mesg) + NG_TEXTRESPONSE, M_NOWAIT);
|
|
|
|
|
|
|
|
if (resp == NULL)
|
|
|
|
{
|
|
|
|
error = ENOMEM;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
arg = (char *) resp->data;
|
|
|
|
|
|
|
|
switch(sc->sc_state)
|
|
|
|
{
|
|
|
|
case ST_IDLE:
|
|
|
|
p = "idle";
|
|
|
|
break;
|
|
|
|
case ST_DIALING:
|
|
|
|
p = "dialing";
|
|
|
|
break;
|
|
|
|
case ST_CONNECTED:
|
|
|
|
p = "connected";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
p = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = sprintf(arg, "state = %s (%d)\n", p, sc->sc_state);
|
|
|
|
#if I4BINGACCT
|
|
|
|
pos += sprintf(arg + pos, "%d bytes in, %d bytes out\n", sc->sc_inb, sc->sc_outb);
|
|
|
|
#endif
|
|
|
|
pos += sprintf(arg + pos, "%d pkts in, %d pkts out\n", sc->sc_inpkt, sc->sc_outpkt);
|
|
|
|
|
|
|
|
resp->header.arglen = pos + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
error = EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(msg->header.typecookie == NGM_ING_COOKIE)
|
|
|
|
{
|
|
|
|
switch (msg->header.cmd)
|
|
|
|
{
|
|
|
|
case NGM_ING_GET_STATUS:
|
|
|
|
{
|
|
|
|
struct ngingstat *stats;
|
|
|
|
|
|
|
|
NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT);
|
|
|
|
|
|
|
|
if (!resp)
|
|
|
|
{
|
|
|
|
error = ENOMEM;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
stats = (struct ngingstat *) resp->data;
|
|
|
|
stats->packets_in = sc->packets_in;
|
|
|
|
stats->packets_out = sc->packets_out;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case NGM_ING_SET_FLAG:
|
|
|
|
if (msg->header.arglen != sizeof(u_int32_t))
|
|
|
|
{
|
|
|
|
error = EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
sc->flags = *((u_int32_t *) msg->data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
error = EINVAL; /* unknown command */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
error = EINVAL; /* unknown cookie type */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Take care of synchronous response, if any */
|
2001-01-06 00:46:47 +00:00
|
|
|
NG_RESPOND_MSG(error, node, item, resp);
|
2000-10-09 13:18:17 +00:00
|
|
|
/* Free the message and return */
|
2001-01-06 00:46:47 +00:00
|
|
|
NG_FREE_MSG(msg);
|
2000-10-09 13:18:17 +00:00
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* get data from another node and transmit it out on a B-channel
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
2001-01-06 00:46:47 +00:00
|
|
|
ng_ing_rcvdata(hook_p hook, item_p item)
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
struct ing_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
|
2000-10-09 13:18:17 +00:00
|
|
|
struct ifqueue *xmitq_p;
|
|
|
|
int s;
|
2001-01-06 00:46:47 +00:00
|
|
|
struct mbuf *m;
|
|
|
|
meta_p meta;
|
2000-10-09 13:18:17 +00:00
|
|
|
|
2001-01-06 00:46:47 +00:00
|
|
|
NGI_GET_M(item, m);
|
|
|
|
NGI_GET_META(item, meta);
|
|
|
|
NG_FREE_ITEM(item);
|
2002-03-17 09:33:15 +00:00
|
|
|
|
2001-01-08 05:34:06 +00:00
|
|
|
if(NG_HOOK_PRIVATE(hook) == NULL)
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
2001-01-06 00:46:47 +00:00
|
|
|
NG_FREE_M(m);
|
|
|
|
NG_FREE_META(meta);
|
2000-10-09 13:18:17 +00:00
|
|
|
return(ENETDOWN);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(sc->sc_state == ST_IDLE || sc->sc_state == ST_DIALING)
|
|
|
|
{
|
|
|
|
i4b_l4_dialout(BDRV_ING, sc->sc_unit);
|
|
|
|
sc->sc_state = ST_DIALING;
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->sc_outpkt++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now queue the data for when it can be sent
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (meta && meta->priority > 0)
|
|
|
|
{
|
|
|
|
xmitq_p = (&sc->xmitq_hipri);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
xmitq_p = (&sc->xmitq);
|
|
|
|
}
|
|
|
|
|
|
|
|
s = splimp();
|
|
|
|
|
Lock down the network interface queues. The queue mutex must be obtained
before adding/removing packets from the queue. Also, the if_obytes and
if_omcasts fields should only be manipulated under protection of the mutex.
IF_ENQUEUE, IF_PREPEND, and IF_DEQUEUE perform all necessary locking on
the queue. An IF_LOCK macro is provided, as well as the old (mutex-less)
versions of the macros in the form _IF_ENQUEUE, _IF_QFULL, for code which
needs them, but their use is discouraged.
Two new macros are introduced: IF_DRAIN() to drain a queue, and IF_HANDOFF,
which takes care of locking/enqueue, and also statistics updating/start
if necessary.
2000-11-25 07:35:38 +00:00
|
|
|
IF_LOCK(xmitq_p);
|
|
|
|
if (_IF_QFULL(xmitq_p))
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
Lock down the network interface queues. The queue mutex must be obtained
before adding/removing packets from the queue. Also, the if_obytes and
if_omcasts fields should only be manipulated under protection of the mutex.
IF_ENQUEUE, IF_PREPEND, and IF_DEQUEUE perform all necessary locking on
the queue. An IF_LOCK macro is provided, as well as the old (mutex-less)
versions of the macros in the form _IF_ENQUEUE, _IF_QFULL, for code which
needs them, but their use is discouraged.
Two new macros are introduced: IF_DRAIN() to drain a queue, and IF_HANDOFF,
which takes care of locking/enqueue, and also statistics updating/start
if necessary.
2000-11-25 07:35:38 +00:00
|
|
|
_IF_DROP(xmitq_p);
|
|
|
|
IF_UNLOCK(xmitq_p);
|
2000-10-09 13:18:17 +00:00
|
|
|
splx(s);
|
2001-01-06 00:46:47 +00:00
|
|
|
NG_FREE_M(m);
|
|
|
|
NG_FREE_META(meta);
|
2000-10-09 13:18:17 +00:00
|
|
|
return(ENOBUFS);
|
|
|
|
}
|
|
|
|
|
Lock down the network interface queues. The queue mutex must be obtained
before adding/removing packets from the queue. Also, the if_obytes and
if_omcasts fields should only be manipulated under protection of the mutex.
IF_ENQUEUE, IF_PREPEND, and IF_DEQUEUE perform all necessary locking on
the queue. An IF_LOCK macro is provided, as well as the old (mutex-less)
versions of the macros in the form _IF_ENQUEUE, _IF_QFULL, for code which
needs them, but their use is discouraged.
Two new macros are introduced: IF_DRAIN() to drain a queue, and IF_HANDOFF,
which takes care of locking/enqueue, and also statistics updating/start
if necessary.
2000-11-25 07:35:38 +00:00
|
|
|
_IF_ENQUEUE(xmitq_p, m);
|
|
|
|
IF_UNLOCK(xmitq_p);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
ing_tx_queue_empty(sc->sc_unit);
|
|
|
|
|
|
|
|
splx(s);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* Do local shutdown processing..
|
|
|
|
* If we are a persistant device, we might refuse to go away, and
|
|
|
|
* we'd only remove our links and reset ourself.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
2001-01-06 00:46:47 +00:00
|
|
|
ng_ing_shutdown(node_p node)
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
struct ing_softc *sc = NG_NODE_PRIVATE(node);
|
2001-01-06 00:46:47 +00:00
|
|
|
int ret;
|
2000-10-09 13:18:17 +00:00
|
|
|
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_NODE_UNREF(node);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
sc->packets_in = 0; /* reset stats */
|
|
|
|
sc->packets_out = 0;
|
|
|
|
|
2001-01-06 00:46:47 +00:00
|
|
|
if ((ret = ng_make_node_common(&typestruct, &sc->node)))
|
|
|
|
{
|
|
|
|
printf("ing: ng_make_node_common, ret = %d\n!", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* name the netgraph node */
|
|
|
|
sprintf(sc->nodename, "%s%d", NG_ING_NODE_TYPE, sc->sc_unit);
|
|
|
|
if((ret = ng_name_node(sc->node, sc->nodename)))
|
|
|
|
{
|
|
|
|
printf("ing: ng_name node, ret = %d\n!", ret);
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_NODE_UNREF(sc->node);
|
2001-01-06 00:46:47 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_NODE_SET_PRIVATE(sc->node, sc);
|
2000-10-09 13:18:17 +00:00
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
|
|
* This is called once we've already connected a new hook to the other node.
|
|
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
static int
|
|
|
|
ng_ing_connect(hook_p hook)
|
|
|
|
{
|
2000-12-12 18:52:14 +00:00
|
|
|
/* probably not at splnet, force outward queueing */
|
2001-01-08 05:34:06 +00:00
|
|
|
NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
|
2000-10-09 13:18:17 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dook disconnection
|
|
|
|
*
|
|
|
|
* For this type, removal of the last link destroys the node
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
ng_ing_disconnect(hook_p hook)
|
|
|
|
{
|
2001-01-08 05:34:06 +00:00
|
|
|
struct ing_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
|
2000-10-09 13:18:17 +00:00
|
|
|
int s;
|
|
|
|
|
2001-01-08 05:34:06 +00:00
|
|
|
if(NG_HOOK_PRIVATE(hook))
|
2000-10-09 13:18:17 +00:00
|
|
|
{
|
|
|
|
s = splimp();
|
|
|
|
splx(s);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc->debughook = NULL;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*/
|
|
|
|
|
|
|
|
#endif /* NI4BING > 0 */
|