/* * Copyright (c) 1999, 2002 Hellmuth Michaelis. All rights reserved. * * 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$ * * last edit-date: [Sat Mar 9 14:09:53 2002] * *---------------------------------------------------------------------------*/ #include "i4bing.h" #if NI4BING > 0 #include "opt_i4b.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; static ng_shutdown_t ng_ing_shutdown; 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 = { NG_ABI_VERSION, NG_ING_NODE_TYPE, NULL, ng_ing_constructor, ng_ing_rcvmsg, ng_ing_shutdown, ng_ing_newhook, NULL, ng_ing_connect, ng_ing_rcvdata, ng_ing_disconnect, ng_ing_cmdlist }; NETGRAPH_INIT_ORDERED(ing, &typestruct, SI_SUB_DRIVERS, SI_ORDER_ANY); /*===========================================================================* * 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; if(!mtx_initialized(&sc->sc_fastq.ifq_mtx)) mtx_init(&sc->sc_fastq.ifq_mtx, "i4b_ing_fastq", NULL, MTX_DEF); #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); if((ret = ng_name_node(sc->node, sc->nodename))) { printf("ing: ng_name node, ret = %d\n!", ret); NG_NODE_UNREF(sc->node); break; } NG_NODE_SET_PRIVATE(sc->node, sc); sc->xmitq.ifq_maxlen = IFQ_MAXLEN; sc->xmitq_hipri.ifq_maxlen = IFQ_MAXLEN; if(!mtx_initialized(&sc->xmitq.ifq_mtx)) mtx_init(&sc->xmitq.ifq_mtx, "i4b_ing_xmitq", NULL, MTX_DEF); if(!mtx_initialized(&sc->xmitq_hipri.ifq_mtx)) mtx_init(&sc->xmitq_hipri.ifq_mtx, "i4b_ing_hipri", NULL, MTX_DEF); } } #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; x = splimp(); IF_DRAIN(iq); splx(x); } #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; int error; 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++; NG_SEND_DATA_ONLY(error, sc->hook, m); } /*---------------------------------------------------------------------------* * 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; if(! IF_HANDOFF(isdn_linktab[unit]->tx_queue, m, NULL)) { 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 ng_ing_constructor(node_p node) { 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) { struct ing_softc *sc = NG_NODE_PRIVATE(node); /* * check if it's our friend the debug hook */ if(strcmp(name, NG_ING_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE(hook, NULL); /* paranoid */ sc->debughook = hook; return (0); } /* * Check for raw mode hook. */ if(strcmp(name, NG_ING_HOOK_RAW) == 0) { NG_HOOK_SET_PRIVATE(hook, sc); 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 ng_ing_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ing_softc *sc = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); 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 */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /*---------------------------------------------------------------------------* * get data from another node and transmit it out on a B-channel *---------------------------------------------------------------------------*/ static int ng_ing_rcvdata(hook_p hook, item_p item) { struct ing_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ifqueue *xmitq_p; int s; struct mbuf *m; meta_p meta; NGI_GET_M(item, m); NGI_GET_META(item, meta); NG_FREE_ITEM(item); if(NG_HOOK_PRIVATE(hook) == NULL) { NG_FREE_M(m); NG_FREE_META(meta); 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(); IF_LOCK(xmitq_p); if (_IF_QFULL(xmitq_p)) { _IF_DROP(xmitq_p); IF_UNLOCK(xmitq_p); splx(s); NG_FREE_M(m); NG_FREE_META(meta); return(ENOBUFS); } _IF_ENQUEUE(xmitq_p, m); IF_UNLOCK(xmitq_p); 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 ng_ing_shutdown(node_p node) { struct ing_softc *sc = NG_NODE_PRIVATE(node); int ret; NG_NODE_UNREF(node); sc->packets_in = 0; /* reset stats */ sc->packets_out = 0; 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); NG_NODE_UNREF(sc->node); return (0); } NG_NODE_SET_PRIVATE(sc->node, sc); 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) { /* probably not at splnet, force outward queueing */ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); return (0); } /* * Dook disconnection * * For this type, removal of the last link destroys the node */ static int ng_ing_disconnect(hook_p hook) { struct ing_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int s; if(NG_HOOK_PRIVATE(hook)) { s = splimp(); splx(s); } else { sc->debughook = NULL; } return (0); } /*===========================================================================*/ #endif /* NI4BING > 0 */