Index: head/sys/dev/ath/ath_rate/amrr/amrr.c =================================================================== --- head/sys/dev/ath/ath_rate/amrr/amrr.c (revision 171612) +++ head/sys/dev/ath/ath_rate/amrr/amrr.c (revision 171613) @@ -1,546 +1,546 @@ /*- * Copyright (c) 2004 INRIA * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * 3. Neither the names of the above-listed copyright holders nor the names * of any contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. * */ #include __FBSDID("$FreeBSD$"); /* * AMRR rate control. See: * http://www-sop.inria.fr/rapports/sophia/RR-5208.html * "IEEE 802.11 Rate Adaptation: A Practical Approach" by * Mathieu Lacage, Hossein Manshaei, Thierry Turletti */ #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for ether_sprintf */ #include #include #ifdef INET #include #include #endif #include #include #include #define AMRR_DEBUG #ifdef AMRR_DEBUG #define DPRINTF(sc, _fmt, ...) do { \ if (sc->sc_debug & 0x10) \ printf(_fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, _fmt, ...) #endif static int ath_rateinterval = 1000; /* rate ctl interval (ms) */ static int ath_rate_max_success_threshold = 10; static int ath_rate_min_success_threshold = 1; static void ath_ratectl(void *); static void ath_rate_update(struct ath_softc *, struct ieee80211_node *, int rate); static void ath_rate_ctl_start(struct ath_softc *, struct ieee80211_node *); static void ath_rate_ctl(void *, struct ieee80211_node *); void ath_rate_node_init(struct ath_softc *sc, struct ath_node *an) { /* NB: assumed to be zero'd by caller */ ath_rate_update(sc, &an->an_node, 0); } void ath_rate_node_cleanup(struct ath_softc *sc, struct ath_node *an) { } void ath_rate_findrate(struct ath_softc *sc, struct ath_node *an, int shortPreamble, size_t frameLen, u_int8_t *rix, int *try0, u_int8_t *txrate) { struct amrr_node *amn = ATH_NODE_AMRR(an); *rix = amn->amn_tx_rix0; *try0 = amn->amn_tx_try0; if (shortPreamble) *txrate = amn->amn_tx_rate0sp; else *txrate = amn->amn_tx_rate0; } void ath_rate_setupxtxdesc(struct ath_softc *sc, struct ath_node *an, struct ath_desc *ds, int shortPreamble, u_int8_t rix) { struct amrr_node *amn = ATH_NODE_AMRR(an); ath_hal_setupxtxdesc(sc->sc_ah, ds , amn->amn_tx_rate1sp, amn->amn_tx_try1 /* series 1 */ , amn->amn_tx_rate2sp, amn->amn_tx_try2 /* series 2 */ , amn->amn_tx_rate3sp, amn->amn_tx_try3 /* series 3 */ ); } void ath_rate_tx_complete(struct ath_softc *sc, struct ath_node *an, const struct ath_buf *bf) { struct amrr_node *amn = ATH_NODE_AMRR(an); const struct ath_tx_status *ts = &bf->bf_status.ds_txstat; int sr = ts->ts_shortretry; int lr = ts->ts_longretry; int retry_count = sr + lr; amn->amn_tx_try0_cnt++; if (retry_count == 1) { amn->amn_tx_try1_cnt++; } else if (retry_count == 2) { amn->amn_tx_try1_cnt++; amn->amn_tx_try2_cnt++; } else if (retry_count == 3) { amn->amn_tx_try1_cnt++; amn->amn_tx_try2_cnt++; amn->amn_tx_try3_cnt++; } else if (retry_count > 3) { amn->amn_tx_try1_cnt++; amn->amn_tx_try2_cnt++; amn->amn_tx_try3_cnt++; amn->amn_tx_failure_cnt++; } } void ath_rate_newassoc(struct ath_softc *sc, struct ath_node *an, int isnew) { if (isnew) ath_rate_ctl_start(sc, &an->an_node); } static void node_reset (struct amrr_node *amn) { amn->amn_tx_try0_cnt = 0; amn->amn_tx_try1_cnt = 0; amn->amn_tx_try2_cnt = 0; amn->amn_tx_try3_cnt = 0; amn->amn_tx_failure_cnt = 0; amn->amn_success = 0; amn->amn_recovery = 0; amn->amn_success_threshold = ath_rate_min_success_threshold; } /** * The code below assumes that we are dealing with hardware multi rate retry * I have no idea what will happen if you try to use this module with another * type of hardware. Your machine might catch fire or it might work with * horrible performance... */ static void ath_rate_update(struct ath_softc *sc, struct ieee80211_node *ni, int rate) { struct ath_node *an = ATH_NODE(ni); struct amrr_node *amn = ATH_NODE_AMRR(an); const HAL_RATE_TABLE *rt = sc->sc_currates; u_int8_t rix; KASSERT(rt != NULL, ("no rate table, mode %u", sc->sc_curmode)); DPRINTF(sc, "%s: set xmit rate for %s to %dM\n", __func__, ether_sprintf(ni->ni_macaddr), ni->ni_rates.rs_nrates > 0 ? (ni->ni_rates.rs_rates[rate] & IEEE80211_RATE_VAL) / 2 : 0); ni->ni_txrate = rate; /* * Before associating a node has no rate set setup * so we can't calculate any transmit codes to use. * This is ok since we should never be sending anything * but management frames and those always go at the * lowest hardware rate. */ if (ni->ni_rates.rs_nrates > 0) { amn->amn_tx_rix0 = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate] & IEEE80211_RATE_VAL]; amn->amn_tx_rate0 = rt->info[amn->amn_tx_rix0].rateCode; amn->amn_tx_rate0sp = amn->amn_tx_rate0 | rt->info[amn->amn_tx_rix0].shortPreamble; if (sc->sc_mrretry) { amn->amn_tx_try0 = 1; amn->amn_tx_try1 = 1; amn->amn_tx_try2 = 1; amn->amn_tx_try3 = 1; if (--rate >= 0) { rix = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate]&IEEE80211_RATE_VAL]; amn->amn_tx_rate1 = rt->info[rix].rateCode; amn->amn_tx_rate1sp = amn->amn_tx_rate1 | rt->info[rix].shortPreamble; } else { amn->amn_tx_rate1 = amn->amn_tx_rate1sp = 0; } if (--rate >= 0) { rix = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate]&IEEE80211_RATE_VAL]; amn->amn_tx_rate2 = rt->info[rix].rateCode; amn->amn_tx_rate2sp = amn->amn_tx_rate2 | rt->info[rix].shortPreamble; } else { amn->amn_tx_rate2 = amn->amn_tx_rate2sp = 0; } if (rate > 0) { /* NB: only do this if we didn't already do it above */ amn->amn_tx_rate3 = rt->info[0].rateCode; amn->amn_tx_rate3sp = amn->amn_tx_rate3 | rt->info[0].shortPreamble; } else { amn->amn_tx_rate3 = amn->amn_tx_rate3sp = 0; } } else { amn->amn_tx_try0 = ATH_TXMAXTRY; /* theorically, these statements are useless because * the code which uses them tests for an_tx_try0 == ATH_TXMAXTRY */ amn->amn_tx_try1 = 0; amn->amn_tx_try2 = 0; amn->amn_tx_try3 = 0; amn->amn_tx_rate1 = amn->amn_tx_rate1sp = 0; amn->amn_tx_rate2 = amn->amn_tx_rate2sp = 0; amn->amn_tx_rate3 = amn->amn_tx_rate3sp = 0; } } node_reset (amn); } /* * Set the starting transmit rate for a node. */ static void ath_rate_ctl_start(struct ath_softc *sc, struct ieee80211_node *ni) { #define RATE(_ix) (ni->ni_rates.rs_rates[(_ix)] & IEEE80211_RATE_VAL) struct ieee80211com *ic = &sc->sc_ic; int srate; KASSERT(ni->ni_rates.rs_nrates > 0, ("no rates")); if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) { /* * No fixed rate is requested. For 11b start with * the highest negotiated rate; otherwise, for 11g * and 11a, we start "in the middle" at 24Mb or 36Mb. */ srate = ni->ni_rates.rs_nrates - 1; if (sc->sc_curmode != IEEE80211_MODE_11B) { /* * Scan the negotiated rate set to find the * closest rate. */ /* NB: the rate set is assumed sorted */ for (; srate >= 0 && RATE(srate) > 72; srate--) ; } } else { /* * A fixed rate is to be used; ic_fixed_rate is the * IEEE code for this rate (sans basic bit). Convert this * to the index into the negotiated rate set for * the node. We know the rate is there because the * rate set is checked when the station associates. */ /* NB: the rate set is assumed sorted */ srate = ni->ni_rates.rs_nrates - 1; for (; srate >= 0 && RATE(srate) != ic->ic_fixed_rate; srate--) ; } /* * The selected rate may not be available due to races * and mode settings. Also orphaned nodes created in * adhoc mode may not have any rate set so this lookup * can fail. This is not fatal. */ ath_rate_update(sc, ni, srate < 0 ? 0 : srate); #undef RATE } static void ath_rate_cb(void *arg, struct ieee80211_node *ni) { struct ath_softc *sc = arg; ath_rate_update(sc, ni, 0); } /* * Reset the rate control state for each 802.11 state transition. */ void ath_rate_newstate(struct ath_softc *sc, enum ieee80211_state state) { struct amrr_softc *asc = (struct amrr_softc *) sc->sc_rc; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; if (state == IEEE80211_S_INIT) { callout_stop(&asc->timer); return; } if (ic->ic_opmode == IEEE80211_M_STA) { /* * Reset local xmit state; this is really only * meaningful when operating in station mode. */ ni = ic->ic_bss; if (state == IEEE80211_S_RUN) { ath_rate_ctl_start(sc, ni); } else { ath_rate_update(sc, ni, 0); } } else { /* * When operating as a station the node table holds * the AP's that were discovered during scanning. * For any other operating mode we want to reset the * tx rate state of each node. */ ieee80211_iterate_nodes(&ic->ic_sta, ath_rate_cb, sc); ath_rate_update(sc, ic->ic_bss, 0); } if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE && state == IEEE80211_S_RUN) { int interval; /* * Start the background rate control thread if we * are not configured to use a fixed xmit rate. */ interval = ath_rateinterval; if (ic->ic_opmode == IEEE80211_M_STA) interval /= 2; callout_reset(&asc->timer, (interval * hz) / 1000, ath_ratectl, sc->sc_ifp); } } /* * Examine and potentially adjust the transmit rate. */ static void ath_rate_ctl(void *arg, struct ieee80211_node *ni) { struct ath_softc *sc = arg; struct amrr_node *amn = ATH_NODE_AMRR(ATH_NODE (ni)); int old_rate; #define is_success(amn) \ (amn->amn_tx_try1_cnt < (amn->amn_tx_try0_cnt/10)) #define is_enough(amn) \ (amn->amn_tx_try0_cnt > 10) #define is_failure(amn) \ (amn->amn_tx_try1_cnt > (amn->amn_tx_try0_cnt/3)) #define is_max_rate(ni) \ ((ni->ni_txrate + 1) >= ni->ni_rates.rs_nrates) #define is_min_rate(ni) \ (ni->ni_txrate == 0) old_rate = ni->ni_txrate; DPRINTF (sc, "cnt0: %d cnt1: %d cnt2: %d cnt3: %d -- threshold: %d\n", amn->amn_tx_try0_cnt, amn->amn_tx_try1_cnt, amn->amn_tx_try2_cnt, amn->amn_tx_try3_cnt, amn->amn_success_threshold); if (is_success (amn) && is_enough (amn)) { amn->amn_success++; if (amn->amn_success == amn->amn_success_threshold && !is_max_rate (ni)) { amn->amn_recovery = 1; amn->amn_success = 0; ni->ni_txrate++; DPRINTF (sc, "increase rate to %d\n", ni->ni_txrate); } else { amn->amn_recovery = 0; } } else if (is_failure (amn)) { amn->amn_success = 0; if (!is_min_rate (ni)) { if (amn->amn_recovery) { /* recovery failure. */ amn->amn_success_threshold *= 2; amn->amn_success_threshold = min (amn->amn_success_threshold, (u_int)ath_rate_max_success_threshold); DPRINTF (sc, "decrease rate recovery thr: %d\n", amn->amn_success_threshold); } else { /* simple failure. */ amn->amn_success_threshold = ath_rate_min_success_threshold; DPRINTF (sc, "decrease rate normal thr: %d\n", amn->amn_success_threshold); } amn->amn_recovery = 0; ni->ni_txrate--; } else { amn->amn_recovery = 0; } } if (is_enough (amn) || old_rate != ni->ni_txrate) { /* reset counters. */ amn->amn_tx_try0_cnt = 0; amn->amn_tx_try1_cnt = 0; amn->amn_tx_try2_cnt = 0; amn->amn_tx_try3_cnt = 0; amn->amn_tx_failure_cnt = 0; } if (old_rate != ni->ni_txrate) { ath_rate_update(sc, ni, ni->ni_txrate); } } static void ath_ratectl(void *arg) { struct ifnet *ifp = arg; struct ath_softc *sc = ifp->if_softc; struct amrr_softc *asc = (struct amrr_softc *) sc->sc_rc; struct ieee80211com *ic = &sc->sc_ic; int interval; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { sc->sc_stats.ast_rate_calls++; if (ic->ic_opmode == IEEE80211_M_STA) ath_rate_ctl(sc, ic->ic_bss); /* NB: no reference */ else ieee80211_iterate_nodes(&ic->ic_sta, ath_rate_ctl, sc); } interval = ath_rateinterval; if (ic->ic_opmode == IEEE80211_M_STA) interval /= 2; callout_reset(&asc->timer, (interval * hz) / 1000, ath_ratectl, sc->sc_ifp); } static void ath_rate_sysctlattach(struct ath_softc *sc) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "rate_interval", CTLFLAG_RW, &ath_rateinterval, 0, "rate control: operation interval (ms)"); /* XXX bounds check values */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "max_sucess_threshold", CTLFLAG_RW, &ath_rate_max_success_threshold, 0, ""); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "min_sucess_threshold", CTLFLAG_RW, &ath_rate_min_success_threshold, 0, ""); } struct ath_ratectrl * ath_rate_attach(struct ath_softc *sc) { struct amrr_softc *asc; asc = malloc(sizeof(struct amrr_softc), M_DEVBUF, M_NOWAIT|M_ZERO); if (asc == NULL) return NULL; asc->arc.arc_space = sizeof(struct amrr_node); - callout_init(&asc->timer, debug_mpsafenet ? CALLOUT_MPSAFE : 0); + callout_init(&asc->timer, CALLOUT_MPSAFE); ath_rate_sysctlattach(sc); return &asc->arc; } void ath_rate_detach(struct ath_ratectrl *arc) { struct amrr_softc *asc = (struct amrr_softc *) arc; callout_drain(&asc->timer); free(asc, M_DEVBUF); } /* * Module glue. */ static int amrr_modevent(module_t mod, int type, void *unused) { switch (type) { case MOD_LOAD: if (bootverbose) printf("ath_rate: version 0.1\n"); return 0; case MOD_UNLOAD: return 0; } return EINVAL; } static moduledata_t amrr_mod = { "ath_rate", amrr_modevent, 0 }; DECLARE_MODULE(ath_rate, amrr_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); MODULE_VERSION(ath_rate, 1); MODULE_DEPEND(ath_rate, wlan, 1, 1, 1); Index: head/sys/dev/ath/ath_rate/onoe/onoe.c =================================================================== --- head/sys/dev/ath/ath_rate/onoe/onoe.c (revision 171612) +++ head/sys/dev/ath/ath_rate/onoe/onoe.c (revision 171613) @@ -1,520 +1,520 @@ /*- * Copyright (c) 2002-2007 Sam Leffler, Errno Consulting * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * Atsushi Onoe's rate control algorithm. */ #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for ether_sprintf */ #include #include #ifdef INET #include #include #endif #include #include #include #define ONOE_DEBUG #ifdef ONOE_DEBUG enum { ATH_DEBUG_RATE = 0x00000010, /* rate control */ }; #define DPRINTF(sc, _fmt, ...) do { \ if (sc->sc_debug & ATH_DEBUG_RATE) \ printf(_fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, _fmt, ...) #endif /* * Default parameters for the rate control algorithm. These are * all tunable with sysctls. The rate controller runs periodically * (each ath_rateinterval ms) analyzing transmit statistics for each * neighbor/station (when operating in station mode this is only the AP). * If transmits look to be working well over a sampling period then * it gives a "raise rate credit". If transmits look to not be working * well than it deducts a credit. If the credits cross a threshold then * the transmit rate is raised. Various error conditions force the * the transmit rate to be dropped. * * The decision to issue/deduct a credit is based on the errors and * retries accumulated over the sampling period. ath_rate_raise defines * the percent of retransmits for which a credit is issued/deducted. * ath_rate_raise_threshold defines the threshold on credits at which * the transmit rate is increased. * * XXX this algorithm is flawed. */ static int ath_rateinterval = 1000; /* rate ctl interval (ms) */ static int ath_rate_raise = 10; /* add credit threshold */ static int ath_rate_raise_threshold = 10; /* rate ctl raise threshold */ static void ath_ratectl(void *); static void ath_rate_update(struct ath_softc *, struct ieee80211_node *, int rate); static void ath_rate_ctl_start(struct ath_softc *, struct ieee80211_node *); static void ath_rate_ctl(void *, struct ieee80211_node *); void ath_rate_node_init(struct ath_softc *sc, struct ath_node *an) { /* NB: assumed to be zero'd by caller */ ath_rate_update(sc, &an->an_node, 0); } void ath_rate_node_cleanup(struct ath_softc *sc, struct ath_node *an) { } void ath_rate_findrate(struct ath_softc *sc, struct ath_node *an, int shortPreamble, size_t frameLen, u_int8_t *rix, int *try0, u_int8_t *txrate) { struct onoe_node *on = ATH_NODE_ONOE(an); *rix = on->on_tx_rix0; *try0 = on->on_tx_try0; if (shortPreamble) *txrate = on->on_tx_rate0sp; else *txrate = on->on_tx_rate0; } void ath_rate_setupxtxdesc(struct ath_softc *sc, struct ath_node *an, struct ath_desc *ds, int shortPreamble, u_int8_t rix) { struct onoe_node *on = ATH_NODE_ONOE(an); ath_hal_setupxtxdesc(sc->sc_ah, ds , on->on_tx_rate1sp, 2 /* series 1 */ , on->on_tx_rate2sp, 2 /* series 2 */ , on->on_tx_rate3sp, 2 /* series 3 */ ); } void ath_rate_tx_complete(struct ath_softc *sc, struct ath_node *an, const struct ath_buf *bf) { struct onoe_node *on = ATH_NODE_ONOE(an); const struct ath_tx_status *ts = &bf->bf_status.ds_txstat; if (ts->ts_status == 0) on->on_tx_ok++; else on->on_tx_err++; on->on_tx_retr += ts->ts_shortretry + ts->ts_longretry; } void ath_rate_newassoc(struct ath_softc *sc, struct ath_node *an, int isnew) { if (isnew) ath_rate_ctl_start(sc, &an->an_node); } static void ath_rate_update(struct ath_softc *sc, struct ieee80211_node *ni, int rate) { struct ath_node *an = ATH_NODE(ni); struct onoe_node *on = ATH_NODE_ONOE(an); const HAL_RATE_TABLE *rt = sc->sc_currates; u_int8_t rix; KASSERT(rt != NULL, ("no rate table, mode %u", sc->sc_curmode)); DPRINTF(sc, "%s: set xmit rate for %s to %dM\n", __func__, ether_sprintf(ni->ni_macaddr), ni->ni_rates.rs_nrates > 0 ? (ni->ni_rates.rs_rates[rate] & IEEE80211_RATE_VAL) / 2 : 0); ni->ni_txrate = rate; /* * Before associating a node has no rate set setup * so we can't calculate any transmit codes to use. * This is ok since we should never be sending anything * but management frames and those always go at the * lowest hardware rate. */ if (ni->ni_rates.rs_nrates == 0) goto done; on->on_tx_rix0 = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate] & IEEE80211_RATE_VAL]; on->on_tx_rate0 = rt->info[on->on_tx_rix0].rateCode; on->on_tx_rate0sp = on->on_tx_rate0 | rt->info[on->on_tx_rix0].shortPreamble; if (sc->sc_mrretry) { /* * Hardware supports multi-rate retry; setup two * step-down retry rates and make the lowest rate * be the ``last chance''. We use 4, 2, 2, 2 tries * respectively (4 is set here, the rest are fixed * in the xmit routine). */ on->on_tx_try0 = 1 + 3; /* 4 tries at rate 0 */ if (--rate >= 0) { rix = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate]&IEEE80211_RATE_VAL]; on->on_tx_rate1 = rt->info[rix].rateCode; on->on_tx_rate1sp = on->on_tx_rate1 | rt->info[rix].shortPreamble; } else { on->on_tx_rate1 = on->on_tx_rate1sp = 0; } if (--rate >= 0) { rix = sc->sc_rixmap[ ni->ni_rates.rs_rates[rate]&IEEE80211_RATE_VAL]; on->on_tx_rate2 = rt->info[rix].rateCode; on->on_tx_rate2sp = on->on_tx_rate2 | rt->info[rix].shortPreamble; } else { on->on_tx_rate2 = on->on_tx_rate2sp = 0; } if (rate > 0) { /* NB: only do this if we didn't already do it above */ on->on_tx_rate3 = rt->info[0].rateCode; on->on_tx_rate3sp = on->on_tx_rate3 | rt->info[0].shortPreamble; } else { on->on_tx_rate3 = on->on_tx_rate3sp = 0; } } else { on->on_tx_try0 = ATH_TXMAXTRY; /* max tries at rate 0 */ on->on_tx_rate1 = on->on_tx_rate1sp = 0; on->on_tx_rate2 = on->on_tx_rate2sp = 0; on->on_tx_rate3 = on->on_tx_rate3sp = 0; } done: on->on_tx_ok = on->on_tx_err = on->on_tx_retr = on->on_tx_upper = 0; } /* * Set the starting transmit rate for a node. */ static void ath_rate_ctl_start(struct ath_softc *sc, struct ieee80211_node *ni) { #define RATE(_ix) (ni->ni_rates.rs_rates[(_ix)] & IEEE80211_RATE_VAL) struct ieee80211com *ic = &sc->sc_ic; int srate; KASSERT(ni->ni_rates.rs_nrates > 0, ("no rates")); if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) { /* * No fixed rate is requested. For 11b start with * the highest negotiated rate; otherwise, for 11g * and 11a, we start "in the middle" at 24Mb or 36Mb. */ srate = ni->ni_rates.rs_nrates - 1; if (sc->sc_curmode != IEEE80211_MODE_11B) { /* * Scan the negotiated rate set to find the * closest rate. */ /* NB: the rate set is assumed sorted */ for (; srate >= 0 && RATE(srate) > 72; srate--) ; } } else { /* * A fixed rate is to be used; ic_fixed_rate is the * IEEE code for this rate (sans basic bit). Convert this * to the index into the negotiated rate set for * the node. We know the rate is there because the * rate set is checked when the station associates. */ /* NB: the rate set is assumed sorted */ srate = ni->ni_rates.rs_nrates - 1; for (; srate >= 0 && RATE(srate) != ic->ic_fixed_rate; srate--) ; } /* * The selected rate may not be available due to races * and mode settings. Also orphaned nodes created in * adhoc mode may not have any rate set so this lookup * can fail. This is not fatal. */ ath_rate_update(sc, ni, srate < 0 ? 0 : srate); #undef RATE } static void ath_rate_cb(void *arg, struct ieee80211_node *ni) { struct ath_softc *sc = arg; ath_rate_update(sc, ni, 0); } /* * Reset the rate control state for each 802.11 state transition. */ void ath_rate_newstate(struct ath_softc *sc, enum ieee80211_state state) { struct onoe_softc *osc = (struct onoe_softc *) sc->sc_rc; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; if (state == IEEE80211_S_INIT) { callout_stop(&osc->timer); return; } if (ic->ic_opmode == IEEE80211_M_STA) { /* * Reset local xmit state; this is really only * meaningful when operating in station mode. */ ni = ic->ic_bss; if (state == IEEE80211_S_RUN) { ath_rate_ctl_start(sc, ni); } else { ath_rate_update(sc, ni, 0); } } else { /* * When operating as a station the node table holds * the AP's that were discovered during scanning. * For any other operating mode we want to reset the * tx rate state of each node. */ ieee80211_iterate_nodes(&ic->ic_sta, ath_rate_cb, sc); ath_rate_update(sc, ic->ic_bss, 0); } if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE && state == IEEE80211_S_RUN) { int interval; /* * Start the background rate control thread if we * are not configured to use a fixed xmit rate. */ interval = ath_rateinterval; if (ic->ic_opmode == IEEE80211_M_STA) interval /= 2; callout_reset(&osc->timer, (interval * hz) / 1000, ath_ratectl, sc->sc_ifp); } } /* * Examine and potentially adjust the transmit rate. */ static void ath_rate_ctl(void *arg, struct ieee80211_node *ni) { struct ath_softc *sc = arg; struct onoe_node *on = ATH_NODE_ONOE(ATH_NODE(ni)); struct ieee80211_rateset *rs = &ni->ni_rates; int dir = 0, nrate, enough; /* * Rate control * XXX: very primitive version. */ enough = (on->on_tx_ok + on->on_tx_err >= 10); /* no packet reached -> down */ if (on->on_tx_err > 0 && on->on_tx_ok == 0) dir = -1; /* all packets needs retry in average -> down */ if (enough && on->on_tx_ok < on->on_tx_retr) dir = -1; /* no error and less than rate_raise% of packets need retry -> up */ if (enough && on->on_tx_err == 0 && on->on_tx_retr < (on->on_tx_ok * ath_rate_raise) / 100) dir = 1; DPRINTF(sc, "%s: ok %d err %d retr %d upper %d dir %d\n", ether_sprintf(ni->ni_macaddr), on->on_tx_ok, on->on_tx_err, on->on_tx_retr, on->on_tx_upper, dir); nrate = ni->ni_txrate; switch (dir) { case 0: if (enough && on->on_tx_upper > 0) on->on_tx_upper--; break; case -1: if (nrate > 0) { nrate--; sc->sc_stats.ast_rate_drop++; } on->on_tx_upper = 0; break; case 1: /* raise rate if we hit rate_raise_threshold */ if (++on->on_tx_upper < ath_rate_raise_threshold) break; on->on_tx_upper = 0; if (nrate + 1 < rs->rs_nrates) { nrate++; sc->sc_stats.ast_rate_raise++; } break; } if (nrate != ni->ni_txrate) { DPRINTF(sc, "%s: %dM -> %dM (%d ok, %d err, %d retr)\n", __func__, (rs->rs_rates[ni->ni_txrate] & IEEE80211_RATE_VAL) / 2, (rs->rs_rates[nrate] & IEEE80211_RATE_VAL) / 2, on->on_tx_ok, on->on_tx_err, on->on_tx_retr); ath_rate_update(sc, ni, nrate); } else if (enough) on->on_tx_ok = on->on_tx_err = on->on_tx_retr = 0; } static void ath_ratectl(void *arg) { struct ifnet *ifp = arg; struct ath_softc *sc = ifp->if_softc; struct onoe_softc *osc = (struct onoe_softc *) sc->sc_rc; struct ieee80211com *ic = &sc->sc_ic; int interval; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { sc->sc_stats.ast_rate_calls++; if (ic->ic_opmode == IEEE80211_M_STA) ath_rate_ctl(sc, ic->ic_bss); /* NB: no reference */ else ieee80211_iterate_nodes(&ic->ic_sta, ath_rate_ctl, sc); } interval = ath_rateinterval; if (ic->ic_opmode == IEEE80211_M_STA) interval /= 2; callout_reset(&osc->timer, (interval * hz) / 1000, ath_ratectl, sc->sc_ifp); } static void ath_rate_sysctlattach(struct ath_softc *sc) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "rate_interval", CTLFLAG_RW, &ath_rateinterval, 0, "rate control: operation interval (ms)"); /* XXX bounds check values */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "rate_raise", CTLFLAG_RW, &ath_rate_raise, 0, "rate control: retry threshold to credit rate raise (%%)"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "rate_raise_threshold", CTLFLAG_RW, &ath_rate_raise_threshold,0, "rate control: # good periods before raising rate"); } struct ath_ratectrl * ath_rate_attach(struct ath_softc *sc) { struct onoe_softc *osc; osc = malloc(sizeof(struct onoe_softc), M_DEVBUF, M_NOWAIT|M_ZERO); if (osc == NULL) return NULL; osc->arc.arc_space = sizeof(struct onoe_node); - callout_init(&osc->timer, debug_mpsafenet ? CALLOUT_MPSAFE : 0); + callout_init(&osc->timer, CALLOUT_MPSAFE); ath_rate_sysctlattach(sc); return &osc->arc; } void ath_rate_detach(struct ath_ratectrl *arc) { struct onoe_softc *osc = (struct onoe_softc *) arc; callout_drain(&osc->timer); free(osc, M_DEVBUF); } /* * Module glue. */ static int onoe_modevent(module_t mod, int type, void *unused) { switch (type) { case MOD_LOAD: if (bootverbose) printf("ath_rate: \n"); return 0; case MOD_UNLOAD: return 0; } return EINVAL; } static moduledata_t onoe_mod = { "ath_rate", onoe_modevent, 0 }; DECLARE_MODULE(ath_rate, onoe_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); MODULE_VERSION(ath_rate, 1); MODULE_DEPEND(ath_rate, wlan, 1, 1, 1); Index: head/sys/dev/ce/if_ce.c =================================================================== --- head/sys/dev/ce/if_ce.c (revision 171612) +++ head/sys/dev/ce/if_ce.c (revision 171613) @@ -1,2725 +1,2718 @@ /* * Cronyx-Tau32-PCI adapter driver for FreeBSD. * * Copyright (C) 2003-2005 Cronyx Engineering. * Copyright (C) 2003-2005 Kurakin Roman, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations a permission to use, * modify and redistribute this software in source and binary forms, * as long as this message is kept with the software, all derivative * works or modified versions. * * $Cronyx: if_ce.c,v 1.9.2.8 2005/11/21 14:17:44 rik Exp $ */ #include __FBSDID("$FreeBSD$"); #include #if __FreeBSD_version >= 500000 # define NPCI 1 #else # include "pci.h" #endif #if NPCI > 0 #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version >= 504000 #include #endif #include #include #include #include #include #if __FreeBSD_version > 501000 # include # include #else # include # include #endif #include #include #include "opt_ng_cronyx.h" #ifdef NETGRAPH_CRONYX # include "opt_netgraph.h" # ifndef NETGRAPH # error #option NETGRAPH missed from configuration # endif # include # include # include #else # include # include # define PP_CISCO IFF_LINK2 # include #endif #include #include #include #include #include /* If we don't have Cronyx's sppp version, we don't have fr support via sppp */ #ifndef PP_FR #define PP_FR 0 #endif #ifndef IFP2SP #define IFP2SP(ifp) ((struct sppp*)ifp) #endif #ifndef SP2IFP #define SP2IFP(sp) ((struct ifnet*)sp) #endif #ifndef PCIR_BAR #define PCIR_BAR(x) (PCIR_MAPS + (x) * 4) #endif /* define as our previous return value */ #ifndef BUS_PROBE_DEFAULT #define BUS_PROBE_DEFAULT 0 #endif #define CE_DEBUG(d,s) ({if (d->chan->debug) {\ printf ("%s: ", d->name); printf s;}}) #define CE_DEBUG2(d,s) ({if (d->chan->debug>1) {\ printf ("%s: ", d->name); printf s;}}) #ifndef CALLOUT_MPSAFE #define CALLOUT_MPSAFE 0 #endif #ifndef IF_DRAIN #define IF_DRAIN(ifq) do { \ struct mbuf *m; \ for (;;) { \ IF_DEQUEUE(ifq, m); \ if (m == NULL) \ break; \ m_freem(m); \ } \ } while (0) #endif #ifndef _IF_QLEN #define _IF_QLEN(ifq) ((ifq)->ifq_len) #endif #ifndef callout_drain #define callout_drain callout_stop #endif #if __FreeBSD_version >= 504000 #define CE_LOCK_NAME "ceX" static int ce_mpsafenet = 1; TUNABLE_INT("debug.ce.mpsafenet", &ce_mpsafenet); SYSCTL_NODE(_debug, OID_AUTO, ce, CTLFLAG_RD, 0, "Cronyx Tau32-PCI Adapters"); SYSCTL_INT(_debug_ce, OID_AUTO, mpsafenet, CTLFLAG_RD, &ce_mpsafenet, 0, "Enable/disable MPSAFE network support for Cronyx Tau32-PCI Adapters"); #define CE_LOCK(_bd) do { \ if (ce_mpsafenet) \ mtx_lock (&(_bd)->ce_mtx); \ } while (0) #define CE_UNLOCK(_bd) do { \ if (ce_mpsafenet) \ mtx_unlock (&(_bd)->ce_mtx); \ } while (0) #define CE_LOCK_ASSERT(_bd) do { \ if (ce_mpsafenet) \ mtx_assert (&(_bd)->ce_mtx, MA_OWNED); \ } while (0) #else static int ce_mpsafenet = 0; #define CE_LOCK(_bd) do {} while (0 && (_bd) && ce_mpsafenet) #define CE_UNLOCK(_bd) do {} while (0 && (_bd) && ce_mpsafenet) #define CE_LOCK_ASSERT(_bd) do {} while (0 && (_bd) && ce_mpsafenet) #endif #define CDEV_MAJOR 185 static int ce_probe __P((device_t)); static int ce_attach __P((device_t)); static int ce_detach __P((device_t)); static device_method_t ce_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ce_probe), DEVMETHOD(device_attach, ce_attach), DEVMETHOD(device_detach, ce_detach), {0, 0} }; typedef struct _ce_dma_mem_t { unsigned long phys; void *virt; size_t size; #if __FreeBSD_version >= 500000 bus_dma_tag_t dmat; bus_dmamap_t mapp; #endif } ce_dma_mem_t; typedef struct _drv_t { char name [8]; int running; ce_board_t *board; ce_chan_t *chan; struct ifqueue rqueue; #ifdef NETGRAPH char nodename [NG_NODELEN+1]; hook_p hook; hook_p debug_hook; node_p node; struct ifqueue queue; struct ifqueue hi_queue; short timeout; struct callout timeout_handle; #else struct ifnet *ifp; #endif #if __FreeBSD_version >= 500000 struct cdev *devt; #else /* __FreeBSD_version < 500000 */ dev_t devt; #endif ce_dma_mem_t dmamem; } drv_t; typedef struct _bdrv_t { ce_board_t *board; struct resource *ce_res; struct resource *ce_irq; void *ce_intrhand; ce_dma_mem_t dmamem; drv_t channel [NCHAN]; #if __FreeBSD_version >= 504000 struct mtx ce_mtx; #endif } bdrv_t; static driver_t ce_driver = { "ce", ce_methods, sizeof(bdrv_t), }; static devclass_t ce_devclass; static void ce_receive (ce_chan_t *c, unsigned char *data, int len); static void ce_transmit (ce_chan_t *c, void *attachment, int len); static void ce_error (ce_chan_t *c, int data); static void ce_up (drv_t *d); static void ce_start (drv_t *d); static void ce_down (drv_t *d); static void ce_watchdog (drv_t *d); #ifdef NETGRAPH extern struct ng_type typestruct; #else static void ce_ifstart (struct ifnet *ifp); static void ce_tlf (struct sppp *sp); static void ce_tls (struct sppp *sp); static void ce_ifwatchdog (struct ifnet *ifp); static int ce_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data); static void ce_initialize (void *softc); #endif static ce_board_t *adapter [NBRD]; static drv_t *channel [NBRD*NCHAN]; static struct callout led_timo [NBRD]; static struct callout timeout_handle; static int ce_destroy = 0; #if __FreeBSD_version < 500000 static int ce_open (dev_t dev, int oflags, int devtype, struct proc *p); static int ce_close (dev_t dev, int fflag, int devtype, struct proc *p); static int ce_ioctl (dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p); #else static int ce_open (struct cdev *dev, int oflags, int devtype, struct thread *td); static int ce_close (struct cdev *dev, int fflag, int devtype, struct thread *td); static int ce_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td); #endif #if __FreeBSD_version < 500000 static struct cdevsw ce_cdevsw = { ce_open, ce_close, noread, nowrite, ce_ioctl, nopoll, nommap, nostrategy, "ce", CDEV_MAJOR, nodump, nopsize, D_NAGGED, -1 }; #elif __FreeBSD_version == 500000 static struct cdevsw ce_cdevsw = { ce_open, ce_close, noread, nowrite, ce_ioctl, nopoll, nommap, nostrategy, "ce", CDEV_MAJOR, nodump, nopsize, D_NAGGED, }; #elif __FreeBSD_version <= 501000 static struct cdevsw ce_cdevsw = { .d_open = ce_open, .d_close = ce_close, .d_read = noread, .d_write = nowrite, .d_ioctl = ce_ioctl, .d_poll = nopoll, .d_mmap = nommap, .d_strategy = nostrategy, .d_name = "ce", .d_maj = CDEV_MAJOR, .d_dump = nodump, .d_flags = D_NAGGED, }; #elif __FreeBSD_version < 502103 static struct cdevsw ce_cdevsw = { .d_open = ce_open, .d_close = ce_close, .d_ioctl = ce_ioctl, .d_name = "ce", .d_maj = CDEV_MAJOR, .d_flags = D_NAGGED, }; #elif __FreeBSD_version < 600000 static struct cdevsw ce_cdevsw = { .d_version = D_VERSION, .d_open = ce_open, .d_close = ce_close, .d_ioctl = ce_ioctl, .d_name = "ce", .d_maj = CDEV_MAJOR, .d_flags = D_NEEDGIANT, }; #else /* __FreeBSD_version >= 600000 */ static struct cdevsw ce_cdevsw = { .d_version = D_VERSION, .d_open = ce_open, .d_close = ce_close, .d_ioctl = ce_ioctl, .d_name = "ce", .d_flags = D_NEEDGIANT, }; #endif /* * Print the mbuf chain, for debug purposes only. */ static void printmbuf (struct mbuf *m) { printf ("mbuf:"); for (; m; m=m->m_next) { if (m->m_flags & M_PKTHDR) printf (" HDR %d:", m->m_pkthdr.len); if (m->m_flags & M_EXT) printf (" EXT:"); printf (" %d", m->m_len); } printf ("\n"); } /* * Make an mbuf from data. */ static struct mbuf *makembuf (void *buf, unsigned len) { struct mbuf *m; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return 0; MCLGET (m, M_DONTWAIT); if (! (m->m_flags & M_EXT)) { m_freem (m); return 0; } m->m_pkthdr.len = m->m_len = len; bcopy (buf, mtod (m, caddr_t), len); return m; } static int ce_probe (device_t dev) { if ((pci_get_vendor (dev) == TAU32_PCI_VENDOR_ID) && (pci_get_device (dev) == TAU32_PCI_DEVICE_ID)) { device_set_desc (dev, "Cronyx-Tau32-PCI serial adapter"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void ce_timeout (void *arg) { drv_t *d; int s, i, k; for (i = 0; i < NBRD; ++i) { if (adapter[i] == NULL) continue; for (k = 0; k < NCHAN; ++k) { s = splimp (); if (ce_destroy) { splx (s); return; } d = channel[i * NCHAN + k]; if (!d) { splx (s); continue; } CE_LOCK ((bdrv_t *)d->board->sys); switch (d->chan->type) { case T_E1: ce_e1_timer (d->chan); break; default: break; } CE_UNLOCK ((bdrv_t *)d->board->sys); splx (s); } } s = splimp (); if (!ce_destroy) callout_reset (&timeout_handle, hz, ce_timeout, 0); splx (s); } static void ce_led_off (void *arg) { ce_board_t *b = arg; bdrv_t *bd = (bdrv_t *) b->sys; int s; s = splimp (); if (ce_destroy) { splx (s); return; } CE_LOCK (bd); TAU32_LedSet (b->ddk.pControllerObject, 0); CE_UNLOCK (bd); splx (s); } static void ce_intr (void *arg) { bdrv_t *bd = arg; ce_board_t *b = bd->board; int s; int i; #if __FreeBSD_version >= 500000 && defined NETGRAPH int error; #endif s = splimp (); if (ce_destroy) { splx (s); return; } CE_LOCK (bd); /* Turn LED on. */ TAU32_LedSet (b->ddk.pControllerObject, 1); TAU32_HandleInterrupt (b->ddk.pControllerObject); /* Turn LED off 50 msec later. */ callout_reset (&led_timo[b->num], hz/20, ce_led_off, b); CE_UNLOCK (bd); splx (s); /* Pass packets in a lock-free state */ for (i = 0; i < NCHAN && b->chan[i].type; i++) { drv_t *d = b->chan[i].sys; struct mbuf *m; if (!d || !d->running) continue; while (_IF_QLEN(&d->rqueue)) { IF_DEQUEUE (&d->rqueue,m); if (!m) continue; #ifdef NETGRAPH if (d->hook) { #if __FreeBSD_version >= 500000 NG_SEND_DATA_ONLY (error, d->hook, m); #else ng_queue_data (d->hook, m, 0); #endif } else { IF_DRAIN (&d->rqueue); } #else sppp_input (d->ifp, m); #endif } } } #if __FreeBSD_version >= 500000 static void ce_bus_dmamap_addr (void *arg, bus_dma_segment_t *segs, int nseg, int error) { unsigned long *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; } #ifndef BUS_DMA_ZERO #define BUS_DMA_ZERO 0 #endif static int ce_bus_dma_mem_alloc (int bnum, int cnum, ce_dma_mem_t *dmem) { int error; error = bus_dma_tag_create (NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, dmem->size, 1, dmem->size, 0, #if __FreeBSD_version >= 502000 NULL, NULL, #endif &dmem->dmat); if (error) { if (cnum >= 0) printf ("ce%d-%d: ", bnum, cnum); else printf ("ce%d: ", bnum); printf ("couldn't allocate tag for dma memory\n"); return 0; } error = bus_dmamem_alloc (dmem->dmat, (void **)&dmem->virt, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &dmem->mapp); if (error) { if (cnum >= 0) printf ("ce%d-%d: ", bnum, cnum); else printf ("ce%d: ", bnum); printf ("couldn't allocate mem for dma memory\n"); bus_dma_tag_destroy (dmem->dmat); return 0; } error = bus_dmamap_load (dmem->dmat, dmem->mapp, dmem->virt, dmem->size, ce_bus_dmamap_addr, &dmem->phys, 0); if (error) { if (cnum >= 0) printf ("ce%d-%d: ", bnum, cnum); else printf ("ce%d: ", bnum); printf ("couldn't load mem map for dma memory\n"); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); return 0; } #if __FreeBSD_version >= 502000 bzero (dmem->virt, dmem->size); #endif return 1; } static void ce_bus_dma_mem_free (ce_dma_mem_t *dmem) { bus_dmamap_unload (dmem->dmat, dmem->mapp); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); } #else static int ce_bus_dma_mem_alloc (int bnum, int cnum, ce_dma_mem_t *dmem) { dmem->virt = contigmalloc (dmem->size, M_DEVBUF, M_WAITOK, 0x100000, 0xffffffff, 16, 0); if (dmem->virt == NULL) { if (cnum >= 0) printf ("ce%d-%d: ", bnum, cnum); else printf ("ce%d: ", bnum); printf ("couldn't allocate dma memory\n"); return 0; } dmem->phys = vtophys (dmem->virt); bzero (dmem->virt, dmem->size); return 1; } static void ce_bus_dma_mem_free (ce_dma_mem_t *dmem) { contigfree (dmem->virt, dmem->size, M_DEVBUF); } #endif /* * Called if the probe succeeded. */ static int ce_attach (device_t dev) { bdrv_t *bd = device_get_softc (dev); int unit = device_get_unit (dev); #if __FreeBSD_version >= 504000 char *ce_ln = CE_LOCK_NAME; #endif vm_offset_t vbase; int rid, error; ce_board_t *b; ce_chan_t *c; drv_t *d; int s; b = malloc (sizeof(ce_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("ce%d: couldn't allocate memory\n", unit); return (ENXIO); } bzero (b, sizeof(ce_board_t)); b->ddk.sys = &b; #if __FreeBSD_version >= 440000 pci_enable_busmaster (dev); #endif bd->dmamem.size = TAU32_ControllerObjectSize; if (! ce_bus_dma_mem_alloc (unit, -1, &bd->dmamem)) { free (b, M_DEVBUF); return (ENXIO); } b->ddk.pControllerObject = bd->dmamem.virt; bd->board = b; b->sys = bd; rid = PCIR_BAR(0); bd->ce_res = bus_alloc_resource (dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (! bd->ce_res) { printf ("ce%d: cannot map memory\n", unit); ce_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); return (ENXIO); } vbase = (vm_offset_t) rman_get_virtual (bd->ce_res); b->ddk.PciBar1VirtualAddress = (void *)vbase; b->ddk.ControllerObjectPhysicalAddress = bd->dmamem.phys; b->ddk.pErrorNotifyCallback = ce_error_callback; b->ddk.pStatusNotifyCallback = ce_status_callback; b->num = unit; TAU32_BeforeReset(&b->ddk); pci_write_config (dev, TAU32_PCI_RESET_ADDRESS, TAU32_PCI_RESET_ON, 4); pci_write_config (dev, TAU32_PCI_RESET_ADDRESS, TAU32_PCI_RESET_OFF, 4); if(!TAU32_Initialize(&b->ddk, 0)) { printf ("ce%d: init adapter error 0x%08x, bus dead bits 0x%08lx\n", unit, b->ddk.InitErrors, b->ddk.DeadBits); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->ce_res); ce_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); return (ENXIO); } s = splimp (); ce_init_board (b); rid = 0; bd->ce_irq = bus_alloc_resource (dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (! bd->ce_irq) { printf ("ce%d: cannot map interrupt\n", unit); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->ce_res); ce_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); splx (s); return (ENXIO); } #if __FreeBSD_version >= 500000 callout_init (&led_timo[unit], ce_mpsafenet ? CALLOUT_MPSAFE : 0); #else callout_init (&led_timo[unit]); #endif error = bus_setup_intr (dev, bd->ce_irq, #if __FreeBSD_version >= 500013 INTR_TYPE_NET|(ce_mpsafenet?INTR_MPSAFE:0), #else INTR_TYPE_NET, #endif NULL, ce_intr, bd, &bd->ce_intrhand); if (error) { printf ("ce%d: cannot set up irq\n", unit); bus_release_resource (dev, SYS_RES_IRQ, 0, bd->ce_irq); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->ce_res); ce_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); splx (s); return (ENXIO); } switch (b->ddk.Model) { case 1: strcpy (b->name, TAU32_BASE_NAME); break; case 2: strcpy (b->name, TAU32_LITE_NAME); break; case 3: strcpy (b->name, TAU32_ADPCM_NAME); break; default: strcpy (b->name, TAU32_UNKNOWN_NAME); break; } printf ("ce%d: %s\n", unit, b->name); for (c = b->chan; c < b->chan + NCHAN; ++c) { c->num = (c - b->chan); c->board = b; d = &bd->channel[c->num]; d->dmamem.size = sizeof(ce_buf_t); if (! ce_bus_dma_mem_alloc (unit, c->num, &d->dmamem)) continue; channel [b->num * NCHAN + c->num] = d; sprintf (d->name, "ce%d.%d", b->num, c->num); d->board = b; d->chan = c; c->sys = d; } for (c = b->chan; c < b->chan + NCHAN; ++c) { if (c->sys == NULL) continue; d = c->sys; #ifdef NETGRAPH if (ng_make_node_common (&typestruct, &d->node) != 0) { printf ("%s: cannot make common node\n", d->name); d->node = NULL; continue; } #if __FreeBSD_version >= 500000 NG_NODE_SET_PRIVATE (d->node, d); callout_init (&d->timeout_handle, ce_mpsafenet ? CALLOUT_MPSAFE : 0); #else d->node->private = d; #endif sprintf (d->nodename, "%s%d", NG_CE_NODE_TYPE, c->board->num * NCHAN + c->num); if (ng_name_node (d->node, d->nodename)) { printf ("%s: cannot name node\n", d->nodename); #if __FreeBSD_version >= 500000 NG_NODE_UNREF (d->node); #else ng_rmnode (d->node); ng_unref (d->node); #endif continue; } d->queue.ifq_maxlen = IFQ_MAXLEN; d->hi_queue.ifq_maxlen = IFQ_MAXLEN; d->rqueue.ifq_maxlen = IFQ_MAXLEN; #if __FreeBSD_version >= 500000 mtx_init (&d->queue.ifq_mtx, "ce_queue", NULL, MTX_DEF); mtx_init (&d->hi_queue.ifq_mtx, "ce_queue_hi", NULL, MTX_DEF); mtx_init (&d->rqueue.ifq_mtx, "ce_rqueue", NULL, MTX_DEF); #endif #else /*NETGRAPH*/ #if __FreeBSD_version >= 600031 d->ifp = if_alloc(IFT_PPP); #else d->ifp = malloc (sizeof(struct sppp), M_DEVBUF, M_WAITOK); bzero (d->ifp, sizeof(struct sppp)); #endif if (!d->ifp) { printf ("%s: cannot if_alloc() interface\n", d->name); continue; } d->ifp->if_softc = d; #if __FreeBSD_version > 501000 if_initname (d->ifp, "ce", b->num * NCHAN + c->num); #else d->ifp->if_unit = b->num * NCHAN + c->num; d->ifp->if_name = "ce"; #endif d->ifp->if_mtu = PP_MTU; d->ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; #if __FreeBSD_version >= 502125 if (!ce_mpsafenet) d->ifp->if_flags |= IFF_NEEDSGIANT; #endif d->ifp->if_ioctl = ce_sioctl; d->ifp->if_start = ce_ifstart; d->ifp->if_watchdog = ce_ifwatchdog; d->ifp->if_init = ce_initialize; d->rqueue.ifq_maxlen = IFQ_MAXLEN; #if __FreeBSD_version >= 500000 mtx_init (&d->rqueue.ifq_mtx, "ce_rqueue", NULL, MTX_DEF); #endif sppp_attach (d->ifp); if_attach (d->ifp); IFP2SP(d->ifp)->pp_tlf = ce_tlf; IFP2SP(d->ifp)->pp_tls = ce_tls; /* If BPF is in the kernel, call the attach for it. * The header size of PPP or Cisco/HDLC is 4 bytes. */ bpfattach (d->ifp, DLT_PPP, 4); #endif /*NETGRAPH*/ ce_start_chan (c, 1, 1, d->dmamem.virt, d->dmamem.phys); /* Register callback functions. */ ce_register_transmit (c, &ce_transmit); ce_register_receive (c, &ce_receive); ce_register_error (c, &ce_error); d->devt = make_dev (&ce_cdevsw, b->num*NCHAN+c->num, UID_ROOT, GID_WHEEL, 0600, "ce%d", b->num*NCHAN+c->num); } #if __FreeBSD_version >= 504000 ce_ln[2] = '0' + unit; mtx_init (&bd->ce_mtx, ce_ln, MTX_NETWORK_LOCK, MTX_DEF|MTX_RECURSE); #endif CE_LOCK (bd); TAU32_EnableInterrupts(b->ddk.pControllerObject); adapter[unit] = b; CE_UNLOCK (bd); splx (s); return 0; } static int ce_detach (device_t dev) { bdrv_t *bd = device_get_softc (dev); ce_board_t *b = bd->board; ce_chan_t *c; int s; #if __FreeBSD_version >= 504000 KASSERT (mtx_initialized (&bd->ce_mtx), ("ce mutex not initialized")); #endif s = splimp (); CE_LOCK (bd); /* Check if the device is busy (open). */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; /* XXX Non existen chan! */ if (! d || ! d->chan) continue; if (d->running) { CE_UNLOCK (bd); splx (s); return EBUSY; } } /* Ok, we can unload driver */ /* At first we should disable interrupts */ ce_destroy = 1; TAU32_DisableInterrupts(b->ddk.pControllerObject); callout_stop (&led_timo[b->num]); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan) continue; #ifndef NETGRAPH /* Detach from the packet filter list of interfaces. */ bpfdetach (d->ifp); /* Detach from the sync PPP list. */ sppp_detach (d->ifp); /* Detach from the system list of interfaces. */ if_detach (d->ifp); #if __FreeBSD_version > 600031 if_free(d->ifp); #else free (d->ifp, M_DEVBUF); #endif IF_DRAIN (&d->rqueue); #if __FreeBSD_version >= 500000 mtx_destroy (&d->rqueue.ifq_mtx); #endif #else #if __FreeBSD_version >= 500000 if (d->node) { ng_rmnode_self (d->node); NG_NODE_UNREF (d->node); d->node = NULL; } IF_DRAIN (&d->rqueue); mtx_destroy (&d->queue.ifq_mtx); mtx_destroy (&d->hi_queue.ifq_mtx); mtx_destroy (&d->rqueue.ifq_mtx); #else ng_rmnode (d->node); d->node = 0; #endif #endif destroy_dev (d->devt); } CE_UNLOCK (bd); splx (s); callout_drain (&led_timo[b->num]); /* Disable the interrupt request. */ bus_teardown_intr (dev, bd->ce_irq, bd->ce_intrhand); bus_release_resource (dev, SYS_RES_IRQ, 0, bd->ce_irq); TAU32_DestructiveHalt (b->ddk.pControllerObject, 0); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->ce_res); s = splimp (); CE_LOCK (bd); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan) continue; channel [b->num * NCHAN + c->num] = 0; /* Deallocate buffers. */ ce_bus_dma_mem_free (&d->dmamem); } adapter [b->num] = 0; ce_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); CE_UNLOCK (bd); splx (s); #if __FreeBSD_version >= 504000 mtx_destroy (&bd->ce_mtx); #endif return 0; } #ifndef NETGRAPH static void ce_ifstart (struct ifnet *ifp) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; CE_LOCK (bd); ce_start (d); CE_UNLOCK (bd); } static void ce_ifwatchdog (struct ifnet *ifp) { drv_t *d = ifp->if_softc; ce_watchdog (d); } static void ce_tlf (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CE_DEBUG2 (d, ("ce_tlf\n")); sp->pp_down (sp); } static void ce_tls (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CE_DEBUG2 (d, ("ce_tls\n")); sp->pp_up (sp); } /* * Process an ioctl request. */ static int ce_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; int error, s, was_up, should_be_up; #if __FreeBSD_version >= 600034 was_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; #else was_up = (ifp->if_flags & IFF_RUNNING) != 0; #endif error = sppp_ioctl (ifp, cmd, data); if (error) return error; if (! (ifp->if_flags & IFF_DEBUG)) d->chan->debug = 0; else if (! d->chan->debug) d->chan->debug = 1; switch (cmd) { default: CE_DEBUG2 (d, ("ioctl 0x%lx\n", cmd)); return 0; case SIOCADDMULTI: CE_DEBUG2 (d, ("ioctl SIOCADDMULTI\n")); return 0; case SIOCDELMULTI: CE_DEBUG2 (d, ("ioctl SIOCDELMULTI\n")); return 0; case SIOCSIFFLAGS: CE_DEBUG2 (d, ("ioctl SIOCSIFFLAGS\n")); break; case SIOCSIFADDR: CE_DEBUG2 (d, ("ioctl SIOCSIFADDR\n")); break; } /* We get here only in case of SIFFLAGS or SIFADDR. */ s = splimp (); CE_LOCK (bd); #if __FreeBSD_version >= 600034 should_be_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; #else should_be_up = (ifp->if_flags & IFF_RUNNING) != 0; #endif if (! was_up && should_be_up) { /* Interface goes up -- start it. */ ce_up (d); ce_start (d); } else if (was_up && ! should_be_up) { /* Interface is going down -- stop it. */ /* if ((IFP2SP(ifp)->pp_flags & PP_FR) || (ifp->if_flags & PP_CISCO))*/ ce_down (d); } CE_DEBUG (d, ("ioctl 0x%lx p4\n", cmd)); CE_UNLOCK (bd); splx (s); return 0; } /* * Initialization of interface. * It seems to be never called by upper level? */ static void ce_initialize (void *softc) { drv_t *d = softc; CE_DEBUG (d, ("ce_initialize\n")); } #endif /*NETGRAPH*/ /* * Stop the interface. Called on splimp(). */ static void ce_down (drv_t *d) { CE_DEBUG (d, ("ce_down\n")); /* Interface is going down -- stop it. */ ce_set_dtr (d->chan, 0); ce_set_rts (d->chan, 0); d->running = 0; } /* * Start the interface. Called on splimp(). */ static void ce_up (drv_t *d) { CE_DEBUG (d, ("ce_up\n")); ce_set_dtr (d->chan, 1); ce_set_rts (d->chan, 1); d->running = 1; } /* * Start output on the interface. Get another datagram to send * off of the interface queue, and copy it to the interface * before starting the output. */ static void ce_send (drv_t *d) { struct mbuf *m; u_short len; CE_DEBUG2 (d, ("ce_send\n")); /* No output if the interface is down. */ if (! d->running) return; while (ce_transmit_space (d->chan)) { /* Get the packet to send. */ #ifdef NETGRAPH IF_DEQUEUE (&d->hi_queue, m); if (! m) IF_DEQUEUE (&d->queue, m); #else m = sppp_dequeue (d->ifp); #endif if (! m) return; #ifndef NETGRAPH #if __FreeBSD_version >= 500000 BPF_MTAP (d->ifp, m); #else if (d->ifp->if_bpf) bpf_mtap (d->ifp, m); #endif #endif #if __FreeBSD_version >= 490000 len = m_length (m, NULL); #else len = m->m_pkthdr.len; #endif if (len >= BUFSZ) printf ("%s: too long packet: %d bytes: ", d->name, len); else if (! m->m_next) ce_send_packet (d->chan, (u_char*) mtod (m, caddr_t), len, 0); else { ce_buf_item_t *item = (ce_buf_item_t*)d->chan->tx_queue; m_copydata (m, 0, len, item->buf); ce_send_packet (d->chan, item->buf, len, 0); } m_freem (m); /* Set up transmit timeout, if the transmit ring is not empty.*/ #ifdef NETGRAPH d->timeout = 10; #else d->ifp->if_timer = 10; #endif } #ifndef NETGRAPH #if __FreeBSD_version >= 600034 d->ifp->if_flags |= IFF_DRV_OACTIVE; #else d->ifp->if_flags |= IFF_OACTIVE; #endif #endif } /* * Start output on the interface. * Always called on splimp(). */ static void ce_start (drv_t *d) { if (d->running) { if (! d->chan->dtr) ce_set_dtr (d->chan, 1); if (! d->chan->rts) ce_set_rts (d->chan, 1); ce_send (d); } } /* * Handle transmit timeouts. * Recover after lost transmit interrupts. * Always called on splimp(). */ static void ce_watchdog (drv_t *d) { bdrv_t *bd = d->board->sys; CE_DEBUG (d, ("device timeout\n")); if (d->running) { int s = splimp (); CE_LOCK (bd); ce_set_dtr (d->chan, 0); ce_set_rts (d->chan, 0); /* ce_stop_chan (d->chan);*/ /* ce_start_chan (d->chan, 1, 1, 0, 0);*/ ce_set_dtr (d->chan, 1); ce_set_rts (d->chan, 1); ce_start (d); CE_UNLOCK (bd); splx (s); } } static void ce_transmit (ce_chan_t *c, void *attachment, int len) { drv_t *d = c->sys; #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_opackets; #if __FreeBSD_version >= 600034 d->ifp->if_flags &= ~IFF_DRV_OACTIVE; #else d->ifp->if_flags &= ~IFF_OACTIVE; #endif d->ifp->if_timer = 0; #endif ce_start (d); } static void ce_receive (ce_chan_t *c, unsigned char *data, int len) { drv_t *d = c->sys; struct mbuf *m; if (! d->running) return; m = makembuf (data, len); if (! m) { CE_DEBUG (d, ("no memory for packet\n")); #ifndef NETGRAPH ++d->ifp->if_iqdrops; #endif return; } if (c->debug > 1) printmbuf (m); #ifdef NETGRAPH m->m_pkthdr.rcvif = 0; IF_ENQUEUE(&d->rqueue, m); #else ++d->ifp->if_ipackets; m->m_pkthdr.rcvif = d->ifp; /* Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ #if __FreeBSD_version >= 500000 BPF_TAP (d->ifp, data, len); #else if (d->ifp->if_bpf) bpf_tap (d->ifp, data, len); #endif IF_ENQUEUE(&d->rqueue, m); #endif } static void ce_error (ce_chan_t *c, int data) { drv_t *d = c->sys; switch (data) { case CE_FRAME: CE_DEBUG (d, ("frame error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CE_CRC: CE_DEBUG (d, ("crc error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CE_OVERRUN: CE_DEBUG (d, ("overrun error\n")); #ifndef NETGRAPH ++d->ifp->if_collisions; ++d->ifp->if_ierrors; #endif break; case CE_OVERFLOW: CE_DEBUG (d, ("overflow error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CE_UNDERRUN: CE_DEBUG (d, ("underrun error\n")); #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_oerrors; #if __FreeBSD_version >= 600034 d->ifp->if_flags &= ~IFF_DRV_OACTIVE; #else d->ifp->if_flags &= ~IFF_OACTIVE; #endif d->ifp->if_timer = 0; #endif ce_start (d); break; default: CE_DEBUG (d, ("error #%d\n", data)); break; } } /* * You also need read, write, open, close routines. * This should get you started */ #if __FreeBSD_version < 500000 static int ce_open (dev_t dev, int oflags, int devtype, struct proc *p) #else static int ce_open (struct cdev *dev, int oflags, int devtype, struct thread *td) #endif { int unit = minor (dev); drv_t *d; if (unit >= NBRD*NCHAN || ! (d = channel[unit])) return ENXIO; CE_DEBUG2 (d, ("ce_open\n")); return 0; } /* * Only called on the LAST close. */ #if __FreeBSD_version < 500000 static int ce_close (dev_t dev, int fflag, int devtype, struct proc *p) #else static int ce_close (struct cdev *dev, int fflag, int devtype, struct thread *td) #endif { drv_t *d = channel [minor (dev)]; CE_DEBUG2 (d, ("ce_close\n")); return 0; } static int ce_modem_status (ce_chan_t *c) { drv_t *d = c->sys; bdrv_t *bd = d->board->sys; int status, s; status = d->running ? TIOCM_LE : 0; s = splimp (); CE_LOCK (bd); if (ce_get_cd (c)) status |= TIOCM_CD; if (ce_get_cts (c)) status |= TIOCM_CTS; if (ce_get_dsr (c)) status |= TIOCM_DSR; if (c->dtr) status |= TIOCM_DTR; if (c->rts) status |= TIOCM_RTS; CE_UNLOCK (bd); splx (s); return status; } #if __FreeBSD_version < 500000 static int ce_ioctl (dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) #else static int ce_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) #endif { drv_t *d = channel [minor (dev)]; bdrv_t *bd = d->board->sys; ce_chan_t *c = d->chan; struct serial_statistics *st; struct e1_statistics *opte1; int error, s; char mask[16]; switch (cmd) { case SERIAL_GETREGISTERED: CE_DEBUG2 (d, ("ioctl: getregistered\n")); bzero (mask, sizeof(mask)); for (s=0; sifp)->pp_flags & PP_FR) ? "fr" : (d->ifp->if_flags & PP_CISCO) ? "cisco" : "ppp"); return 0; case SERIAL_SETPROTO: CE_DEBUG2 (d, ("ioctl: setproto\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; #if __FreeBSD_version >= 600034 if (d->ifp->if_flags & IFF_DRV_RUNNING) #else if (d->ifp->if_flags & IFF_RUNNING) #endif return EBUSY; if (! strcmp ("cisco", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR); IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; d->ifp->if_flags |= PP_CISCO; } else if (! strcmp ("fr", (char*)data) && PP_FR) { d->ifp->if_flags &= ~(PP_CISCO); IFP2SP(d->ifp)->pp_flags |= PP_FR | PP_KEEPALIVE; } else if (! strcmp ("ppp", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~PP_FR; IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; d->ifp->if_flags &= ~(PP_CISCO); } else return EINVAL; return 0; case SERIAL_GETKEEPALIVE: CE_DEBUG2 (d, ("ioctl: getkeepalive\n")); if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; *(int*)data = (IFP2SP(d->ifp)->pp_flags & PP_KEEPALIVE) ? 1 : 0; return 0; case SERIAL_SETKEEPALIVE: CE_DEBUG2 (d, ("ioctl: setkeepalive\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; s = splimp (); CE_LOCK (bd); if (*(int*)data) IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; else IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; CE_UNLOCK (bd); splx (s); return 0; #endif /*NETGRAPH*/ case SERIAL_GETMODE: CE_DEBUG2 (d, ("ioctl: getmode\n")); *(int*)data = SERIAL_HDLC; return 0; case SERIAL_SETMODE: /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (*(int*)data != SERIAL_HDLC) return EINVAL; return 0; case SERIAL_GETCFG: CE_DEBUG2 (d, ("ioctl: getcfg\n")); *(char*)data = 'c'; return 0; case SERIAL_SETCFG: CE_DEBUG2 (d, ("ioctl: setcfg\n")); #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (*((char*)data) != 'c') return EINVAL; return 0; case SERIAL_GETSTAT: CE_DEBUG2 (d, ("ioctl: getstat\n")); st = (struct serial_statistics*) data; st->rintr = c->rintr; st->tintr = c->tintr; st->mintr = 0; st->ibytes = c->ibytes; st->ipkts = c->ipkts; st->obytes = c->obytes; st->opkts = c->opkts; st->ierrs = c->overrun + c->frame + c->crc; st->oerrs = c->underrun; return 0; case SERIAL_GETESTAT: CE_DEBUG2 (d, ("ioctl: getestat\n")); if (c->type != T_E1) return EINVAL; opte1 = (struct e1_statistics*) data; opte1->status = 0; if (c->status & ESTS_NOALARM) opte1->status |= E1_NOALARM; if (c->status & ESTS_LOS) opte1->status |= E1_LOS; if (c->status & ESTS_LOF) opte1->status |= E1_LOF; if (c->status & ESTS_AIS) opte1->status |= E1_AIS; if (c->status & ESTS_LOMF) opte1->status |= E1_LOMF; if (c->status & ESTS_AIS16) opte1->status |= E1_AIS16; if (c->status & ESTS_FARLOF) opte1->status |= E1_FARLOF; if (c->status & ESTS_FARLOMF) opte1->status |= E1_FARLOMF; if (c->status & ESTS_TSTREQ) opte1->status |= E1_TSTREQ; if (c->status & ESTS_TSTERR) opte1->status |= E1_TSTERR; opte1->cursec = c->cursec; opte1->totsec = c->totsec + c->cursec; opte1->currnt.bpv = c->currnt.bpv; opte1->currnt.fse = c->currnt.fse; opte1->currnt.crce = c->currnt.crce; opte1->currnt.rcrce = c->currnt.rcrce; opte1->currnt.uas = c->currnt.uas; opte1->currnt.les = c->currnt.les; opte1->currnt.es = c->currnt.es; opte1->currnt.bes = c->currnt.bes; opte1->currnt.ses = c->currnt.ses; opte1->currnt.oofs = c->currnt.oofs; opte1->currnt.css = c->currnt.css; opte1->currnt.dm = c->currnt.dm; opte1->total.bpv = c->total.bpv + c->currnt.bpv; opte1->total.fse = c->total.fse + c->currnt.fse; opte1->total.crce = c->total.crce + c->currnt.crce; opte1->total.rcrce = c->total.rcrce + c->currnt.rcrce; opte1->total.uas = c->total.uas + c->currnt.uas; opte1->total.les = c->total.les + c->currnt.les; opte1->total.es = c->total.es + c->currnt.es; opte1->total.bes = c->total.bes + c->currnt.bes; opte1->total.ses = c->total.ses + c->currnt.ses; opte1->total.oofs = c->total.oofs + c->currnt.oofs; opte1->total.css = c->total.css + c->currnt.css; opte1->total.dm = c->total.dm + c->currnt.dm; for (s=0; s<48; ++s) { opte1->interval[s].bpv = c->interval[s].bpv; opte1->interval[s].fse = c->interval[s].fse; opte1->interval[s].crce = c->interval[s].crce; opte1->interval[s].rcrce = c->interval[s].rcrce; opte1->interval[s].uas = c->interval[s].uas; opte1->interval[s].les = c->interval[s].les; opte1->interval[s].es = c->interval[s].es; opte1->interval[s].bes = c->interval[s].bes; opte1->interval[s].ses = c->interval[s].ses; opte1->interval[s].oofs = c->interval[s].oofs; opte1->interval[s].css = c->interval[s].css; opte1->interval[s].dm = c->interval[s].dm; } return 0; case SERIAL_CLRSTAT: CE_DEBUG2 (d, ("ioctl: clrstat\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; c->rintr = 0; c->tintr = 0; c->ibytes = 0; c->obytes = 0; c->ipkts = 0; c->opkts = 0; c->overrun = 0; c->frame = 0; c->crc = 0; c->underrun = 0; bzero (&c->currnt, sizeof (c->currnt)); bzero (&c->total, sizeof (c->total)); bzero (c->interval, sizeof (c->interval)); return 0; case SERIAL_GETLOOP: CE_DEBUG2 (d, ("ioctl: getloop\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->lloop; return 0; case SERIAL_SETLOOP: CE_DEBUG2 (d, ("ioctl: setloop\n")); if (c->type != T_E1) return EINVAL; /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_lloop (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETRLOOP: CE_DEBUG2 (d, ("ioctl: getrloop\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->rloop; return 0; case SERIAL_SETRLOOP: CE_DEBUG2 (d, ("ioctl: setloop\n")); if (c->type != T_E1) return EINVAL; /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_rloop (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDEBUG: CE_DEBUG2 (d, ("ioctl: getdebug\n")); *(int*)data = d->chan->debug; return 0; case SERIAL_SETDEBUG: CE_DEBUG2 (d, ("ioctl: setdebug\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; d->chan->debug = *(int*)data; #ifndef NETGRAPH if (d->chan->debug) d->ifp->if_flags |= IFF_DEBUG; else d->ifp->if_flags &= ~IFF_DEBUG; #endif return 0; case SERIAL_GETBAUD: CE_DEBUG2 (d, ("ioctl: getbaud\n")); *(long*)data = c->baud; return 0; case SERIAL_SETBAUD: CE_DEBUG2 (d, ("ioctl: setbaud\n")); if (c->type != T_E1 || !c->unfram) return EINVAL; /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_baud (c, *(long*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETTIMESLOTS: CE_DEBUG2 (d, ("ioctl: gettimeslots\n")); if ((c->type != T_E1 || c->unfram) && c->type != T_DATA) return EINVAL; *(u_long*)data = c->ts; return 0; case SERIAL_SETTIMESLOTS: CE_DEBUG2 (d, ("ioctl: settimeslots\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if ((c->type != T_E1 || c->unfram) && c->type != T_DATA) return EINVAL; s = splimp (); CE_LOCK (bd); ce_set_ts (c, *(u_long*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETHIGAIN: CE_DEBUG2 (d, ("ioctl: gethigain\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->higain; return 0; case SERIAL_SETHIGAIN: CE_DEBUG2 (d, ("ioctl: sethigain\n")); if (c->type != T_E1) return EINVAL; /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_higain (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETPHONY: CE_DEBUG2 (d, ("ioctl: getphony\n")); *(int*)data = c->phony; return 0; case SERIAL_SETPHONY: CE_DEBUG2 (d, ("ioctl: setphony\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_phony (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETUNFRAM: CE_DEBUG2 (d, ("ioctl: getunfram\n")); if (c->type != T_E1 || c->num != 0) return EINVAL; *(int*)data = c->unfram; return 0; case SERIAL_SETUNFRAM: CE_DEBUG2 (d, ("ioctl: setunfram\n")); if (c->type != T_E1 || c->num != 0) return EINVAL; /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_unfram (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSCRAMBLER: CE_DEBUG2 (d, ("ioctl: getscrambler\n")); if (!c->unfram) return EINVAL; *(int*)data = c->scrambler; return 0; case SERIAL_SETSCRAMBLER: CE_DEBUG2 (d, ("ioctl: setscrambler\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (!c->unfram) return EINVAL; s = splimp (); CE_LOCK (bd); ce_set_scrambler (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETMONITOR: CE_DEBUG2 (d, ("ioctl: getmonitor\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->monitor; return 0; case SERIAL_SETMONITOR: CE_DEBUG2 (d, ("ioctl: setmonitor\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CE_LOCK (bd); ce_set_monitor (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETUSE16: CE_DEBUG2 (d, ("ioctl: getuse16\n")); if (c->type != T_E1 || c->unfram) return EINVAL; *(int*)data = c->use16; return 0; case SERIAL_SETUSE16: CE_DEBUG2 (d, ("ioctl: setuse16\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CE_LOCK (bd); ce_set_use16 (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCRC4: CE_DEBUG2 (d, ("ioctl: getcrc4\n")); if (c->type != T_E1 || c->unfram) return EINVAL; *(int*)data = c->crc4; return 0; case SERIAL_SETCRC4: CE_DEBUG2 (d, ("ioctl: setcrc4\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (c->type != T_E1 || c->unfram) return EINVAL; s = splimp (); CE_LOCK (bd); ce_set_crc4 (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCLK: CE_DEBUG2 (d, ("ioctl: getclk\n")); if (c->type != T_E1) return EINVAL; switch (c->gsyn) { default: *(int*)data = E1CLK_INTERNAL; break; case GSYN_RCV: *(int*)data = E1CLK_RECEIVE; break; case GSYN_RCV0: *(int*)data = E1CLK_RECEIVE_CHAN0; break; case GSYN_RCV1: *(int*)data = E1CLK_RECEIVE_CHAN1; break; } return 0; case SERIAL_SETCLK: CE_DEBUG2 (d, ("ioctl: setclk\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CE_LOCK (bd); switch (*(int*)data) { default: ce_set_gsyn (c, GSYN_INT); break; case E1CLK_RECEIVE: ce_set_gsyn (c, GSYN_RCV); break; case E1CLK_RECEIVE_CHAN0: ce_set_gsyn (c, GSYN_RCV0); break; case E1CLK_RECEIVE_CHAN1: ce_set_gsyn (c, GSYN_RCV1); break; } CE_UNLOCK (bd); splx (s); return 0; #if 0 case SERIAL_RESET: CE_DEBUG2 (d, ("ioctl: reset\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); /* ce_reset (c->board, 0, 0);*/ CE_UNLOCK (bd); splx (s); return 0; case SERIAL_HARDRESET: CE_DEBUG2 (d, ("ioctl: hardreset\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); /* hard_reset (c->board); */ CE_UNLOCK (bd); splx (s); return 0; #endif case SERIAL_GETCABLE: CE_DEBUG2 (d, ("ioctl: getcable\n")); if (c->type != T_E1) return EINVAL; s = splimp (); CE_LOCK (bd); *(int*)data = CABLE_TP; CE_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDIR: CE_DEBUG2 (d, ("ioctl: getdir\n")); if (c->type != T_E1 && c->type != T_DATA) return EINVAL; *(int*)data = c->dir; return 0; case SERIAL_SETDIR: CE_DEBUG2 (d, ("ioctl: setdir\n")); /* Only for superuser! */ #if __FreeBSD_version < 500000 error = suser (p); #elsif __FreeBSD_version < 700000 error = suser (td); #else error = priv_check (td, PRIV_DRIVER); #endif if (error) return error; s = splimp (); CE_LOCK (bd); ce_set_dir (c, *(int*)data); CE_UNLOCK (bd); splx (s); return 0; case TIOCSDTR: /* Set DTR */ s = splimp (); CE_LOCK (bd); ce_set_dtr (c, 1); CE_UNLOCK (bd); splx (s); return 0; case TIOCCDTR: /* Clear DTR */ s = splimp (); CE_LOCK (bd); ce_set_dtr (c, 0); CE_UNLOCK (bd); splx (s); return 0; case TIOCMSET: /* Set DTR/RTS */ s = splimp (); CE_LOCK (bd); ce_set_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0); ce_set_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0); CE_UNLOCK (bd); splx (s); return 0; case TIOCMBIS: /* Add DTR/RTS */ s = splimp (); CE_LOCK (bd); if (*(int*)data & TIOCM_DTR) ce_set_dtr (c, 1); if (*(int*)data & TIOCM_RTS) ce_set_rts (c, 1); CE_UNLOCK (bd); splx (s); return 0; case TIOCMBIC: /* Clear DTR/RTS */ s = splimp (); CE_LOCK (bd); if (*(int*)data & TIOCM_DTR) ce_set_dtr (c, 0); if (*(int*)data & TIOCM_RTS) ce_set_rts (c, 0); CE_UNLOCK (bd); splx (s); return 0; case TIOCMGET: /* Get modem status */ *(int*)data = ce_modem_status (c); return 0; } return ENOTTY; } #ifdef NETGRAPH #if __FreeBSD_version >= 500000 static int ng_ce_constructor (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); #else static int ng_ce_constructor (node_p *node) { drv_t *d = (*node)->private; #endif CE_DEBUG (d, ("Constructor\n")); return EINVAL; } static int ng_ce_newhook (node_p node, hook_p hook, const char *name) { int s; #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (node); #else drv_t *d = node->private; #endif bdrv_t *bd = d->board->sys; CE_DEBUG (d, ("Newhook\n")); /* Attach debug hook */ if (strcmp (name, NG_CE_HOOK_DEBUG) == 0) { #if __FreeBSD_version >= 500000 NG_HOOK_SET_PRIVATE (hook, NULL); #else hook->private = 0; #endif d->debug_hook = hook; return 0; } /* Check for raw hook */ if (strcmp (name, NG_CE_HOOK_RAW) != 0) return EINVAL; #if __FreeBSD_version >= 500000 NG_HOOK_SET_PRIVATE (hook, d); #else hook->private = d; #endif d->hook = hook; s = splimp (); CE_LOCK (bd); ce_up (d); CE_UNLOCK (bd); splx (s); return 0; } static char *format_timeslots (u_long s) { static char buf [100]; char *p = buf; int i; for (i=1; i<32; ++i) if ((s >> i) & 1) { int prev = (i > 1) & (s >> (i-1)); int next = (i < 31) & (s >> (i+1)); if (prev) { if (next) continue; *p++ = '-'; } else if (p > buf) *p++ = ','; if (i >= 10) *p++ = '0' + i / 10; *p++ = '0' + i % 10; } *p = 0; return buf; } static int print_modems (char *s, ce_chan_t *c, int need_header) { int status = ce_modem_status (c); int length = 0; if (need_header) length += sprintf (s + length, " LE DTR DSR RTS CTS CD\n"); length += sprintf (s + length, "%4s %4s %4s %4s %4s %4s\n", status & TIOCM_LE ? "On" : "-", status & TIOCM_DTR ? "On" : "-", status & TIOCM_DSR ? "On" : "-", status & TIOCM_RTS ? "On" : "-", status & TIOCM_CTS ? "On" : "-", status & TIOCM_CD ? "On" : "-"); return length; } static int print_stats (char *s, ce_chan_t *c, int need_header) { int length = 0; if (need_header) length += sprintf (s + length, " Rintr Tintr Mintr Ibytes Ipkts Ierrs Obytes Opkts Oerrs\n"); length += sprintf (s + length, "%7ld %7ld %7ld %8lu %7ld %7ld %8lu %7ld %7ld\n", c->rintr, c->tintr, 0l, (unsigned long) c->ibytes, c->ipkts, c->overrun + c->frame + c->crc, (unsigned long) c->obytes, c->opkts, c->underrun); return length; } static char *format_e1_status (u_char status) { static char buf [80]; if (status & E1_NOALARM) return "Ok"; buf[0] = 0; if (status & E1_LOS) strcat (buf, ",LOS"); if (status & E1_AIS) strcat (buf, ",AIS"); if (status & E1_LOF) strcat (buf, ",LOF"); if (status & E1_LOMF) strcat (buf, ",LOMF"); if (status & E1_FARLOF) strcat (buf, ",FARLOF"); if (status & E1_AIS16) strcat (buf, ",AIS16"); if (status & E1_FARLOMF) strcat (buf, ",FARLOMF"); if (status & E1_TSTREQ) strcat (buf, ",TSTREQ"); if (status & E1_TSTERR) strcat (buf, ",TSTERR"); if (buf[0] == ',') return buf+1; return "Unknown"; } static int print_frac (char *s, int leftalign, u_long numerator, u_long divider) { int n, length = 0; if (numerator < 1 || divider < 1) { length += sprintf (s+length, leftalign ? "/- " : " -"); return length; } n = (int) (0.5 + 1000.0 * numerator / divider); if (n < 1000) { length += sprintf (s+length, leftalign ? "/.%-3d" : " .%03d", n); return length; } *(s + length) = leftalign ? '/' : ' '; length ++; if (n >= 1000000) n = (n+500) / 1000 * 1000; else if (n >= 100000) n = (n+50) / 100 * 100; else if (n >= 10000) n = (n+5) / 10 * 10; switch (n) { case 1000: length += printf (s+length, ".999"); return length; case 10000: n = 9990; break; case 100000: n = 99900; break; case 1000000: n = 999000; break; } if (n < 10000) length += sprintf (s+length, "%d.%d", n/1000, n/10%100); else if (n < 100000) length += sprintf (s+length, "%d.%d", n/1000, n/100%10); else if (n < 1000000) length += sprintf (s+length, "%d.", n/1000); else length += sprintf (s+length, "%d", n/1000); return length; } static int print_e1_stats (char *s, ce_chan_t *c) { struct e1_counters total; u_long totsec; int length = 0; totsec = c->totsec + c->cursec; total.bpv = c->total.bpv + c->currnt.bpv; total.fse = c->total.fse + c->currnt.fse; total.crce = c->total.crce + c->currnt.crce; total.rcrce = c->total.rcrce + c->currnt.rcrce; total.uas = c->total.uas + c->currnt.uas; total.les = c->total.les + c->currnt.les; total.es = c->total.es + c->currnt.es; total.bes = c->total.bes + c->currnt.bes; total.ses = c->total.ses + c->currnt.ses; total.oofs = c->total.oofs + c->currnt.oofs; total.css = c->total.css + c->currnt.css; total.dm = c->total.dm + c->currnt.dm; length += sprintf (s + length, " Unav/Degr Bpv/Fsyn CRC/RCRC Err/Lerr Sev/Bur Oof/Slp Status\n"); /* Unavailable seconds, degraded minutes */ length += print_frac (s + length, 0, c->currnt.uas, c->cursec); length += print_frac (s + length, 1, 60 * c->currnt.dm, c->cursec); /* Bipolar violations, frame sync errors */ length += print_frac (s + length, 0, c->currnt.bpv, c->cursec); length += print_frac (s + length, 1, c->currnt.fse, c->cursec); /* CRC errors, remote CRC errors (E-bit) */ length += print_frac (s + length, 0, c->currnt.crce, c->cursec); length += print_frac (s + length, 1, c->currnt.rcrce, c->cursec); /* Errored seconds, line errored seconds */ length += print_frac (s + length, 0, c->currnt.es, c->cursec); length += print_frac (s + length, 1, c->currnt.les, c->cursec); /* Severely errored seconds, burst errored seconds */ length += print_frac (s + length, 0, c->currnt.ses, c->cursec); length += print_frac (s + length, 1, c->currnt.bes, c->cursec); /* Out of frame seconds, controlled slip seconds */ length += print_frac (s + length, 0, c->currnt.oofs, c->cursec); length += print_frac (s + length, 1, c->currnt.css, c->cursec); length += sprintf (s + length, " %s\n", format_e1_status (c->status)); /* Print total statistics. */ length += print_frac (s + length, 0, total.uas, totsec); length += print_frac (s + length, 1, 60 * total.dm, totsec); length += print_frac (s + length, 0, total.bpv, totsec); length += print_frac (s + length, 1, total.fse, totsec); length += print_frac (s + length, 0, total.crce, totsec); length += print_frac (s + length, 1, total.rcrce, totsec); length += print_frac (s + length, 0, total.es, totsec); length += print_frac (s + length, 1, total.les, totsec); length += print_frac (s + length, 0, total.ses, totsec); length += print_frac (s + length, 1, total.bes, totsec); length += print_frac (s + length, 0, total.oofs, totsec); length += print_frac (s + length, 1, total.css, totsec); length += sprintf (s + length, " -- Total\n"); return length; } static int print_chan (char *s, ce_chan_t *c) { drv_t *d = c->sys; int length = 0; length += sprintf (s + length, "ce%d", c->board->num * NCHAN + c->num); if (d->chan->debug) length += sprintf (s + length, " debug=%d", d->chan->debug); if (c->board->mux) { length += sprintf (s + length, " cfg=C"); } else { length += sprintf (s + length, " cfg=A"); } if (c->baud) length += sprintf (s + length, " %ld", c->baud); else length += sprintf (s + length, " extclock"); if (c->type == T_E1) switch (c->gsyn) { case GSYN_INT : length += sprintf (s + length, " syn=int"); break; case GSYN_RCV : length += sprintf (s + length, " syn=rcv"); break; case GSYN_RCV0 : length += sprintf (s + length, " syn=rcv0"); break; case GSYN_RCV1 : length += sprintf (s + length, " syn=rcv1"); break; } if (c->type == T_E1) length += sprintf (s + length, " higain=%s", c->higain ? "on" : "off"); length += sprintf (s + length, " loop=%s", c->lloop ? "on" : "off"); if (c->type == T_E1) length += sprintf (s + length, " ts=%s", format_timeslots (c->ts)); length += sprintf (s + length, "\n"); return length; } #if __FreeBSD_version >= 500000 static int ng_ce_rcvmsg (node_p node, item_p item, hook_p lasthook) { drv_t *d = NG_NODE_PRIVATE (node); struct ng_mesg *msg; #else static int ng_ce_rcvmsg (node_p node, struct ng_mesg *msg, const char *retaddr, struct ng_mesg **rptr) { drv_t *d = node->private; #endif struct ng_mesg *resp = NULL; int error = 0; CE_DEBUG (d, ("Rcvmsg\n")); #if __FreeBSD_version >= 500000 NGI_GET_MSG (item, msg); #endif switch (msg->header.typecookie) { default: error = EINVAL; break; case NGM_CE_COOKIE: printf ("Not implemented yet\n"); error = EINVAL; break; case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { default: error = EINVAL; break; case NGM_TEXT_STATUS: { char *s; int l = 0; int dl = sizeof (struct ng_mesg) + 730; #if __FreeBSD_version >= 500000 NG_MKRESPONSE (resp, msg, dl, M_NOWAIT); if (! resp) { error = ENOMEM; break; } #else MALLOC (resp, struct ng_mesg *, dl, M_NETGRAPH, M_NOWAIT); if (! resp) { error = ENOMEM; break; } bzero (resp, dl); #endif s = (resp)->data; if (d) { l += print_chan (s + l, d->chan); l += print_stats (s + l, d->chan, 1); l += print_modems (s + l, d->chan, 1); l += print_e1_stats (s + l, d->chan); } else l += sprintf (s + l, "Error: node not connect to channel"); #if __FreeBSD_version < 500000 (resp)->header.version = NG_VERSION; (resp)->header.arglen = strlen (s) + 1; (resp)->header.token = msg->header.token; (resp)->header.typecookie = NGM_CE_COOKIE; (resp)->header.cmd = msg->header.cmd; #endif strncpy ((resp)->header.cmdstr, "status", NG_CMDSTRLEN); } break; } break; } #if __FreeBSD_version >= 500000 NG_RESPOND_MSG (error, node, item, resp); NG_FREE_MSG (msg); #else *rptr = resp; FREE (msg, M_NETGRAPH); #endif return error; } #if __FreeBSD_version >= 500000 static int ng_ce_rcvdata (hook_p hook, item_p item) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE(hook)); struct mbuf *m; #if __FreeBSD_version < 502120 meta_p meta; #else struct ng_tag_prio *ptag; #endif #else static int ng_ce_rcvdata (hook_p hook, struct mbuf *m, meta_p meta) { drv_t *d = hook->node->private; #endif bdrv_t *bd = d->board->sys; struct ifqueue *q; int s; CE_DEBUG2 (d, ("Rcvdata\n")); #if __FreeBSD_version >= 500000 NGI_GET_M (item, m); #if __FreeBSD_version < 502120 NGI_GET_META (item, meta); #endif NG_FREE_ITEM (item); if (! NG_HOOK_PRIVATE (hook) || ! d) { NG_FREE_M (m); #if __FreeBSD_version < 502120 NG_FREE_META (meta); #endif #else if (! hook->private || ! d) { NG_FREE_DATA (m,meta); #endif return ENETDOWN; } #if __FreeBSD_version >= 502120 /* Check for high priority data */ if ((ptag = (struct ng_tag_prio *)m_tag_locate(m, NGM_GENERIC_COOKIE, NG_TAG_PRIO, NULL)) != NULL && (ptag->priority > NG_PRIO_CUTOFF) ) q = &d->hi_queue; else q = &d->queue; #else q = (meta && meta->priority > 0) ? &d->hi_queue : &d->queue; #endif s = splimp (); CE_LOCK (bd); #if __FreeBSD_version >= 500000 IF_LOCK (q); if (_IF_QFULL (q)) { _IF_DROP (q); IF_UNLOCK (q); CE_UNLOCK (bd); splx (s); NG_FREE_M (m); #if __FreeBSD_version < 502120 NG_FREE_META (meta); #endif return ENOBUFS; } _IF_ENQUEUE (q, m); IF_UNLOCK (q); #else if (IF_QFULL (q)) { IF_DROP (q); CE_UNLOCK (bd); splx (s); NG_FREE_DATA (m, meta); return ENOBUFS; } IF_ENQUEUE (q, m); #endif ce_start (d); CE_UNLOCK (bd); splx (s); return 0; } static int ng_ce_rmnode (node_p node) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (node); CE_DEBUG (d, ("Rmnode\n")); if (d && d->running) { bdrv_t *bd = d->board->sys; int s = splimp (); CE_LOCK (bd); ce_down (d); CE_UNLOCK (bd); splx (s); } #ifdef KLD_MODULE #if __FreeBSD_version >= 502120 if (node->nd_flags & NGF_REALLY_DIE) { #else if (node->nd_flags & NG_REALLY_DIE) { #endif NG_NODE_SET_PRIVATE (node, NULL); NG_NODE_UNREF (node); } #if __FreeBSD_version >= 502120 NG_NODE_REVIVE(node); /* Persistant node */ #else node->nd_flags &= ~NG_INVALID; #endif #endif #else /* __FreeBSD_version < 500000 */ drv_t *d = node->private; if (d && d->running) { bdrv_t *bd = d->board->sys; int s = splimp (); CE_LOCK (bd); ce_down (d); CE_UNLOCK (bd); splx (s); } node->flags |= NG_INVALID; ng_cutlinks (node); #ifdef KLD_MODULE ng_unname (node); ng_unref (node); #endif #endif return 0; } static void ng_ce_watchdog (void *arg) { drv_t *d = arg; if (d) { if (d->timeout == 1) ce_watchdog (d); if (d->timeout) d->timeout--; callout_reset (&d->timeout_handle, hz, ng_ce_watchdog, d); } } static int ng_ce_connect (hook_p hook) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); #else drv_t *d = hook->node->private; #endif if (d) { CE_DEBUG (d, ("Connect\n")); callout_reset (&d->timeout_handle, hz, ng_ce_watchdog, d); } return 0; } static int ng_ce_disconnect (hook_p hook) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); #else drv_t *d = hook->node->private; #endif if (d) { CE_DEBUG (d, ("Disconnect\n")); #if __FreeBSD_version >= 500000 if (NG_HOOK_PRIVATE (hook)) #else if (hook->private) #endif { bdrv_t *bd = d->board->sys; int s = splimp (); CE_LOCK (bd); ce_down (d); CE_UNLOCK (bd); splx (s); } /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&d->timeout_handle)) callout_stop (&d->timeout_handle); } return 0; } #endif static int ce_modevent (module_t mod, int type, void *unused) { #if __FreeBSD_version < 500000 dev_t dev; struct cdevsw *cdsw; #endif static int load_count = 0; #if __FreeBSD_version < 500000 dev = makedev (CDEV_MAJOR, 0); #endif -#if __FreeBSD_version >= 501114 - if (!debug_mpsafenet && ce_mpsafenet) { - printf ("WORNING! Network stack is not MPSAFE. " - "Turning off debug.ce.mpsafenet.\n"); - ce_mpsafenet = 0; - } -#endif #if __FreeBSD_version >= 502103 if (ce_mpsafenet) ce_cdevsw.d_flags &= ~D_NEEDGIANT; #endif switch (type) { case MOD_LOAD: #if __FreeBSD_version < 500000 if (dev != NODEV && (cdsw = devsw (dev)) && cdsw->d_maj == CDEV_MAJOR) { printf ("Tau32-PCI driver is already in system\n"); return (ENXIO); } #endif #if __FreeBSD_version >= 500000 && defined NETGRAPH if (ng_newtype (&typestruct)) printf ("Failed to register ng_ce\n"); #endif ++load_count; #if __FreeBSD_version <= 500000 cdevsw_add (&ce_cdevsw); #endif #if __FreeBSD_version >= 500000 callout_init (&timeout_handle, ce_mpsafenet?CALLOUT_MPSAFE:0); #else callout_init (&timeout_handle); #endif callout_reset (&timeout_handle, hz*5, ce_timeout, 0); break; case MOD_UNLOAD: if (load_count == 1) { printf ("Removing device entry for Tau32-PCI\n"); #if __FreeBSD_version <= 500000 cdevsw_remove (&ce_cdevsw); #endif #if __FreeBSD_version >= 500000 && defined NETGRAPH ng_rmtype (&typestruct); #endif } /* If we were wait it than it reasserted now, just stop it. * Actually we shouldn't get this condition. But code could be * changed in the future, so just be a litle paranoid. */ if (!callout_drain (&timeout_handle)) callout_stop (&timeout_handle); --load_count; break; case MOD_SHUTDOWN: break; } return 0; } #ifdef NETGRAPH #if __FreeBSD_version >= 502100 static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_CE_NODE_TYPE, .constructor = ng_ce_constructor, .rcvmsg = ng_ce_rcvmsg, .shutdown = ng_ce_rmnode, .newhook = ng_ce_newhook, .connect = ng_ce_connect, .rcvdata = ng_ce_rcvdata, .disconnect = ng_ce_disconnect, }; #else /* __FreeBSD_version < 502100 */ static struct ng_type typestruct = { #if __FreeBSD_version >= 500000 NG_ABI_VERSION, #else NG_VERSION, #endif NG_CE_NODE_TYPE, ce_modevent, ng_ce_constructor, ng_ce_rcvmsg, ng_ce_rmnode, ng_ce_newhook, NULL, ng_ce_connect, ng_ce_rcvdata, #if __FreeBSD_version < 500000 NULL, #endif ng_ce_disconnect, NULL }; #endif /* __FreeBSD_version < 502100 */ #endif /*NETGRAPH*/ #if __FreeBSD_version >= 500000 #ifdef NETGRAPH MODULE_DEPEND (ng_ce, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); #else MODULE_DEPEND (ce, sppp, 1, 1, 1); #endif #ifdef KLD_MODULE DRIVER_MODULE (cemod, pci, ce_driver, ce_devclass, ce_modevent, NULL); #else DRIVER_MODULE (ce, pci, ce_driver, ce_devclass, ce_modevent, NULL); #endif #else /* if __FreeBSD_version < 500000*/ #ifdef NETGRAPH DRIVER_MODULE (ce, pci, ce_driver, ce_devclass, ng_mod_event, &typestruct); #else DRIVER_MODULE (ce, pci, ce_driver, ce_devclass, ce_modevent, NULL); #endif #endif /* __FreeBSD_version < 500000 */ #endif /* NPCI */ Index: head/sys/dev/cp/if_cp.c =================================================================== --- head/sys/dev/cp/if_cp.c (revision 171612) +++ head/sys/dev/cp/if_cp.c (revision 171613) @@ -1,2327 +1,2322 @@ /*- * Cronyx-Tau-PCI adapter driver for FreeBSD. * Supports PPP/HDLC, Cisco/HDLC and FrameRelay protocol in synchronous mode, * and asyncronous channels with full modem control. * Keepalive protocol implemented in both Cisco and PPP modes. * * Copyright (C) 1999-2004 Cronyx Engineering. * Author: Kurakin Roman, * * Copyright (C) 1999-2002 Cronyx Engineering. * Author: Serge Vakulenko, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations a permission to use, * modify and redistribute this software in source and binary forms, * as long as this message is kept with the software, all derivative * works or modified versions. * * Cronyx Id: if_cp.c,v 1.1.2.41 2004/06/23 17:09:13 rik Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_ng_cronyx.h" #ifdef NETGRAPH_CRONYX # include "opt_netgraph.h" # ifndef NETGRAPH # error #option NETGRAPH missed from configuration # endif # include # include # include #else # include # include #include # define PP_CISCO IFF_LINK2 # include #endif #include #include #include #include #include /* If we don't have Cronyx's sppp version, we don't have fr support via sppp */ #ifndef PP_FR #define PP_FR 0 #endif #define CP_DEBUG(d,s) ({if (d->chan->debug) {\ printf ("%s: ", d->name); printf s;}}) #define CP_DEBUG2(d,s) ({if (d->chan->debug>1) {\ printf ("%s: ", d->name); printf s;}}) #define CP_LOCK_NAME "cpX" static int cp_mpsafenet = 1; TUNABLE_INT("debug.cp.mpsafenet", &cp_mpsafenet); SYSCTL_NODE(_debug, OID_AUTO, cp, CTLFLAG_RD, 0, "Cronyx Tau-PCI Adapters"); SYSCTL_INT(_debug_cp, OID_AUTO, mpsafenet, CTLFLAG_RD, &cp_mpsafenet, 0, "Enable/disable MPSAFE network support for Cronyx Tau-PCI Adapters"); #define CP_LOCK(_bd) do { \ if (cp_mpsafenet) \ mtx_lock (&(_bd)->cp_mtx); \ } while (0) #define CP_UNLOCK(_bd) do { \ if (cp_mpsafenet) \ mtx_unlock (&(_bd)->cp_mtx); \ } while (0) #define CP_LOCK_ASSERT(_bd) do { \ if (cp_mpsafenet) \ mtx_assert (&(_bd)->cp_mtx, MA_OWNED); \ } while (0) static int cp_probe __P((device_t)); static int cp_attach __P((device_t)); static int cp_detach __P((device_t)); static device_method_t cp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cp_probe), DEVMETHOD(device_attach, cp_attach), DEVMETHOD(device_detach, cp_detach), {0, 0} }; typedef struct _cp_dma_mem_t { unsigned long phys; void *virt; size_t size; bus_dma_tag_t dmat; bus_dmamap_t mapp; } cp_dma_mem_t; typedef struct _drv_t { char name [8]; int running; cp_chan_t *chan; cp_board_t *board; cp_dma_mem_t dmamem; #ifdef NETGRAPH char nodename [NG_NODELEN+1]; hook_p hook; hook_p debug_hook; node_p node; struct ifqueue queue; struct ifqueue hi_queue; short timeout; struct callout timeout_handle; #else struct ifqueue queue; struct ifnet *ifp; #endif struct cdev *devt; } drv_t; typedef struct _bdrv_t { cp_board_t *board; struct resource *cp_res; struct resource *cp_irq; void *cp_intrhand; cp_dma_mem_t dmamem; drv_t channel [NCHAN]; struct mtx cp_mtx; } bdrv_t; static driver_t cp_driver = { "cp", cp_methods, sizeof(bdrv_t), }; static devclass_t cp_devclass; static void cp_receive (cp_chan_t *c, unsigned char *data, int len); static void cp_transmit (cp_chan_t *c, void *attachment, int len); static void cp_error (cp_chan_t *c, int data); static void cp_up (drv_t *d); static void cp_start (drv_t *d); static void cp_down (drv_t *d); static void cp_watchdog (drv_t *d); #ifdef NETGRAPH extern struct ng_type typestruct; #else static void cp_ifstart (struct ifnet *ifp); static void cp_tlf (struct sppp *sp); static void cp_tls (struct sppp *sp); static void cp_ifwatchdog (struct ifnet *ifp); static int cp_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data); static void cp_initialize (void *softc); #endif static cp_board_t *adapter [NBRD]; static drv_t *channel [NBRD*NCHAN]; static struct callout led_timo [NBRD]; static struct callout timeout_handle; static int cp_destroy = 0; static int cp_open (struct cdev *dev, int oflags, int devtype, struct thread *td); static int cp_close (struct cdev *dev, int fflag, int devtype, struct thread *td); static int cp_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td); static struct cdevsw cp_cdevsw = { .d_version = D_VERSION, .d_open = cp_open, .d_close = cp_close, .d_ioctl = cp_ioctl, .d_name = "cp", .d_flags = D_NEEDGIANT, }; /* * Print the mbuf chain, for debug purposes only. */ static void printmbuf (struct mbuf *m) { printf ("mbuf:"); for (; m; m=m->m_next) { if (m->m_flags & M_PKTHDR) printf (" HDR %d:", m->m_pkthdr.len); if (m->m_flags & M_EXT) printf (" EXT:"); printf (" %d", m->m_len); } printf ("\n"); } /* * Make an mbuf from data. */ static struct mbuf *makembuf (void *buf, unsigned len) { struct mbuf *m; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return 0; MCLGET (m, M_DONTWAIT); if (! (m->m_flags & M_EXT)) { m_freem (m); return 0; } m->m_pkthdr.len = m->m_len = len; bcopy (buf, mtod (m, caddr_t), len); return m; } static int cp_probe (device_t dev) { if ((pci_get_vendor (dev) == cp_vendor_id) && (pci_get_device (dev) == cp_device_id)) { device_set_desc (dev, "Cronyx-Tau-PCI serial adapter"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void cp_timeout (void *arg) { drv_t *d; int s, i, k; for (i = 0; i < NBRD; ++i) { if (adapter[i] == NULL) continue; for (k = 0; k < NCHAN; ++k) { s = splimp (); if (cp_destroy) { splx (s); return; } d = channel[i * NCHAN + k]; if (!d) { splx (s); continue; } CP_LOCK ((bdrv_t *)d->board->sys); switch (d->chan->type) { case T_G703: cp_g703_timer (d->chan); break; case T_E1: cp_e1_timer (d->chan); break; case T_E3: case T_T3: case T_STS1: cp_e3_timer (d->chan); break; default: break; } CP_UNLOCK ((bdrv_t *)d->board->sys); splx (s); } } s = splimp (); if (!cp_destroy) callout_reset (&timeout_handle, hz, cp_timeout, 0); splx (s); } static void cp_led_off (void *arg) { cp_board_t *b = arg; bdrv_t *bd = (bdrv_t *) b->sys; int s; s = splimp (); if (cp_destroy) { splx (s); return; } CP_LOCK (bd); cp_led (b, 0); CP_UNLOCK (bd); splx (s); } static void cp_intr (void *arg) { bdrv_t *bd = arg; cp_board_t *b = bd->board; #ifndef NETGRAPH int i; #endif int s = splimp (); if (cp_destroy) { splx (s); return; } CP_LOCK (bd); /* Check if we are ready */ if (b->sys == NULL) { /* Not we are not, just cleanup. */ cp_interrupt_poll (b, 1); CP_UNLOCK (bd); return; } /* Turn LED on. */ cp_led (b, 1); cp_interrupt (b); /* Turn LED off 50 msec later. */ callout_reset (&led_timo[b->num], hz/20, cp_led_off, b); CP_UNLOCK (bd); splx (s); #ifndef NETGRAPH /* Pass packets in a lock-free state */ for (i = 0; i < NCHAN && b->chan[i].type; i++) { drv_t *d = b->chan[i].sys; struct mbuf *m; if (!d || !d->running) continue; while (_IF_QLEN(&d->queue)) { IF_DEQUEUE (&d->queue,m); if (!m) continue; sppp_input (d->ifp, m); } } #endif } static void cp_bus_dmamap_addr (void *arg, bus_dma_segment_t *segs, int nseg, int error) { unsigned long *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; } static int cp_bus_dma_mem_alloc (int bnum, int cnum, cp_dma_mem_t *dmem) { int error; error = bus_dma_tag_create (NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, dmem->size, 1, dmem->size, 0, NULL, NULL, &dmem->dmat); if (error) { if (cnum >= 0) printf ("cp%d-%d: ", bnum, cnum); else printf ("cp%d: ", bnum); printf ("couldn't allocate tag for dma memory\n"); return 0; } error = bus_dmamem_alloc (dmem->dmat, (void **)&dmem->virt, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &dmem->mapp); if (error) { if (cnum >= 0) printf ("cp%d-%d: ", bnum, cnum); else printf ("cp%d: ", bnum); printf ("couldn't allocate mem for dma memory\n"); bus_dma_tag_destroy (dmem->dmat); return 0; } error = bus_dmamap_load (dmem->dmat, dmem->mapp, dmem->virt, dmem->size, cp_bus_dmamap_addr, &dmem->phys, 0); if (error) { if (cnum >= 0) printf ("cp%d-%d: ", bnum, cnum); else printf ("cp%d: ", bnum); printf ("couldn't load mem map for dma memory\n"); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); return 0; } return 1; } static void cp_bus_dma_mem_free (cp_dma_mem_t *dmem) { bus_dmamap_unload (dmem->dmat, dmem->mapp); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); } /* * Called if the probe succeeded. */ static int cp_attach (device_t dev) { bdrv_t *bd = device_get_softc (dev); int unit = device_get_unit (dev); char *cp_ln = CP_LOCK_NAME; unsigned short res; vm_offset_t vbase; int rid, error; cp_board_t *b; cp_chan_t *c; drv_t *d; int s = splimp (); b = malloc (sizeof(cp_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("cp%d: couldn't allocate memory\n", unit); splx (s); return (ENXIO); } bzero (b, sizeof(cp_board_t)); bd->board = b; rid = PCIR_BAR(0); bd->cp_res = bus_alloc_resource (dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (! bd->cp_res) { printf ("cp%d: cannot map memory\n", unit); free (b, M_DEVBUF); splx (s); return (ENXIO); } vbase = (vm_offset_t) rman_get_virtual (bd->cp_res); cp_ln[2] = '0' + unit; mtx_init (&bd->cp_mtx, cp_ln, MTX_NETWORK_LOCK, MTX_DEF|MTX_RECURSE); res = cp_init (b, unit, (u_char*) vbase); if (res) { printf ("cp%d: can't init, error code:%x\n", unit, res); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->cp_res); free (b, M_DEVBUF); splx (s); return (ENXIO); } bd->dmamem.size = sizeof(cp_qbuf_t); if (! cp_bus_dma_mem_alloc (unit, -1, &bd->dmamem)) { free (b, M_DEVBUF); splx (s); return (ENXIO); } CP_LOCK (bd); cp_reset (b, bd->dmamem.virt, bd->dmamem.phys); CP_UNLOCK (bd); rid = 0; bd->cp_irq = bus_alloc_resource (dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (! bd->cp_irq) { cp_destroy = 1; printf ("cp%d: cannot map interrupt\n", unit); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->cp_res); mtx_destroy (&bd->cp_mtx); free (b, M_DEVBUF); splx (s); return (ENXIO); } callout_init (&led_timo[unit], cp_mpsafenet ? CALLOUT_MPSAFE : 0); error = bus_setup_intr (dev, bd->cp_irq, INTR_TYPE_NET|(cp_mpsafenet?INTR_MPSAFE:0), NULL, cp_intr, bd, &bd->cp_intrhand); if (error) { cp_destroy = 1; printf ("cp%d: cannot set up irq\n", unit); bus_release_resource (dev, SYS_RES_IRQ, 0, bd->cp_irq); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->cp_res); mtx_destroy (&bd->cp_mtx); free (b, M_DEVBUF); splx (s); return (ENXIO); } printf ("cp%d: %s, clock %ld MHz\n", unit, b->name, b->osc / 1000000); for (c = b->chan; c < b->chan + NCHAN; ++c) { if (! c->type) continue; d = &bd->channel[c->num]; d->dmamem.size = sizeof(cp_buf_t); if (! cp_bus_dma_mem_alloc (unit, c->num, &d->dmamem)) continue; channel [b->num*NCHAN + c->num] = d; sprintf (d->name, "cp%d.%d", b->num, c->num); d->board = b; d->chan = c; c->sys = d; #ifdef NETGRAPH if (ng_make_node_common (&typestruct, &d->node) != 0) { printf ("%s: cannot make common node\n", d->name); d->node = NULL; continue; } NG_NODE_SET_PRIVATE (d->node, d); sprintf (d->nodename, "%s%d", NG_CP_NODE_TYPE, c->board->num*NCHAN + c->num); if (ng_name_node (d->node, d->nodename)) { printf ("%s: cannot name node\n", d->nodename); NG_NODE_UNREF (d->node); continue; } d->queue.ifq_maxlen = IFQ_MAXLEN; d->hi_queue.ifq_maxlen = IFQ_MAXLEN; mtx_init (&d->queue.ifq_mtx, "cp_queue", NULL, MTX_DEF); mtx_init (&d->hi_queue.ifq_mtx, "cp_queue_hi", NULL, MTX_DEF); callout_init (&d->timeout_handle, cp_mpsafenet ? CALLOUT_MPSAFE : 0); #else /*NETGRAPH*/ d->ifp = if_alloc(IFT_PPP); if (d->ifp == NULL) { printf ("%s: cannot if_alloc() interface\n", d->name); continue; } d->ifp->if_softc = d; if_initname (d->ifp, "cp", b->num * NCHAN + c->num); d->ifp->if_mtu = PP_MTU; d->ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; if (!cp_mpsafenet) d->ifp->if_flags |= IFF_NEEDSGIANT; d->ifp->if_ioctl = cp_sioctl; d->ifp->if_start = cp_ifstart; d->ifp->if_watchdog = cp_ifwatchdog; d->ifp->if_init = cp_initialize; d->queue.ifq_maxlen = NRBUF; mtx_init (&d->queue.ifq_mtx, "cp_queue", NULL, MTX_DEF); sppp_attach (d->ifp); if_attach (d->ifp); IFP2SP(d->ifp)->pp_tlf = cp_tlf; IFP2SP(d->ifp)->pp_tls = cp_tls; /* If BPF is in the kernel, call the attach for it. * The header size of PPP or Cisco/HDLC is 4 bytes. */ bpfattach (d->ifp, DLT_PPP, 4); #endif /*NETGRAPH*/ cp_start_e1 (c); cp_start_chan (c, 1, 1, d->dmamem.virt, d->dmamem.phys); /* Register callback functions. */ cp_register_transmit (c, &cp_transmit); cp_register_receive (c, &cp_receive); cp_register_error (c, &cp_error); d->devt = make_dev (&cp_cdevsw, b->num*NCHAN+c->num, UID_ROOT, GID_WHEEL, 0600, "cp%d", b->num*NCHAN+c->num); } CP_LOCK (bd); b->sys = bd; adapter[unit] = b; CP_UNLOCK (bd); splx (s); return 0; } static int cp_detach (device_t dev) { bdrv_t *bd = device_get_softc (dev); cp_board_t *b = bd->board; cp_chan_t *c; int s; KASSERT (mtx_initialized (&bd->cp_mtx), ("cp mutex not initialized")); s = splimp (); CP_LOCK (bd); /* Check if the device is busy (open). */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan->type) continue; if (d->running) { CP_UNLOCK (bd); splx (s); return EBUSY; } } /* Ok, we can unload driver */ /* At first we should stop all channels */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan->type) continue; cp_stop_chan (c); cp_stop_e1 (c); cp_set_dtr (d->chan, 0); cp_set_rts (d->chan, 0); } /* Reset the adapter. */ cp_destroy = 1; cp_interrupt_poll (b, 1); cp_led_off (b); cp_reset (b, 0 ,0); callout_stop (&led_timo[b->num]); /* Disable the interrupt request. */ bus_teardown_intr (dev, bd->cp_irq, bd->cp_intrhand); for (c=b->chan; cchan+NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan->type) continue; #ifndef NETGRAPH /* Detach from the packet filter list of interfaces. */ bpfdetach (d->ifp); /* Detach from the sync PPP list. */ sppp_detach (d->ifp); /* Detach from the system list of interfaces. */ if_detach (d->ifp); if_free (d->ifp); IF_DRAIN (&d->queue); mtx_destroy (&d->queue.ifq_mtx); #else if (d->node) { ng_rmnode_self (d->node); NG_NODE_UNREF (d->node); d->node = NULL; } mtx_destroy (&d->queue.ifq_mtx); mtx_destroy (&d->hi_queue.ifq_mtx); #endif destroy_dev (d->devt); } b->sys = NULL; CP_UNLOCK (bd); bus_release_resource (dev, SYS_RES_IRQ, 0, bd->cp_irq); bus_release_resource (dev, SYS_RES_MEMORY, PCIR_BAR(0), bd->cp_res); CP_LOCK (bd); cp_led_off (b); CP_UNLOCK (bd); callout_drain (&led_timo[b->num]); splx (s); s = splimp (); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (! d || ! d->chan->type) continue; channel [b->num*NCHAN + c->num] = 0; /* Deallocate buffers. */ cp_bus_dma_mem_free (&d->dmamem); } adapter [b->num] = 0; cp_bus_dma_mem_free (&bd->dmamem); free (b, M_DEVBUF); splx (s); mtx_destroy (&bd->cp_mtx); return 0; } #ifndef NETGRAPH static void cp_ifstart (struct ifnet *ifp) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; CP_LOCK (bd); cp_start (d); CP_UNLOCK (bd); } static void cp_ifwatchdog (struct ifnet *ifp) { drv_t *d = ifp->if_softc; cp_watchdog (d); } static void cp_tlf (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CP_DEBUG2 (d, ("cp_tlf\n")); /* XXXRIK: Don't forget to protect them by LOCK, or kill them. */ /* cp_set_dtr (d->chan, 0);*/ /* cp_set_rts (d->chan, 0);*/ if (!(sp->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_down (sp); } static void cp_tls (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CP_DEBUG2 (d, ("cp_tls\n")); if (!(sp->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_up (sp); } /* * Process an ioctl request. */ static int cp_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; int error, s, was_up, should_be_up; was_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; error = sppp_ioctl (ifp, cmd, data); if (error) return error; if (! (ifp->if_flags & IFF_DEBUG)) d->chan->debug = 0; else if (! d->chan->debug) d->chan->debug = 1; switch (cmd) { default: CP_DEBUG2 (d, ("ioctl 0x%lx\n", cmd)); return 0; case SIOCADDMULTI: CP_DEBUG2 (d, ("ioctl SIOCADDMULTI\n")); return 0; case SIOCDELMULTI: CP_DEBUG2 (d, ("ioctl SIOCDELMULTI\n")); return 0; case SIOCSIFFLAGS: CP_DEBUG2 (d, ("ioctl SIOCSIFFLAGS\n")); break; case SIOCSIFADDR: CP_DEBUG2 (d, ("ioctl SIOCSIFADDR\n")); break; } /* We get here only in case of SIFFLAGS or SIFADDR. */ s = splimp (); CP_LOCK (bd); should_be_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; if (! was_up && should_be_up) { /* Interface goes up -- start it. */ cp_up (d); cp_start (d); } else if (was_up && ! should_be_up) { /* Interface is going down -- stop it. */ /* if ((IFP2SP(ifp)->pp_flags & PP_FR) || (ifp->if_flags & PP_CISCO))*/ cp_down (d); } CP_DEBUG (d, ("ioctl 0x%lx p4\n", cmd)); CP_UNLOCK (bd); splx (s); return 0; } /* * Initialization of interface. * It seems to be never called by upper level? */ static void cp_initialize (void *softc) { drv_t *d = softc; CP_DEBUG (d, ("cp_initialize\n")); } #endif /*NETGRAPH*/ /* * Stop the interface. Called on splimp(). */ static void cp_down (drv_t *d) { CP_DEBUG (d, ("cp_down\n")); /* Interface is going down -- stop it. */ cp_set_dtr (d->chan, 0); cp_set_rts (d->chan, 0); d->running = 0; } /* * Start the interface. Called on splimp(). */ static void cp_up (drv_t *d) { CP_DEBUG (d, ("cp_up\n")); cp_set_dtr (d->chan, 1); cp_set_rts (d->chan, 1); d->running = 1; } /* * Start output on the interface. Get another datagram to send * off of the interface queue, and copy it to the interface * before starting the output. */ static void cp_send (drv_t *d) { struct mbuf *m; u_short len; CP_DEBUG2 (d, ("cp_send, tn=%d te=%d\n", d->chan->tn, d->chan->te)); /* No output if the interface is down. */ if (! d->running) return; /* No output if the modem is off. */ if (! (d->chan->lloop || d->chan->type != T_SERIAL || cp_get_dsr (d->chan))) return; while (cp_transmit_space (d->chan)) { /* Get the packet to send. */ #ifdef NETGRAPH IF_DEQUEUE (&d->hi_queue, m); if (! m) IF_DEQUEUE (&d->queue, m); #else m = sppp_dequeue (d->ifp); #endif if (! m) return; #ifndef NETGRAPH BPF_MTAP (d->ifp, m); #endif len = m_length (m, NULL); if (len >= BUFSZ) printf ("%s: too long packet: %d bytes: ", d->name, len); else if (! m->m_next) cp_send_packet (d->chan, (u_char*) mtod (m, caddr_t), len, 0); else { u_char *buf = d->chan->tbuf[d->chan->te]; m_copydata (m, 0, len, buf); cp_send_packet (d->chan, buf, len, 0); } m_freem (m); /* Set up transmit timeout, if the transmit ring is not empty.*/ #ifdef NETGRAPH d->timeout = 10; #else d->ifp->if_timer = 10; #endif } #ifndef NETGRAPH d->ifp->if_drv_flags |= IFF_DRV_OACTIVE; #endif } /* * Start output on the interface. * Always called on splimp(). */ static void cp_start (drv_t *d) { if (d->running) { if (! d->chan->dtr) cp_set_dtr (d->chan, 1); if (! d->chan->rts) cp_set_rts (d->chan, 1); cp_send (d); } } /* * Handle transmit timeouts. * Recover after lost transmit interrupts. * Always called on splimp(). */ static void cp_watchdog (drv_t *d) { bdrv_t *bd = d->board->sys; CP_DEBUG (d, ("device timeout\n")); if (d->running) { int s = splimp (); CP_LOCK (bd); cp_stop_chan (d->chan); cp_stop_e1 (d->chan); cp_start_e1 (d->chan); cp_start_chan (d->chan, 1, 1, 0, 0); cp_set_dtr (d->chan, 1); cp_set_rts (d->chan, 1); cp_start (d); CP_UNLOCK (bd); splx (s); } } static void cp_transmit (cp_chan_t *c, void *attachment, int len) { drv_t *d = c->sys; #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_opackets; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; #endif cp_start (d); } static void cp_receive (cp_chan_t *c, unsigned char *data, int len) { drv_t *d = c->sys; struct mbuf *m; #ifdef NETGRAPH int error; #endif if (! d->running) return; m = makembuf (data, len); if (! m) { CP_DEBUG (d, ("no memory for packet\n")); #ifndef NETGRAPH ++d->ifp->if_iqdrops; #endif return; } if (c->debug > 1) printmbuf (m); #ifdef NETGRAPH m->m_pkthdr.rcvif = 0; NG_SEND_DATA_ONLY (error, d->hook, m); #else ++d->ifp->if_ipackets; m->m_pkthdr.rcvif = d->ifp; /* Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ BPF_TAP (d->ifp, data, len); IF_ENQUEUE (&d->queue, m); #endif } static void cp_error (cp_chan_t *c, int data) { drv_t *d = c->sys; switch (data) { case CP_FRAME: CP_DEBUG (d, ("frame error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CP_CRC: CP_DEBUG (d, ("crc error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CP_OVERRUN: CP_DEBUG (d, ("overrun error\n")); #ifndef NETGRAPH ++d->ifp->if_collisions; ++d->ifp->if_ierrors; #endif break; case CP_OVERFLOW: CP_DEBUG (d, ("overflow error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CP_UNDERRUN: CP_DEBUG (d, ("underrun error\n")); #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_oerrors; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; #endif cp_start (d); break; default: CP_DEBUG (d, ("error #%d\n", data)); break; } } /* * You also need read, write, open, close routines. * This should get you started */ static int cp_open (struct cdev *dev, int oflags, int devtype, struct thread *td) { int unit = minor (dev); drv_t *d; if (unit >= NBRD*NCHAN || ! (d = channel[unit])) return ENXIO; CP_DEBUG2 (d, ("cp_open\n")); return 0; } /* * Only called on the LAST close. */ static int cp_close (struct cdev *dev, int fflag, int devtype, struct thread *td) { drv_t *d = channel [minor (dev)]; CP_DEBUG2 (d, ("cp_close\n")); return 0; } static int cp_modem_status (cp_chan_t *c) { drv_t *d = c->sys; bdrv_t *bd = d->board->sys; int status, s; status = d->running ? TIOCM_LE : 0; s = splimp (); CP_LOCK (bd); if (cp_get_cd (c)) status |= TIOCM_CD; if (cp_get_cts (c)) status |= TIOCM_CTS; if (cp_get_dsr (c)) status |= TIOCM_DSR; if (c->dtr) status |= TIOCM_DTR; if (c->rts) status |= TIOCM_RTS; CP_UNLOCK (bd); splx (s); return status; } static int cp_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { drv_t *d = channel [minor (dev)]; bdrv_t *bd = d->board->sys; cp_chan_t *c = d->chan; struct serial_statistics *st; struct e1_statistics *opte1; struct e3_statistics *opte3; int error, s; char mask[16]; switch (cmd) { case SERIAL_GETREGISTERED: CP_DEBUG2 (d, ("ioctl: getregistered\n")); bzero (mask, sizeof(mask)); for (s=0; sifp)->pp_flags & PP_FR) ? "fr" : (d->ifp->if_flags & PP_CISCO) ? "cisco" : "ppp"); return 0; case SERIAL_SETPROTO: CP_DEBUG2 (d, ("ioctl: setproto\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (d->ifp->if_drv_flags & IFF_DRV_RUNNING) return EBUSY; if (! strcmp ("cisco", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR); IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; d->ifp->if_flags |= PP_CISCO; } else if (! strcmp ("fr", (char*)data) && PP_FR) { d->ifp->if_flags &= ~(PP_CISCO); IFP2SP(d->ifp)->pp_flags |= PP_FR | PP_KEEPALIVE; } else if (! strcmp ("ppp", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~PP_FR; IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; d->ifp->if_flags &= ~(PP_CISCO); } else return EINVAL; return 0; case SERIAL_GETKEEPALIVE: CP_DEBUG2 (d, ("ioctl: getkeepalive\n")); if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; *(int*)data = (IFP2SP(d->ifp)->pp_flags & PP_KEEPALIVE) ? 1 : 0; return 0; case SERIAL_SETKEEPALIVE: CP_DEBUG2 (d, ("ioctl: setkeepalive\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; s = splimp (); CP_LOCK (bd); if (*(int*)data) IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; else IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; CP_UNLOCK (bd); splx (s); return 0; #endif /*NETGRAPH*/ case SERIAL_GETMODE: CP_DEBUG2 (d, ("ioctl: getmode\n")); *(int*)data = SERIAL_HDLC; return 0; case SERIAL_SETMODE: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (*(int*)data != SERIAL_HDLC) return EINVAL; return 0; case SERIAL_GETCFG: CP_DEBUG2 (d, ("ioctl: getcfg\n")); if (c->type != T_E1 || c->unfram) return EINVAL; *(char*)data = c->board->mux ? 'c' : 'a'; return 0; case SERIAL_SETCFG: CP_DEBUG2 (d, ("ioctl: setcfg\n")); error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_mux (c->board, *((char*)data) == 'c'); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSTAT: CP_DEBUG2 (d, ("ioctl: getstat\n")); st = (struct serial_statistics*) data; st->rintr = c->rintr; st->tintr = c->tintr; st->mintr = 0; st->ibytes = c->ibytes; st->ipkts = c->ipkts; st->obytes = c->obytes; st->opkts = c->opkts; st->ierrs = c->overrun + c->frame + c->crc; st->oerrs = c->underrun; return 0; case SERIAL_GETESTAT: CP_DEBUG2 (d, ("ioctl: getestat\n")); if (c->type != T_E1 && c->type != T_G703) return EINVAL; opte1 = (struct e1_statistics*) data; opte1->status = c->status; opte1->cursec = c->cursec; opte1->totsec = c->totsec + c->cursec; opte1->currnt.bpv = c->currnt.bpv; opte1->currnt.fse = c->currnt.fse; opte1->currnt.crce = c->currnt.crce; opte1->currnt.rcrce = c->currnt.rcrce; opte1->currnt.uas = c->currnt.uas; opte1->currnt.les = c->currnt.les; opte1->currnt.es = c->currnt.es; opte1->currnt.bes = c->currnt.bes; opte1->currnt.ses = c->currnt.ses; opte1->currnt.oofs = c->currnt.oofs; opte1->currnt.css = c->currnt.css; opte1->currnt.dm = c->currnt.dm; opte1->total.bpv = c->total.bpv + c->currnt.bpv; opte1->total.fse = c->total.fse + c->currnt.fse; opte1->total.crce = c->total.crce + c->currnt.crce; opte1->total.rcrce = c->total.rcrce + c->currnt.rcrce; opte1->total.uas = c->total.uas + c->currnt.uas; opte1->total.les = c->total.les + c->currnt.les; opte1->total.es = c->total.es + c->currnt.es; opte1->total.bes = c->total.bes + c->currnt.bes; opte1->total.ses = c->total.ses + c->currnt.ses; opte1->total.oofs = c->total.oofs + c->currnt.oofs; opte1->total.css = c->total.css + c->currnt.css; opte1->total.dm = c->total.dm + c->currnt.dm; for (s=0; s<48; ++s) { opte1->interval[s].bpv = c->interval[s].bpv; opte1->interval[s].fse = c->interval[s].fse; opte1->interval[s].crce = c->interval[s].crce; opte1->interval[s].rcrce = c->interval[s].rcrce; opte1->interval[s].uas = c->interval[s].uas; opte1->interval[s].les = c->interval[s].les; opte1->interval[s].es = c->interval[s].es; opte1->interval[s].bes = c->interval[s].bes; opte1->interval[s].ses = c->interval[s].ses; opte1->interval[s].oofs = c->interval[s].oofs; opte1->interval[s].css = c->interval[s].css; opte1->interval[s].dm = c->interval[s].dm; } return 0; case SERIAL_GETE3STAT: CP_DEBUG2 (d, ("ioctl: gete3stat\n")); if (c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; opte3 = (struct e3_statistics*) data; opte3->status = c->e3status; opte3->cursec = (c->e3csec_5 * 2 + 1) / 10; opte3->totsec = c->e3tsec + opte3->cursec; opte3->ccv = c->e3ccv; opte3->tcv = c->e3tcv + opte3->ccv; for (s = 0; s < 48; ++s) { opte3->icv[s] = c->e3icv[s]; } return 0; case SERIAL_CLRSTAT: CP_DEBUG2 (d, ("ioctl: clrstat\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; c->rintr = 0; c->tintr = 0; c->ibytes = 0; c->obytes = 0; c->ipkts = 0; c->opkts = 0; c->overrun = 0; c->frame = 0; c->crc = 0; c->underrun = 0; bzero (&c->currnt, sizeof (c->currnt)); bzero (&c->total, sizeof (c->total)); bzero (c->interval, sizeof (c->interval)); c->e3ccv = 0; c->e3tcv = 0; bzero (c->e3icv, sizeof (c->e3icv)); return 0; case SERIAL_GETBAUD: CP_DEBUG2 (d, ("ioctl: getbaud\n")); *(long*)data = c->baud; return 0; case SERIAL_SETBAUD: CP_DEBUG2 (d, ("ioctl: setbaud\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_set_baud (c, *(long*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETLOOP: CP_DEBUG2 (d, ("ioctl: getloop\n")); *(int*)data = c->lloop; return 0; case SERIAL_SETLOOP: CP_DEBUG2 (d, ("ioctl: setloop\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_set_lloop (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDPLL: CP_DEBUG2 (d, ("ioctl: getdpll\n")); if (c->type != T_SERIAL) return EINVAL; *(int*)data = c->dpll; return 0; case SERIAL_SETDPLL: CP_DEBUG2 (d, ("ioctl: setdpll\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_dpll (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETNRZI: CP_DEBUG2 (d, ("ioctl: getnrzi\n")); if (c->type != T_SERIAL) return EINVAL; *(int*)data = c->nrzi; return 0; case SERIAL_SETNRZI: CP_DEBUG2 (d, ("ioctl: setnrzi\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_nrzi (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDEBUG: CP_DEBUG2 (d, ("ioctl: getdebug\n")); *(int*)data = d->chan->debug; return 0; case SERIAL_SETDEBUG: CP_DEBUG2 (d, ("ioctl: setdebug\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; d->chan->debug = *(int*)data; #ifndef NETGRAPH if (d->chan->debug) d->ifp->if_flags |= IFF_DEBUG; else d->ifp->if_flags &= ~IFF_DEBUG; #endif return 0; case SERIAL_GETHIGAIN: CP_DEBUG2 (d, ("ioctl: gethigain\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->higain; return 0; case SERIAL_SETHIGAIN: CP_DEBUG2 (d, ("ioctl: sethigain\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_higain (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETPHONY: CP_DEBUG2 (d, ("ioctl: getphony\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->phony; return 0; case SERIAL_SETPHONY: CP_DEBUG2 (d, ("ioctl: setphony\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_phony (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETUNFRAM: CP_DEBUG2 (d, ("ioctl: getunfram\n")); if (c->type != T_E1) return EINVAL; *(int*)data = c->unfram; return 0; case SERIAL_SETUNFRAM: CP_DEBUG2 (d, ("ioctl: setunfram\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_unfram (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSCRAMBLER: CP_DEBUG2 (d, ("ioctl: getscrambler\n")); if (c->type != T_G703 && !c->unfram) return EINVAL; *(int*)data = c->scrambler; return 0; case SERIAL_SETSCRAMBLER: CP_DEBUG2 (d, ("ioctl: setscrambler\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_G703 && !c->unfram) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_scrambler (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETMONITOR: CP_DEBUG2 (d, ("ioctl: getmonitor\n")); if (c->type != T_E1 && c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; *(int*)data = c->monitor; return 0; case SERIAL_SETMONITOR: CP_DEBUG2 (d, ("ioctl: setmonitor\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_monitor (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETUSE16: CP_DEBUG2 (d, ("ioctl: getuse16\n")); if (c->type != T_E1 || c->unfram) return EINVAL; *(int*)data = c->use16; return 0; case SERIAL_SETUSE16: CP_DEBUG2 (d, ("ioctl: setuse16\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_use16 (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCRC4: CP_DEBUG2 (d, ("ioctl: getcrc4\n")); if (c->type != T_E1 || c->unfram) return EINVAL; *(int*)data = c->crc4; return 0; case SERIAL_SETCRC4: CP_DEBUG2 (d, ("ioctl: setcrc4\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_crc4 (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCLK: CP_DEBUG2 (d, ("ioctl: getclk\n")); if (c->type != T_E1 && c->type != T_G703 && c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; switch (c->gsyn) { default: *(int*)data = E1CLK_INTERNAL; break; case GSYN_RCV: *(int*)data = E1CLK_RECEIVE; break; case GSYN_RCV0: *(int*)data = E1CLK_RECEIVE_CHAN0; break; case GSYN_RCV1: *(int*)data = E1CLK_RECEIVE_CHAN1; break; case GSYN_RCV2: *(int*)data = E1CLK_RECEIVE_CHAN2; break; case GSYN_RCV3: *(int*)data = E1CLK_RECEIVE_CHAN3; break; } return 0; case SERIAL_SETCLK: CP_DEBUG2 (d, ("ioctl: setclk\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_E1 && c->type != T_G703 && c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; s = splimp (); CP_LOCK (bd); switch (*(int*)data) { default: cp_set_gsyn (c, GSYN_INT); break; case E1CLK_RECEIVE: cp_set_gsyn (c, GSYN_RCV); break; case E1CLK_RECEIVE_CHAN0: cp_set_gsyn (c, GSYN_RCV0); break; case E1CLK_RECEIVE_CHAN1: cp_set_gsyn (c, GSYN_RCV1); break; case E1CLK_RECEIVE_CHAN2: cp_set_gsyn (c, GSYN_RCV2); break; case E1CLK_RECEIVE_CHAN3: cp_set_gsyn (c, GSYN_RCV3); break; } CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETTIMESLOTS: CP_DEBUG2 (d, ("ioctl: gettimeslots\n")); if ((c->type != T_E1 || c->unfram) && c->type != T_DATA) return EINVAL; *(u_long*)data = c->ts; return 0; case SERIAL_SETTIMESLOTS: CP_DEBUG2 (d, ("ioctl: settimeslots\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if ((c->type != T_E1 || c->unfram) && c->type != T_DATA) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_ts (c, *(u_long*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETINVCLK: CP_DEBUG2 (d, ("ioctl: getinvclk\n")); #if 1 return EINVAL; #else if (c->type != T_SERIAL) return EINVAL; *(int*)data = c->invtxc; return 0; #endif case SERIAL_SETINVCLK: CP_DEBUG2 (d, ("ioctl: setinvclk\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_invtxc (c, *(int*)data); cp_set_invrxc (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETINVTCLK: CP_DEBUG2 (d, ("ioctl: getinvtclk\n")); if (c->type != T_SERIAL) return EINVAL; *(int*)data = c->invtxc; return 0; case SERIAL_SETINVTCLK: CP_DEBUG2 (d, ("ioctl: setinvtclk\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_invtxc (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETINVRCLK: CP_DEBUG2 (d, ("ioctl: getinvrclk\n")); if (c->type != T_SERIAL) return EINVAL; *(int*)data = c->invrxc; return 0; case SERIAL_SETINVRCLK: CP_DEBUG2 (d, ("ioctl: setinvrclk\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); cp_set_invrxc (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETLEVEL: CP_DEBUG2 (d, ("ioctl: getlevel\n")); if (c->type != T_G703) return EINVAL; s = splimp (); CP_LOCK (bd); *(int*)data = cp_get_lq (c); CP_UNLOCK (bd); splx (s); return 0; #if 0 case SERIAL_RESET: CP_DEBUG2 (d, ("ioctl: reset\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_reset (c->board, 0, 0); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_HARDRESET: CP_DEBUG2 (d, ("ioctl: hardreset\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); /* hard_reset (c->board); */ CP_UNLOCK (bd); splx (s); return 0; #endif case SERIAL_GETCABLE: CP_DEBUG2 (d, ("ioctl: getcable\n")); if (c->type != T_SERIAL) return EINVAL; s = splimp (); CP_LOCK (bd); *(int*)data = cp_get_cable (c); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDIR: CP_DEBUG2 (d, ("ioctl: getdir\n")); if (c->type != T_E1 && c->type != T_DATA) return EINVAL; *(int*)data = c->dir; return 0; case SERIAL_SETDIR: CP_DEBUG2 (d, ("ioctl: setdir\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_set_dir (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETRLOOP: CP_DEBUG2 (d, ("ioctl: getrloop\n")); if (c->type != T_G703 && c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; *(int*)data = cp_get_rloop (c); return 0; case SERIAL_SETRLOOP: CP_DEBUG2 (d, ("ioctl: setloop\n")); if (c->type != T_E3 && c->type != T_T3 && c->type != T_STS1) return EINVAL; /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_set_rloop (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCABLEN: CP_DEBUG2 (d, ("ioctl: getcablen\n")); if (c->type != T_T3 && c->type != T_STS1) return EINVAL; *(int*)data = c->cablen; return 0; case SERIAL_SETCABLEN: CP_DEBUG2 (d, ("ioctl: setloop\n")); if (c->type != T_T3 && c->type != T_STS1) return EINVAL; /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CP_LOCK (bd); cp_set_cablen (c, *(int*)data); CP_UNLOCK (bd); splx (s); return 0; case TIOCSDTR: /* Set DTR */ s = splimp (); CP_LOCK (bd); cp_set_dtr (c, 1); CP_UNLOCK (bd); splx (s); return 0; case TIOCCDTR: /* Clear DTR */ s = splimp (); CP_LOCK (bd); cp_set_dtr (c, 0); CP_UNLOCK (bd); splx (s); return 0; case TIOCMSET: /* Set DTR/RTS */ s = splimp (); CP_LOCK (bd); cp_set_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0); cp_set_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0); CP_UNLOCK (bd); splx (s); return 0; case TIOCMBIS: /* Add DTR/RTS */ s = splimp (); CP_LOCK (bd); if (*(int*)data & TIOCM_DTR) cp_set_dtr (c, 1); if (*(int*)data & TIOCM_RTS) cp_set_rts (c, 1); CP_UNLOCK (bd); splx (s); return 0; case TIOCMBIC: /* Clear DTR/RTS */ s = splimp (); CP_LOCK (bd); if (*(int*)data & TIOCM_DTR) cp_set_dtr (c, 0); if (*(int*)data & TIOCM_RTS) cp_set_rts (c, 0); CP_UNLOCK (bd); splx (s); return 0; case TIOCMGET: /* Get modem status */ *(int*)data = cp_modem_status (c); return 0; } return ENOTTY; } #ifdef NETGRAPH static int ng_cp_constructor (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); CP_DEBUG (d, ("Constructor\n")); return EINVAL; } static int ng_cp_newhook (node_p node, hook_p hook, const char *name) { int s; drv_t *d = NG_NODE_PRIVATE (node); bdrv_t *bd = d->board->sys; CP_DEBUG (d, ("Newhook\n")); /* Attach debug hook */ if (strcmp (name, NG_CP_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE (hook, NULL); d->debug_hook = hook; return 0; } /* Check for raw hook */ if (strcmp (name, NG_CP_HOOK_RAW) != 0) return EINVAL; NG_HOOK_SET_PRIVATE (hook, d); d->hook = hook; s = splimp (); CP_LOCK (bd); cp_up (d); CP_UNLOCK (bd); splx (s); return 0; } static char *format_timeslots (u_long s) { static char buf [100]; char *p = buf; int i; for (i=1; i<32; ++i) if ((s >> i) & 1) { int prev = (i > 1) & (s >> (i-1)); int next = (i < 31) & (s >> (i+1)); if (prev) { if (next) continue; *p++ = '-'; } else if (p > buf) *p++ = ','; if (i >= 10) *p++ = '0' + i / 10; *p++ = '0' + i % 10; } *p = 0; return buf; } static int print_modems (char *s, cp_chan_t *c, int need_header) { int status = cp_modem_status (c); int length = 0; if (need_header) length += sprintf (s + length, " LE DTR DSR RTS CTS CD\n"); length += sprintf (s + length, "%4s %4s %4s %4s %4s %4s\n", status & TIOCM_LE ? "On" : "-", status & TIOCM_DTR ? "On" : "-", status & TIOCM_DSR ? "On" : "-", status & TIOCM_RTS ? "On" : "-", status & TIOCM_CTS ? "On" : "-", status & TIOCM_CD ? "On" : "-"); return length; } static int print_stats (char *s, cp_chan_t *c, int need_header) { int length = 0; if (need_header) length += sprintf (s + length, " Rintr Tintr Mintr Ibytes Ipkts Ierrs Obytes Opkts Oerrs\n"); length += sprintf (s + length, "%7ld %7ld %7ld %8lu %7ld %7ld %8lu %7ld %7ld\n", c->rintr, c->tintr, 0l, (unsigned long) c->ibytes, c->ipkts, c->overrun + c->frame + c->crc, (unsigned long) c->obytes, c->opkts, c->underrun); return length; } static char *format_e1_status (u_char status) { static char buf [80]; if (status & E1_NOALARM) return "Ok"; buf[0] = 0; if (status & E1_LOS) strcat (buf, ",LOS"); if (status & E1_AIS) strcat (buf, ",AIS"); if (status & E1_LOF) strcat (buf, ",LOF"); if (status & E1_LOMF) strcat (buf, ",LOMF"); if (status & E1_FARLOF) strcat (buf, ",FARLOF"); if (status & E1_AIS16) strcat (buf, ",AIS16"); if (status & E1_FARLOMF) strcat (buf, ",FARLOMF"); if (status & E1_TSTREQ) strcat (buf, ",TSTREQ"); if (status & E1_TSTERR) strcat (buf, ",TSTERR"); if (buf[0] == ',') return buf+1; return "Unknown"; } static int print_frac (char *s, int leftalign, u_long numerator, u_long divider) { int n, length = 0; if (numerator < 1 || divider < 1) { length += sprintf (s+length, leftalign ? "/- " : " -"); return length; } n = (int) (0.5 + 1000.0 * numerator / divider); if (n < 1000) { length += sprintf (s+length, leftalign ? "/.%-3d" : " .%03d", n); return length; } *(s + length) = leftalign ? '/' : ' '; length ++; if (n >= 1000000) n = (n+500) / 1000 * 1000; else if (n >= 100000) n = (n+50) / 100 * 100; else if (n >= 10000) n = (n+5) / 10 * 10; switch (n) { case 1000: length += printf (s+length, ".999"); return length; case 10000: n = 9990; break; case 100000: n = 99900; break; case 1000000: n = 999000; break; } if (n < 10000) length += sprintf (s+length, "%d.%d", n/1000, n/10%100); else if (n < 100000) length += sprintf (s+length, "%d.%d", n/1000, n/100%10); else if (n < 1000000) length += sprintf (s+length, "%d.", n/1000); else length += sprintf (s+length, "%d", n/1000); return length; } static int print_e1_stats (char *s, cp_chan_t *c) { struct e1_counters total; u_long totsec; int length = 0; totsec = c->totsec + c->cursec; total.bpv = c->total.bpv + c->currnt.bpv; total.fse = c->total.fse + c->currnt.fse; total.crce = c->total.crce + c->currnt.crce; total.rcrce = c->total.rcrce + c->currnt.rcrce; total.uas = c->total.uas + c->currnt.uas; total.les = c->total.les + c->currnt.les; total.es = c->total.es + c->currnt.es; total.bes = c->total.bes + c->currnt.bes; total.ses = c->total.ses + c->currnt.ses; total.oofs = c->total.oofs + c->currnt.oofs; total.css = c->total.css + c->currnt.css; total.dm = c->total.dm + c->currnt.dm; length += sprintf (s + length, " Unav/Degr Bpv/Fsyn CRC/RCRC Err/Lerr Sev/Bur Oof/Slp Status\n"); /* Unavailable seconds, degraded minutes */ length += print_frac (s + length, 0, c->currnt.uas, c->cursec); length += print_frac (s + length, 1, 60 * c->currnt.dm, c->cursec); /* Bipolar violations, frame sync errors */ length += print_frac (s + length, 0, c->currnt.bpv, c->cursec); length += print_frac (s + length, 1, c->currnt.fse, c->cursec); /* CRC errors, remote CRC errors (E-bit) */ length += print_frac (s + length, 0, c->currnt.crce, c->cursec); length += print_frac (s + length, 1, c->currnt.rcrce, c->cursec); /* Errored seconds, line errored seconds */ length += print_frac (s + length, 0, c->currnt.es, c->cursec); length += print_frac (s + length, 1, c->currnt.les, c->cursec); /* Severely errored seconds, burst errored seconds */ length += print_frac (s + length, 0, c->currnt.ses, c->cursec); length += print_frac (s + length, 1, c->currnt.bes, c->cursec); /* Out of frame seconds, controlled slip seconds */ length += print_frac (s + length, 0, c->currnt.oofs, c->cursec); length += print_frac (s + length, 1, c->currnt.css, c->cursec); length += sprintf (s + length, " %s\n", format_e1_status (c->status)); /* Print total statistics. */ length += print_frac (s + length, 0, total.uas, totsec); length += print_frac (s + length, 1, 60 * total.dm, totsec); length += print_frac (s + length, 0, total.bpv, totsec); length += print_frac (s + length, 1, total.fse, totsec); length += print_frac (s + length, 0, total.crce, totsec); length += print_frac (s + length, 1, total.rcrce, totsec); length += print_frac (s + length, 0, total.es, totsec); length += print_frac (s + length, 1, total.les, totsec); length += print_frac (s + length, 0, total.ses, totsec); length += print_frac (s + length, 1, total.bes, totsec); length += print_frac (s + length, 0, total.oofs, totsec); length += print_frac (s + length, 1, total.css, totsec); length += sprintf (s + length, " -- Total\n"); return length; } static int print_chan (char *s, cp_chan_t *c) { drv_t *d = c->sys; bdrv_t *bd = d->board->sys; int length = 0; length += sprintf (s + length, "cp%d", c->board->num * NCHAN + c->num); if (d->chan->debug) length += sprintf (s + length, " debug=%d", d->chan->debug); if (c->board->mux) { length += sprintf (s + length, " cfg=C"); } else { length += sprintf (s + length, " cfg=A"); } if (c->baud) length += sprintf (s + length, " %ld", c->baud); else length += sprintf (s + length, " extclock"); if (c->type == T_E1 || c->type == T_G703) switch (c->gsyn) { case GSYN_INT : length += sprintf (s + length, " syn=int"); break; case GSYN_RCV : length += sprintf (s + length, " syn=rcv"); break; case GSYN_RCV0 : length += sprintf (s + length, " syn=rcv0"); break; case GSYN_RCV1 : length += sprintf (s + length, " syn=rcv1"); break; case GSYN_RCV2 : length += sprintf (s + length, " syn=rcv2"); break; case GSYN_RCV3 : length += sprintf (s + length, " syn=rcv3"); break; } if (c->type == T_SERIAL) { length += sprintf (s + length, " dpll=%s", c->dpll ? "on" : "off"); length += sprintf (s + length, " nrzi=%s", c->nrzi ? "on" : "off"); length += sprintf (s + length, " invclk=%s", c->invtxc ? "on" : "off"); } if (c->type == T_E1) length += sprintf (s + length, " higain=%s", c->higain ? "on" : "off"); length += sprintf (s + length, " loop=%s", c->lloop ? "on" : "off"); if (c->type == T_E1) length += sprintf (s + length, " ts=%s", format_timeslots (c->ts)); if (c->type == T_G703) { int lq, x; x = splimp (); CP_LOCK (bd); lq = cp_get_lq (c); CP_UNLOCK (bd); splx (x); length += sprintf (s + length, " (level=-%.1fdB)", lq / 10.0); } length += sprintf (s + length, "\n"); return length; } static int ng_cp_rcvmsg (node_p node, item_p item, hook_p lasthook) { drv_t *d = NG_NODE_PRIVATE (node); struct ng_mesg *msg; struct ng_mesg *resp = NULL; int error = 0; CP_DEBUG (d, ("Rcvmsg\n")); NGI_GET_MSG (item, msg); switch (msg->header.typecookie) { default: error = EINVAL; break; case NGM_CP_COOKIE: printf ("Not implemented yet\n"); error = EINVAL; break; case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { default: error = EINVAL; break; case NGM_TEXT_STATUS: { char *s; int l = 0; int dl = sizeof (struct ng_mesg) + 730; NG_MKRESPONSE (resp, msg, dl, M_NOWAIT); if (! resp) { error = ENOMEM; break; } s = (resp)->data; if (d) { l += print_chan (s + l, d->chan); l += print_stats (s + l, d->chan, 1); l += print_modems (s + l, d->chan, 1); l += print_e1_stats (s + l, d->chan); } else l += sprintf (s + l, "Error: node not connect to channel"); strncpy ((resp)->header.cmdstr, "status", NG_CMDSTRLEN); } break; } break; } NG_RESPOND_MSG (error, node, item, resp); NG_FREE_MSG (msg); return error; } static int ng_cp_rcvdata (hook_p hook, item_p item) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE(hook)); struct mbuf *m; struct ng_tag_prio *ptag; bdrv_t *bd = d->board->sys; struct ifqueue *q; int s; CP_DEBUG2 (d, ("Rcvdata\n")); NGI_GET_M (item, m); NG_FREE_ITEM (item); if (! NG_HOOK_PRIVATE (hook) || ! d) { NG_FREE_M (m); return ENETDOWN; } /* Check for high priority data */ if ((ptag = (struct ng_tag_prio *)m_tag_locate(m, NGM_GENERIC_COOKIE, NG_TAG_PRIO, NULL)) != NULL && (ptag->priority > NG_PRIO_CUTOFF) ) q = &d->hi_queue; else q = &d->queue; s = splimp (); CP_LOCK (bd); IF_LOCK (q); if (_IF_QFULL (q)) { _IF_DROP (q); IF_UNLOCK (q); CP_UNLOCK (bd); splx (s); NG_FREE_M (m); return ENOBUFS; } _IF_ENQUEUE (q, m); IF_UNLOCK (q); cp_start (d); CP_UNLOCK (bd); splx (s); return 0; } static int ng_cp_rmnode (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); CP_DEBUG (d, ("Rmnode\n")); if (d && d->running) { bdrv_t *bd = d->board->sys; int s = splimp (); CP_LOCK (bd); cp_down (d); CP_UNLOCK (bd); splx (s); } #ifdef KLD_MODULE if (node->nd_flags & NGF_REALLY_DIE) { NG_NODE_SET_PRIVATE (node, NULL); NG_NODE_UNREF (node); } NG_NODE_REVIVE(node); /* Persistant node */ #endif return 0; } static void ng_cp_watchdog (void *arg) { drv_t *d = arg; if (d) { if (d->timeout == 1) cp_watchdog (d); if (d->timeout) d->timeout--; callout_reset (&d->timeout_handle, hz, ng_cp_watchdog, d); } } static int ng_cp_connect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); if (d) { CP_DEBUG (d, ("Connect\n")); callout_reset (&d->timeout_handle, hz, ng_cp_watchdog, d); } return 0; } static int ng_cp_disconnect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); if (d) { CP_DEBUG (d, ("Disconnect\n")); if (NG_HOOK_PRIVATE (hook)) { bdrv_t *bd = d->board->sys; int s = splimp (); CP_LOCK (bd); cp_down (d); CP_UNLOCK (bd); splx (s); } /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&d->timeout_handle)) callout_stop (&d->timeout_handle); } return 0; } #endif static int cp_modevent (module_t mod, int type, void *unused) { static int load_count = 0; - if (!debug_mpsafenet && cp_mpsafenet) { - printf ("WORNING! Network stack is not MPSAFE. " - "Turning off debug.cp.mpsafenet.\n"); - cp_mpsafenet = 0; - } if (cp_mpsafenet) cp_cdevsw.d_flags &= ~D_NEEDGIANT; switch (type) { case MOD_LOAD: #ifdef NETGRAPH if (ng_newtype (&typestruct)) printf ("Failed to register ng_cp\n"); #endif ++load_count; callout_init (&timeout_handle, cp_mpsafenet?CALLOUT_MPSAFE:0); callout_reset (&timeout_handle, hz*5, cp_timeout, 0); break; case MOD_UNLOAD: if (load_count == 1) { printf ("Removing device entry for Tau-PCI\n"); #ifdef NETGRAPH ng_rmtype (&typestruct); #endif } /* If we were wait it than it reasserted now, just stop it. * Actually we shouldn't get this condition. But code could be * changed in the future, so just be a litle paranoid. */ if (!callout_drain (&timeout_handle)) callout_stop (&timeout_handle); --load_count; break; case MOD_SHUTDOWN: break; } return 0; } #ifdef NETGRAPH static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_CP_NODE_TYPE, .constructor = ng_cp_constructor, .rcvmsg = ng_cp_rcvmsg, .shutdown = ng_cp_rmnode, .newhook = ng_cp_newhook, .connect = ng_cp_connect, .rcvdata = ng_cp_rcvdata, .disconnect = ng_cp_disconnect, }; #endif /*NETGRAPH*/ #ifdef NETGRAPH MODULE_DEPEND (ng_cp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); #else MODULE_DEPEND (cp, sppp, 1, 1, 1); #endif DRIVER_MODULE (cp, pci, cp_driver, cp_devclass, cp_modevent, NULL); MODULE_VERSION (cp, 1); Index: head/sys/dev/ctau/if_ct.c =================================================================== --- head/sys/dev/ctau/if_ct.c (revision 171612) +++ head/sys/dev/ctau/if_ct.c (revision 171613) @@ -1,2265 +1,2260 @@ /*- * Cronyx-Tau adapter driver for FreeBSD. * Supports PPP/HDLC and Cisco/HDLC protocol in synchronous mode, * and asyncronous channels with full modem control. * Keepalive protocol implemented in both Cisco and PPP modes. * * Copyright (C) 1994-2002 Cronyx Engineering. * Author: Serge Vakulenko, * * Copyright (C) 1999-2004 Cronyx Engineering. * Author: Roman Kurakin, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations a permission to use, * modify and redistribute this software in source and binary forms, * as long as this message is kept with the software, all derivative * works or modified versions. * * Cronyx Id: if_ct.c,v 1.1.2.31 2004/06/23 17:09:13 rik Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_ng_cronyx.h" #ifdef NETGRAPH_CRONYX # include "opt_netgraph.h" # include # include # include #else # include # include # define PP_CISCO IFF_LINK2 # include #endif #define NCTAU 1 /* If we don't have Cronyx's sppp version, we don't have fr support via sppp */ #ifndef PP_FR #define PP_FR 0 #endif #define CT_DEBUG(d,s) ({if (d->chan->debug) {\ printf ("%s: ", d->name); printf s;}}) #define CT_DEBUG2(d,s) ({if (d->chan->debug>1) {\ printf ("%s: ", d->name); printf s;}}) #define CT_LOCK_NAME "ctX" static int ct_mpsafenet = 1; TUNABLE_INT("debug.ctau.mpsafenet", &ct_mpsafenet); SYSCTL_NODE(_debug, OID_AUTO, ctau, CTLFLAG_RD, 0, "Cronyx Tau-ISA Adapters"); SYSCTL_INT(_debug_ctau, OID_AUTO, mpsafenet, CTLFLAG_RD, &ct_mpsafenet, 0, "Enable/disable MPSAFE network support for Cronyx Tau-ISA Adapters"); #define CT_LOCK(_bd) do { \ if (ct_mpsafenet) \ mtx_lock (&(_bd)->ct_mtx); \ } while (0) #define CT_UNLOCK(_bd) do { \ if (ct_mpsafenet) \ mtx_unlock (&(_bd)->ct_mtx); \ } while (0) #define CT_LOCK_ASSERT(_bd) do { \ if (ct_mpsafenet) \ mtx_assert (&(_bd)->ct_mtx, MA_OWNED); \ } while (0) static void ct_identify __P((driver_t *, device_t)); static int ct_probe __P((device_t)); static int ct_attach __P((device_t)); static int ct_detach __P((device_t)); static device_method_t ct_isa_methods [] = { DEVMETHOD(device_identify, ct_identify), DEVMETHOD(device_probe, ct_probe), DEVMETHOD(device_attach, ct_attach), DEVMETHOD(device_detach, ct_detach), {0, 0} }; typedef struct _ct_dma_mem_t { unsigned long phys; void *virt; size_t size; bus_dma_tag_t dmat; bus_dmamap_t mapp; } ct_dma_mem_t; typedef struct _drv_t { char name [8]; ct_chan_t *chan; ct_board_t *board; struct _bdrv_t *bd; ct_dma_mem_t dmamem; int running; #ifdef NETGRAPH char nodename [NG_NODELEN+1]; hook_p hook; hook_p debug_hook; node_p node; struct ifqueue queue; struct ifqueue hi_queue; short timeout; struct callout timeout_handle; #else struct ifqueue queue; struct ifnet *ifp; #endif struct cdev *devt; } drv_t; typedef struct _bdrv_t { ct_board_t *board; struct resource *base_res; struct resource *drq_res; struct resource *irq_res; int base_rid; int drq_rid; int irq_rid; void *intrhand; drv_t channel [NCHAN]; struct mtx ct_mtx; } bdrv_t; static driver_t ct_isa_driver = { "ct", ct_isa_methods, sizeof (bdrv_t), }; static devclass_t ct_devclass; static void ct_receive (ct_chan_t *c, char *data, int len); static void ct_transmit (ct_chan_t *c, void *attachment, int len); static void ct_error (ct_chan_t *c, int data); static void ct_up (drv_t *d); static void ct_start (drv_t *d); static void ct_down (drv_t *d); static void ct_watchdog (drv_t *d); #ifdef NETGRAPH extern struct ng_type typestruct; #else static void ct_ifstart (struct ifnet *ifp); static void ct_tlf (struct sppp *sp); static void ct_tls (struct sppp *sp); static void ct_ifwatchdog (struct ifnet *ifp); static int ct_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data); static void ct_initialize (void *softc); #endif static ct_board_t *adapter [NCTAU]; static drv_t *channel [NCTAU*NCHAN]; static struct callout led_timo [NCTAU]; static struct callout timeout_handle; static int ct_open (struct cdev *dev, int oflags, int devtype, struct thread *td); static int ct_close (struct cdev *dev, int fflag, int devtype, struct thread *td); static int ct_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td); static struct cdevsw ct_cdevsw = { .d_version = D_VERSION, .d_open = ct_open, .d_close = ct_close, .d_ioctl = ct_ioctl, .d_name = "ct", .d_flags = D_NEEDGIANT, }; /* * Print the mbuf chain, for debug purposes only. */ static void printmbuf (struct mbuf *m) { printf ("mbuf:"); for (; m; m=m->m_next) { if (m->m_flags & M_PKTHDR) printf (" HDR %d:", m->m_pkthdr.len); if (m->m_flags & M_EXT) printf (" EXT:"); printf (" %d", m->m_len); } printf ("\n"); } /* * Make an mbuf from data. */ static struct mbuf *makembuf (void *buf, u_int len) { struct mbuf *m; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return 0; MCLGET (m, M_DONTWAIT); if (! (m->m_flags & M_EXT)) { m_freem (m); return 0; } m->m_pkthdr.len = m->m_len = len; bcopy (buf, mtod (m, caddr_t), len); return m; } static void ct_timeout (void *arg) { drv_t *d; int s, i, k; for (i = 0; i < NCTAU; ++i) { if (adapter[i] == NULL) continue; for (k = 0; k < NCHAN; k++) { d = channel[i * NCHAN + k]; if (! d) continue; if (d->chan->mode != M_G703) continue; s = splimp (); CT_LOCK ((bdrv_t *)d->bd); ct_g703_timer (d->chan); CT_UNLOCK ((bdrv_t *)d->bd); splx (s); } } callout_reset (&timeout_handle, hz, ct_timeout, 0); } static void ct_led_off (void *arg) { ct_board_t *b = arg; bdrv_t *bd = ((drv_t *)b->chan->sys)->bd; int s = splimp (); CT_LOCK (bd); ct_led (b, 0); CT_UNLOCK (bd); splx (s); } /* * Activate interupt handler from DDK. */ static void ct_intr (void *arg) { bdrv_t *bd = arg; ct_board_t *b = bd->board; #ifndef NETGRAPH int i; #endif int s = splimp (); CT_LOCK (bd); /* Turn LED on. */ ct_led (b, 1); ct_int_handler (b); /* Turn LED off 50 msec later. */ callout_reset (&led_timo[b->num], hz/20, ct_led_off, b); CT_UNLOCK (bd); splx (s); #ifndef NETGRAPH /* Pass packets in a lock-free state */ for (i = 0; i < NCHAN && b->chan[i].type; i++) { drv_t *d = b->chan[i].sys; struct mbuf *m; if (!d || !d->running) continue; while (_IF_QLEN(&d->queue)) { IF_DEQUEUE (&d->queue,m); if (!m) continue; sppp_input (d->ifp, m); } } #endif } static int probe_irq (ct_board_t *b, int irq) { int mask, busy, cnt; /* Clear pending irq, if any. */ ct_probe_irq (b, -irq); DELAY (100); for (cnt=0; cnt<5; ++cnt) { /* Get the mask of pending irqs, assuming they are busy. * Activate the adapter on given irq. */ busy = ct_probe_irq (b, irq); DELAY (1000); /* Get the mask of active irqs. * Deactivate our irq. */ mask = ct_probe_irq (b, -irq); DELAY (100); if ((mask & ~busy) == 1 << irq) { ct_probe_irq (b, 0); /* printf ("ct%d: irq %d ok, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ return 1; } } /* printf ("ct%d: irq %d not functional, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ ct_probe_irq (b, 0); return 0; } static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; static char dmatab [] = { 7, 6, 5, 0 }; static char irqtab [] = { 5, 10, 11, 7, 3, 15, 12, 0 }; static int ct_is_free_res (device_t dev, int rid, int type, u_long start, u_long end, u_long count) { struct resource *res; if (!(res = bus_alloc_resource (dev, type, &rid, start, end, count, RF_ALLOCATED))) return 0; bus_release_resource (dev, type, rid, res); return 1; } static void ct_identify (driver_t *driver, device_t dev) { u_long iobase, rescount; int devcount; device_t *devices; device_t child; devclass_t my_devclass; int i, k; if ((my_devclass = devclass_find ("ct")) == NULL) return; devclass_get_devices (my_devclass, &devices, &devcount); if (devcount == 0) { /* We should find all devices by our self. We could alter other * devices, but we don't have a choise */ for (i = 0; (iobase = porttab [i]) != 0; i++) { if (!ct_is_free_res (dev, 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (ct_probe_board (iobase, -1, -1) == 0) continue; devcount++; child = BUS_ADD_CHILD (dev, ISA_ORDER_SPECULATIVE, "ct", -1); if (child == NULL) return; device_set_desc_copy (child, "Cronyx Tau-ISA"); device_set_driver (child, driver); bus_set_resource (child, SYS_RES_IOPORT, 0, iobase, NPORT); if (devcount >= NCTAU) break; } } else { static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; /* Lets check user choise. */ for (k = 0; k < devcount; k++) { if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) continue; for (i = 0; porttab [i] != 0; i++) { if (porttab [i] != iobase) continue; if (!ct_is_free_res (devices[k], 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (ct_probe_board (iobase, -1, -1) == 0) continue; porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Tau-ISA"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); devices[k] = 0; continue; } } for (k = 0; k < devcount; k++) { if (devices[k] == 0) continue; if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) == 0) continue; for (i = 0; (iobase = porttab [i]) != 0; i++) { if (porttab [i] == -1) continue; if (!ct_is_free_res (devices[k], 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (ct_probe_board (iobase, -1, -1) == 0) continue; bus_set_resource (devices[k], SYS_RES_IOPORT, 0, iobase, NPORT); porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Tau-ISA"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); } } free (devices, M_TEMP); } return; } static int ct_probe (device_t dev) { int unit = device_get_unit (dev); u_long iobase, rescount; if (!device_get_desc (dev) || strcmp (device_get_desc (dev), "Cronyx Tau-ISA")) return ENXIO; /* KASSERT ((bd != NULL), ("ct%d: NULL device softc\n", unit));*/ if (bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) { printf ("ct%d: Couldn't get IOPORT\n", unit); return ENXIO; } if (!ct_is_free_res (dev, 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) { printf ("ct%d: Resource IOPORT isn't free\n", unit); return ENXIO; } if (!ct_probe_board (iobase, -1, -1)) { printf ("ct%d: probing for Tau-ISA at %lx faild\n", unit, iobase); return ENXIO; } return 0; } static void ct_bus_dmamap_addr (void *arg, bus_dma_segment_t *segs, int nseg, int error) { unsigned long *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; } static int ct_bus_dma_mem_alloc (int bnum, int cnum, ct_dma_mem_t *dmem) { int error; error = bus_dma_tag_create (NULL, 16, 0, BUS_SPACE_MAXADDR_24BIT, BUS_SPACE_MAXADDR, NULL, NULL, dmem->size, 1, dmem->size, 0, NULL, NULL, &dmem->dmat); if (error) { if (cnum >= 0) printf ("ct%d-%d: ", bnum, cnum); else printf ("ct%d: ", bnum); printf ("couldn't allocate tag for dma memory\n"); return 0; } error = bus_dmamem_alloc (dmem->dmat, (void **)&dmem->virt, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &dmem->mapp); if (error) { if (cnum >= 0) printf ("ct%d-%d: ", bnum, cnum); else printf ("ct%d: ", bnum); printf ("couldn't allocate mem for dma memory\n"); bus_dma_tag_destroy (dmem->dmat); return 0; } error = bus_dmamap_load (dmem->dmat, dmem->mapp, dmem->virt, dmem->size, ct_bus_dmamap_addr, &dmem->phys, 0); if (error) { if (cnum >= 0) printf ("ct%d-%d: ", bnum, cnum); else printf ("ct%d: ", bnum); printf ("couldn't load mem map for dma memory\n"); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); return 0; } return 1; } static void ct_bus_dma_mem_free (ct_dma_mem_t *dmem) { bus_dmamap_unload (dmem->dmat, dmem->mapp); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); } /* * The adapter is present, initialize the driver structures. */ static int ct_attach (device_t dev) { bdrv_t *bd = device_get_softc (dev); u_long iobase, drq, irq, rescount; int unit = device_get_unit (dev); char *ct_ln = CT_LOCK_NAME; ct_board_t *b; ct_chan_t *c; drv_t *d; int i; int s; KASSERT ((bd != NULL), ("ct%d: NULL device softc\n", unit)); bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount); bd->base_rid = 0; bd->base_res = bus_alloc_resource (dev, SYS_RES_IOPORT, &bd->base_rid, iobase, iobase + NPORT, NPORT, RF_ACTIVE); if (! bd->base_res) { printf ("ct%d: cannot alloc base address\n", unit); return ENXIO; } if (bus_get_resource (dev, SYS_RES_DRQ, 0, &drq, &rescount) != 0) { for (i = 0; (drq = dmatab [i]) != 0; i++) { if (!ct_is_free_res (dev, 0, SYS_RES_DRQ, drq, drq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_DRQ, 0, drq, 1); break; } if (dmatab[i] == 0) { bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("ct%d: Couldn't get DRQ\n", unit); return ENXIO; } } bd->drq_rid = 0; bd->drq_res = bus_alloc_resource (dev, SYS_RES_DRQ, &bd->drq_rid, drq, drq + 1, 1, RF_ACTIVE); if (! bd->drq_res) { printf ("ct%d: cannot allocate drq\n", unit); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } if (bus_get_resource (dev, SYS_RES_IRQ, 0, &irq, &rescount) != 0) { for (i = 0; (irq = irqtab [i]) != 0; i++) { if (!ct_is_free_res (dev, 0, SYS_RES_IRQ, irq, irq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_IRQ, 0, irq, 1); break; } if (irqtab[i] == 0) { bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("ct%d: Couldn't get IRQ\n", unit); return ENXIO; } } bd->irq_rid = 0; bd->irq_res = bus_alloc_resource (dev, SYS_RES_IRQ, &bd->irq_rid, irq, irq + 1, 1, RF_ACTIVE); if (! bd->irq_res) { printf ("ct%d: Couldn't allocate irq\n", unit); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } b = malloc (sizeof (ct_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("ct:%d: Couldn't allocate memory\n", unit); return (ENXIO); } adapter[unit] = b; bzero (b, sizeof(ct_board_t)); if (! ct_open_board (b, unit, iobase, irq, drq)) { printf ("ct%d: error loading firmware\n", unit); free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } bd->board = b; ct_ln[2] = '0' + unit; mtx_init (&bd->ct_mtx, ct_ln, MTX_NETWORK_LOCK, MTX_DEF|MTX_RECURSE); if (! probe_irq (b, irq)) { printf ("ct%d: irq %ld not functional\n", unit, irq); bd->board = 0; adapter [unit] = 0; free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); mtx_destroy (&bd->ct_mtx); return ENXIO; } callout_init (&led_timo[unit], ct_mpsafenet ? CALLOUT_MPSAFE : 0); s = splimp (); if (bus_setup_intr (dev, bd->irq_res, INTR_TYPE_NET|(ct_mpsafenet?INTR_MPSAFE:0), NULL, ct_intr, bd, &bd->intrhand)) { printf ("ct%d: Can't setup irq %ld\n", unit, irq); bd->board = 0; adapter [unit] = 0; free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); mtx_destroy (&bd->ct_mtx); splx (s); return ENXIO; } CT_LOCK (bd); ct_init_board (b, b->num, b->port, irq, drq, b->type, b->osc); ct_setup_board (b, 0, 0, 0); CT_UNLOCK (bd); printf ("ct%d: , clock %s MHz\n", b->num, b->name, b->osc == 20000000 ? "20" : "16.384"); for (c = b->chan; c < b->chan + NCHAN; ++c) { d = &bd->channel[c->num]; d->dmamem.size = sizeof(ct_buf_t); if (! ct_bus_dma_mem_alloc (unit, c->num, &d->dmamem)) continue; d->board = b; d->chan = c; d->bd = bd; c->sys = d; channel [b->num*NCHAN + c->num] = d; sprintf (d->name, "ct%d.%d", b->num, c->num); #ifdef NETGRAPH if (ng_make_node_common (&typestruct, &d->node) != 0) { printf ("%s: cannot make common node\n", d->name); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; ct_bus_dma_mem_free (&d->dmamem); continue; } NG_NODE_SET_PRIVATE (d->node, d); sprintf (d->nodename, "%s%d", NG_CT_NODE_TYPE, c->board->num*NCHAN + c->num); if (ng_name_node (d->node, d->nodename)) { printf ("%s: cannot name node\n", d->nodename); NG_NODE_UNREF (d->node); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; ct_bus_dma_mem_free (&d->dmamem); continue; } d->queue.ifq_maxlen = IFQ_MAXLEN; d->hi_queue.ifq_maxlen = IFQ_MAXLEN; mtx_init (&d->queue.ifq_mtx, "ct_queue", NULL, MTX_DEF); mtx_init (&d->hi_queue.ifq_mtx, "ct_queue_hi", NULL, MTX_DEF); callout_init (&d->timeout_handle, ct_mpsafenet ? CALLOUT_MPSAFE : 0); #else /*NETGRAPH*/ d->ifp = if_alloc(IFT_PPP); if (d->ifp == NULL) { printf ("%s: cannot if_alloc common interface\n", d->name); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; ct_bus_dma_mem_free (&d->dmamem); continue; } d->ifp->if_softc = d; if_initname (d->ifp, "ct", b->num * NCHAN + c->num); d->ifp->if_mtu = PP_MTU; d->ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; if (!ct_mpsafenet) d->ifp->if_flags |= IFF_NEEDSGIANT; d->ifp->if_ioctl = ct_sioctl; d->ifp->if_start = ct_ifstart; d->ifp->if_watchdog = ct_ifwatchdog; d->ifp->if_init = ct_initialize; d->queue.ifq_maxlen = NBUF; mtx_init (&d->queue.ifq_mtx, "ct_queue", NULL, MTX_DEF); sppp_attach (d->ifp); if_attach (d->ifp); IFP2SP(d->ifp)->pp_tlf = ct_tlf; IFP2SP(d->ifp)->pp_tls = ct_tls; /* If BPF is in the kernel, call the attach for it. * Header size is 4 bytes. */ bpfattach (d->ifp, DLT_PPP, 4); #endif /*NETGRAPH*/ CT_LOCK (bd); ct_start_chan (c, d->dmamem.virt, d->dmamem.phys); ct_register_receive (c, &ct_receive); ct_register_transmit (c, &ct_transmit); ct_register_error (c, &ct_error); CT_UNLOCK (bd); d->devt = make_dev (&ct_cdevsw, b->num*NCHAN+c->num, UID_ROOT, GID_WHEEL, 0600, "ct%d", b->num*NCHAN+c->num); } splx (s); return 0; } static int ct_detach (device_t dev) { bdrv_t *bd = device_get_softc (dev); ct_board_t *b = bd->board; ct_chan_t *c; int s; KASSERT (mtx_initialized (&bd->ct_mtx), ("ct mutex not initialized")); s = splimp (); CT_LOCK (bd); /* Check if the device is busy (open). */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || !d->chan->type) continue; if (d->running) { CT_UNLOCK (bd); splx (s); return EBUSY; } } /* Deactivate the timeout routine. */ callout_stop (&led_timo[b->num]); CT_UNLOCK (bd); bus_teardown_intr (dev, bd->irq_res, bd->intrhand); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); CT_LOCK (bd); ct_close_board (b); CT_UNLOCK (bd); /* Detach the interfaces, free buffer memory. */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || !d->chan->type) continue; #ifdef NETGRAPH if (d->node) { ng_rmnode_self (d->node); NG_NODE_UNREF (d->node); d->node = NULL; } mtx_destroy (&d->queue.ifq_mtx); mtx_destroy (&d->hi_queue.ifq_mtx); #else /* Detach from the packet filter list of interfaces. */ bpfdetach (d->ifp); /* Detach from the sync PPP list. */ sppp_detach (d->ifp); if_detach (d->ifp); if_free (d->ifp); IF_DRAIN (&d->queue); mtx_destroy (&d->queue.ifq_mtx); #endif destroy_dev (d->devt); } CT_LOCK (bd); ct_led_off (b); CT_UNLOCK (bd); callout_drain (&led_timo[b->num]); splx (s); s = splimp (); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || !d->chan->type) continue; /* Deallocate buffers. */ ct_bus_dma_mem_free (&d->dmamem); } bd->board = 0; adapter [b->num] = 0; free (b, M_DEVBUF); splx (s); mtx_destroy (&bd->ct_mtx); return 0; } #ifndef NETGRAPH static void ct_ifstart (struct ifnet *ifp) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->bd; CT_LOCK (bd); ct_start (d); CT_UNLOCK (bd); } static void ct_ifwatchdog (struct ifnet *ifp) { drv_t *d = ifp->if_softc; ct_watchdog (d); } static void ct_tlf (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CT_DEBUG (d, ("ct_tlf\n")); /* ct_set_dtr (d->chan, 0);*/ /* ct_set_rts (d->chan, 0);*/ if (!(sp->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_down (sp); } static void ct_tls (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CT_DEBUG (d, ("ct_tls\n")); if (!(sp->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_up (sp); } /* * Initialization of interface. * Ii seems to be never called by upper level. */ static void ct_initialize (void *softc) { drv_t *d = softc; CT_DEBUG (d, ("ct_initialize\n")); } /* * Process an ioctl request. */ static int ct_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->bd; int error, s, was_up, should_be_up; was_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; error = sppp_ioctl (ifp, cmd, data); if (error) return error; if (! (ifp->if_flags & IFF_DEBUG)) d->chan->debug = 0; else if (! d->chan->debug) d->chan->debug = 1; switch (cmd) { default: CT_DEBUG2 (d, ("ioctl 0x%lx\n", cmd)); return 0; case SIOCADDMULTI: CT_DEBUG2 (d, ("SIOCADDMULTI\n")); return 0; case SIOCDELMULTI: CT_DEBUG2 (d, ("SIOCDELMULTI\n")); return 0; case SIOCSIFFLAGS: CT_DEBUG2 (d, ("SIOCSIFFLAGS\n")); break; case SIOCSIFADDR: CT_DEBUG2 (d, ("SIOCSIFADDR\n")); break; } /* We get here only in case of SIFFLAGS or SIFADDR. */ s = splimp (); CT_LOCK (bd); should_be_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; if (! was_up && should_be_up) { /* Interface goes up -- start it. */ ct_up (d); ct_start (d); } else if (was_up && ! should_be_up) { /* Interface is going down -- stop it. */ /* if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (ifp->if_flags & PP_CISCO))*/ ct_down (d); } CT_UNLOCK (bd); splx (s); return 0; } #endif /*NETGRAPH*/ /* * Stop the interface. Called on splimp(). */ static void ct_down (drv_t *d) { int s = splimp (); CT_DEBUG (d, ("ct_down\n")); ct_set_dtr (d->chan, 0); ct_set_rts (d->chan, 0); d->running = 0; splx (s); } /* * Start the interface. Called on splimp(). */ static void ct_up (drv_t *d) { int s = splimp (); CT_DEBUG (d, ("ct_up\n")); ct_set_dtr (d->chan, 1); ct_set_rts (d->chan, 1); d->running = 1; splx (s); } /* * Start output on the (slave) interface. Get another datagram to send * off of the interface queue, and copy it to the interface * before starting the output. */ static void ct_send (drv_t *d) { struct mbuf *m; u_short len; CT_DEBUG2 (d, ("ct_send, tn=%d\n", d->chan->tn)); /* No output if the interface is down. */ if (! d->running) return; /* No output if the modem is off. */ if (! ct_get_dsr (d->chan) && !ct_get_loop (d->chan)) return; while (ct_buf_free (d->chan)) { /* Get the packet to send. */ #ifdef NETGRAPH IF_DEQUEUE (&d->hi_queue, m); if (! m) IF_DEQUEUE (&d->queue, m); #else m = sppp_dequeue (d->ifp); #endif if (! m) return; #ifndef NETGRAPH BPF_MTAP (d->ifp, m); #endif len = m_length (m, NULL); if (! m->m_next) ct_send_packet (d->chan, (u_char*)mtod (m, caddr_t), len, 0); else { m_copydata (m, 0, len, d->chan->tbuf[d->chan->te]); ct_send_packet (d->chan, d->chan->tbuf[d->chan->te], len, 0); } m_freem (m); /* Set up transmit timeout, if the transmit ring is not empty. * Transmit timeout is 10 seconds. */ #ifdef NETGRAPH d->timeout = 10; #else d->ifp->if_timer = 10; #endif } #ifndef NETGRAPH d->ifp->if_drv_flags |= IFF_DRV_OACTIVE; #endif } /* * Start output on the interface. * Always called on splimp(). */ static void ct_start (drv_t *d) { int s = splimp (); if (d->running) { if (! d->chan->dtr) ct_set_dtr (d->chan, 1); if (! d->chan->rts) ct_set_rts (d->chan, 1); ct_send (d); } splx (s); } /* * Handle transmit timeouts. * Recover after lost transmit interrupts. * Always called on splimp(). */ static void ct_watchdog (drv_t *d) { bdrv_t *bd = d->bd; int s; s = splimp (); CT_LOCK (bd); CT_DEBUG (d, ("device timeout\n")); if (d->running) { ct_setup_chan (d->chan); ct_start_chan (d->chan, 0, 0); ct_set_dtr (d->chan, 1); ct_set_rts (d->chan, 1); ct_start (d); } CT_UNLOCK (bd); splx (s); } /* * Transmit callback function. */ static void ct_transmit (ct_chan_t *c, void *attachment, int len) { drv_t *d = c->sys; if (!d) return; #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_opackets; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; #endif ct_start (d); } /* * Process the received packet. */ static void ct_receive (ct_chan_t *c, char *data, int len) { drv_t *d = c->sys; struct mbuf *m; #ifdef NETGRAPH int error; #endif if (!d || !d->running) return; m = makembuf (data, len); if (! m) { CT_DEBUG (d, ("no memory for packet\n")); #ifndef NETGRAPH ++d->ifp->if_iqdrops; #endif return; } if (c->debug > 1) printmbuf (m); #ifdef NETGRAPH m->m_pkthdr.rcvif = 0; NG_SEND_DATA_ONLY (error, d->hook, m); #else ++d->ifp->if_ipackets; m->m_pkthdr.rcvif = d->ifp; /* Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ BPF_TAP (d->ifp, data, len); IF_ENQUEUE (&d->queue, m); #endif } /* * Error callback function. */ static void ct_error (ct_chan_t *c, int data) { drv_t *d = c->sys; if (!d) return; switch (data) { case CT_FRAME: CT_DEBUG (d, ("frame error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CT_CRC: CT_DEBUG (d, ("crc error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CT_OVERRUN: CT_DEBUG (d, ("overrun error\n")); #ifndef NETGRAPH ++d->ifp->if_collisions; ++d->ifp->if_ierrors; #endif break; case CT_OVERFLOW: CT_DEBUG (d, ("overflow error\n")); #ifndef NETGRAPH ++d->ifp->if_ierrors; #endif break; case CT_UNDERRUN: CT_DEBUG (d, ("underrun error\n")); #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_oerrors; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; #endif ct_start (d); break; default: CT_DEBUG (d, ("error #%d\n", data)); } } static int ct_open (struct cdev *dev, int oflags, int devtype, struct thread *td) { drv_t *d; if (minor(dev) >= NCTAU*NCHAN || ! (d = channel[minor(dev)])) return ENXIO; CT_DEBUG2 (d, ("ct_open\n")); return 0; } static int ct_close (struct cdev *dev, int fflag, int devtype, struct thread *td) { drv_t *d = channel [minor(dev)]; if (!d) return 0; CT_DEBUG2 (d, ("ct_close\n")); return 0; } static int ct_modem_status (ct_chan_t *c) { drv_t *d = c->sys; bdrv_t *bd; int status, s; if (!d) return 0; bd = d->bd; status = d->running ? TIOCM_LE : 0; s = splimp (); CT_LOCK (bd); if (ct_get_cd (c)) status |= TIOCM_CD; if (ct_get_cts (c)) status |= TIOCM_CTS; if (ct_get_dsr (c)) status |= TIOCM_DSR; if (c->dtr) status |= TIOCM_DTR; if (c->rts) status |= TIOCM_RTS; CT_UNLOCK (bd); splx (s); return status; } /* * Process an ioctl request on /dev/cronyx/ctauN. */ static int ct_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { drv_t *d = channel [minor (dev)]; bdrv_t *bd; ct_chan_t *c; struct serial_statistics *st; struct e1_statistics *opte1; int error, s; char mask[16]; if (!d || !d->chan) return 0; bd = d->bd; c = d->chan; switch (cmd) { case SERIAL_GETREGISTERED: bzero (mask, sizeof(mask)); for (s=0; sifp)->pp_flags & PP_FR) ? "fr" : (d->ifp->if_flags & PP_CISCO) ? "cisco" : "ppp"); return 0; case SERIAL_SETPROTO: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (d->ifp->if_drv_flags & IFF_DRV_RUNNING) return EBUSY; if (! strcmp ("cisco", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR); IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; d->ifp->if_flags |= PP_CISCO; } else if (! strcmp ("fr", (char*)data)) { d->ifp->if_flags &= ~(PP_CISCO); IFP2SP(d->ifp)->pp_flags |= PP_FR | PP_KEEPALIVE; } else if (! strcmp ("ppp", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR | PP_KEEPALIVE); d->ifp->if_flags &= ~(PP_CISCO); } else return EINVAL; return 0; case SERIAL_GETKEEPALIVE: if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; *(int*)data = (IFP2SP(d->ifp)->pp_flags & PP_KEEPALIVE) ? 1 : 0; return 0; case SERIAL_SETKEEPALIVE: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; if (*(int*)data) IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; else IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; return 0; #endif /*NETGRAPH*/ case SERIAL_GETMODE: *(int*)data = SERIAL_HDLC; return 0; case SERIAL_GETCFG: if (c->mode == M_HDLC) return EINVAL; switch (ct_get_config (c->board)) { default: *(char*)data = 'a'; break; case CFG_B: *(char*)data = 'b'; break; case CFG_C: *(char*)data = 'c'; break; } return 0; case SERIAL_SETCFG: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_HDLC) return EINVAL; s = splimp (); CT_LOCK (bd); switch (*(char*)data) { case 'a': ct_set_config (c->board, CFG_A); break; case 'b': ct_set_config (c->board, CFG_B); break; case 'c': ct_set_config (c->board, CFG_C); break; } CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSTAT: st = (struct serial_statistics*) data; st->rintr = c->rintr; st->tintr = c->tintr; st->mintr = c->mintr; st->ibytes = c->ibytes; st->ipkts = c->ipkts; st->ierrs = c->ierrs; st->obytes = c->obytes; st->opkts = c->opkts; st->oerrs = c->oerrs; return 0; case SERIAL_GETESTAT: opte1 = (struct e1_statistics*)data; opte1->status = c->status; opte1->cursec = c->cursec; opte1->totsec = c->totsec + c->cursec; opte1->currnt.bpv = c->currnt.bpv; opte1->currnt.fse = c->currnt.fse; opte1->currnt.crce = c->currnt.crce; opte1->currnt.rcrce = c->currnt.rcrce; opte1->currnt.uas = c->currnt.uas; opte1->currnt.les = c->currnt.les; opte1->currnt.es = c->currnt.es; opte1->currnt.bes = c->currnt.bes; opte1->currnt.ses = c->currnt.ses; opte1->currnt.oofs = c->currnt.oofs; opte1->currnt.css = c->currnt.css; opte1->currnt.dm = c->currnt.dm; opte1->total.bpv = c->total.bpv + c->currnt.bpv; opte1->total.fse = c->total.fse + c->currnt.fse; opte1->total.crce = c->total.crce + c->currnt.crce; opte1->total.rcrce = c->total.rcrce + c->currnt.rcrce; opte1->total.uas = c->total.uas + c->currnt.uas; opte1->total.les = c->total.les + c->currnt.les; opte1->total.es = c->total.es + c->currnt.es; opte1->total.bes = c->total.bes + c->currnt.bes; opte1->total.ses = c->total.ses + c->currnt.ses; opte1->total.oofs = c->total.oofs + c->currnt.oofs; opte1->total.css = c->total.css + c->currnt.css; opte1->total.dm = c->total.dm + c->currnt.dm; for (s=0; s<48; ++s) { opte1->interval[s].bpv = c->interval[s].bpv; opte1->interval[s].fse = c->interval[s].fse; opte1->interval[s].crce = c->interval[s].crce; opte1->interval[s].rcrce = c->interval[s].rcrce; opte1->interval[s].uas = c->interval[s].uas; opte1->interval[s].les = c->interval[s].les; opte1->interval[s].es = c->interval[s].es; opte1->interval[s].bes = c->interval[s].bes; opte1->interval[s].ses = c->interval[s].ses; opte1->interval[s].oofs = c->interval[s].oofs; opte1->interval[s].css = c->interval[s].css; opte1->interval[s].dm = c->interval[s].dm; } return 0; case SERIAL_CLRSTAT: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; c->rintr = 0; c->tintr = 0; c->mintr = 0; c->ibytes = 0; c->ipkts = 0; c->ierrs = 0; c->obytes = 0; c->opkts = 0; c->oerrs = 0; bzero (&c->currnt, sizeof (c->currnt)); bzero (&c->total, sizeof (c->total)); bzero (c->interval, sizeof (c->interval)); return 0; case SERIAL_GETBAUD: *(long*)data = ct_get_baud(c); return 0; case SERIAL_SETBAUD: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_baud (c, *(long*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETLOOP: *(int*)data = ct_get_loop (c); return 0; case SERIAL_SETLOOP: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_loop (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDPLL: if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; *(int*)data = ct_get_dpll (c); return 0; case SERIAL_SETDPLL: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; s = splimp (); CT_LOCK (bd); ct_set_dpll (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETNRZI: if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; *(int*)data = ct_get_nrzi (c); return 0; case SERIAL_SETNRZI: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; s = splimp (); CT_LOCK (bd); ct_set_nrzi (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDEBUG: *(int*)data = c->debug; return 0; case SERIAL_SETDEBUG: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; c->debug = *(int*)data; #ifndef NETGRAPH if (d->chan->debug) d->ifp->if_flags |= IFF_DEBUG; else d->ifp->if_flags &= (~IFF_DEBUG); #endif return 0; case SERIAL_GETHIGAIN: if (c->mode != M_E1) return EINVAL; *(int*)data = ct_get_higain (c); return 0; case SERIAL_SETHIGAIN: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_higain (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETPHONY: CT_DEBUG2 (d, ("ioctl: getphony\n")); if (c->mode != M_E1) return EINVAL; *(int*)data = c->gopt.phony; return 0; case SERIAL_SETPHONY: CT_DEBUG2 (d, ("ioctl: setphony\n")); if (c->mode != M_E1) return EINVAL; /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_phony (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETCLK: if (c->mode != M_E1 && c->mode != M_G703) return EINVAL; switch (ct_get_clk(c)) { default: *(int*)data = E1CLK_INTERNAL; break; case GCLK_RCV: *(int*)data = E1CLK_RECEIVE; break; case GCLK_RCLKO: *(int*)data = c->num ? E1CLK_RECEIVE_CHAN0 : E1CLK_RECEIVE_CHAN1; break; } return 0; case SERIAL_SETCLK: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); switch (*(int*)data) { default: ct_set_clk (c, GCLK_INT); break; case E1CLK_RECEIVE: ct_set_clk (c, GCLK_RCV); break; case E1CLK_RECEIVE_CHAN0: case E1CLK_RECEIVE_CHAN1: ct_set_clk (c, GCLK_RCLKO); break; } CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETTIMESLOTS: if (c->mode != M_E1) return EINVAL; *(long*)data = ct_get_ts (c); return 0; case SERIAL_SETTIMESLOTS: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_ts (c, *(long*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSUBCHAN: if (c->mode != M_E1) return EINVAL; *(long*)data = ct_get_subchan (c->board); return 0; case SERIAL_SETSUBCHAN: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splimp (); CT_LOCK (bd); ct_set_subchan (c->board, *(long*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETINVCLK: case SERIAL_GETINVTCLK: if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; *(int*)data = ct_get_invtxc (c); return 0; case SERIAL_GETINVRCLK: if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; *(int*)data = ct_get_invrxc (c); return 0; case SERIAL_SETINVCLK: case SERIAL_SETINVTCLK: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; s = splimp (); CT_LOCK (bd); ct_set_invtxc (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_SETINVRCLK: /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_E1 || c->mode == M_G703) return EINVAL; s = splimp (); CT_LOCK (bd); ct_set_invrxc (c, *(int*)data); CT_UNLOCK (bd); splx (s); return 0; case SERIAL_GETLEVEL: if (c->mode != M_G703) return EINVAL; s = splimp (); CT_LOCK (bd); *(int*)data = ct_get_lq (c); CT_UNLOCK (bd); splx (s); return 0; case TIOCSDTR: /* Set DTR */ s = splimp (); CT_LOCK (bd); ct_set_dtr (c, 1); CT_UNLOCK (bd); splx (s); return 0; case TIOCCDTR: /* Clear DTR */ s = splimp (); CT_LOCK (bd); ct_set_dtr (c, 0); CT_UNLOCK (bd); splx (s); return 0; case TIOCMSET: /* Set DTR/RTS */ s = splimp (); CT_LOCK (bd); ct_set_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0); ct_set_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0); CT_UNLOCK (bd); splx (s); return 0; case TIOCMBIS: /* Add DTR/RTS */ s = splimp (); CT_LOCK (bd); if (*(int*)data & TIOCM_DTR) ct_set_dtr (c, 1); if (*(int*)data & TIOCM_RTS) ct_set_rts (c, 1); CT_UNLOCK (bd); splx (s); return 0; case TIOCMBIC: /* Clear DTR/RTS */ s = splimp (); CT_LOCK (bd); if (*(int*)data & TIOCM_DTR) ct_set_dtr (c, 0); if (*(int*)data & TIOCM_RTS) ct_set_rts (c, 0); CT_UNLOCK (bd); splx (s); return 0; case TIOCMGET: /* Get modem status */ *(int*)data = ct_modem_status (c); return 0; } return ENOTTY; } #ifdef NETGRAPH static int ng_ct_constructor (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); CT_DEBUG (d, ("Constructor\n")); return EINVAL; } static int ng_ct_newhook (node_p node, hook_p hook, const char *name) { int s; drv_t *d = NG_NODE_PRIVATE (node); if (!d) return EINVAL; bdrv_t *bd = d->bd; /* Attach debug hook */ if (strcmp (name, NG_CT_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE (hook, NULL); d->debug_hook = hook; return 0; } /* Check for raw hook */ if (strcmp (name, NG_CT_HOOK_RAW) != 0) return EINVAL; NG_HOOK_SET_PRIVATE (hook, d); d->hook = hook; s = splimp (); CT_LOCK (bd); ct_up (d); CT_UNLOCK (bd); splx (s); return 0; } static char *format_timeslots (u_long s) { static char buf [100]; char *p = buf; int i; for (i=1; i<32; ++i) if ((s >> i) & 1) { int prev = (i > 1) & (s >> (i-1)); int next = (i < 31) & (s >> (i+1)); if (prev) { if (next) continue; *p++ = '-'; } else if (p > buf) *p++ = ','; if (i >= 10) *p++ = '0' + i / 10; *p++ = '0' + i % 10; } *p = 0; return buf; } static int print_modems (char *s, ct_chan_t *c, int need_header) { int status = ct_modem_status (c); int length = 0; if (need_header) length += sprintf (s + length, " LE DTR DSR RTS CTS CD\n"); length += sprintf (s + length, "%4s %4s %4s %4s %4s %4s\n", status & TIOCM_LE ? "On" : "-", status & TIOCM_DTR ? "On" : "-", status & TIOCM_DSR ? "On" : "-", status & TIOCM_RTS ? "On" : "-", status & TIOCM_CTS ? "On" : "-", status & TIOCM_CD ? "On" : "-"); return length; } static int print_stats (char *s, ct_chan_t *c, int need_header) { struct serial_statistics st; int length = 0; st.rintr = c->rintr; st.tintr = c->tintr; st.mintr = c->mintr; st.ibytes = c->ibytes; st.ipkts = c->ipkts; st.ierrs = c->ierrs; st.obytes = c->obytes; st.opkts = c->opkts; st.oerrs = c->oerrs; if (need_header) length += sprintf (s + length, " Rintr Tintr Mintr Ibytes Ipkts Ierrs Obytes Opkts Oerrs\n"); length += sprintf (s + length, "%7ld %7ld %7ld %8ld %7ld %7ld %8ld %7ld %7ld\n", st.rintr, st.tintr, st.mintr, st.ibytes, st.ipkts, st.ierrs, st.obytes, st.opkts, st.oerrs); return length; } static char *format_e1_status (u_char status) { static char buf [80]; if (status & E1_NOALARM) return "Ok"; buf[0] = 0; if (status & E1_LOS) strcat (buf, ",LOS"); if (status & E1_AIS) strcat (buf, ",AIS"); if (status & E1_LOF) strcat (buf, ",LOF"); if (status & E1_LOMF) strcat (buf, ",LOMF"); if (status & E1_FARLOF) strcat (buf, ",FARLOF"); if (status & E1_AIS16) strcat (buf, ",AIS16"); if (status & E1_FARLOMF) strcat (buf, ",FARLOMF"); if (status & E1_TSTREQ) strcat (buf, ",TSTREQ"); if (status & E1_TSTERR) strcat (buf, ",TSTERR"); if (buf[0] == ',') return buf+1; return "Unknown"; } static int print_frac (char *s, int leftalign, u_long numerator, u_long divider) { int n, length = 0; if (numerator < 1 || divider < 1) { length += sprintf (s+length, leftalign ? "/- " : " -"); return length; } n = (int) (0.5 + 1000.0 * numerator / divider); if (n < 1000) { length += sprintf (s+length, leftalign ? "/.%-3d" : " .%03d", n); return length; } *(s + length) = leftalign ? '/' : ' '; length ++; if (n >= 1000000) n = (n+500) / 1000 * 1000; else if (n >= 100000) n = (n+50) / 100 * 100; else if (n >= 10000) n = (n+5) / 10 * 10; switch (n) { case 1000: length += printf (s+length, ".999"); return length; case 10000: n = 9990; break; case 100000: n = 99900; break; case 1000000: n = 999000; break; } if (n < 10000) length += sprintf (s+length, "%d.%d", n/1000, n/10%100); else if (n < 100000) length += sprintf (s+length, "%d.%d", n/1000, n/100%10); else if (n < 1000000) length += sprintf (s+length, "%d.", n/1000); else length += sprintf (s+length, "%d", n/1000); return length; } static int print_e1_stats (char *s, ct_chan_t *c) { struct e1_counters total; u_long totsec; int length = 0; totsec = c->totsec + c->cursec; total.bpv = c->total.bpv + c->currnt.bpv; total.fse = c->total.fse + c->currnt.fse; total.crce = c->total.crce + c->currnt.crce; total.rcrce = c->total.rcrce + c->currnt.rcrce; total.uas = c->total.uas + c->currnt.uas; total.les = c->total.les + c->currnt.les; total.es = c->total.es + c->currnt.es; total.bes = c->total.bes + c->currnt.bes; total.ses = c->total.ses + c->currnt.ses; total.oofs = c->total.oofs + c->currnt.oofs; total.css = c->total.css + c->currnt.css; total.dm = c->total.dm + c->currnt.dm; length += sprintf (s + length, " Unav/Degr Bpv/Fsyn CRC/RCRC Err/Lerr Sev/Bur Oof/Slp Status\n"); /* Unavailable seconds, degraded minutes */ length += print_frac (s + length, 0, c->currnt.uas, c->cursec); length += print_frac (s + length, 1, 60 * c->currnt.dm, c->cursec); /* Bipolar violations, frame sync errors */ length += print_frac (s + length, 0, c->currnt.bpv, c->cursec); length += print_frac (s + length, 1, c->currnt.fse, c->cursec); /* CRC errors, remote CRC errors (E-bit) */ length += print_frac (s + length, 0, c->currnt.crce, c->cursec); length += print_frac (s + length, 1, c->currnt.rcrce, c->cursec); /* Errored seconds, line errored seconds */ length += print_frac (s + length, 0, c->currnt.es, c->cursec); length += print_frac (s + length, 1, c->currnt.les, c->cursec); /* Severely errored seconds, burst errored seconds */ length += print_frac (s + length, 0, c->currnt.ses, c->cursec); length += print_frac (s + length, 1, c->currnt.bes, c->cursec); /* Out of frame seconds, controlled slip seconds */ length += print_frac (s + length, 0, c->currnt.oofs, c->cursec); length += print_frac (s + length, 1, c->currnt.css, c->cursec); length += sprintf (s + length, " %s\n", format_e1_status (c->status)); /* Print total statistics. */ length += print_frac (s + length, 0, total.uas, totsec); length += print_frac (s + length, 1, 60 * total.dm, totsec); length += print_frac (s + length, 0, total.bpv, totsec); length += print_frac (s + length, 1, total.fse, totsec); length += print_frac (s + length, 0, total.crce, totsec); length += print_frac (s + length, 1, total.rcrce, totsec); length += print_frac (s + length, 0, total.es, totsec); length += print_frac (s + length, 1, total.les, totsec); length += print_frac (s + length, 0, total.ses, totsec); length += print_frac (s + length, 1, total.bes, totsec); length += print_frac (s + length, 0, total.oofs, totsec); length += print_frac (s + length, 1, total.css, totsec); length += sprintf (s + length, " -- Total\n"); return length; } static int print_chan (char *s, ct_chan_t *c) { drv_t *d = c->sys; bdrv_t *bd = d->bd; int length = 0; length += sprintf (s + length, "ct%d", c->board->num * NCHAN + c->num); if (d->chan->debug) length += sprintf (s + length, " debug=%d", d->chan->debug); switch (ct_get_config (c->board)) { case CFG_A: length += sprintf (s + length, " cfg=A"); break; case CFG_B: length += sprintf (s + length, " cfg=B"); break; case CFG_C: length += sprintf (s + length, " cfg=C"); break; default: length += sprintf (s + length, " cfg=unknown"); break; } if (ct_get_baud (c)) length += sprintf (s + length, " %ld", ct_get_baud (c)); else length += sprintf (s + length, " extclock"); if (c->mode == M_E1 || c->mode == M_G703) switch (ct_get_clk(c)) { case GCLK_INT : length += sprintf (s + length, " syn=int"); break; case GCLK_RCV : length += sprintf (s + length, " syn=rcv"); break; case GCLK_RCLKO : length += sprintf (s + length, " syn=xrcv"); break; } if (c->mode == M_HDLC) { length += sprintf (s + length, " dpll=%s", ct_get_dpll (c) ? "on" : "off"); length += sprintf (s + length, " nrzi=%s", ct_get_nrzi (c) ? "on" : "off"); length += sprintf (s + length, " invtclk=%s", ct_get_invtxc (c) ? "on" : "off"); length += sprintf (s + length, " invrclk=%s", ct_get_invrxc (c) ? "on" : "off"); } if (c->mode == M_E1) length += sprintf (s + length, " higain=%s", ct_get_higain (c)? "on" : "off"); length += sprintf (s + length, " loop=%s", ct_get_loop (c) ? "on" : "off"); if (c->mode == M_E1) length += sprintf (s + length, " ts=%s", format_timeslots (ct_get_ts(c))); if (c->mode == M_E1 && ct_get_config (c->board) != CFG_A) length += sprintf (s + length, " pass=%s", format_timeslots (ct_get_subchan(c->board))); if (c->mode == M_G703) { int lq, x; x = splimp (); CT_LOCK (bd); lq = ct_get_lq (c); CT_UNLOCK (bd); splx (x); length += sprintf (s + length, " (level=-%.1fdB)", lq / 10.0); } length += sprintf (s + length, "\n"); return length; } static int ng_ct_rcvmsg (node_p node, item_p item, hook_p lasthook) { drv_t *d = NG_NODE_PRIVATE (node); struct ng_mesg *msg; struct ng_mesg *resp = NULL; int error = 0; if (!d) return EINVAL; CT_DEBUG (d, ("Rcvmsg\n")); NGI_GET_MSG (item, msg); switch (msg->header.typecookie) { default: error = EINVAL; break; case NGM_CT_COOKIE: printf ("Don't forget to implement\n"); error = EINVAL; break; case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { default: error = EINVAL; break; case NGM_TEXT_STATUS: { char *s; int l = 0; int dl = sizeof (struct ng_mesg) + 730; NG_MKRESPONSE (resp, msg, dl, M_NOWAIT); if (! resp) { error = ENOMEM; break; } s = (resp)->data; l += print_chan (s + l, d->chan); l += print_stats (s + l, d->chan, 1); l += print_modems (s + l, d->chan, 1); l += print_e1_stats (s + l, d->chan); strncpy ((resp)->header.cmdstr, "status", NG_CMDSTRLEN); } break; } break; } NG_RESPOND_MSG (error, node, item, resp); NG_FREE_MSG (msg); return error; } static int ng_ct_rcvdata (hook_p hook, item_p item) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE(hook)); struct mbuf *m; struct ng_tag_prio *ptag; bdrv_t *bd; struct ifqueue *q; int s; if (!d) return ENETDOWN; bd = d->bd; NGI_GET_M (item, m); NG_FREE_ITEM (item); if (! NG_HOOK_PRIVATE (hook) || ! d) { NG_FREE_M (m); return ENETDOWN; } /* Check for high priority data */ if ((ptag = (struct ng_tag_prio *)m_tag_locate(m, NGM_GENERIC_COOKIE, NG_TAG_PRIO, NULL)) != NULL && (ptag->priority > NG_PRIO_CUTOFF) ) q = &d->hi_queue; else q = &d->queue; s = splimp (); CT_LOCK (bd); IF_LOCK (q); if (_IF_QFULL (q)) { _IF_DROP (q); IF_UNLOCK (q); CT_UNLOCK (bd); splx (s); NG_FREE_M (m); return ENOBUFS; } _IF_ENQUEUE (q, m); IF_UNLOCK (q); ct_start (d); CT_UNLOCK (bd); splx (s); return 0; } static int ng_ct_rmnode (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); bdrv_t *bd; CT_DEBUG (d, ("Rmnode\n")); if (d && d->running) { bd = d->bd; int s = splimp (); CT_LOCK (bd); ct_down (d); CT_UNLOCK (bd); splx (s); } #ifdef KLD_MODULE if (node->nd_flags & NGF_REALLY_DIE) { NG_NODE_SET_PRIVATE (node, NULL); NG_NODE_UNREF (node); } NG_NODE_REVIVE(node); /* Persistant node */ #endif return 0; } static void ng_ct_watchdog (void *arg) { drv_t *d = arg; if (!d) return; if (d->timeout == 1) ct_watchdog (d); if (d->timeout) d->timeout--; callout_reset (&d->timeout_handle, hz, ng_ct_watchdog, d); } static int ng_ct_connect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); if (!d) return 0; callout_reset (&d->timeout_handle, hz, ng_ct_watchdog, d); return 0; } static int ng_ct_disconnect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); bdrv_t *bd; if (!d) return 0; bd = d->bd; CT_LOCK (bd); if (NG_HOOK_PRIVATE (hook)) ct_down (d); CT_UNLOCK (bd); /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&d->timeout_handle)) callout_stop (&d->timeout_handle); return 0; } #endif static int ct_modevent (module_t mod, int type, void *unused) { static int load_count = 0; - if (!debug_mpsafenet && ct_mpsafenet) { - printf ("WORNING! Network stack is not MPSAFE. " - "Turning off debug.ct.mpsafenet.\n"); - ct_mpsafenet = 0; - } if (ct_mpsafenet) ct_cdevsw.d_flags &= ~D_NEEDGIANT; switch (type) { case MOD_LOAD: #ifdef NETGRAPH if (ng_newtype (&typestruct)) printf ("Failed to register ng_ct\n"); #endif ++load_count; callout_init (&timeout_handle, ct_mpsafenet?CALLOUT_MPSAFE:0); callout_reset (&timeout_handle, hz*5, ct_timeout, 0); break; case MOD_UNLOAD: if (load_count == 1) { printf ("Removing device entry for Tau-ISA\n"); #ifdef NETGRAPH ng_rmtype (&typestruct); #endif } /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&timeout_handle)) callout_stop (&timeout_handle); --load_count; break; case MOD_SHUTDOWN: break; } return 0; } #ifdef NETGRAPH static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_CT_NODE_TYPE, .constructor = ng_ct_constructor, .rcvmsg = ng_ct_rcvmsg, .shutdown = ng_ct_rmnode, .newhook = ng_ct_newhook, .connect = ng_ct_connect, .rcvdata = ng_ct_rcvdata, .disconnect = ng_ct_disconnect, }; #endif /*NETGRAPH*/ #ifdef NETGRAPH MODULE_DEPEND (ng_ct, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); #else MODULE_DEPEND (ct, sppp, 1, 1, 1); #endif DRIVER_MODULE (ct, isa, ct_isa_driver, ct_devclass, ct_modevent, NULL); MODULE_VERSION (ct, 1); Index: head/sys/dev/cx/if_cx.c =================================================================== --- head/sys/dev/cx/if_cx.c (revision 171612) +++ head/sys/dev/cx/if_cx.c (revision 171613) @@ -1,2587 +1,2582 @@ /*- * Cronyx-Sigma adapter driver for FreeBSD. * Supports PPP/HDLC and Cisco/HDLC protocol in synchronous mode, * and asyncronous channels with full modem control. * Keepalive protocol implemented in both Cisco and PPP modes. * * Copyright (C) 1994-2002 Cronyx Engineering. * Author: Serge Vakulenko, * * Copyright (C) 1999-2004 Cronyx Engineering. * Rewritten on DDK, ported to NETGRAPH, rewritten for FreeBSD 3.x-5.x by * Kurakin Roman, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations a permission to use, * modify and redistribute this software in source and binary forms, * as long as this message is kept with the software, all derivative * works or modified versions. * * Cronyx Id: if_cx.c,v 1.1.2.34 2004/06/23 17:09:13 rik Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_ng_cronyx.h" #ifdef NETGRAPH_CRONYX # include "opt_netgraph.h" # include # include # include #else # include # include # define PP_CISCO IFF_LINK2 # include #endif #define NCX 1 /* If we don't have Cronyx's sppp version, we don't have fr support via sppp */ #ifndef PP_FR #define PP_FR 0 #endif #define CX_DEBUG(d,s) ({if (d->chan->debug) {\ printf ("%s: ", d->name); printf s;}}) #define CX_DEBUG2(d,s) ({if (d->chan->debug>1) {\ printf ("%s: ", d->name); printf s;}}) #define CX_LOCK_NAME "cxX" static int cx_mpsafenet = 1; TUNABLE_INT("debug.cx.mpsafenet", &cx_mpsafenet); SYSCTL_NODE(_debug, OID_AUTO, cx, CTLFLAG_RD, 0, "Cronyx Sigma Adapters"); SYSCTL_INT(_debug_cx, OID_AUTO, mpsafenet, CTLFLAG_RD, &cx_mpsafenet, 0, "Enable/disable MPSAFE network support for Cronyx Sigma Adapters"); #define CX_LOCK(_bd) do { \ if (cx_mpsafenet) \ mtx_lock (&(_bd)->cx_mtx); \ } while (0) #define CX_UNLOCK(_bd) do { \ if (cx_mpsafenet) \ mtx_unlock (&(_bd)->cx_mtx); \ } while (0) #define CX_LOCK_ASSERT(_bd) do { \ if (cx_mpsafenet) \ mtx_assert (&(_bd)->cx_mtx, MA_OWNED); \ } while (0) typedef struct _async_q { int beg; int end; #define BF_SZ 14400 int buf[BF_SZ+1]; } async_q; #define AQ_GSZ(q) ((BF_SZ + (q)->end - (q)->beg)%BF_SZ) #define AQ_PUSH(q,c) {*((q)->buf + (q)->end) = c;\ (q)->end = ((q)->end + 1)%BF_SZ;} #define AQ_POP(q,c) {c = *((q)->buf + (q)->beg);\ (q)->beg = ((q)->beg + 1)%BF_SZ;} static void cx_identify __P((driver_t *, device_t)); static int cx_probe __P((device_t)); static int cx_attach __P((device_t)); static int cx_detach __P((device_t)); static t_open_t cx_topen; static t_modem_t cx_tmodem; static t_close_t cx_tclose; static device_method_t cx_isa_methods [] = { DEVMETHOD(device_identify, cx_identify), DEVMETHOD(device_probe, cx_probe), DEVMETHOD(device_attach, cx_attach), DEVMETHOD(device_detach, cx_detach), {0, 0} }; typedef struct _cx_dma_mem_t { unsigned long phys; void *virt; size_t size; bus_dma_tag_t dmat; bus_dmamap_t mapp; } cx_dma_mem_t; typedef struct _drv_t { char name [8]; cx_chan_t *chan; cx_board_t *board; cx_dma_mem_t dmamem; struct tty *tty; struct callout dcd_timeout_handle; unsigned callout; unsigned lock; int open_dev; int cd; int running; #ifdef NETGRAPH char nodename [NG_NODELEN+1]; hook_p hook; hook_p debug_hook; node_p node; struct ifqueue lo_queue; struct ifqueue hi_queue; short timeout; struct callout timeout_handle; #else struct ifqueue queue; struct ifnet *ifp; #endif struct cdev *devt; async_q aqueue; #define CX_READ 1 #define CX_WRITE 2 int intr_action; short atimeout; } drv_t; typedef struct _bdrv_t { cx_board_t *board; struct resource *base_res; struct resource *drq_res; struct resource *irq_res; int base_rid; int drq_rid; int irq_rid; void *intrhand; drv_t channel [NCHAN]; struct mtx cx_mtx; } bdrv_t; static driver_t cx_isa_driver = { "cx", cx_isa_methods, sizeof (bdrv_t), }; static devclass_t cx_devclass; extern long csigma_fw_len; extern const char *csigma_fw_version; extern const char *csigma_fw_date; extern const char *csigma_fw_copyright; extern const cr_dat_tst_t csigma_fw_tvec[]; extern const u_char csigma_fw_data[]; static void cx_oproc (struct tty *tp); static int cx_param (struct tty *tp, struct termios *t); static void cx_stop (struct tty *tp, int flag); static void cx_receive (cx_chan_t *c, char *data, int len); static void cx_transmit (cx_chan_t *c, void *attachment, int len); static void cx_error (cx_chan_t *c, int data); static void cx_modem (cx_chan_t *c); static void cx_up (drv_t *d); static void cx_start (drv_t *d); static void cx_softintr (void *); static void *cx_fast_ih; static void cx_down (drv_t *d); static void cx_watchdog (drv_t *d); static void cx_carrier (void *arg); #ifdef NETGRAPH extern struct ng_type typestruct; #else static void cx_ifstart (struct ifnet *ifp); static void cx_tlf (struct sppp *sp); static void cx_tls (struct sppp *sp); static void cx_ifwatchdog (struct ifnet *ifp); static int cx_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data); static void cx_initialize (void *softc); #endif static cx_board_t *adapter [NCX]; static drv_t *channel [NCX*NCHAN]; static struct callout led_timo [NCX]; static struct callout timeout_handle; static int cx_open (struct cdev *dev, int flag, int mode, struct thread *td); static int cx_close (struct cdev *dev, int flag, int mode, struct thread *td); static int cx_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td); static struct cdevsw cx_cdevsw = { .d_version = D_VERSION, .d_open = cx_open, .d_close = cx_close, .d_ioctl = cx_ioctl, .d_name = "cx", .d_flags = D_TTY | D_NEEDGIANT, }; static int MY_SOFT_INTR; /* * Print the mbuf chain, for debug purposes only. */ static void printmbuf (struct mbuf *m) { printf ("mbuf:"); for (; m; m=m->m_next) { if (m->m_flags & M_PKTHDR) printf (" HDR %d:", m->m_pkthdr.len); if (m->m_flags & M_EXT) printf (" EXT:"); printf (" %d", m->m_len); } printf ("\n"); } /* * Make an mbuf from data. */ static struct mbuf *makembuf (void *buf, u_int len) { struct mbuf *m, *o, *p; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return 0; if (len >= MINCLSIZE) MCLGET (m, M_DONTWAIT); m->m_pkthdr.len = len; m->m_len = 0; p = m; while (len) { u_int n = M_TRAILINGSPACE (p); if (n > len) n = len; if (! n) { /* Allocate new mbuf. */ o = p; MGET (p, M_DONTWAIT, MT_DATA); if (! p) { m_freem (m); return 0; } if (len >= MINCLSIZE) MCLGET (p, M_DONTWAIT); p->m_len = 0; o->m_next = p; n = M_TRAILINGSPACE (p); if (n > len) n = len; } bcopy (buf, mtod (p, caddr_t) + p->m_len, n); p->m_len += n; buf = n + (char*) buf; len -= n; } return m; } /* * Recover after lost transmit interrupts. */ static void cx_timeout (void *arg) { drv_t *d; int s, i, k; for (i = 0; i < NCX; i++) { if (adapter[i] == NULL) continue; for (k = 0; k < NCHAN; ++k) { d = channel[i * NCHAN + k]; if (! d) continue; s = splhigh (); CX_LOCK ((bdrv_t *)d->board->sys); if (d->atimeout == 1 && d->tty && d->tty->t_state & TS_BUSY) { d->tty->t_state &= ~TS_BUSY; if (d->tty->t_dev) { d->intr_action |= CX_WRITE; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } CX_DEBUG (d, ("cx_timeout\n")); } if (d->atimeout) d->atimeout--; CX_UNLOCK ((bdrv_t *)d->board->sys); splx (s); } } callout_reset (&timeout_handle, hz*5, cx_timeout, 0); } static void cx_led_off (void *arg) { cx_board_t *b = arg; bdrv_t *bd = b->sys; int s; s = splhigh (); CX_LOCK (bd); cx_led (b, 0); CX_UNLOCK (bd); splx (s); } /* * Activate interupt handler from DDK. */ static void cx_intr (void *arg) { bdrv_t *bd = arg; cx_board_t *b = bd->board; #ifndef NETGRAPH int i; #endif int s = splhigh (); CX_LOCK (bd); /* Turn LED on. */ cx_led (b, 1); cx_int_handler (b); /* Turn LED off 50 msec later. */ callout_reset (&led_timo[b->num], hz/20, cx_led_off, b); CX_UNLOCK (bd); splx (s); #ifndef NETGRAPH /* Pass packets in a lock-free state */ for (i = 0; i < NCHAN && b->chan[i].type; i++) { drv_t *d = b->chan[i].sys; struct mbuf *m; if (!d || !d->running) continue; while (_IF_QLEN(&d->queue)) { IF_DEQUEUE (&d->queue,m); if (!m) continue; sppp_input (d->ifp, m); } } #endif } static int probe_irq (cx_board_t *b, int irq) { int mask, busy, cnt; /* Clear pending irq, if any. */ cx_probe_irq (b, -irq); DELAY (100); for (cnt=0; cnt<5; ++cnt) { /* Get the mask of pending irqs, assuming they are busy. * Activate the adapter on given irq. */ busy = cx_probe_irq (b, irq); DELAY (100); /* Get the mask of active irqs. * Deactivate our irq. */ mask = cx_probe_irq (b, -irq); DELAY (100); if ((mask & ~busy) == 1 << irq) { cx_probe_irq (b, 0); /* printf ("cx%d: irq %d ok, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ return 1; } } /* printf ("cx%d: irq %d not functional, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ cx_probe_irq (b, 0); return 0; } static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; static char dmatab [] = { 7, 6, 5, 0 }; static char irqtab [] = { 5, 10, 11, 7, 3, 15, 12, 0 }; static int cx_is_free_res (device_t dev, int rid, int type, u_long start, u_long end, u_long count) { struct resource *res; if (!(res = bus_alloc_resource (dev, type, &rid, start, end, count, RF_ALLOCATED))) return 0; bus_release_resource (dev, type, rid, res); return 1; } static void cx_identify (driver_t *driver, device_t dev) { u_long iobase, rescount; int devcount; device_t *devices; device_t child; devclass_t my_devclass; int i, k; if ((my_devclass = devclass_find ("cx")) == NULL) return; devclass_get_devices (my_devclass, &devices, &devcount); if (devcount == 0) { /* We should find all devices by our self. We could alter other * devices, but we don't have a choise */ for (i = 0; (iobase = porttab [i]) != 0; i++) { if (!cx_is_free_res (dev, 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; devcount++; child = BUS_ADD_CHILD (dev, ISA_ORDER_SPECULATIVE, "cx", -1); if (child == NULL) return; device_set_desc_copy (child, "Cronyx Sigma"); device_set_driver (child, driver); bus_set_resource (child, SYS_RES_IOPORT, 0, iobase, NPORT); if (devcount >= NCX) break; } } else { static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; /* Lets check user choise. */ for (k = 0; k < devcount; k++) { if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) continue; for (i = 0; porttab [i] != 0; i++) { if (porttab [i] != iobase) continue; if (!cx_is_free_res (devices[k], 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Sigma"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); devices[k] = 0; continue; } } for (k = 0; k < devcount; k++) { if (devices[k] == 0) continue; if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) == 0) continue; for (i = 0; (iobase = porttab [i]) != 0; i++) { if (porttab [i] == -1) { continue; } if (!cx_is_free_res (devices[k], 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; bus_set_resource (devices[k], SYS_RES_IOPORT, 0, iobase, NPORT); porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Sigma"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); } } free (devices, M_TEMP); } return; } static int cx_probe (device_t dev) { int unit = device_get_unit (dev); int i; u_long iobase, rescount; if (!device_get_desc (dev) || strcmp (device_get_desc (dev), "Cronyx Sigma")) return ENXIO; if (bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) { printf ("cx%d: Couldn't get IOPORT\n", unit); return ENXIO; } if (!cx_is_free_res (dev, 0, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) { printf ("cx%d: Resource IOPORT isn't free %lx\n", unit, iobase); return ENXIO; } for (i = 0; porttab [i] != 0; i++) { if (porttab [i] == iobase) { porttab [i] = -1; break; } } if (porttab [i] == 0) { return ENXIO; } if (!cx_probe_board (iobase, -1, -1)) { printf ("cx%d: probing for Sigma at %lx faild\n", unit, iobase); return ENXIO; } return 0; } static void cx_bus_dmamap_addr (void *arg, bus_dma_segment_t *segs, int nseg, int error) { unsigned long *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; } static int cx_bus_dma_mem_alloc (int bnum, int cnum, cx_dma_mem_t *dmem) { int error; error = bus_dma_tag_create (NULL, 16, 0, BUS_SPACE_MAXADDR_24BIT, BUS_SPACE_MAXADDR, NULL, NULL, dmem->size, 1, dmem->size, 0, NULL, NULL, &dmem->dmat); if (error) { if (cnum >= 0) printf ("cx%d-%d: ", bnum, cnum); else printf ("cx%d: ", bnum); printf ("couldn't allocate tag for dma memory\n"); return 0; } error = bus_dmamem_alloc (dmem->dmat, (void **)&dmem->virt, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &dmem->mapp); if (error) { if (cnum >= 0) printf ("cx%d-%d: ", bnum, cnum); else printf ("cx%d: ", bnum); printf ("couldn't allocate mem for dma memory\n"); bus_dma_tag_destroy (dmem->dmat); return 0; } error = bus_dmamap_load (dmem->dmat, dmem->mapp, dmem->virt, dmem->size, cx_bus_dmamap_addr, &dmem->phys, 0); if (error) { if (cnum >= 0) printf ("cx%d-%d: ", bnum, cnum); else printf ("cx%d: ", bnum); printf ("couldn't load mem map for dma memory\n"); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); return 0; } return 1; } static void cx_bus_dma_mem_free (cx_dma_mem_t *dmem) { bus_dmamap_unload (dmem->dmat, dmem->mapp); bus_dmamem_free (dmem->dmat, dmem->virt, dmem->mapp); bus_dma_tag_destroy (dmem->dmat); } /* * The adapter is present, initialize the driver structures. */ static int cx_attach (device_t dev) { bdrv_t *bd = device_get_softc (dev); u_long iobase, drq, irq, rescount; int unit = device_get_unit (dev); char *cx_ln = CX_LOCK_NAME; cx_board_t *b; cx_chan_t *c; drv_t *d; int i; int s; KASSERT ((bd != NULL), ("cx%d: NULL device softc\n", unit)); bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount); bd->base_rid = 0; bd->base_res = bus_alloc_resource (dev, SYS_RES_IOPORT, &bd->base_rid, iobase, iobase + NPORT, NPORT, RF_ACTIVE); if (! bd->base_res) { printf ("cx%d: cannot allocate base address\n", unit); return ENXIO; } if (bus_get_resource (dev, SYS_RES_DRQ, 0, &drq, &rescount) != 0) { for (i = 0; (drq = dmatab [i]) != 0; i++) { if (!cx_is_free_res (dev, 0, SYS_RES_DRQ, drq, drq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_DRQ, 0, drq, 1); break; } if (dmatab[i] == 0) { bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("cx%d: Couldn't get DRQ\n", unit); return ENXIO; } } bd->drq_rid = 0; bd->drq_res = bus_alloc_resource (dev, SYS_RES_DRQ, &bd->drq_rid, drq, drq + 1, 1, RF_ACTIVE); if (! bd->drq_res) { printf ("cx%d: cannot allocate drq\n", unit); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } if (bus_get_resource (dev, SYS_RES_IRQ, 0, &irq, &rescount) != 0) { for (i = 0; (irq = irqtab [i]) != 0; i++) { if (!cx_is_free_res (dev, 0, SYS_RES_IRQ, irq, irq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_IRQ, 0, irq, 1); break; } if (irqtab[i] == 0) { bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("cx%d: Couldn't get IRQ\n", unit); return ENXIO; } } bd->irq_rid = 0; bd->irq_res = bus_alloc_resource (dev, SYS_RES_IRQ, &bd->irq_rid, irq, irq + 1, 1, RF_ACTIVE); if (! bd->irq_res) { printf ("cx%d: Couldn't allocate irq\n", unit); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } b = malloc (sizeof (cx_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("cx:%d: Couldn't allocate memory\n", unit); return (ENXIO); } adapter[unit] = b; bzero (b, sizeof(cx_board_t)); if (! cx_open_board (b, unit, iobase, irq, drq)) { printf ("cx%d: error loading firmware\n", unit); free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } bd->board = b; cx_ln[2] = '0' + unit; mtx_init (&bd->cx_mtx, cx_ln, MTX_NETWORK_LOCK, MTX_DEF|MTX_RECURSE); if (! probe_irq (b, irq)) { printf ("cx%d: irq %ld not functional\n", unit, irq); bd->board = 0; adapter [unit] = 0; mtx_destroy (&bd->cx_mtx); free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } b->sys = bd; callout_init (&led_timo[b->num], cx_mpsafenet ? CALLOUT_MPSAFE : 0); s = splhigh (); if (bus_setup_intr (dev, bd->irq_res, INTR_TYPE_NET|(cx_mpsafenet?INTR_MPSAFE:0), NULL, cx_intr, bd, &bd->intrhand)) { printf ("cx%d: Can't setup irq %ld\n", unit, irq); bd->board = 0; b->sys = 0; adapter [unit] = 0; mtx_destroy (&bd->cx_mtx); free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); splx (s); return ENXIO; } CX_LOCK (bd); cx_init (b, b->num, b->port, irq, drq); cx_setup_board (b, 0, 0, 0); CX_UNLOCK (bd); printf ("cx%d: \n", b->num, b->name); for (c=b->chan; cchan+NCHAN; ++c) { if (c->type == T_NONE) continue; d = &bd->channel[c->num]; d->dmamem.size = sizeof(cx_buf_t); if (! cx_bus_dma_mem_alloc (unit, c->num, &d->dmamem)) continue; d->board = b; d->chan = c; d->open_dev = 0; c->sys = d; channel [b->num*NCHAN + c->num] = d; sprintf (d->name, "cx%d.%d", b->num, c->num); switch (c->type) { case T_SYNC_RS232: case T_SYNC_V35: case T_SYNC_RS449: case T_UNIV: case T_UNIV_RS232: case T_UNIV_RS449: case T_UNIV_V35: #ifdef NETGRAPH if (ng_make_node_common (&typestruct, &d->node) != 0) { printf ("%s: cannot make common node\n", d->name); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; cx_bus_dma_mem_free (&d->dmamem); continue; } NG_NODE_SET_PRIVATE (d->node, d); sprintf (d->nodename, "%s%d", NG_CX_NODE_TYPE, c->board->num*NCHAN + c->num); if (ng_name_node (d->node, d->nodename)) { printf ("%s: cannot name node\n", d->nodename); NG_NODE_UNREF (d->node); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; cx_bus_dma_mem_free (&d->dmamem); continue; } d->lo_queue.ifq_maxlen = IFQ_MAXLEN; d->hi_queue.ifq_maxlen = IFQ_MAXLEN; mtx_init (&d->lo_queue.ifq_mtx, "cx_queue_lo", NULL, MTX_DEF); mtx_init (&d->hi_queue.ifq_mtx, "cx_queue_hi", NULL, MTX_DEF); callout_init (&d->timeout_handle, cx_mpsafenet ? CALLOUT_MPSAFE : 0); #else /*NETGRAPH*/ d->ifp = if_alloc(IFT_PPP); if (d->ifp == NULL) { printf ("%s: cannot if_alloc() common interface\n", d->name); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; cx_bus_dma_mem_free (&d->dmamem); continue; } d->ifp->if_softc = d; if_initname (d->ifp, "cx", b->num * NCHAN + c->num); d->ifp->if_mtu = PP_MTU; d->ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; if (!cx_mpsafenet) d->ifp->if_flags |= IFF_NEEDSGIANT; d->ifp->if_ioctl = cx_sioctl; d->ifp->if_start = cx_ifstart; d->ifp->if_watchdog = cx_ifwatchdog; d->ifp->if_init = cx_initialize; d->queue.ifq_maxlen = 2; mtx_init (&d->queue.ifq_mtx, "cx_queue", NULL, MTX_DEF); sppp_attach (d->ifp); if_attach (d->ifp); IFP2SP(d->ifp)->pp_tlf = cx_tlf; IFP2SP(d->ifp)->pp_tls = cx_tls; /* If BPF is in the kernel, call the attach for it. * Size of PPP header is 4 bytes. */ bpfattach (d->ifp, DLT_PPP, 4); #endif /*NETGRAPH*/ } d->tty = ttyalloc (); d->tty->t_open = cx_topen; d->tty->t_close = cx_tclose; d->tty->t_param = cx_param; d->tty->t_stop = cx_stop; d->tty->t_modem = cx_tmodem; d->tty->t_oproc = cx_oproc; d->tty->t_sc = d; CX_LOCK (bd); cx_start_chan (c, d->dmamem.virt, d->dmamem.phys); cx_register_receive (c, &cx_receive); cx_register_transmit (c, &cx_transmit); cx_register_error (c, &cx_error); cx_register_modem (c, &cx_modem); CX_UNLOCK (bd); ttycreate(d->tty, TS_CALLOUT, "x%r%r", b->num, c->num); d->devt = make_dev (&cx_cdevsw, b->num*NCHAN + c->num + 64, UID_ROOT, GID_WHEEL, 0600, "cx%d", b->num*NCHAN + c->num); d->devt->si_drv1 = d; callout_init (&d->dcd_timeout_handle, cx_mpsafenet ? CALLOUT_MPSAFE : 0); } splx (s); return 0; } static int cx_detach (device_t dev) { bdrv_t *bd = device_get_softc (dev); cx_board_t *b = bd->board; cx_chan_t *c; int s; KASSERT (mtx_initialized (&bd->cx_mtx), ("cx mutex not initialized")); s = splhigh (); CX_LOCK (bd); /* Check if the device is busy (open). */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; if (d->lock) { CX_UNLOCK (bd); splx (s); return EBUSY; } if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (d->open_dev|0x2)) { CX_UNLOCK (bd); splx (s); return EBUSY; } if (d->running) { CX_UNLOCK (bd); splx (s); return EBUSY; } } /* Deactivate the timeout routine. And soft interrupt*/ callout_stop (&led_timo[b->num]); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = c->sys; if (!d || d->chan->type == T_NONE) continue; callout_stop (&d->dcd_timeout_handle); } CX_UNLOCK (bd); bus_teardown_intr (dev, bd->irq_res, bd->intrhand); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); CX_LOCK (bd); cx_close_board (b); /* Detach the interfaces, free buffer memory. */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; if (d->tty) { ttyfree (d->tty); d->tty = NULL; } #ifdef NETGRAPH if (d->node) { ng_rmnode_self (d->node); NG_NODE_UNREF (d->node); d->node = NULL; } mtx_destroy (&d->lo_queue.ifq_mtx); mtx_destroy (&d->hi_queue.ifq_mtx); #else /* Detach from the packet filter list of interfaces. */ bpfdetach (d->ifp); /* Detach from the sync PPP list. */ sppp_detach (d->ifp); if_detach (d->ifp); if_free(d->ifp); /* XXXRIK: check interconnection with irq handler */ IF_DRAIN (&d->queue); mtx_destroy (&d->queue.ifq_mtx); #endif destroy_dev (d->devt); } cx_led_off (b); CX_UNLOCK (bd); callout_drain (&led_timo[b->num]); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = c->sys; if (!d || d->chan->type == T_NONE) continue; callout_drain (&d->dcd_timeout_handle); } splx (s); s = splhigh (); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; /* Deallocate buffers. */ cx_bus_dma_mem_free (&d->dmamem); } bd->board = 0; adapter [b->num] = 0; free (b, M_DEVBUF); splx (s); mtx_destroy (&bd->cx_mtx); return 0; } #ifndef NETGRAPH static void cx_ifstart (struct ifnet *ifp) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; CX_LOCK (bd); cx_start (d); CX_UNLOCK (bd); } static void cx_ifwatchdog (struct ifnet *ifp) { drv_t *d = ifp->if_softc; cx_watchdog (d); } static void cx_tlf (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CX_DEBUG (d, ("cx_tlf\n")); /* cx_set_dtr (d->chan, 0);*/ /* cx_set_rts (d->chan, 0);*/ if (!(IFP2SP(d->ifp)->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_down (sp); } static void cx_tls (struct sppp *sp) { drv_t *d = SP2IFP(sp)->if_softc; CX_DEBUG (d, ("cx_tls\n")); if (!(IFP2SP(d->ifp)->pp_flags & PP_FR) && !(d->ifp->if_flags & PP_CISCO)) sp->pp_up (sp); } /* * Initialization of interface. * It seems to be never called by upper level. */ static void cx_initialize (void *softc) { drv_t *d = softc; CX_DEBUG (d, ("cx_initialize\n")); } /* * Process an ioctl request. */ static int cx_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data) { drv_t *d = ifp->if_softc; bdrv_t *bd = d->board->sys; int error, s, was_up, should_be_up; /* No socket ioctls while the channel is in async mode. */ if (d->chan->type == T_NONE || d->chan->mode == M_ASYNC) return EBUSY; /* Socket ioctls on slave subchannels are not allowed. */ was_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; error = sppp_ioctl (ifp, cmd, data); if (error) return error; if (! (ifp->if_flags & IFF_DEBUG)) d->chan->debug = 0; else if (! d->chan->debug) d->chan->debug = 1; switch (cmd) { default: CX_DEBUG2 (d, ("ioctl 0x%lx\n", cmd)); return 0; case SIOCADDMULTI: CX_DEBUG2 (d, ("SIOCADDMULTI\n")); return 0; case SIOCDELMULTI: CX_DEBUG2 (d, ("SIOCDELMULTI\n")); return 0; case SIOCSIFFLAGS: CX_DEBUG2 (d, ("SIOCSIFFLAGS\n")); break; case SIOCSIFADDR: CX_DEBUG2 (d, ("SIOCSIFADDR\n")); break; } /* We get here only in case of SIFFLAGS or SIFADDR. */ s = splhigh (); CX_LOCK (bd); should_be_up = (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; if (!was_up && should_be_up) { /* Interface goes up -- start it. */ cx_up (d); cx_start (d); } else if (was_up && !should_be_up) { /* Interface is going down -- stop it. */ /* if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (ifp->if_flags & PP_CISCO))*/ cx_down (d); } CX_UNLOCK (bd); splx (s); return 0; } #endif /*NETGRAPH*/ /* * Stop the interface. Called on splimp(). */ static void cx_down (drv_t *d) { int s = splhigh (); CX_DEBUG (d, ("cx_down\n")); cx_set_dtr (d->chan, 0); cx_set_rts (d->chan, 0); d->running = 0; splx (s); } /* * Start the interface. Called on splimp(). */ static void cx_up (drv_t *d) { int s = splhigh (); CX_DEBUG (d, ("cx_up\n")); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); d->running = 1; splx (s); } /* * Start output on the (slave) interface. Get another datagram to send * off of the interface queue, and copy it to the interface * before starting the output. */ static void cx_send (drv_t *d) { struct mbuf *m; u_short len; CX_DEBUG2 (d, ("cx_send\n")); /* No output if the interface is down. */ if (! d->running) return; /* No output if the modem is off. */ if (! cx_get_dsr (d->chan) && ! cx_get_loop(d->chan)) return; if (cx_buf_free (d->chan)) { /* Get the packet to send. */ #ifdef NETGRAPH IF_DEQUEUE (&d->hi_queue, m); if (! m) IF_DEQUEUE (&d->lo_queue, m); #else m = sppp_dequeue (d->ifp); #endif if (! m) return; #ifndef NETGRAPH BPF_MTAP (d->ifp, m); #endif len = m_length (m, NULL); if (! m->m_next) cx_send_packet (d->chan, (u_char*)mtod (m, caddr_t), len, 0); else { u_char buf [DMABUFSZ]; m_copydata (m, 0, len, buf); cx_send_packet (d->chan, buf, len, 0); } m_freem (m); /* Set up transmit timeout, 10 seconds. */ #ifdef NETGRAPH d->timeout = 10; #else d->ifp->if_timer = 10; #endif } #ifndef NETGRAPH d->ifp->if_drv_flags |= IFF_DRV_OACTIVE; #endif } /* * Start output on the interface. * Always called on splimp(). */ static void cx_start (drv_t *d) { int s = splhigh (); if (d->running) { if (! d->chan->dtr) cx_set_dtr (d->chan, 1); if (! d->chan->rts) cx_set_rts (d->chan, 1); cx_send (d); } splx (s); } /* * Handle transmit timeouts. * Recover after lost transmit interrupts. * Always called on splimp(). */ static void cx_watchdog (drv_t *d) { bdrv_t *bd = d->board->sys; int s = splhigh (); CX_LOCK (bd); CX_DEBUG (d, ("device timeout\n")); if (d->running) { cx_setup_chan (d->chan); cx_start_chan (d->chan, 0, 0); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); cx_start (d); } CX_UNLOCK (bd); splx (s); } /* * Transmit callback function. */ static void cx_transmit (cx_chan_t *c, void *attachment, int len) { drv_t *d = c->sys; if (!d) return; if (c->mode == M_ASYNC && d->tty) { d->tty->t_state &= ~(TS_BUSY | TS_FLUSH); d->atimeout = 0; if (d->tty->t_dev) { d->intr_action |= CX_WRITE; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } return; } #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_opackets; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; #endif cx_start (d); } /* * Process the received packet. */ static void cx_receive (cx_chan_t *c, char *data, int len) { drv_t *d = c->sys; struct mbuf *m; char *cc = data; #ifdef NETGRAPH int error; #endif if (!d) return; if (c->mode == M_ASYNC && d->tty) { if (d->tty->t_state & TS_ISOPEN) { async_q *q = &d->aqueue; int size = BF_SZ - 1 - AQ_GSZ (q); if (len <= 0 && !size) return; if (len > size) { c->ierrs++; cx_error (c, CX_OVERRUN); len = size - 1; } while (len--) { AQ_PUSH (q, *(unsigned char *)cc); cc++; } d->intr_action |= CX_READ; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } return; } if (! d->running) return; m = makembuf (data, len); if (! m) { CX_DEBUG (d, ("no memory for packet\n")); #ifndef NETGRAPH ++d->ifp->if_iqdrops; #endif return; } if (c->debug > 1) printmbuf (m); #ifdef NETGRAPH m->m_pkthdr.rcvif = 0; NG_SEND_DATA_ONLY (error, d->hook, m); #else ++d->ifp->if_ipackets; m->m_pkthdr.rcvif = d->ifp; /* Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ BPF_TAP (d->ifp, data, len); IF_ENQUEUE (&d->queue, m); #endif } #define CONDITION(t,tp) (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON))\ && (!(tp->t_iflag & BRKINT) || (tp->t_iflag & IGNBRK))\ && (!(tp->t_iflag & PARMRK)\ || (tp->t_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))\ && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN))\ && linesw[tp->t_line]->l_rint == ttyinput) /* * Error callback function. */ static void cx_error (cx_chan_t *c, int data) { drv_t *d = c->sys; async_q *q; if (!d) return; q = &(d->aqueue); switch (data) { case CX_FRAME: CX_DEBUG (d, ("frame error\n")); if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty->t_termios), (d->tty)) || !(d->tty->t_iflag & (IGNPAR | PARMRK)))) { AQ_PUSH (q, TTY_FE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } #ifndef NETGRAPH else ++d->ifp->if_ierrors; #endif break; case CX_CRC: CX_DEBUG (d, ("crc error\n")); if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty->t_termios), (d->tty)) || !(d->tty->t_iflag & INPCK) || !(d->tty->t_iflag & (IGNPAR | PARMRK)))) { AQ_PUSH (q, TTY_PE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } #ifndef NETGRAPH else ++d->ifp->if_ierrors; #endif break; case CX_OVERRUN: CX_DEBUG (d, ("overrun error\n")); #ifdef TTY_OE if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty->t_termios), (d->tty)))) { AQ_PUSH (q, TTY_OE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } #endif #ifndef NETGRAPH else { ++d->ifp->if_collisions; ++d->ifp->if_ierrors; } #endif break; case CX_OVERFLOW: CX_DEBUG (d, ("overflow error\n")); #ifndef NETGRAPH if (c->mode != M_ASYNC) ++d->ifp->if_ierrors; #endif break; case CX_UNDERRUN: CX_DEBUG (d, ("underrun error\n")); if (c->mode != M_ASYNC) { #ifdef NETGRAPH d->timeout = 0; #else ++d->ifp->if_oerrors; d->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; d->ifp->if_timer = 0; cx_start (d); #endif } break; case CX_BREAK: CX_DEBUG (d, ("break error\n")); if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty->t_termios), (d->tty)) || !(d->tty->t_iflag & (IGNBRK | BRKINT | PARMRK)))) { AQ_PUSH (q, TTY_BI); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; swi_sched (cx_fast_ih, 0); } #ifndef NETGRAPH else ++d->ifp->if_ierrors; #endif break; default: CX_DEBUG (d, ("error #%d\n", data)); } } static int cx_topen (struct tty *tp, struct cdev *dev) { bdrv_t *bd; drv_t *d; d = tp->t_sc; CX_DEBUG2 (d, ("cx_open (serial)\n")); bd = d->board->sys; if (d->chan->mode != M_ASYNC) return (EBUSY); d->open_dev |= 0x2; CX_LOCK (bd); cx_start_chan (d->chan, 0, 0); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); d->cd = cx_get_cd (d->chan); CX_UNLOCK (bd); CX_DEBUG2 (d, ("cx_open done\n")); return 0; } static void cx_tclose (struct tty *tp) { drv_t *d; bdrv_t *bd; d = tp->t_sc; CX_DEBUG2 (d, ("cx_close\n")); bd = d->board->sys; CX_LOCK (bd); /* Disable receiver. * Transmitter continues sending the queued data. */ cx_enable_receive (d->chan, 0); CX_UNLOCK (bd); d->open_dev &= ~0x2; } static int cx_tmodem (struct tty *tp, int sigon, int sigoff) { drv_t *d; bdrv_t *bd; d = tp->t_sc; bd = d->board->sys; CX_LOCK (bd); if (!sigon && !sigoff) { if (cx_get_dsr (d->chan)) sigon |= SER_DSR; if (cx_get_cd (d->chan)) sigon |= SER_DCD; if (cx_get_cts (d->chan)) sigon |= SER_CTS; if (d->chan->dtr) sigon |= SER_DTR; if (d->chan->rts) sigon |= SER_RTS; CX_UNLOCK (bd); return sigon; } if (sigon & SER_DTR) cx_set_dtr (d->chan, 1); if (sigoff & SER_DTR) cx_set_dtr (d->chan, 0); if (sigon & SER_RTS) cx_set_rts (d->chan, 1); if (sigoff & SER_RTS) cx_set_rts (d->chan, 0); CX_UNLOCK (bd); return (0); } static int cx_open (struct cdev *dev, int flag, int mode, struct thread *td) { int unit; drv_t *d; d = dev->si_drv1; unit = d->chan->num; CX_DEBUG2 (d, ("cx_open unit=%d, flag=0x%x, mode=0x%x\n", unit, flag, mode)); d->open_dev |= 0x1; CX_DEBUG2 (d, ("cx_open done\n")); return 0; } static int cx_close (struct cdev *dev, int flag, int mode, struct thread *td) { drv_t *d; d = dev->si_drv1; CX_DEBUG2 (d, ("cx_close\n")); d->open_dev &= ~0x1; return 0; } static int cx_modem_status (drv_t *d) { bdrv_t *bd = d->board->sys; int status = 0, s = splhigh (); CX_LOCK (bd); /* Already opened by someone or network interface is up? */ if ((d->chan->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (d->open_dev|0x2)) || (d->chan->mode != M_ASYNC && d->running)) status = TIOCM_LE; /* always enabled while open */ if (cx_get_dsr (d->chan)) status |= TIOCM_DSR; if (cx_get_cd (d->chan)) status |= TIOCM_CD; if (cx_get_cts (d->chan)) status |= TIOCM_CTS; if (d->chan->dtr) status |= TIOCM_DTR; if (d->chan->rts) status |= TIOCM_RTS; CX_UNLOCK (bd); splx (s); return status; } static int cx_ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { drv_t *d; bdrv_t *bd; cx_chan_t *c; struct serial_statistics *st; int error, s; char mask[16]; d = dev->si_drv1; c = d->chan; bd = d->board->sys; switch (cmd) { case SERIAL_GETREGISTERED: CX_DEBUG2 (d, ("ioctl: getregistered\n")); bzero (mask, sizeof(mask)); for (s=0; smode == M_ASYNC) ? "async" : (IFP2SP(d->ifp)->pp_flags & PP_FR) ? "fr" : (d->ifp->if_flags & PP_CISCO) ? "cisco" : "ppp"); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETPROTO: CX_DEBUG2 (d, ("ioctl: setproto\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_ASYNC) return EBUSY; if (d->ifp->if_drv_flags & IFF_DRV_RUNNING) return EBUSY; if (! strcmp ("cisco", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR); IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; d->ifp->if_flags |= PP_CISCO; } else if (! strcmp ("fr", (char*)data)) { d->ifp->if_flags &= ~(PP_CISCO); IFP2SP(d->ifp)->pp_flags |= PP_FR | PP_KEEPALIVE; } else if (! strcmp ("ppp", (char*)data)) { IFP2SP(d->ifp)->pp_flags &= ~(PP_FR | PP_KEEPALIVE); d->ifp->if_flags &= ~(PP_CISCO); } else return EINVAL; return 0; case SERIAL_GETKEEPALIVE: CX_DEBUG2 (d, ("ioctl: getkeepalive\n")); if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO) || (c->mode == M_ASYNC)) return EINVAL; s = splhigh (); CX_LOCK (bd); *(int*)data = (IFP2SP(d->ifp)->pp_flags & PP_KEEPALIVE) ? 1 : 0; CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETKEEPALIVE: CX_DEBUG2 (d, ("ioctl: setkeepalive\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if ((IFP2SP(d->ifp)->pp_flags & PP_FR) || (d->ifp->if_flags & PP_CISCO)) return EINVAL; s = splhigh (); CX_LOCK (bd); if (*(int*)data) IFP2SP(d->ifp)->pp_flags |= PP_KEEPALIVE; else IFP2SP(d->ifp)->pp_flags &= ~PP_KEEPALIVE; CX_UNLOCK (bd); splx (s); return 0; #endif /*NETGRAPH*/ case SERIAL_GETMODE: CX_DEBUG2 (d, ("ioctl: getmode\n")); s = splhigh (); CX_LOCK (bd); *(int*)data = (c->mode == M_ASYNC) ? SERIAL_ASYNC : SERIAL_HDLC; CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETMODE: CX_DEBUG2 (d, ("ioctl: setmode\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; /* Somebody is waiting for carrier? */ if (d->lock) return EBUSY; /* /dev/ttyXX is already opened by someone? */ if (c->mode == M_ASYNC && d->tty && (d->tty->t_state & TS_ISOPEN) && (d->open_dev|0x2)) return EBUSY; /* Network interface is up? * Cannot change to async mode. */ if (c->mode != M_ASYNC && d->running && (*(int*)data == SERIAL_ASYNC)) return EBUSY; s = splhigh (); CX_LOCK (bd); if (c->mode == M_HDLC && *(int*)data == SERIAL_ASYNC) { cx_set_mode (c, M_ASYNC); cx_enable_receive (c, 0); cx_enable_transmit (c, 0); } else if (c->mode == M_ASYNC && *(int*)data == SERIAL_HDLC) { cx_set_mode (c, M_HDLC); cx_enable_receive (c, 1); cx_enable_transmit (c, 1); } CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETSTAT: CX_DEBUG2 (d, ("ioctl: getestat\n")); st = (struct serial_statistics*) data; s = splhigh (); CX_LOCK (bd); st->rintr = c->rintr; st->tintr = c->tintr; st->mintr = c->mintr; st->ibytes = c->ibytes; st->ipkts = c->ipkts; st->ierrs = c->ierrs; st->obytes = c->obytes; st->opkts = c->opkts; st->oerrs = c->oerrs; CX_UNLOCK (bd); splx (s); return 0; case SERIAL_CLRSTAT: CX_DEBUG2 (d, ("ioctl: clrstat\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splhigh (); CX_LOCK (bd); c->rintr = 0; c->tintr = 0; c->mintr = 0; c->ibytes = 0; c->ipkts = 0; c->ierrs = 0; c->obytes = 0; c->opkts = 0; c->oerrs = 0; CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETBAUD: CX_DEBUG2 (d, ("ioctl: getbaud\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); *(long*)data = cx_get_baud(c); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETBAUD: CX_DEBUG2 (d, ("ioctl: setbaud\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); cx_set_baud (c, *(long*)data); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETLOOP: CX_DEBUG2 (d, ("ioctl: getloop\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); *(int*)data = cx_get_loop (c); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETLOOP: CX_DEBUG2 (d, ("ioctl: setloop\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); cx_set_loop (c, *(int*)data); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDPLL: CX_DEBUG2 (d, ("ioctl: getdpll\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); *(int*)data = cx_get_dpll (c); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETDPLL: CX_DEBUG2 (d, ("ioctl: setdpll\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); cx_set_dpll (c, *(int*)data); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETNRZI: CX_DEBUG2 (d, ("ioctl: getnrzi\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); *(int*)data = cx_get_nrzi (c); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETNRZI: CX_DEBUG2 (d, ("ioctl: setnrzi\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); CX_LOCK (bd); cx_set_nrzi (c, *(int*)data); CX_UNLOCK (bd); splx (s); return 0; case SERIAL_GETDEBUG: CX_DEBUG2 (d, ("ioctl: getdebug\n")); s = splhigh (); CX_LOCK (bd); *(int*)data = c->debug; CX_UNLOCK (bd); splx (s); return 0; case SERIAL_SETDEBUG: CX_DEBUG2 (d, ("ioctl: setdebug\n")); /* Only for superuser! */ error = priv_check (td, PRIV_DRIVER); if (error) return error; s = splhigh (); CX_LOCK (bd); c->debug = *(int*)data; CX_UNLOCK (bd); splx (s); #ifndef NETGRAPH if (d->chan->debug) d->ifp->if_flags |= IFF_DEBUG; else d->ifp->if_flags &= (~IFF_DEBUG); #endif return 0; } switch (cmd) { case TIOCSDTR: /* Set DTR */ CX_DEBUG2 (d, ("ioctl: tiocsdtr\n")); s = splhigh (); CX_LOCK (bd); cx_set_dtr (c, 1); CX_UNLOCK (bd); splx (s); return 0; case TIOCCDTR: /* Clear DTR */ CX_DEBUG2 (d, ("ioctl: tioccdtr\n")); s = splhigh (); CX_LOCK (bd); cx_set_dtr (c, 0); CX_UNLOCK (bd); splx (s); return 0; case TIOCMSET: /* Set DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmset\n")); s = splhigh (); CX_LOCK (bd); cx_set_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0); cx_set_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0); CX_UNLOCK (bd); splx (s); return 0; case TIOCMBIS: /* Add DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmbis\n")); s = splhigh (); CX_LOCK (bd); if (*(int*)data & TIOCM_DTR) cx_set_dtr (c, 1); if (*(int*)data & TIOCM_RTS) cx_set_rts (c, 1); CX_UNLOCK (bd); splx (s); return 0; case TIOCMBIC: /* Clear DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmbic\n")); s = splhigh (); CX_LOCK (bd); if (*(int*)data & TIOCM_DTR) cx_set_dtr (c, 0); if (*(int*)data & TIOCM_RTS) cx_set_rts (c, 0); CX_UNLOCK (bd); splx (s); return 0; case TIOCMGET: /* Get modem status */ CX_DEBUG2 (d, ("ioctl: tiocmget\n")); *(int*)data = cx_modem_status (d); return 0; } CX_DEBUG2 (d, ("ioctl: 0x%lx\n", cmd)); return ENOTTY; } void cx_softintr (void *unused) { drv_t *d; bdrv_t *bd; async_q *q; int i, s, ic, k; while (MY_SOFT_INTR) { MY_SOFT_INTR = 0; for (i=0; ichan || d->chan->type == T_NONE || d->chan->mode != M_ASYNC || !d->tty || !d->tty->t_dev) continue; bd = d->board->sys; s = splhigh (); CX_LOCK (bd); if (d->intr_action & CX_READ) { q = &(d->aqueue); if (d->tty->t_state & TS_CAN_BYPASS_L_RINT) { k = AQ_GSZ(q); if (d->tty->t_rawq.c_cc + k > d->tty->t_ihiwat && (d->tty->t_cflag & CRTS_IFLOW || d->tty->t_iflag & IXOFF) && !(d->tty->t_state & TS_TBLOCK)) ttyblock(d->tty); d->tty->t_rawcc += k; while (k>0) { k--; AQ_POP (q, ic); CX_UNLOCK (bd); splx (s); putc (ic, &d->tty->t_rawq); s = splhigh (); CX_LOCK (bd); } ttwakeup(d->tty); if (d->tty->t_state & TS_TTSTOP && (d->tty->t_iflag & IXANY || d->tty->t_cc[VSTART] == d->tty->t_cc[VSTOP])) { d->tty->t_state &= ~TS_TTSTOP; d->tty->t_lflag &= ~FLUSHO; d->intr_action |= CX_WRITE; } } else { while (q->end != q->beg) { AQ_POP (q, ic); CX_UNLOCK (bd); splx (s); ttyld_rint (d->tty, ic); s = splhigh (); CX_LOCK (bd); } } d->intr_action &= ~CX_READ; } splx (s); CX_UNLOCK (bd); s = splhigh (); CX_LOCK (bd); if (d->intr_action & CX_WRITE) { if (d->tty->t_line) ttyld_start (d->tty); else cx_oproc (d->tty); d->intr_action &= ~CX_WRITE; } CX_UNLOCK (bd); splx (s); } } } /* * Fill transmitter buffer with data. */ static void cx_oproc (struct tty *tp) { int s, k; drv_t *d; bdrv_t *bd; static u_char buf[DMABUFSZ]; u_char *p; u_short len = 0, sublen = 0; d = tp->t_sc; bd = d->board->sys; CX_DEBUG2 (d, ("cx_oproc\n")); s = splhigh (); CX_LOCK (bd); if (tp->t_cflag & CRTSCTS && (tp->t_state & TS_TBLOCK) && d->chan->rts) cx_set_rts (d->chan, 0); else if (tp->t_cflag & CRTSCTS && ! (tp->t_state & TS_TBLOCK) && ! d->chan->rts) cx_set_rts (d->chan, 1); if (! (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))) { /* Start transmitter. */ cx_enable_transmit (d->chan, 1); /* Is it busy? */ if (! cx_buf_free (d->chan)) { tp->t_state |= TS_BUSY; CX_UNLOCK (bd); splx (s); return; } if (tp->t_iflag & IXOFF) { p = (buf + (DMABUFSZ/2)); sublen = q_to_b (&tp->t_outq, p, (DMABUFSZ/2)); k = sublen; while (k--) { /* Send XON/XOFF out of band. */ if (*p == tp->t_cc[VSTOP]) { cx_xflow_ctl (d->chan, 0); p++; continue; } if (*p == tp->t_cc[VSTART]) { cx_xflow_ctl (d->chan, 1); p++; continue; } buf[len] = *p; len++; p++; } } else { p = buf; len = q_to_b (&tp->t_outq, p, (DMABUFSZ/2)); } if (len) { cx_send_packet (d->chan, buf, len, 0); tp->t_state |= TS_BUSY; d->atimeout = 10; CX_DEBUG2 (d, ("out %d bytes\n", len)); } } ttwwakeup (tp); CX_UNLOCK (bd); splx (s); } static int cx_param (struct tty *tp, struct termios *t) { drv_t *d; bdrv_t *bd; int s, bits, parity; d = tp->t_sc; bd = d->board->sys; s = splhigh (); CX_LOCK (bd); if (t->c_ospeed == 0) { /* Clear DTR and RTS. */ cx_set_dtr (d->chan, 0); CX_UNLOCK (bd); splx (s); CX_DEBUG2 (d, ("cx_param (hangup)\n")); return 0; } CX_DEBUG2 (d, ("cx_param\n")); /* Check requested parameters. */ if (t->c_ospeed < 300 || t->c_ospeed > 256*1024) { CX_UNLOCK (bd); splx (s); return EINVAL; } if (t->c_ispeed && (t->c_ispeed < 300 || t->c_ispeed > 256*1024)) { CX_UNLOCK (bd); splx (s); return EINVAL; } /* And copy them to tty and channel structures. */ tp->t_ispeed = t->c_ispeed = tp->t_ospeed = t->c_ospeed; tp->t_cflag = t->c_cflag; /* Set character length and parity mode. */ switch (t->c_cflag & CSIZE) { default: case CS8: bits = 8; break; case CS7: bits = 7; break; case CS6: bits = 6; break; case CS5: bits = 5; break; } parity = ((t->c_cflag & PARENB) ? 1 : 0) * (1 + ((t->c_cflag & PARODD) ? 0 : 1)); /* Set current channel number. */ if (! d->chan->dtr) cx_set_dtr (d->chan, 1); ttyldoptim (tp); cx_set_async_param (d->chan, t->c_ospeed, bits, parity, (t->c_cflag & CSTOPB), !(t->c_cflag & PARENB), (t->c_cflag & CRTSCTS), (t->c_iflag & IXON), (t->c_iflag & IXANY), t->c_cc[VSTART], t->c_cc[VSTOP]); CX_UNLOCK (bd); splx (s); return 0; } /* * Stop output on a line */ static void cx_stop (struct tty *tp, int flag) { drv_t *d; bdrv_t *bd; int s; d = tp->t_sc; bd = d->board->sys; s = splhigh (); CX_LOCK (bd); if (tp->t_state & TS_BUSY) { /* Stop transmitter */ CX_DEBUG2 (d, ("cx_stop\n")); cx_transmitter_ctl (d->chan, 0); } CX_UNLOCK (bd); splx (s); } /* * Process the (delayed) carrier signal setup. */ static void cx_carrier (void *arg) { drv_t *d = arg; bdrv_t *bd = d->board->sys; cx_chan_t *c = d->chan; int s, cd; s = splhigh (); CX_LOCK (bd); cd = cx_get_cd (c); if (d->cd != cd) { if (cd) { CX_DEBUG (d, ("carrier on\n")); d->cd = 1; CX_UNLOCK (bd); splx (s); if (d->tty) ttyld_modem(d->tty, 1); } else { CX_DEBUG (d, ("carrier loss\n")); d->cd = 0; CX_UNLOCK (bd); splx (s); if (d->tty) ttyld_modem(d->tty, 0); } } else { CX_UNLOCK (bd); splx (s); } } /* * Modem signal callback function. */ static void cx_modem (cx_chan_t *c) { drv_t *d = c->sys; if (!d || c->mode != M_ASYNC) return; /* Handle carrier detect/loss. */ /* Carrier changed - delay processing DCD for a while * to give both sides some time to initialize. */ callout_reset (&d->dcd_timeout_handle, hz/2, cx_carrier, d); } #ifdef NETGRAPH static int ng_cx_constructor (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); CX_DEBUG (d, ("Constructor\n")); return EINVAL; } static int ng_cx_newhook (node_p node, hook_p hook, const char *name) { int s; drv_t *d = NG_NODE_PRIVATE (node); bdrv_t *bd = d->board->sys; if (d->chan->mode == M_ASYNC) return EINVAL; /* Attach debug hook */ if (strcmp (name, NG_CX_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE (hook, NULL); d->debug_hook = hook; return 0; } /* Check for raw hook */ if (strcmp (name, NG_CX_HOOK_RAW) != 0) return EINVAL; NG_HOOK_SET_PRIVATE (hook, d); d->hook = hook; s = splhigh (); CX_LOCK (bd); cx_up (d); CX_UNLOCK (bd); splx (s); return 0; } static int print_modems (char *s, cx_chan_t *c, int need_header) { int status = cx_modem_status (c->sys); int length = 0; if (need_header) length += sprintf (s + length, " LE DTR DSR RTS CTS CD\n"); length += sprintf (s + length, "%4s %4s %4s %4s %4s %4s\n", status & TIOCM_LE ? "On" : "-", status & TIOCM_DTR ? "On" : "-", status & TIOCM_DSR ? "On" : "-", status & TIOCM_RTS ? "On" : "-", status & TIOCM_CTS ? "On" : "-", status & TIOCM_CD ? "On" : "-"); return length; } static int print_stats (char *s, cx_chan_t *c, int need_header) { int length = 0; if (need_header) length += sprintf (s + length, " Rintr Tintr Mintr Ibytes Ipkts Ierrs Obytes Opkts Oerrs\n"); length += sprintf (s + length, "%7ld %7ld %7ld %8ld %7ld %7ld %8ld %7ld %7ld\n", c->rintr, c->tintr, c->mintr, c->ibytes, c->ipkts, c->ierrs, c->obytes, c->opkts, c->oerrs); return length; } static int print_chan (char *s, cx_chan_t *c) { drv_t *d = c->sys; int length = 0; length += sprintf (s + length, "cx%d", c->board->num * NCHAN + c->num); if (d->chan->debug) length += sprintf (s + length, " debug=%d", d->chan->debug); if (cx_get_baud (c)) length += sprintf (s + length, " %ld", cx_get_baud (c)); else length += sprintf (s + length, " extclock"); if (c->mode == M_HDLC) { length += sprintf (s + length, " dpll=%s", cx_get_dpll (c) ? "on" : "off"); length += sprintf (s + length, " nrzi=%s", cx_get_nrzi (c) ? "on" : "off"); } length += sprintf (s + length, " loop=%s", cx_get_loop (c) ? "on\n" : "off\n"); return length; } static int ng_cx_rcvmsg (node_p node, item_p item, hook_p lasthook) { drv_t *d = NG_NODE_PRIVATE (node); struct ng_mesg *msg; struct ng_mesg *resp = NULL; int error = 0; if (!d) return EINVAL; CX_DEBUG (d, ("Rcvmsg\n")); NGI_GET_MSG (item, msg); switch (msg->header.typecookie) { default: error = EINVAL; break; case NGM_CX_COOKIE: printf ("Don't forget to implement\n"); error = EINVAL; break; case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { default: error = EINVAL; break; case NGM_TEXT_STATUS: { char *s; int l = 0; int dl = sizeof (struct ng_mesg) + 730; NG_MKRESPONSE (resp, msg, dl, M_NOWAIT); if (! resp) { error = ENOMEM; break; } bzero (resp, dl); s = (resp)->data; l += print_chan (s + l, d->chan); l += print_stats (s + l, d->chan, 1); l += print_modems (s + l, d->chan, 1); strncpy ((resp)->header.cmdstr, "status", NG_CMDSTRLEN); } break; } break; } NG_RESPOND_MSG (error, node, item, resp); NG_FREE_MSG (msg); return error; } static int ng_cx_rcvdata (hook_p hook, item_p item) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE(hook)); struct mbuf *m; struct ng_tag_prio *ptag; bdrv_t *bd; struct ifqueue *q; int s; NGI_GET_M (item, m); NG_FREE_ITEM (item); if (! NG_HOOK_PRIVATE (hook) || ! d) { NG_FREE_M (m); return ENETDOWN; } bd = d->board->sys; /* Check for high priority data */ if ((ptag = (struct ng_tag_prio *)m_tag_locate(m, NGM_GENERIC_COOKIE, NG_TAG_PRIO, NULL)) != NULL && (ptag->priority > NG_PRIO_CUTOFF) ) q = &d->hi_queue; else q = &d->lo_queue; s = splhigh (); CX_LOCK (bd); IF_LOCK (q); if (_IF_QFULL (q)) { _IF_DROP (q); IF_UNLOCK (q); CX_UNLOCK (bd); splx (s); NG_FREE_M (m); return ENOBUFS; } _IF_ENQUEUE (q, m); IF_UNLOCK (q); cx_start (d); CX_UNLOCK (bd); splx (s); return 0; } static int ng_cx_rmnode (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); bdrv_t *bd; CX_DEBUG (d, ("Rmnode\n")); if (d && d->running) { int s = splhigh (); bd = d->board->sys; CX_LOCK (bd); cx_down (d); CX_UNLOCK (bd); splx (s); } #ifdef KLD_MODULE if (node->nd_flags & NGF_REALLY_DIE) { NG_NODE_SET_PRIVATE (node, NULL); NG_NODE_UNREF (node); } NG_NODE_REVIVE(node); /* Persistant node */ #endif return 0; } static void ng_cx_watchdog (void *arg) { drv_t *d = arg; if (d->timeout == 1) cx_watchdog (d); if (d->timeout) d->timeout--; callout_reset (&d->timeout_handle, hz, ng_cx_watchdog, d); } static int ng_cx_connect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); callout_reset (&d->timeout_handle, hz, ng_cx_watchdog, d); return 0; } static int ng_cx_disconnect (hook_p hook) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); bdrv_t *bd = d->board->sys; int s; s = splhigh (); CX_LOCK (bd); if (NG_HOOK_PRIVATE (hook)) cx_down (d); CX_UNLOCK (bd); splx (s); /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&d->timeout_handle)) callout_stop (&d->timeout_handle); return 0; } #endif /*NETGRAPH*/ static int cx_modevent (module_t mod, int type, void *unused) { static int load_count = 0; - if (!debug_mpsafenet && cx_mpsafenet) { - printf ("WORNING! Network stack is not MPSAFE. " - "Turning off debug.cx.mpsafenet.\n"); - cx_mpsafenet = 0; - } if (cx_mpsafenet) cx_cdevsw.d_flags &= ~D_NEEDGIANT; switch (type) { case MOD_LOAD: #ifdef NETGRAPH if (ng_newtype (&typestruct)) printf ("Failed to register ng_cx\n"); #endif ++load_count; callout_init (&timeout_handle, cx_mpsafenet?CALLOUT_MPSAFE:0); callout_reset (&timeout_handle, hz*5, cx_timeout, 0); /* Software interrupt. */ swi_add(&tty_intr_event, "cx", cx_softintr, NULL, SWI_TTY, (cx_mpsafenet?INTR_MPSAFE:0), &cx_fast_ih); break; case MOD_UNLOAD: if (load_count == 1) { printf ("Removing device entry for Sigma\n"); #ifdef NETGRAPH ng_rmtype (&typestruct); #endif } /* If we were wait it than it reasserted now, just stop it. */ if (!callout_drain (&timeout_handle)) callout_stop (&timeout_handle); swi_remove (cx_fast_ih); --load_count; break; case MOD_SHUTDOWN: break; } return 0; } #ifdef NETGRAPH static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_CX_NODE_TYPE, .constructor = ng_cx_constructor, .rcvmsg = ng_cx_rcvmsg, .shutdown = ng_cx_rmnode, .newhook = ng_cx_newhook, .connect = ng_cx_connect, .rcvdata = ng_cx_rcvdata, .disconnect = ng_cx_disconnect, }; #endif /*NETGRAPH*/ #ifdef NETGRAPH MODULE_DEPEND (ng_cx, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); #else MODULE_DEPEND (isa_cx, sppp, 1, 1, 1); #endif DRIVER_MODULE (cx, isa, cx_isa_driver, cx_devclass, cx_modevent, NULL); MODULE_VERSION (cx, 1); Index: head/sys/kern/subr_bus.c =================================================================== --- head/sys/kern/subr_bus.c (revision 171612) +++ head/sys/kern/subr_bus.c (revision 171613) @@ -1,4141 +1,4138 @@ /*- * Copyright (c) 1997,1998,2003 Doug Rabson * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SYSCTL_NODE(_hw, OID_AUTO, bus, CTLFLAG_RW, NULL, NULL); SYSCTL_NODE(, OID_AUTO, dev, CTLFLAG_RW, NULL, NULL); /* * Used to attach drivers to devclasses. */ typedef struct driverlink *driverlink_t; struct driverlink { kobj_class_t driver; TAILQ_ENTRY(driverlink) link; /* list of drivers in devclass */ }; /* * Forward declarations */ typedef TAILQ_HEAD(devclass_list, devclass) devclass_list_t; typedef TAILQ_HEAD(driver_list, driverlink) driver_list_t; typedef TAILQ_HEAD(device_list, device) device_list_t; struct devclass { TAILQ_ENTRY(devclass) link; devclass_t parent; /* parent in devclass hierarchy */ driver_list_t drivers; /* bus devclasses store drivers for bus */ char *name; device_t *devices; /* array of devices indexed by unit */ int maxunit; /* size of devices array */ struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; }; /** * @brief Implementation of device. */ struct device { /* * A device is a kernel object. The first field must be the * current ops table for the object. */ KOBJ_FIELDS; /* * Device hierarchy. */ TAILQ_ENTRY(device) link; /**< list of devices in parent */ TAILQ_ENTRY(device) devlink; /**< global device list membership */ device_t parent; /**< parent of this device */ device_list_t children; /**< list of child devices */ /* * Details of this device. */ driver_t *driver; /**< current driver */ devclass_t devclass; /**< current device class */ int unit; /**< current unit number */ char* nameunit; /**< name+unit e.g. foodev0 */ char* desc; /**< driver specific description */ int busy; /**< count of calls to device_busy() */ device_state_t state; /**< current device state */ u_int32_t devflags; /**< api level flags for device_get_flags() */ u_short flags; /**< internal device flags */ #define DF_ENABLED 1 /* device should be probed/attached */ #define DF_FIXEDCLASS 2 /* devclass specified at create time */ #define DF_WILDCARD 4 /* unit was originally wildcard */ #define DF_DESCMALLOCED 8 /* description was malloced */ #define DF_QUIET 16 /* don't print verbose attach message */ #define DF_DONENOMATCH 32 /* don't execute DEVICE_NOMATCH again */ #define DF_EXTERNALSOFTC 64 /* softc not allocated by us */ #define DF_REBID 128 /* Can rebid after attach */ u_char order; /**< order from device_add_child_ordered() */ u_char pad; void *ivars; /**< instance variables */ void *softc; /**< current driver's variables */ struct sysctl_ctx_list sysctl_ctx; /**< state for sysctl variables */ struct sysctl_oid *sysctl_tree; /**< state for sysctl variables */ }; static MALLOC_DEFINE(M_BUS, "bus", "Bus data structures"); static MALLOC_DEFINE(M_BUS_SC, "bus-sc", "Bus data structures, softc"); #ifdef BUS_DEBUG static int bus_debug = 1; TUNABLE_INT("bus.debug", &bus_debug); SYSCTL_INT(_debug, OID_AUTO, bus_debug, CTLFLAG_RW, &bus_debug, 0, "Debug bus code"); #define PDEBUG(a) if (bus_debug) {printf("%s:%d: ", __func__, __LINE__), printf a; printf("\n");} #define DEVICENAME(d) ((d)? device_get_name(d): "no device") #define DRIVERNAME(d) ((d)? d->name : "no driver") #define DEVCLANAME(d) ((d)? d->name : "no devclass") /** * Produce the indenting, indent*2 spaces plus a '.' ahead of that to * prevent syslog from deleting initial spaces */ #define indentprintf(p) do { int iJ; printf("."); for (iJ=0; iJparent ? dc->parent->name : ""; break; default: return (EINVAL); } return (SYSCTL_OUT(req, value, strlen(value))); } static void devclass_sysctl_init(devclass_t dc) { if (dc->sysctl_tree != NULL) return; sysctl_ctx_init(&dc->sysctl_ctx); dc->sysctl_tree = SYSCTL_ADD_NODE(&dc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_dev), OID_AUTO, dc->name, CTLFLAG_RD, 0, ""); SYSCTL_ADD_PROC(&dc->sysctl_ctx, SYSCTL_CHILDREN(dc->sysctl_tree), OID_AUTO, "%parent", CTLFLAG_RD, dc, DEVCLASS_SYSCTL_PARENT, devclass_sysctl_handler, "A", "parent class"); } enum { DEVICE_SYSCTL_DESC, DEVICE_SYSCTL_DRIVER, DEVICE_SYSCTL_LOCATION, DEVICE_SYSCTL_PNPINFO, DEVICE_SYSCTL_PARENT, }; static int device_sysctl_handler(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t)arg1; const char *value; char *buf; int error; buf = NULL; switch (arg2) { case DEVICE_SYSCTL_DESC: value = dev->desc ? dev->desc : ""; break; case DEVICE_SYSCTL_DRIVER: value = dev->driver ? dev->driver->name : ""; break; case DEVICE_SYSCTL_LOCATION: value = buf = malloc(1024, M_BUS, M_WAITOK | M_ZERO); bus_child_location_str(dev, buf, 1024); break; case DEVICE_SYSCTL_PNPINFO: value = buf = malloc(1024, M_BUS, M_WAITOK | M_ZERO); bus_child_pnpinfo_str(dev, buf, 1024); break; case DEVICE_SYSCTL_PARENT: value = dev->parent ? dev->parent->nameunit : ""; break; default: return (EINVAL); } error = SYSCTL_OUT(req, value, strlen(value)); if (buf != NULL) free(buf, M_BUS); return (error); } static void device_sysctl_init(device_t dev) { devclass_t dc = dev->devclass; if (dev->sysctl_tree != NULL) return; devclass_sysctl_init(dc); sysctl_ctx_init(&dev->sysctl_ctx); dev->sysctl_tree = SYSCTL_ADD_NODE(&dev->sysctl_ctx, SYSCTL_CHILDREN(dc->sysctl_tree), OID_AUTO, dev->nameunit + strlen(dc->name), CTLFLAG_RD, 0, ""); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%desc", CTLFLAG_RD, dev, DEVICE_SYSCTL_DESC, device_sysctl_handler, "A", "device description"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%driver", CTLFLAG_RD, dev, DEVICE_SYSCTL_DRIVER, device_sysctl_handler, "A", "device driver name"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%location", CTLFLAG_RD, dev, DEVICE_SYSCTL_LOCATION, device_sysctl_handler, "A", "device location relative to parent"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%pnpinfo", CTLFLAG_RD, dev, DEVICE_SYSCTL_PNPINFO, device_sysctl_handler, "A", "device identification"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%parent", CTLFLAG_RD, dev, DEVICE_SYSCTL_PARENT, device_sysctl_handler, "A", "parent device"); } static void device_sysctl_fini(device_t dev) { if (dev->sysctl_tree == NULL) return; sysctl_ctx_free(&dev->sysctl_ctx); dev->sysctl_tree = NULL; } /* * /dev/devctl implementation */ /* * This design allows only one reader for /dev/devctl. This is not desirable * in the long run, but will get a lot of hair out of this implementation. * Maybe we should make this device a clonable device. * * Also note: we specifically do not attach a device to the device_t tree * to avoid potential chicken and egg problems. One could argue that all * of this belongs to the root node. One could also further argue that the * sysctl interface that we have not might more properly be an ioctl * interface, but at this stage of the game, I'm not inclined to rock that * boat. * * I'm also not sure that the SIGIO support is done correctly or not, as * I copied it from a driver that had SIGIO support that likely hasn't been * tested since 3.4 or 2.2.8! */ static int sysctl_devctl_disable(SYSCTL_HANDLER_ARGS); static int devctl_disable = 0; TUNABLE_INT("hw.bus.devctl_disable", &devctl_disable); SYSCTL_PROC(_hw_bus, OID_AUTO, devctl_disable, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_devctl_disable, "I", "devctl disable"); static d_open_t devopen; static d_close_t devclose; static d_read_t devread; static d_ioctl_t devioctl; static d_poll_t devpoll; static struct cdevsw dev_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = devopen, .d_close = devclose, .d_read = devread, .d_ioctl = devioctl, .d_poll = devpoll, .d_name = "devctl", }; struct dev_event_info { char *dei_data; TAILQ_ENTRY(dev_event_info) dei_link; }; TAILQ_HEAD(devq, dev_event_info); static struct dev_softc { int inuse; int nonblock; struct mtx mtx; struct cv cv; struct selinfo sel; struct devq devq; struct proc *async_proc; } devsoftc; static struct cdev *devctl_dev; static void devinit(void) { devctl_dev = make_dev(&dev_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "devctl"); mtx_init(&devsoftc.mtx, "dev mtx", "devd", MTX_DEF); cv_init(&devsoftc.cv, "dev cv"); TAILQ_INIT(&devsoftc.devq); } static int devopen(struct cdev *dev, int oflags, int devtype, d_thread_t *td) { if (devsoftc.inuse) return (EBUSY); /* move to init */ devsoftc.inuse = 1; devsoftc.nonblock = 0; devsoftc.async_proc = NULL; return (0); } static int devclose(struct cdev *dev, int fflag, int devtype, d_thread_t *td) { devsoftc.inuse = 0; mtx_lock(&devsoftc.mtx); cv_broadcast(&devsoftc.cv); mtx_unlock(&devsoftc.mtx); return (0); } /* * The read channel for this device is used to report changes to * userland in realtime. We are required to free the data as well as * the n1 object because we allocate them separately. Also note that * we return one record at a time. If you try to read this device a * character at a time, you will lose the rest of the data. Listening * programs are expected to cope. */ static int devread(struct cdev *dev, struct uio *uio, int ioflag) { struct dev_event_info *n1; int rv; mtx_lock(&devsoftc.mtx); while (TAILQ_EMPTY(&devsoftc.devq)) { if (devsoftc.nonblock) { mtx_unlock(&devsoftc.mtx); return (EAGAIN); } rv = cv_wait_sig(&devsoftc.cv, &devsoftc.mtx); if (rv) { /* * Need to translate ERESTART to EINTR here? -- jake */ mtx_unlock(&devsoftc.mtx); return (rv); } } n1 = TAILQ_FIRST(&devsoftc.devq); TAILQ_REMOVE(&devsoftc.devq, n1, dei_link); mtx_unlock(&devsoftc.mtx); rv = uiomove(n1->dei_data, strlen(n1->dei_data), uio); free(n1->dei_data, M_BUS); free(n1, M_BUS); return (rv); } static int devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, d_thread_t *td) { switch (cmd) { case FIONBIO: if (*(int*)data) devsoftc.nonblock = 1; else devsoftc.nonblock = 0; return (0); case FIOASYNC: if (*(int*)data) devsoftc.async_proc = td->td_proc; else devsoftc.async_proc = NULL; return (0); /* (un)Support for other fcntl() calls. */ case FIOCLEX: case FIONCLEX: case FIONREAD: case FIOSETOWN: case FIOGETOWN: default: break; } return (ENOTTY); } static int devpoll(struct cdev *dev, int events, d_thread_t *td) { int revents = 0; mtx_lock(&devsoftc.mtx); if (events & (POLLIN | POLLRDNORM)) { if (!TAILQ_EMPTY(&devsoftc.devq)) revents = events & (POLLIN | POLLRDNORM); else selrecord(td, &devsoftc.sel); } mtx_unlock(&devsoftc.mtx); return (revents); } /** * @brief Queue data to be read from the devctl device * * Generic interface to queue data to the devctl device. It is * assumed that @p data is properly formatted. It is further assumed * that @p data is allocated using the M_BUS malloc type. */ void devctl_queue_data(char *data) { struct dev_event_info *n1 = NULL; struct proc *p; n1 = malloc(sizeof(*n1), M_BUS, M_NOWAIT); if (n1 == NULL) return; n1->dei_data = data; mtx_lock(&devsoftc.mtx); TAILQ_INSERT_TAIL(&devsoftc.devq, n1, dei_link); cv_broadcast(&devsoftc.cv); mtx_unlock(&devsoftc.mtx); selwakeup(&devsoftc.sel); p = devsoftc.async_proc; if (p != NULL) { PROC_LOCK(p); psignal(p, SIGIO); PROC_UNLOCK(p); } } /** * @brief Send a 'notification' to userland, using standard ways */ void devctl_notify(const char *system, const char *subsystem, const char *type, const char *data) { int len = 0; char *msg; if (system == NULL) return; /* BOGUS! Must specify system. */ if (subsystem == NULL) return; /* BOGUS! Must specify subsystem. */ if (type == NULL) return; /* BOGUS! Must specify type. */ len += strlen(" system=") + strlen(system); len += strlen(" subsystem=") + strlen(subsystem); len += strlen(" type=") + strlen(type); /* add in the data message plus newline. */ if (data != NULL) len += strlen(data); len += 3; /* '!', '\n', and NUL */ msg = malloc(len, M_BUS, M_NOWAIT); if (msg == NULL) return; /* Drop it on the floor */ if (data != NULL) snprintf(msg, len, "!system=%s subsystem=%s type=%s %s\n", system, subsystem, type, data); else snprintf(msg, len, "!system=%s subsystem=%s type=%s\n", system, subsystem, type); devctl_queue_data(msg); } /* * Common routine that tries to make sending messages as easy as possible. * We allocate memory for the data, copy strings into that, but do not * free it unless there's an error. The dequeue part of the driver should * free the data. We don't send data when the device is disabled. We do * send data, even when we have no listeners, because we wish to avoid * races relating to startup and restart of listening applications. * * devaddq is designed to string together the type of event, with the * object of that event, plus the plug and play info and location info * for that event. This is likely most useful for devices, but less * useful for other consumers of this interface. Those should use * the devctl_queue_data() interface instead. */ static void devaddq(const char *type, const char *what, device_t dev) { char *data = NULL; char *loc = NULL; char *pnp = NULL; const char *parstr; if (devctl_disable) return; data = malloc(1024, M_BUS, M_NOWAIT); if (data == NULL) goto bad; /* get the bus specific location of this device */ loc = malloc(1024, M_BUS, M_NOWAIT); if (loc == NULL) goto bad; *loc = '\0'; bus_child_location_str(dev, loc, 1024); /* Get the bus specific pnp info of this device */ pnp = malloc(1024, M_BUS, M_NOWAIT); if (pnp == NULL) goto bad; *pnp = '\0'; bus_child_pnpinfo_str(dev, pnp, 1024); /* Get the parent of this device, or / if high enough in the tree. */ if (device_get_parent(dev) == NULL) parstr = "."; /* Or '/' ? */ else parstr = device_get_nameunit(device_get_parent(dev)); /* String it all together. */ snprintf(data, 1024, "%s%s at %s %s on %s\n", type, what, loc, pnp, parstr); free(loc, M_BUS); free(pnp, M_BUS); devctl_queue_data(data); return; bad: free(pnp, M_BUS); free(loc, M_BUS); free(data, M_BUS); return; } /* * A device was added to the tree. We are called just after it successfully * attaches (that is, probe and attach success for this device). No call * is made if a device is merely parented into the tree. See devnomatch * if probe fails. If attach fails, no notification is sent (but maybe * we should have a different message for this). */ static void devadded(device_t dev) { char *pnp = NULL; char *tmp = NULL; pnp = malloc(1024, M_BUS, M_NOWAIT); if (pnp == NULL) goto fail; tmp = malloc(1024, M_BUS, M_NOWAIT); if (tmp == NULL) goto fail; *pnp = '\0'; bus_child_pnpinfo_str(dev, pnp, 1024); snprintf(tmp, 1024, "%s %s", device_get_nameunit(dev), pnp); devaddq("+", tmp, dev); fail: if (pnp != NULL) free(pnp, M_BUS); if (tmp != NULL) free(tmp, M_BUS); return; } /* * A device was removed from the tree. We are called just before this * happens. */ static void devremoved(device_t dev) { char *pnp = NULL; char *tmp = NULL; pnp = malloc(1024, M_BUS, M_NOWAIT); if (pnp == NULL) goto fail; tmp = malloc(1024, M_BUS, M_NOWAIT); if (tmp == NULL) goto fail; *pnp = '\0'; bus_child_pnpinfo_str(dev, pnp, 1024); snprintf(tmp, 1024, "%s %s", device_get_nameunit(dev), pnp); devaddq("-", tmp, dev); fail: if (pnp != NULL) free(pnp, M_BUS); if (tmp != NULL) free(tmp, M_BUS); return; } /* * Called when there's no match for this device. This is only called * the first time that no match happens, so we don't keep getitng this * message. Should that prove to be undesirable, we can change it. * This is called when all drivers that can attach to a given bus * decline to accept this device. Other errrors may not be detected. */ static void devnomatch(device_t dev) { devaddq("?", "", dev); } static int sysctl_devctl_disable(SYSCTL_HANDLER_ARGS) { struct dev_event_info *n1; int dis, error; dis = devctl_disable; error = sysctl_handle_int(oidp, &dis, 0, req); if (error || !req->newptr) return (error); mtx_lock(&devsoftc.mtx); devctl_disable = dis; if (dis) { while (!TAILQ_EMPTY(&devsoftc.devq)) { n1 = TAILQ_FIRST(&devsoftc.devq); TAILQ_REMOVE(&devsoftc.devq, n1, dei_link); free(n1->dei_data, M_BUS); free(n1, M_BUS); } } mtx_unlock(&devsoftc.mtx); return (0); } /* End of /dev/devctl code */ TAILQ_HEAD(,device) bus_data_devices; static int bus_data_generation = 1; kobj_method_t null_methods[] = { { 0, 0 } }; DEFINE_CLASS(null, null_methods, 0); /* * Devclass implementation */ static devclass_list_t devclasses = TAILQ_HEAD_INITIALIZER(devclasses); /** * @internal * @brief Find or create a device class * * If a device class with the name @p classname exists, return it, * otherwise if @p create is non-zero create and return a new device * class. * * If @p parentname is non-NULL, the parent of the devclass is set to * the devclass of that name. * * @param classname the devclass name to find or create * @param parentname the parent devclass name or @c NULL * @param create non-zero to create a devclass */ static devclass_t devclass_find_internal(const char *classname, const char *parentname, int create) { devclass_t dc; PDEBUG(("looking for %s", classname)); if (!classname) return (NULL); TAILQ_FOREACH(dc, &devclasses, link) { if (!strcmp(dc->name, classname)) break; } if (create && !dc) { PDEBUG(("creating %s", classname)); dc = malloc(sizeof(struct devclass) + strlen(classname) + 1, M_BUS, M_NOWAIT|M_ZERO); if (!dc) return (NULL); dc->parent = NULL; dc->name = (char*) (dc + 1); strcpy(dc->name, classname); TAILQ_INIT(&dc->drivers); TAILQ_INSERT_TAIL(&devclasses, dc, link); bus_data_generation_update(); } /* * If a parent class is specified, then set that as our parent so * that this devclass will support drivers for the parent class as * well. If the parent class has the same name don't do this though * as it creates a cycle that can trigger an infinite loop in * device_probe_child() if a device exists for which there is no * suitable driver. */ if (parentname && dc && !dc->parent && strcmp(classname, parentname) != 0) { dc->parent = devclass_find_internal(parentname, NULL, FALSE); } return (dc); } /** * @brief Create a device class * * If a device class with the name @p classname exists, return it, * otherwise create and return a new device class. * * @param classname the devclass name to find or create */ devclass_t devclass_create(const char *classname) { return (devclass_find_internal(classname, NULL, TRUE)); } /** * @brief Find a device class * * If a device class with the name @p classname exists, return it, * otherwise return @c NULL. * * @param classname the devclass name to find */ devclass_t devclass_find(const char *classname) { return (devclass_find_internal(classname, NULL, FALSE)); } /** * @brief Add a device driver to a device class * * Add a device driver to a devclass. This is normally called * automatically by DRIVER_MODULE(). The BUS_DRIVER_ADDED() method of * all devices in the devclass will be called to allow them to attempt * to re-probe any unmatched children. * * @param dc the devclass to edit * @param driver the driver to register */ int devclass_add_driver(devclass_t dc, driver_t *driver) { driverlink_t dl; int i; PDEBUG(("%s", DRIVERNAME(driver))); dl = malloc(sizeof *dl, M_BUS, M_NOWAIT|M_ZERO); if (!dl) return (ENOMEM); /* * Compile the driver's methods. Also increase the reference count * so that the class doesn't get freed when the last instance * goes. This means we can safely use static methods and avoids a * double-free in devclass_delete_driver. */ kobj_class_compile((kobj_class_t) driver); /* * Make sure the devclass which the driver is implementing exists. */ devclass_find_internal(driver->name, NULL, TRUE); dl->driver = driver; TAILQ_INSERT_TAIL(&dc->drivers, dl, link); driver->refs++; /* XXX: kobj_mtx */ /* * Call BUS_DRIVER_ADDED for any existing busses in this class. */ for (i = 0; i < dc->maxunit; i++) if (dc->devices[i]) BUS_DRIVER_ADDED(dc->devices[i], driver); bus_data_generation_update(); return (0); } /** * @brief Delete a device driver from a device class * * Delete a device driver from a devclass. This is normally called * automatically by DRIVER_MODULE(). * * If the driver is currently attached to any devices, * devclass_delete_driver() will first attempt to detach from each * device. If one of the detach calls fails, the driver will not be * deleted. * * @param dc the devclass to edit * @param driver the driver to unregister */ int devclass_delete_driver(devclass_t busclass, driver_t *driver) { devclass_t dc = devclass_find(driver->name); driverlink_t dl; device_t dev; int i; int error; PDEBUG(("%s from devclass %s", driver->name, DEVCLANAME(busclass))); if (!dc) return (0); /* * Find the link structure in the bus' list of drivers. */ TAILQ_FOREACH(dl, &busclass->drivers, link) { if (dl->driver == driver) break; } if (!dl) { PDEBUG(("%s not found in %s list", driver->name, busclass->name)); return (ENOENT); } /* * Disassociate from any devices. We iterate through all the * devices in the devclass of the driver and detach any which are * using the driver and which have a parent in the devclass which * we are deleting from. * * Note that since a driver can be in multiple devclasses, we * should not detach devices which are not children of devices in * the affected devclass. */ for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { dev = dc->devices[i]; if (dev->driver == driver && dev->parent && dev->parent->devclass == busclass) { if ((error = device_detach(dev)) != 0) return (error); device_set_driver(dev, NULL); } } } TAILQ_REMOVE(&busclass->drivers, dl, link); free(dl, M_BUS); /* XXX: kobj_mtx */ driver->refs--; if (driver->refs == 0) kobj_class_free((kobj_class_t) driver); bus_data_generation_update(); return (0); } /** * @brief Quiesces a set of device drivers from a device class * * Quiesce a device driver from a devclass. This is normally called * automatically by DRIVER_MODULE(). * * If the driver is currently attached to any devices, * devclass_quiesece_driver() will first attempt to quiesce each * device. * * @param dc the devclass to edit * @param driver the driver to unregister */ int devclass_quiesce_driver(devclass_t busclass, driver_t *driver) { devclass_t dc = devclass_find(driver->name); driverlink_t dl; device_t dev; int i; int error; PDEBUG(("%s from devclass %s", driver->name, DEVCLANAME(busclass))); if (!dc) return (0); /* * Find the link structure in the bus' list of drivers. */ TAILQ_FOREACH(dl, &busclass->drivers, link) { if (dl->driver == driver) break; } if (!dl) { PDEBUG(("%s not found in %s list", driver->name, busclass->name)); return (ENOENT); } /* * Quiesce all devices. We iterate through all the devices in * the devclass of the driver and quiesce any which are using * the driver and which have a parent in the devclass which we * are quiescing. * * Note that since a driver can be in multiple devclasses, we * should not quiesce devices which are not children of * devices in the affected devclass. */ for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { dev = dc->devices[i]; if (dev->driver == driver && dev->parent && dev->parent->devclass == busclass) { if ((error = device_quiesce(dev)) != 0) return (error); } } } return (0); } /** * @internal */ static driverlink_t devclass_find_driver_internal(devclass_t dc, const char *classname) { driverlink_t dl; PDEBUG(("%s in devclass %s", classname, DEVCLANAME(dc))); TAILQ_FOREACH(dl, &dc->drivers, link) { if (!strcmp(dl->driver->name, classname)) return (dl); } PDEBUG(("not found")); return (NULL); } /** * @brief Search a devclass for a driver * * This function searches the devclass's list of drivers and returns * the first driver whose name is @p classname or @c NULL if there is * no driver of that name. * * @param dc the devclass to search * @param classname the driver name to search for */ kobj_class_t devclass_find_driver(devclass_t dc, const char *classname) { driverlink_t dl; dl = devclass_find_driver_internal(dc, classname); if (dl) return (dl->driver); return (NULL); } /** * @brief Return the name of the devclass */ const char * devclass_get_name(devclass_t dc) { return (dc->name); } /** * @brief Find a device given a unit number * * @param dc the devclass to search * @param unit the unit number to search for * * @returns the device with the given unit number or @c * NULL if there is no such device */ device_t devclass_get_device(devclass_t dc, int unit) { if (dc == NULL || unit < 0 || unit >= dc->maxunit) return (NULL); return (dc->devices[unit]); } /** * @brief Find the softc field of a device given a unit number * * @param dc the devclass to search * @param unit the unit number to search for * * @returns the softc field of the device with the given * unit number or @c NULL if there is no such * device */ void * devclass_get_softc(devclass_t dc, int unit) { device_t dev; dev = devclass_get_device(dc, unit); if (!dev) return (NULL); return (device_get_softc(dev)); } /** * @brief Get a list of devices in the devclass * * An array containing a list of all the devices in the given devclass * is allocated and returned in @p *devlistp. The number of devices * in the array is returned in @p *devcountp. The caller should free * the array using @c free(p, M_TEMP), even if @p *devcountp is 0. * * @param dc the devclass to examine * @param devlistp points at location for array pointer return * value * @param devcountp points at location for array size return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int devclass_get_devices(devclass_t dc, device_t **devlistp, int *devcountp) { int count, i; device_t *list; count = devclass_get_count(dc); list = malloc(count * sizeof(device_t), M_TEMP, M_NOWAIT|M_ZERO); if (!list) return (ENOMEM); count = 0; for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { list[count] = dc->devices[i]; count++; } } *devlistp = list; *devcountp = count; return (0); } /** * @brief Get a list of drivers in the devclass * * An array containing a list of pointers to all the drivers in the * given devclass is allocated and returned in @p *listp. The number * of drivers in the array is returned in @p *countp. The caller should * free the array using @c free(p, M_TEMP). * * @param dc the devclass to examine * @param listp gives location for array pointer return value * @param countp gives location for number of array elements * return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int devclass_get_drivers(devclass_t dc, driver_t ***listp, int *countp) { driverlink_t dl; driver_t **list; int count; count = 0; TAILQ_FOREACH(dl, &dc->drivers, link) count++; list = malloc(count * sizeof(driver_t *), M_TEMP, M_NOWAIT); if (list == NULL) return (ENOMEM); count = 0; TAILQ_FOREACH(dl, &dc->drivers, link) { list[count] = dl->driver; count++; } *listp = list; *countp = count; return (0); } /** * @brief Get the number of devices in a devclass * * @param dc the devclass to examine */ int devclass_get_count(devclass_t dc) { int count, i; count = 0; for (i = 0; i < dc->maxunit; i++) if (dc->devices[i]) count++; return (count); } /** * @brief Get the maximum unit number used in a devclass * * Note that this is one greater than the highest currently-allocated * unit. * * @param dc the devclass to examine */ int devclass_get_maxunit(devclass_t dc) { return (dc->maxunit); } /** * @brief Find a free unit number in a devclass * * This function searches for the first unused unit number greater * that or equal to @p unit. * * @param dc the devclass to examine * @param unit the first unit number to check */ int devclass_find_free_unit(devclass_t dc, int unit) { if (dc == NULL) return (unit); while (unit < dc->maxunit && dc->devices[unit] != NULL) unit++; return (unit); } /** * @brief Set the parent of a devclass * * The parent class is normally initialised automatically by * DRIVER_MODULE(). * * @param dc the devclass to edit * @param pdc the new parent devclass */ void devclass_set_parent(devclass_t dc, devclass_t pdc) { dc->parent = pdc; } /** * @brief Get the parent of a devclass * * @param dc the devclass to examine */ devclass_t devclass_get_parent(devclass_t dc) { return (dc->parent); } struct sysctl_ctx_list * devclass_get_sysctl_ctx(devclass_t dc) { return (&dc->sysctl_ctx); } struct sysctl_oid * devclass_get_sysctl_tree(devclass_t dc) { return (dc->sysctl_tree); } /** * @internal * @brief Allocate a unit number * * On entry, @p *unitp is the desired unit number (or @c -1 if any * will do). The allocated unit number is returned in @p *unitp. * @param dc the devclass to allocate from * @param unitp points at the location for the allocated unit * number * * @retval 0 success * @retval EEXIST the requested unit number is already allocated * @retval ENOMEM memory allocation failure */ static int devclass_alloc_unit(devclass_t dc, int *unitp) { int unit = *unitp; PDEBUG(("unit %d in devclass %s", unit, DEVCLANAME(dc))); /* If we were given a wired unit number, check for existing device */ /* XXX imp XXX */ if (unit != -1) { if (unit >= 0 && unit < dc->maxunit && dc->devices[unit] != NULL) { if (bootverbose) printf("%s: %s%d already exists; skipping it\n", dc->name, dc->name, *unitp); return (EEXIST); } } else { /* Unwired device, find the next available slot for it */ unit = 0; while (unit < dc->maxunit && dc->devices[unit] != NULL) unit++; } /* * We've selected a unit beyond the length of the table, so let's * extend the table to make room for all units up to and including * this one. */ if (unit >= dc->maxunit) { device_t *newlist; int newsize; newsize = roundup((unit + 1), MINALLOCSIZE / sizeof(device_t)); newlist = malloc(sizeof(device_t) * newsize, M_BUS, M_NOWAIT); if (!newlist) return (ENOMEM); bcopy(dc->devices, newlist, sizeof(device_t) * dc->maxunit); bzero(newlist + dc->maxunit, sizeof(device_t) * (newsize - dc->maxunit)); if (dc->devices) free(dc->devices, M_BUS); dc->devices = newlist; dc->maxunit = newsize; } PDEBUG(("now: unit %d in devclass %s", unit, DEVCLANAME(dc))); *unitp = unit; return (0); } /** * @internal * @brief Add a device to a devclass * * A unit number is allocated for the device (using the device's * preferred unit number if any) and the device is registered in the * devclass. This allows the device to be looked up by its unit * number, e.g. by decoding a dev_t minor number. * * @param dc the devclass to add to * @param dev the device to add * * @retval 0 success * @retval EEXIST the requested unit number is already allocated * @retval ENOMEM memory allocation failure */ static int devclass_add_device(devclass_t dc, device_t dev) { int buflen, error; PDEBUG(("%s in devclass %s", DEVICENAME(dev), DEVCLANAME(dc))); buflen = snprintf(NULL, 0, "%s%d$", dc->name, dev->unit); if (buflen < 0) return (ENOMEM); dev->nameunit = malloc(buflen, M_BUS, M_NOWAIT|M_ZERO); if (!dev->nameunit) return (ENOMEM); if ((error = devclass_alloc_unit(dc, &dev->unit)) != 0) { free(dev->nameunit, M_BUS); dev->nameunit = NULL; return (error); } dc->devices[dev->unit] = dev; dev->devclass = dc; snprintf(dev->nameunit, buflen, "%s%d", dc->name, dev->unit); return (0); } /** * @internal * @brief Delete a device from a devclass * * The device is removed from the devclass's device list and its unit * number is freed. * @param dc the devclass to delete from * @param dev the device to delete * * @retval 0 success */ static int devclass_delete_device(devclass_t dc, device_t dev) { if (!dc || !dev) return (0); PDEBUG(("%s in devclass %s", DEVICENAME(dev), DEVCLANAME(dc))); if (dev->devclass != dc || dc->devices[dev->unit] != dev) panic("devclass_delete_device: inconsistent device class"); dc->devices[dev->unit] = NULL; if (dev->flags & DF_WILDCARD) dev->unit = -1; dev->devclass = NULL; free(dev->nameunit, M_BUS); dev->nameunit = NULL; return (0); } /** * @internal * @brief Make a new device and add it as a child of @p parent * * @param parent the parent of the new device * @param name the devclass name of the new device or @c NULL * to leave the devclass unspecified * @parem unit the unit number of the new device of @c -1 to * leave the unit number unspecified * * @returns the new device */ static device_t make_device(device_t parent, const char *name, int unit) { device_t dev; devclass_t dc; PDEBUG(("%s at %s as unit %d", name, DEVICENAME(parent), unit)); if (name) { dc = devclass_find_internal(name, NULL, TRUE); if (!dc) { printf("make_device: can't find device class %s\n", name); return (NULL); } } else { dc = NULL; } dev = malloc(sizeof(struct device), M_BUS, M_NOWAIT|M_ZERO); if (!dev) return (NULL); dev->parent = parent; TAILQ_INIT(&dev->children); kobj_init((kobj_t) dev, &null_class); dev->driver = NULL; dev->devclass = NULL; dev->unit = unit; dev->nameunit = NULL; dev->desc = NULL; dev->busy = 0; dev->devflags = 0; dev->flags = DF_ENABLED; dev->order = 0; if (unit == -1) dev->flags |= DF_WILDCARD; if (name) { dev->flags |= DF_FIXEDCLASS; if (devclass_add_device(dc, dev)) { kobj_delete((kobj_t) dev, M_BUS); return (NULL); } } dev->ivars = NULL; dev->softc = NULL; dev->state = DS_NOTPRESENT; TAILQ_INSERT_TAIL(&bus_data_devices, dev, devlink); bus_data_generation_update(); return (dev); } /** * @internal * @brief Print a description of a device. */ static int device_print_child(device_t dev, device_t child) { int retval = 0; if (device_is_alive(child)) retval += BUS_PRINT_CHILD(dev, child); else retval += device_printf(child, " not found\n"); return (retval); } /** * @brief Create a new device * * This creates a new device and adds it as a child of an existing * parent device. The new device will be added after the last existing * child with order zero. * * @param dev the device which will be the parent of the * new child device * @param name devclass name for new device or @c NULL if not * specified * @param unit unit number for new device or @c -1 if not * specified * * @returns the new device */ device_t device_add_child(device_t dev, const char *name, int unit) { return (device_add_child_ordered(dev, 0, name, unit)); } /** * @brief Create a new device * * This creates a new device and adds it as a child of an existing * parent device. The new device will be added after the last existing * child with the same order. * * @param dev the device which will be the parent of the * new child device * @param order a value which is used to partially sort the * children of @p dev - devices created using * lower values of @p order appear first in @p * dev's list of children * @param name devclass name for new device or @c NULL if not * specified * @param unit unit number for new device or @c -1 if not * specified * * @returns the new device */ device_t device_add_child_ordered(device_t dev, int order, const char *name, int unit) { device_t child; device_t place; PDEBUG(("%s at %s with order %d as unit %d", name, DEVICENAME(dev), order, unit)); child = make_device(dev, name, unit); if (child == NULL) return (child); child->order = order; TAILQ_FOREACH(place, &dev->children, link) { if (place->order > order) break; } if (place) { /* * The device 'place' is the first device whose order is * greater than the new child. */ TAILQ_INSERT_BEFORE(place, child, link); } else { /* * The new child's order is greater or equal to the order of * any existing device. Add the child to the tail of the list. */ TAILQ_INSERT_TAIL(&dev->children, child, link); } bus_data_generation_update(); return (child); } /** * @brief Delete a device * * This function deletes a device along with all of its children. If * the device currently has a driver attached to it, the device is * detached first using device_detach(). * * @param dev the parent device * @param child the device to delete * * @retval 0 success * @retval non-zero a unit error code describing the error */ int device_delete_child(device_t dev, device_t child) { int error; device_t grandchild; PDEBUG(("%s from %s", DEVICENAME(child), DEVICENAME(dev))); /* remove children first */ while ( (grandchild = TAILQ_FIRST(&child->children)) ) { error = device_delete_child(child, grandchild); if (error) return (error); } if ((error = device_detach(child)) != 0) return (error); if (child->devclass) devclass_delete_device(child->devclass, child); TAILQ_REMOVE(&dev->children, child, link); TAILQ_REMOVE(&bus_data_devices, child, devlink); kobj_delete((kobj_t) child, M_BUS); bus_data_generation_update(); return (0); } /** * @brief Find a device given a unit number * * This is similar to devclass_get_devices() but only searches for * devices which have @p dev as a parent. * * @param dev the parent device to search * @param unit the unit number to search for. If the unit is -1, * return the first child of @p dev which has name * @p classname (that is, the one with the lowest unit.) * * @returns the device with the given unit number or @c * NULL if there is no such device */ device_t device_find_child(device_t dev, const char *classname, int unit) { devclass_t dc; device_t child; dc = devclass_find(classname); if (!dc) return (NULL); if (unit != -1) { child = devclass_get_device(dc, unit); if (child && child->parent == dev) return (child); } else { for (unit = 0; unit < devclass_get_maxunit(dc); unit++) { child = devclass_get_device(dc, unit); if (child && child->parent == dev) return (child); } } return (NULL); } /** * @internal */ static driverlink_t first_matching_driver(devclass_t dc, device_t dev) { if (dev->devclass) return (devclass_find_driver_internal(dc, dev->devclass->name)); return (TAILQ_FIRST(&dc->drivers)); } /** * @internal */ static driverlink_t next_matching_driver(devclass_t dc, device_t dev, driverlink_t last) { if (dev->devclass) { driverlink_t dl; for (dl = TAILQ_NEXT(last, link); dl; dl = TAILQ_NEXT(dl, link)) if (!strcmp(dev->devclass->name, dl->driver->name)) return (dl); return (NULL); } return (TAILQ_NEXT(last, link)); } /** * @internal */ int device_probe_child(device_t dev, device_t child) { devclass_t dc; driverlink_t best = NULL; driverlink_t dl; int result, pri = 0; int hasclass = (child->devclass != 0); GIANT_REQUIRED; dc = dev->devclass; if (!dc) panic("device_probe_child: parent device has no devclass"); /* * If the state is already probed, then return. However, don't * return if we can rebid this object. */ if (child->state == DS_ALIVE && (child->flags & DF_REBID) == 0) return (0); for (; dc; dc = dc->parent) { for (dl = first_matching_driver(dc, child); dl; dl = next_matching_driver(dc, child, dl)) { PDEBUG(("Trying %s", DRIVERNAME(dl->driver))); device_set_driver(child, dl->driver); if (!hasclass) device_set_devclass(child, dl->driver->name); /* Fetch any flags for the device before probing. */ resource_int_value(dl->driver->name, child->unit, "flags", &child->devflags); result = DEVICE_PROBE(child); /* Reset flags and devclass before the next probe. */ child->devflags = 0; if (!hasclass) device_set_devclass(child, NULL); /* * If the driver returns SUCCESS, there can be * no higher match for this device. */ if (result == 0) { best = dl; pri = 0; break; } /* * The driver returned an error so it * certainly doesn't match. */ if (result > 0) { device_set_driver(child, NULL); continue; } /* * A priority lower than SUCCESS, remember the * best matching driver. Initialise the value * of pri for the first match. */ if (best == NULL || result > pri) { best = dl; pri = result; continue; } } /* * If we have an unambiguous match in this devclass, * don't look in the parent. */ if (best && pri == 0) break; } /* * If we found a driver, change state and initialise the devclass. */ /* XXX What happens if we rebid and got no best? */ if (best) { /* * If this device was atached, and we were asked to * rescan, and it is a different driver, then we have * to detach the old driver and reattach this new one. * Note, we don't have to check for DF_REBID here * because if the state is > DS_ALIVE, we know it must * be. * * This assumes that all DF_REBID drivers can have * their probe routine called at any time and that * they are idempotent as well as completely benign in * normal operations. * * We also have to make sure that the detach * succeeded, otherwise we fail the operation (or * maybe it should just fail silently? I'm torn). */ if (child->state > DS_ALIVE && best->driver != child->driver) if ((result = device_detach(dev)) != 0) return (result); /* Set the winning driver, devclass, and flags. */ if (!child->devclass) device_set_devclass(child, best->driver->name); device_set_driver(child, best->driver); resource_int_value(best->driver->name, child->unit, "flags", &child->devflags); if (pri < 0) { /* * A bit bogus. Call the probe method again to make * sure that we have the right description. */ DEVICE_PROBE(child); #if 0 child->flags |= DF_REBID; #endif } else child->flags &= ~DF_REBID; child->state = DS_ALIVE; bus_data_generation_update(); return (0); } return (ENXIO); } /** * @brief Return the parent of a device */ device_t device_get_parent(device_t dev) { return (dev->parent); } /** * @brief Get a list of children of a device * * An array containing a list of all the children of the given device * is allocated and returned in @p *devlistp. The number of devices * in the array is returned in @p *devcountp. The caller should free * the array using @c free(p, M_TEMP). * * @param dev the device to examine * @param devlistp points at location for array pointer return * value * @param devcountp points at location for array size return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int device_get_children(device_t dev, device_t **devlistp, int *devcountp) { int count; device_t child; device_t *list; count = 0; TAILQ_FOREACH(child, &dev->children, link) { count++; } list = malloc(count * sizeof(device_t), M_TEMP, M_NOWAIT|M_ZERO); if (!list) return (ENOMEM); count = 0; TAILQ_FOREACH(child, &dev->children, link) { list[count] = child; count++; } *devlistp = list; *devcountp = count; return (0); } /** * @brief Return the current driver for the device or @c NULL if there * is no driver currently attached */ driver_t * device_get_driver(device_t dev) { return (dev->driver); } /** * @brief Return the current devclass for the device or @c NULL if * there is none. */ devclass_t device_get_devclass(device_t dev) { return (dev->devclass); } /** * @brief Return the name of the device's devclass or @c NULL if there * is none. */ const char * device_get_name(device_t dev) { if (dev != NULL && dev->devclass) return (devclass_get_name(dev->devclass)); return (NULL); } /** * @brief Return a string containing the device's devclass name * followed by an ascii representation of the device's unit number * (e.g. @c "foo2"). */ const char * device_get_nameunit(device_t dev) { return (dev->nameunit); } /** * @brief Return the device's unit number. */ int device_get_unit(device_t dev) { return (dev->unit); } /** * @brief Return the device's description string */ const char * device_get_desc(device_t dev) { return (dev->desc); } /** * @brief Return the device's flags */ u_int32_t device_get_flags(device_t dev) { return (dev->devflags); } struct sysctl_ctx_list * device_get_sysctl_ctx(device_t dev) { return (&dev->sysctl_ctx); } struct sysctl_oid * device_get_sysctl_tree(device_t dev) { return (dev->sysctl_tree); } /** * @brief Print the name of the device followed by a colon and a space * * @returns the number of characters printed */ int device_print_prettyname(device_t dev) { const char *name = device_get_name(dev); if (name == 0) return (printf("unknown: ")); return (printf("%s%d: ", name, device_get_unit(dev))); } /** * @brief Print the name of the device followed by a colon, a space * and the result of calling vprintf() with the value of @p fmt and * the following arguments. * * @returns the number of characters printed */ int device_printf(device_t dev, const char * fmt, ...) { va_list ap; int retval; retval = device_print_prettyname(dev); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } /** * @internal */ static void device_set_desc_internal(device_t dev, const char* desc, int copy) { if (dev->desc && (dev->flags & DF_DESCMALLOCED)) { free(dev->desc, M_BUS); dev->flags &= ~DF_DESCMALLOCED; dev->desc = NULL; } if (copy && desc) { dev->desc = malloc(strlen(desc) + 1, M_BUS, M_NOWAIT); if (dev->desc) { strcpy(dev->desc, desc); dev->flags |= DF_DESCMALLOCED; } } else { /* Avoid a -Wcast-qual warning */ dev->desc = (char *)(uintptr_t) desc; } bus_data_generation_update(); } /** * @brief Set the device's description * * The value of @c desc should be a string constant that will not * change (at least until the description is changed in a subsequent * call to device_set_desc() or device_set_desc_copy()). */ void device_set_desc(device_t dev, const char* desc) { device_set_desc_internal(dev, desc, FALSE); } /** * @brief Set the device's description * * The string pointed to by @c desc is copied. Use this function if * the device description is generated, (e.g. with sprintf()). */ void device_set_desc_copy(device_t dev, const char* desc) { device_set_desc_internal(dev, desc, TRUE); } /** * @brief Set the device's flags */ void device_set_flags(device_t dev, u_int32_t flags) { dev->devflags = flags; } /** * @brief Return the device's softc field * * The softc is allocated and zeroed when a driver is attached, based * on the size field of the driver. */ void * device_get_softc(device_t dev) { return (dev->softc); } /** * @brief Set the device's softc field * * Most drivers do not need to use this since the softc is allocated * automatically when the driver is attached. */ void device_set_softc(device_t dev, void *softc) { if (dev->softc && !(dev->flags & DF_EXTERNALSOFTC)) free(dev->softc, M_BUS_SC); dev->softc = softc; if (dev->softc) dev->flags |= DF_EXTERNALSOFTC; else dev->flags &= ~DF_EXTERNALSOFTC; } /** * @brief Get the device's ivars field * * The ivars field is used by the parent device to store per-device * state (e.g. the physical location of the device or a list of * resources). */ void * device_get_ivars(device_t dev) { KASSERT(dev != NULL, ("device_get_ivars(NULL, ...)")); return (dev->ivars); } /** * @brief Set the device's ivars field */ void device_set_ivars(device_t dev, void * ivars) { KASSERT(dev != NULL, ("device_set_ivars(NULL, ...)")); dev->ivars = ivars; } /** * @brief Return the device's state */ device_state_t device_get_state(device_t dev) { return (dev->state); } /** * @brief Set the DF_ENABLED flag for the device */ void device_enable(device_t dev) { dev->flags |= DF_ENABLED; } /** * @brief Clear the DF_ENABLED flag for the device */ void device_disable(device_t dev) { dev->flags &= ~DF_ENABLED; } /** * @brief Increment the busy counter for the device */ void device_busy(device_t dev) { if (dev->state < DS_ATTACHED) panic("device_busy: called for unattached device"); if (dev->busy == 0 && dev->parent) device_busy(dev->parent); dev->busy++; dev->state = DS_BUSY; } /** * @brief Decrement the busy counter for the device */ void device_unbusy(device_t dev) { if (dev->state != DS_BUSY) panic("device_unbusy: called for non-busy device %s", device_get_nameunit(dev)); dev->busy--; if (dev->busy == 0) { if (dev->parent) device_unbusy(dev->parent); dev->state = DS_ATTACHED; } } /** * @brief Set the DF_QUIET flag for the device */ void device_quiet(device_t dev) { dev->flags |= DF_QUIET; } /** * @brief Clear the DF_QUIET flag for the device */ void device_verbose(device_t dev) { dev->flags &= ~DF_QUIET; } /** * @brief Return non-zero if the DF_QUIET flag is set on the device */ int device_is_quiet(device_t dev) { return ((dev->flags & DF_QUIET) != 0); } /** * @brief Return non-zero if the DF_ENABLED flag is set on the device */ int device_is_enabled(device_t dev) { return ((dev->flags & DF_ENABLED) != 0); } /** * @brief Return non-zero if the device was successfully probed */ int device_is_alive(device_t dev) { return (dev->state >= DS_ALIVE); } /** * @brief Return non-zero if the device currently has a driver * attached to it */ int device_is_attached(device_t dev) { return (dev->state >= DS_ATTACHED); } /** * @brief Set the devclass of a device * @see devclass_add_device(). */ int device_set_devclass(device_t dev, const char *classname) { devclass_t dc; int error; if (!classname) { if (dev->devclass) devclass_delete_device(dev->devclass, dev); return (0); } if (dev->devclass) { printf("device_set_devclass: device class already set\n"); return (EINVAL); } dc = devclass_find_internal(classname, NULL, TRUE); if (!dc) return (ENOMEM); error = devclass_add_device(dc, dev); bus_data_generation_update(); return (error); } /** * @brief Set the driver of a device * * @retval 0 success * @retval EBUSY the device already has a driver attached * @retval ENOMEM a memory allocation failure occurred */ int device_set_driver(device_t dev, driver_t *driver) { if (dev->state >= DS_ATTACHED) return (EBUSY); if (dev->driver == driver) return (0); if (dev->softc && !(dev->flags & DF_EXTERNALSOFTC)) { free(dev->softc, M_BUS_SC); dev->softc = NULL; } kobj_delete((kobj_t) dev, NULL); dev->driver = driver; if (driver) { kobj_init((kobj_t) dev, (kobj_class_t) driver); if (!(dev->flags & DF_EXTERNALSOFTC) && driver->size > 0) { dev->softc = malloc(driver->size, M_BUS_SC, M_NOWAIT | M_ZERO); if (!dev->softc) { kobj_delete((kobj_t) dev, NULL); kobj_init((kobj_t) dev, &null_class); dev->driver = NULL; return (ENOMEM); } } } else { kobj_init((kobj_t) dev, &null_class); } bus_data_generation_update(); return (0); } /** * @brief Probe a device and attach a driver if possible * * This function is the core of the device autoconfiguration * system. Its purpose is to select a suitable driver for a device and * then call that driver to initialise the hardware appropriately. The * driver is selected by calling the DEVICE_PROBE() method of a set of * candidate drivers and then choosing the driver which returned the * best value. This driver is then attached to the device using * device_attach(). * * The set of suitable drivers is taken from the list of drivers in * the parent device's devclass. If the device was originally created * with a specific class name (see device_add_child()), only drivers * with that name are probed, otherwise all drivers in the devclass * are probed. If no drivers return successful probe values in the * parent devclass, the search continues in the parent of that * devclass (see devclass_get_parent()) if any. * * @param dev the device to initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_probe_and_attach(device_t dev) { int error; GIANT_REQUIRED; if (dev->state >= DS_ALIVE && (dev->flags & DF_REBID) == 0) return (0); if (!(dev->flags & DF_ENABLED)) { if (bootverbose && device_get_name(dev) != NULL) { device_print_prettyname(dev); printf("not probed (disabled)\n"); } return (0); } if ((error = device_probe_child(dev->parent, dev)) != 0) { if (!(dev->flags & DF_DONENOMATCH)) { BUS_PROBE_NOMATCH(dev->parent, dev); devnomatch(dev); dev->flags |= DF_DONENOMATCH; } return (error); } error = device_attach(dev); return (error); } /** * @brief Attach a device driver to a device * * This function is a wrapper around the DEVICE_ATTACH() driver * method. In addition to calling DEVICE_ATTACH(), it initialises the * device's sysctl tree, optionally prints a description of the device * and queues a notification event for user-based device management * services. * * Normally this function is only called internally from * device_probe_and_attach(). * * @param dev the device to initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_attach(device_t dev) { int error; device_sysctl_init(dev); if (!device_is_quiet(dev)) device_print_child(dev->parent, dev); if ((error = DEVICE_ATTACH(dev)) != 0) { printf("device_attach: %s%d attach returned %d\n", dev->driver->name, dev->unit, error); /* Unset the class; set in device_probe_child */ if (dev->devclass == NULL) device_set_devclass(dev, NULL); device_set_driver(dev, NULL); device_sysctl_fini(dev); dev->state = DS_NOTPRESENT; return (error); } dev->state = DS_ATTACHED; devadded(dev); return (0); } /** * @brief Detach a driver from a device * * This function is a wrapper around the DEVICE_DETACH() driver * method. If the call to DEVICE_DETACH() succeeds, it calls * BUS_CHILD_DETACHED() for the parent of @p dev, queues a * notification event for user-based device management services and * cleans up the device's sysctl tree. * * @param dev the device to un-initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_detach(device_t dev) { int error; GIANT_REQUIRED; PDEBUG(("%s", DEVICENAME(dev))); if (dev->state == DS_BUSY) return (EBUSY); if (dev->state != DS_ATTACHED) return (0); if ((error = DEVICE_DETACH(dev)) != 0) return (error); devremoved(dev); device_printf(dev, "detached\n"); if (dev->parent) BUS_CHILD_DETACHED(dev->parent, dev); if (!(dev->flags & DF_FIXEDCLASS)) devclass_delete_device(dev->devclass, dev); dev->state = DS_NOTPRESENT; device_set_driver(dev, NULL); device_set_desc(dev, NULL); device_sysctl_fini(dev); return (0); } /** * @brief Tells a driver to quiesce itself. * * This function is a wrapper around the DEVICE_QUIESCE() driver * method. If the call to DEVICE_QUIESCE() succeeds. * * @param dev the device to quiesce * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_quiesce(device_t dev) { PDEBUG(("%s", DEVICENAME(dev))); if (dev->state == DS_BUSY) return (EBUSY); if (dev->state != DS_ATTACHED) return (0); return (DEVICE_QUIESCE(dev)); } /** * @brief Notify a device of system shutdown * * This function calls the DEVICE_SHUTDOWN() driver method if the * device currently has an attached driver. * * @returns the value returned by DEVICE_SHUTDOWN() */ int device_shutdown(device_t dev) { if (dev->state < DS_ATTACHED) return (0); return (DEVICE_SHUTDOWN(dev)); } /** * @brief Set the unit number of a device * * This function can be used to override the unit number used for a * device (e.g. to wire a device to a pre-configured unit number). */ int device_set_unit(device_t dev, int unit) { devclass_t dc; int err; dc = device_get_devclass(dev); if (unit < dc->maxunit && dc->devices[unit]) return (EBUSY); err = devclass_delete_device(dc, dev); if (err) return (err); dev->unit = unit; err = devclass_add_device(dc, dev); if (err) return (err); bus_data_generation_update(); return (0); } /*======================================*/ /* * Some useful method implementations to make life easier for bus drivers. */ /** * @brief Initialise a resource list. * * @param rl the resource list to initialise */ void resource_list_init(struct resource_list *rl) { STAILQ_INIT(rl); } /** * @brief Reclaim memory used by a resource list. * * This function frees the memory for all resource entries on the list * (if any). * * @param rl the resource list to free */ void resource_list_free(struct resource_list *rl) { struct resource_list_entry *rle; while ((rle = STAILQ_FIRST(rl)) != NULL) { if (rle->res) panic("resource_list_free: resource entry is busy"); STAILQ_REMOVE_HEAD(rl, link); free(rle, M_BUS); } } /** * @brief Add a resource entry. * * This function adds a resource entry using the given @p type, @p * start, @p end and @p count values. A rid value is chosen by * searching sequentially for the first unused rid starting at zero. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param start the start address of the resource * @param end the end address of the resource * @param count XXX end-start+1 */ int resource_list_add_next(struct resource_list *rl, int type, u_long start, u_long end, u_long count) { int rid; rid = 0; while (resource_list_find(rl, type, rid) != NULL) rid++; resource_list_add(rl, type, rid, start, end, count); return (rid); } /** * @brief Add or modify a resource entry. * * If an existing entry exists with the same type and rid, it will be * modified using the given values of @p start, @p end and @p * count. If no entry exists, a new one will be created using the * given values. The resource list entry that matches is then returned. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * @param start the start address of the resource * @param end the end address of the resource * @param count XXX end-start+1 */ struct resource_list_entry * resource_list_add(struct resource_list *rl, int type, int rid, u_long start, u_long end, u_long count) { struct resource_list_entry *rle; rle = resource_list_find(rl, type, rid); if (!rle) { rle = malloc(sizeof(struct resource_list_entry), M_BUS, M_NOWAIT); if (!rle) panic("resource_list_add: can't record entry"); STAILQ_INSERT_TAIL(rl, rle, link); rle->type = type; rle->rid = rid; rle->res = NULL; } if (rle->res) panic("resource_list_add: resource entry is busy"); rle->start = start; rle->end = end; rle->count = count; return (rle); } /** * @brief Find a resource entry by type and rid. * * @param rl the resource list to search * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * * @returns the resource entry pointer or NULL if there is no such * entry. */ struct resource_list_entry * resource_list_find(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle; STAILQ_FOREACH(rle, rl, link) { if (rle->type == type && rle->rid == rid) return (rle); } return (NULL); } /** * @brief Delete a resource entry. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier */ void resource_list_delete(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle = resource_list_find(rl, type, rid); if (rle) { if (rle->res != NULL) panic("resource_list_delete: resource has not been released"); STAILQ_REMOVE(rl, rle, resource_list_entry, link); free(rle, M_BUS); } } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE() * * Implement BUS_ALLOC_RESOURCE() by looking up a resource from the list * and passing the allocation up to the parent of @p bus. This assumes * that the first entry of @c device_get_ivars(child) is a struct * resource_list. This also handles 'passthrough' allocations where a * child is a remote descendant of bus by passing the allocation up to * the parent of bus. * * Typically, a bus driver would store a list of child resources * somewhere in the child device's ivars (see device_get_ivars()) and * its implementation of BUS_ALLOC_RESOURCE() would find that list and * then call resource_list_alloc() to perform the allocation. * * @param rl the resource list to allocate from * @param bus the parent device of @p child * @param child the device which is requesting an allocation * @param type the type of resource to allocate * @param rid a pointer to the resource identifier * @param start hint at the start of the resource range - pass * @c 0UL for any start address * @param end hint at the end of the resource range - pass * @c ~0UL for any end address * @param count hint at the size of range required - pass @c 1 * for any size * @param flags any extra flags to control the resource * allocation - see @c RF_XXX flags in * for details * * @returns the resource which was allocated or @c NULL if no * resource could be allocated */ struct resource * resource_list_alloc(struct resource_list *rl, device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); int isdefault = (start == 0UL && end == ~0UL); if (passthrough) { return (BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags)); } rle = resource_list_find(rl, type, *rid); if (!rle) return (NULL); /* no resource of that type/rid */ if (rle->res) panic("resource_list_alloc: resource entry is busy"); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } rle->res = BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags); /* * Record the new range. */ if (rle->res) { rle->start = rman_get_start(rle->res); rle->end = rman_get_end(rle->res); rle->count = count; } return (rle->res); } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE() * * Implement BUS_RELEASE_RESOURCE() using a resource list. Normally * used with resource_list_alloc(). * * @param rl the resource list which was allocated from * @param bus the parent device of @p child * @param child the device which is requesting a release * @param type the type of resource to allocate * @param rid the resource identifier * @param res the resource to release * * @retval 0 success * @retval non-zero a standard unix error code indicating what * error condition prevented the operation */ int resource_list_release(struct resource_list *rl, device_t bus, device_t child, int type, int rid, struct resource *res) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); int error; if (passthrough) { return (BUS_RELEASE_RESOURCE(device_get_parent(bus), child, type, rid, res)); } rle = resource_list_find(rl, type, rid); if (!rle) panic("resource_list_release: can't find resource"); if (!rle->res) panic("resource_list_release: resource entry is not busy"); error = BUS_RELEASE_RESOURCE(device_get_parent(bus), child, type, rid, res); if (error) return (error); rle->res = NULL; return (0); } /** * @brief Print a description of resources in a resource list * * Print all resources of a specified type, for use in BUS_PRINT_CHILD(). * The name is printed if at least one resource of the given type is available. * The format is used to print resource start and end. * * @param rl the resource list to print * @param name the name of @p type, e.g. @c "memory" * @param type type type of resource entry to print * @param format printf(9) format string to print resource * start and end values * * @returns the number of characters printed */ int resource_list_print_type(struct resource_list *rl, const char *name, int type, const char *format) { struct resource_list_entry *rle; int printed, retval; printed = 0; retval = 0; /* Yes, this is kinda cheating */ STAILQ_FOREACH(rle, rl, link) { if (rle->type == type) { if (printed == 0) retval += printf(" %s ", name); else retval += printf(","); printed++; retval += printf(format, rle->start); if (rle->count > 1) { retval += printf("-"); retval += printf(format, rle->start + rle->count - 1); } } } return (retval); } /** * @brief Releases all the resources in a list. * * @param rl The resource list to purge. * * @returns nothing */ void resource_list_purge(struct resource_list *rl) { struct resource_list_entry *rle; while ((rle = STAILQ_FIRST(rl)) != NULL) { if (rle->res) bus_release_resource(rman_get_device(rle->res), rle->type, rle->rid, rle->res); STAILQ_REMOVE_HEAD(rl, link); free(rle, M_BUS); } } device_t bus_generic_add_child(device_t dev, int order, const char *name, int unit) { return (device_add_child_ordered(dev, order, name, unit)); } /** * @brief Helper function for implementing DEVICE_PROBE() * * This function can be used to help implement the DEVICE_PROBE() for * a bus (i.e. a device which has other devices attached to it). It * calls the DEVICE_IDENTIFY() method of each driver in the device's * devclass. */ int bus_generic_probe(device_t dev) { devclass_t dc = dev->devclass; driverlink_t dl; TAILQ_FOREACH(dl, &dc->drivers, link) { DEVICE_IDENTIFY(dl->driver, dev); } return (0); } /** * @brief Helper function for implementing DEVICE_ATTACH() * * This function can be used to help implement the DEVICE_ATTACH() for * a bus. It calls device_probe_and_attach() for each of the device's * children. */ int bus_generic_attach(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->children, link) { device_probe_and_attach(child); } return (0); } /** * @brief Helper function for implementing DEVICE_DETACH() * * This function can be used to help implement the DEVICE_DETACH() for * a bus. It calls device_detach() for each of the device's * children. */ int bus_generic_detach(device_t dev) { device_t child; int error; if (dev->state != DS_ATTACHED) return (EBUSY); TAILQ_FOREACH(child, &dev->children, link) { if ((error = device_detach(child)) != 0) return (error); } return (0); } /** * @brief Helper function for implementing DEVICE_SHUTDOWN() * * This function can be used to help implement the DEVICE_SHUTDOWN() * for a bus. It calls device_shutdown() for each of the device's * children. */ int bus_generic_shutdown(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->children, link) { device_shutdown(child); } return (0); } /** * @brief Helper function for implementing DEVICE_SUSPEND() * * This function can be used to help implement the DEVICE_SUSPEND() * for a bus. It calls DEVICE_SUSPEND() for each of the device's * children. If any call to DEVICE_SUSPEND() fails, the suspend * operation is aborted and any devices which were suspended are * resumed immediately by calling their DEVICE_RESUME() methods. */ int bus_generic_suspend(device_t dev) { int error; device_t child, child2; TAILQ_FOREACH(child, &dev->children, link) { error = DEVICE_SUSPEND(child); if (error) { for (child2 = TAILQ_FIRST(&dev->children); child2 && child2 != child; child2 = TAILQ_NEXT(child2, link)) DEVICE_RESUME(child2); return (error); } } return (0); } /** * @brief Helper function for implementing DEVICE_RESUME() * * This function can be used to help implement the DEVICE_RESUME() for * a bus. It calls DEVICE_RESUME() on each of the device's children. */ int bus_generic_resume(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->children, link) { DEVICE_RESUME(child); /* if resume fails, there's nothing we can usefully do... */ } return (0); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function prints the first part of the ascii representation of * @p child, including its name, unit and description (if any - see * device_set_desc()). * * @returns the number of characters printed */ int bus_print_child_header(device_t dev, device_t child) { int retval = 0; if (device_get_desc(child)) { retval += device_printf(child, "<%s>", device_get_desc(child)); } else { retval += printf("%s", device_get_nameunit(child)); } return (retval); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function prints the last part of the ascii representation of * @p child, which consists of the string @c " on " followed by the * name and unit of the @p dev. * * @returns the number of characters printed */ int bus_print_child_footer(device_t dev, device_t child) { return (printf(" on %s\n", device_get_nameunit(dev))); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function simply calls bus_print_child_header() followed by * bus_print_child_footer(). * * @returns the number of characters printed */ int bus_generic_print_child(device_t dev, device_t child) { int retval = 0; retval += bus_print_child_header(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } /** * @brief Stub function for implementing BUS_READ_IVAR(). * * @returns ENOENT */ int bus_generic_read_ivar(device_t dev, device_t child, int index, uintptr_t * result) { return (ENOENT); } /** * @brief Stub function for implementing BUS_WRITE_IVAR(). * * @returns ENOENT */ int bus_generic_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { return (ENOENT); } /** * @brief Stub function for implementing BUS_GET_RESOURCE_LIST(). * * @returns NULL */ struct resource_list * bus_generic_get_resource_list(device_t dev, device_t child) { return (NULL); } /** * @brief Helper function for implementing BUS_DRIVER_ADDED(). * * This implementation of BUS_DRIVER_ADDED() simply calls the driver's * DEVICE_IDENTIFY() method to allow it to add new children to the bus * and then calls device_probe_and_attach() for each unattached child. */ void bus_generic_driver_added(device_t dev, driver_t *driver) { device_t child; DEVICE_IDENTIFY(driver, dev); TAILQ_FOREACH(child, &dev->children, link) { if (child->state == DS_NOTPRESENT || (child->flags & DF_REBID)) device_probe_and_attach(child); } } /** * @brief Helper function for implementing BUS_SETUP_INTR(). * * This simple implementation of BUS_SETUP_INTR() simply calls the * BUS_SETUP_INTR() method of the parent of @p dev. */ int bus_generic_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_SETUP_INTR(dev->parent, child, irq, flags, filter, intr, arg, cookiep)); return (EINVAL); } /** * @brief Helper function for implementing BUS_TEARDOWN_INTR(). * * This simple implementation of BUS_TEARDOWN_INTR() simply calls the * BUS_TEARDOWN_INTR() method of the parent of @p dev. */ int bus_generic_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_TEARDOWN_INTR(dev->parent, child, irq, cookie)); return (EINVAL); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE(). * * This simple implementation of BUS_ALLOC_RESOURCE() simply calls the * BUS_ALLOC_RESOURCE() method of the parent of @p dev. */ struct resource * bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_ALLOC_RESOURCE(dev->parent, child, type, rid, start, end, count, flags)); return (NULL); } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE(). * * This simple implementation of BUS_RELEASE_RESOURCE() simply calls the * BUS_RELEASE_RESOURCE() method of the parent of @p dev. */ int bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_RELEASE_RESOURCE(dev->parent, child, type, rid, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_ACTIVATE_RESOURCE(). * * This simple implementation of BUS_ACTIVATE_RESOURCE() simply calls the * BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_ACTIVATE_RESOURCE(dev->parent, child, type, rid, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_DEACTIVATE_RESOURCE(). * * This simple implementation of BUS_DEACTIVATE_RESOURCE() simply calls the * BUS_DEACTIVATE_RESOURCE() method of the parent of @p dev. */ int bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_DEACTIVATE_RESOURCE(dev->parent, child, type, rid, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_CONFIG_INTR(). * * This simple implementation of BUS_CONFIG_INTR() simply calls the * BUS_CONFIG_INTR() method of the parent of @p dev. */ int bus_generic_config_intr(device_t dev, int irq, enum intr_trigger trig, enum intr_polarity pol) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_CONFIG_INTR(dev->parent, irq, trig, pol)); return (EINVAL); } /** * @brief Helper function for implementing BUS_GET_DMA_TAG(). * * This simple implementation of BUS_GET_DMA_TAG() simply calls the * BUS_GET_DMA_TAG() method of the parent of @p dev. */ bus_dma_tag_t bus_generic_get_dma_tag(device_t dev, device_t child) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent != NULL) return (BUS_GET_DMA_TAG(dev->parent, child)); return (NULL); } /** * @brief Helper function for implementing BUS_GET_RESOURCE(). * * This implementation of BUS_GET_RESOURCE() uses the * resource_list_find() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * search. */ int bus_generic_rl_get_resource(device_t dev, device_t child, int type, int rid, u_long *startp, u_long *countp) { struct resource_list * rl = NULL; struct resource_list_entry * rle = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); rle = resource_list_find(rl, type, rid); if (!rle) return (ENOENT); if (startp) *startp = rle->start; if (countp) *countp = rle->count; return (0); } /** * @brief Helper function for implementing BUS_SET_RESOURCE(). * * This implementation of BUS_SET_RESOURCE() uses the * resource_list_add() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * edit. */ int bus_generic_rl_set_resource(device_t dev, device_t child, int type, int rid, u_long start, u_long count) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); resource_list_add(rl, type, rid, start, (start + count - 1), count); return (0); } /** * @brief Helper function for implementing BUS_DELETE_RESOURCE(). * * This implementation of BUS_DELETE_RESOURCE() uses the * resource_list_delete() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * edit. */ void bus_generic_rl_delete_resource(device_t dev, device_t child, int type, int rid) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return; resource_list_delete(rl, type, rid); return; } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE(). * * This implementation of BUS_RELEASE_RESOURCE() uses the * resource_list_release() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list. */ int bus_generic_rl_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); return (resource_list_release(rl, dev, child, type, rid, r)); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE(). * * This implementation of BUS_ALLOC_RESOURCE() uses the * resource_list_alloc() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list. */ struct resource * bus_generic_rl_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (NULL); return (resource_list_alloc(rl, dev, child, type, rid, start, end, count, flags)); } /** * @brief Helper function for implementing BUS_CHILD_PRESENT(). * * This simple implementation of BUS_CHILD_PRESENT() simply calls the * BUS_CHILD_PRESENT() method of the parent of @p dev. */ int bus_generic_child_present(device_t dev, device_t child) { return (BUS_CHILD_PRESENT(device_get_parent(dev), dev)); } /* * Some convenience functions to make it easier for drivers to use the * resource-management functions. All these really do is hide the * indirection through the parent's method table, making for slightly * less-wordy code. In the future, it might make sense for this code * to maintain some sort of a list of resources allocated by each device. */ int bus_alloc_resources(device_t dev, struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) res[i] = NULL; for (i = 0; rs[i].type != -1; i++) { res[i] = bus_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bus_release_resources(dev, rs, res); return (ENXIO); } } return (0); } void bus_release_resources(device_t dev, const struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) if (res[i] != NULL) { bus_release_resource( dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * @brief Wrapper function for BUS_ALLOC_RESOURCE(). * * This function simply calls the BUS_ALLOC_RESOURCE() method of the * parent of @p dev. */ struct resource * bus_alloc_resource(device_t dev, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { if (dev->parent == NULL) return (NULL); return (BUS_ALLOC_RESOURCE(dev->parent, dev, type, rid, start, end, count, flags)); } /** * @brief Wrapper function for BUS_ACTIVATE_RESOURCE(). * * This function simply calls the BUS_ACTIVATE_RESOURCE() method of the * parent of @p dev. */ int bus_activate_resource(device_t dev, int type, int rid, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_ACTIVATE_RESOURCE(dev->parent, dev, type, rid, r)); } /** * @brief Wrapper function for BUS_DEACTIVATE_RESOURCE(). * * This function simply calls the BUS_DEACTIVATE_RESOURCE() method of the * parent of @p dev. */ int bus_deactivate_resource(device_t dev, int type, int rid, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_DEACTIVATE_RESOURCE(dev->parent, dev, type, rid, r)); } /** * @brief Wrapper function for BUS_RELEASE_RESOURCE(). * * This function simply calls the BUS_RELEASE_RESOURCE() method of the * parent of @p dev. */ int bus_release_resource(device_t dev, int type, int rid, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_RELEASE_RESOURCE(dev->parent, dev, type, rid, r)); } /** * @brief Wrapper function for BUS_SETUP_INTR(). * * This function simply calls the BUS_SETUP_INTR() method of the * parent of @p dev. */ int bus_setup_intr(device_t dev, struct resource *r, int flags, driver_filter_t filter, driver_intr_t handler, void *arg, void **cookiep) { int error; if (dev->parent != NULL) { - if ((flags &~ INTR_ENTROPY) == (INTR_TYPE_NET | INTR_MPSAFE) && - !debug_mpsafenet) - flags &= ~INTR_MPSAFE; error = BUS_SETUP_INTR(dev->parent, dev, r, flags, filter, handler, arg, cookiep); if (error == 0) { if (handler != NULL && !(flags & INTR_MPSAFE)) device_printf(dev, "[GIANT-LOCKED]\n"); if (bootverbose && (flags & INTR_MPSAFE)) device_printf(dev, "[MPSAFE]\n"); if (filter != NULL) { if (handler == NULL) device_printf(dev, "[FILTER]\n"); else device_printf(dev, "[FILTER+ITHREAD]\n"); } else device_printf(dev, "[ITHREAD]\n"); } } else error = EINVAL; return (error); } /** * @brief Wrapper function for BUS_TEARDOWN_INTR(). * * This function simply calls the BUS_TEARDOWN_INTR() method of the * parent of @p dev. */ int bus_teardown_intr(device_t dev, struct resource *r, void *cookie) { if (dev->parent == NULL) return (EINVAL); return (BUS_TEARDOWN_INTR(dev->parent, dev, r, cookie)); } /** * @brief Wrapper function for BUS_SET_RESOURCE(). * * This function simply calls the BUS_SET_RESOURCE() method of the * parent of @p dev. */ int bus_set_resource(device_t dev, int type, int rid, u_long start, u_long count) { return (BUS_SET_RESOURCE(device_get_parent(dev), dev, type, rid, start, count)); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev. */ int bus_get_resource(device_t dev, int type, int rid, u_long *startp, u_long *countp) { return (BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, startp, countp)); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev and returns the start value. */ u_long bus_get_resource_start(device_t dev, int type, int rid) { u_long start, count; int error; error = BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, &start, &count); if (error) return (0); return (start); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev and returns the count value. */ u_long bus_get_resource_count(device_t dev, int type, int rid) { u_long start, count; int error; error = BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, &start, &count); if (error) return (0); return (count); } /** * @brief Wrapper function for BUS_DELETE_RESOURCE(). * * This function simply calls the BUS_DELETE_RESOURCE() method of the * parent of @p dev. */ void bus_delete_resource(device_t dev, int type, int rid) { BUS_DELETE_RESOURCE(device_get_parent(dev), dev, type, rid); } /** * @brief Wrapper function for BUS_CHILD_PRESENT(). * * This function simply calls the BUS_CHILD_PRESENT() method of the * parent of @p dev. */ int bus_child_present(device_t child) { return (BUS_CHILD_PRESENT(device_get_parent(child), child)); } /** * @brief Wrapper function for BUS_CHILD_PNPINFO_STR(). * * This function simply calls the BUS_CHILD_PNPINFO_STR() method of the * parent of @p dev. */ int bus_child_pnpinfo_str(device_t child, char *buf, size_t buflen) { device_t parent; parent = device_get_parent(child); if (parent == NULL) { *buf = '\0'; return (0); } return (BUS_CHILD_PNPINFO_STR(parent, child, buf, buflen)); } /** * @brief Wrapper function for BUS_CHILD_LOCATION_STR(). * * This function simply calls the BUS_CHILD_LOCATION_STR() method of the * parent of @p dev. */ int bus_child_location_str(device_t child, char *buf, size_t buflen) { device_t parent; parent = device_get_parent(child); if (parent == NULL) { *buf = '\0'; return (0); } return (BUS_CHILD_LOCATION_STR(parent, child, buf, buflen)); } /** * @brief Wrapper function for BUS_GET_DMA_TAG(). * * This function simply calls the BUS_GET_DMA_TAG() method of the * parent of @p dev. */ bus_dma_tag_t bus_get_dma_tag(device_t dev) { device_t parent; parent = device_get_parent(dev); if (parent == NULL) return (NULL); return (BUS_GET_DMA_TAG(parent, dev)); } /* Resume all devices and then notify userland that we're up again. */ static int root_resume(device_t dev) { int error; error = bus_generic_resume(dev); if (error == 0) devctl_notify("kern", "power", "resume", NULL); return (error); } static int root_print_child(device_t dev, device_t child) { int retval = 0; retval += bus_print_child_header(dev, child); retval += printf("\n"); return (retval); } static int root_setup_intr(device_t dev, device_t child, driver_intr_t *intr, void *arg, void **cookiep) { /* * If an interrupt mapping gets to here something bad has happened. */ panic("root_setup_intr"); } /* * If we get here, assume that the device is permanant and really is * present in the system. Removable bus drivers are expected to intercept * this call long before it gets here. We return -1 so that drivers that * really care can check vs -1 or some ERRNO returned higher in the food * chain. */ static int root_child_present(device_t dev, device_t child) { return (-1); } static kobj_method_t root_methods[] = { /* Device interface */ KOBJMETHOD(device_shutdown, bus_generic_shutdown), KOBJMETHOD(device_suspend, bus_generic_suspend), KOBJMETHOD(device_resume, root_resume), /* Bus interface */ KOBJMETHOD(bus_print_child, root_print_child), KOBJMETHOD(bus_read_ivar, bus_generic_read_ivar), KOBJMETHOD(bus_write_ivar, bus_generic_write_ivar), KOBJMETHOD(bus_setup_intr, root_setup_intr), KOBJMETHOD(bus_child_present, root_child_present), { 0, 0 } }; static driver_t root_driver = { "root", root_methods, 1, /* no softc */ }; device_t root_bus; devclass_t root_devclass; static int root_bus_module_handler(module_t mod, int what, void* arg) { switch (what) { case MOD_LOAD: TAILQ_INIT(&bus_data_devices); kobj_class_compile((kobj_class_t) &root_driver); root_bus = make_device(NULL, "root", 0); root_bus->desc = "System root bus"; kobj_init((kobj_t) root_bus, (kobj_class_t) &root_driver); root_bus->driver = &root_driver; root_bus->state = DS_ATTACHED; root_devclass = devclass_find_internal("root", NULL, FALSE); devinit(); return (0); case MOD_SHUTDOWN: device_shutdown(root_bus); return (0); default: return (EOPNOTSUPP); } return (0); } static moduledata_t root_bus_mod = { "rootbus", root_bus_module_handler, 0 }; DECLARE_MODULE(rootbus, root_bus_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); /** * @brief Automatically configure devices * * This function begins the autoconfiguration process by calling * device_probe_and_attach() for each child of the @c root0 device. */ void root_bus_configure(void) { device_t dev; PDEBUG((".")); TAILQ_FOREACH(dev, &root_bus->children, link) { device_probe_and_attach(dev); } } /** * @brief Module handler for registering device drivers * * This module handler is used to automatically register device * drivers when modules are loaded. If @p what is MOD_LOAD, it calls * devclass_add_driver() for the driver described by the * driver_module_data structure pointed to by @p arg */ int driver_module_handler(module_t mod, int what, void *arg) { int error; struct driver_module_data *dmd; devclass_t bus_devclass; kobj_class_t driver; dmd = (struct driver_module_data *)arg; bus_devclass = devclass_find_internal(dmd->dmd_busname, NULL, TRUE); error = 0; switch (what) { case MOD_LOAD: if (dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); driver = dmd->dmd_driver; PDEBUG(("Loading module: driver %s on bus %s", DRIVERNAME(driver), dmd->dmd_busname)); error = devclass_add_driver(bus_devclass, driver); if (error) break; /* * If the driver has any base classes, make the * devclass inherit from the devclass of the driver's * first base class. This will allow the system to * search for drivers in both devclasses for children * of a device using this driver. */ if (driver->baseclasses) { const char *parentname; parentname = driver->baseclasses[0]->name; *dmd->dmd_devclass = devclass_find_internal(driver->name, parentname, TRUE); } else { *dmd->dmd_devclass = devclass_find_internal(driver->name, NULL, TRUE); } break; case MOD_UNLOAD: PDEBUG(("Unloading module: driver %s from bus %s", DRIVERNAME(dmd->dmd_driver), dmd->dmd_busname)); error = devclass_delete_driver(bus_devclass, dmd->dmd_driver); if (!error && dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); break; case MOD_QUIESCE: PDEBUG(("Quiesce module: driver %s from bus %s", DRIVERNAME(dmd->dmd_driver), dmd->dmd_busname)); error = devclass_quiesce_driver(bus_devclass, dmd->dmd_driver); if (!error && dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); break; default: error = EOPNOTSUPP; break; } return (error); } /** * @brief Enumerate all hinted devices for this bus. * * Walks through the hints for this bus and calls the bus_hinted_child * routine for each one it fines. It searches first for the specific * bus that's being probed for hinted children (eg isa0), and then for * generic children (eg isa). * * @param dev bus device to enumerate */ void bus_enumerate_hinted_children(device_t bus) { int i; const char *dname, *busname; int dunit; /* * enumerate all devices on the specific bus */ busname = device_get_nameunit(bus); i = 0; while (resource_find_match(&i, &dname, &dunit, "at", busname) == 0) BUS_HINTED_CHILD(bus, dname, dunit); /* * and all the generic ones. */ busname = device_get_name(bus); i = 0; while (resource_find_match(&i, &dname, &dunit, "at", busname) == 0) BUS_HINTED_CHILD(bus, dname, dunit); } #ifdef BUS_DEBUG /* the _short versions avoid iteration by not calling anything that prints * more than oneliners. I love oneliners. */ static void print_device_short(device_t dev, int indent) { if (!dev) return; indentprintf(("device %d: <%s> %sparent,%schildren,%s%s%s%s%s,%sivars,%ssoftc,busy=%d\n", dev->unit, dev->desc, (dev->parent? "":"no "), (TAILQ_EMPTY(&dev->children)? "no ":""), (dev->flags&DF_ENABLED? "enabled,":"disabled,"), (dev->flags&DF_FIXEDCLASS? "fixed,":""), (dev->flags&DF_WILDCARD? "wildcard,":""), (dev->flags&DF_DESCMALLOCED? "descmalloced,":""), (dev->flags&DF_REBID? "rebiddable,":""), (dev->ivars? "":"no "), (dev->softc? "":"no "), dev->busy)); } static void print_device(device_t dev, int indent) { if (!dev) return; print_device_short(dev, indent); indentprintf(("Parent:\n")); print_device_short(dev->parent, indent+1); indentprintf(("Driver:\n")); print_driver_short(dev->driver, indent+1); indentprintf(("Devclass:\n")); print_devclass_short(dev->devclass, indent+1); } void print_device_tree_short(device_t dev, int indent) /* print the device and all its children (indented) */ { device_t child; if (!dev) return; print_device_short(dev, indent); TAILQ_FOREACH(child, &dev->children, link) { print_device_tree_short(child, indent+1); } } void print_device_tree(device_t dev, int indent) /* print the device and all its children (indented) */ { device_t child; if (!dev) return; print_device(dev, indent); TAILQ_FOREACH(child, &dev->children, link) { print_device_tree(child, indent+1); } } static void print_driver_short(driver_t *driver, int indent) { if (!driver) return; indentprintf(("driver %s: softc size = %zd\n", driver->name, driver->size)); } static void print_driver(driver_t *driver, int indent) { if (!driver) return; print_driver_short(driver, indent); } static void print_driver_list(driver_list_t drivers, int indent) { driverlink_t driver; TAILQ_FOREACH(driver, &drivers, link) { print_driver(driver->driver, indent); } } static void print_devclass_short(devclass_t dc, int indent) { if ( !dc ) return; indentprintf(("devclass %s: max units = %d\n", dc->name, dc->maxunit)); } static void print_devclass(devclass_t dc, int indent) { int i; if ( !dc ) return; print_devclass_short(dc, indent); indentprintf(("Drivers:\n")); print_driver_list(dc->drivers, indent+1); indentprintf(("Devices:\n")); for (i = 0; i < dc->maxunit; i++) if (dc->devices[i]) print_device(dc->devices[i], indent+1); } void print_devclass_list_short(void) { devclass_t dc; printf("Short listing of devclasses, drivers & devices:\n"); TAILQ_FOREACH(dc, &devclasses, link) { print_devclass_short(dc, 0); } } void print_devclass_list(void) { devclass_t dc; printf("Full listing of devclasses, drivers & devices:\n"); TAILQ_FOREACH(dc, &devclasses, link) { print_devclass(dc, 0); } } #endif /* * User-space access to the device tree. * * We implement a small set of nodes: * * hw.bus Single integer read method to obtain the * current generation count. * hw.bus.devices Reads the entire device tree in flat space. * hw.bus.rman Resource manager interface * * We might like to add the ability to scan devclasses and/or drivers to * determine what else is currently loaded/available. */ static int sysctl_bus(SYSCTL_HANDLER_ARGS) { struct u_businfo ubus; ubus.ub_version = BUS_USER_VERSION; ubus.ub_generation = bus_data_generation; return (SYSCTL_OUT(req, &ubus, sizeof(ubus))); } SYSCTL_NODE(_hw_bus, OID_AUTO, info, CTLFLAG_RW, sysctl_bus, "bus-related data"); static int sysctl_devices(SYSCTL_HANDLER_ARGS) { int *name = (int *)arg1; u_int namelen = arg2; int index; struct device *dev; struct u_device udev; /* XXX this is a bit big */ int error; if (namelen != 2) return (EINVAL); if (bus_data_generation_check(name[0])) return (EINVAL); index = name[1]; /* * Scan the list of devices, looking for the requested index. */ TAILQ_FOREACH(dev, &bus_data_devices, devlink) { if (index-- == 0) break; } if (dev == NULL) return (ENOENT); /* * Populate the return array. */ bzero(&udev, sizeof(udev)); udev.dv_handle = (uintptr_t)dev; udev.dv_parent = (uintptr_t)dev->parent; if (dev->nameunit != NULL) strlcpy(udev.dv_name, dev->nameunit, sizeof(udev.dv_name)); if (dev->desc != NULL) strlcpy(udev.dv_desc, dev->desc, sizeof(udev.dv_desc)); if (dev->driver != NULL && dev->driver->name != NULL) strlcpy(udev.dv_drivername, dev->driver->name, sizeof(udev.dv_drivername)); bus_child_pnpinfo_str(dev, udev.dv_pnpinfo, sizeof(udev.dv_pnpinfo)); bus_child_location_str(dev, udev.dv_location, sizeof(udev.dv_location)); udev.dv_devflags = dev->devflags; udev.dv_flags = dev->flags; udev.dv_state = dev->state; error = SYSCTL_OUT(req, &udev, sizeof(udev)); return (error); } SYSCTL_NODE(_hw_bus, OID_AUTO, devices, CTLFLAG_RD, sysctl_devices, "system device tree"); int bus_data_generation_check(int generation) { if (generation != bus_data_generation) return (1); /* XXX generate optimised lists here? */ return (0); } void bus_data_generation_update(void) { bus_data_generation++; } int bus_free_resource(device_t dev, int type, struct resource *r) { if (r == NULL) return (0); return (bus_release_resource(dev, type, rman_get_rid(r), r)); } Index: head/sys/kern/uipc_domain.c =================================================================== --- head/sys/kern/uipc_domain.c (revision 171612) +++ head/sys/kern/uipc_domain.c (revision 171613) @@ -1,483 +1,478 @@ /*- * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)uipc_domain.c 8.2 (Berkeley) 10/18/93 */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include /* * System initialization * * Note: domain initialization takes place on a per domain basis * as a result of traversing a SYSINIT linker set. Most likely, * each domain would want to call DOMAIN_SET(9) itself, which * would cause the domain to be added just after domaininit() * is called during startup. * * See DOMAIN_SET(9) for details on its use. */ static void domaininit(void *); SYSINIT(domain, SI_SUB_PROTO_DOMAIN, SI_ORDER_FIRST, domaininit, NULL) static void domainfinalize(void *); SYSINIT(domainfin, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_FIRST, domainfinalize, NULL) static struct callout pffast_callout; static struct callout pfslow_callout; static void pffasttimo(void *); static void pfslowtimo(void *); struct domain *domains; /* registered protocol domains */ int domain_init_status = 0; struct mtx dom_mtx; /* domain list lock */ MTX_SYSINIT(domain, &dom_mtx, "domain list", MTX_DEF); /* * Dummy protocol specific user requests function pointer array. * All functions return EOPNOTSUPP. */ struct pr_usrreqs nousrreqs = { .pru_accept = pru_accept_notsupp, .pru_attach = pru_attach_notsupp, .pru_bind = pru_bind_notsupp, .pru_connect = pru_connect_notsupp, .pru_connect2 = pru_connect2_notsupp, .pru_control = pru_control_notsupp, .pru_disconnect = pru_disconnect_notsupp, .pru_listen = pru_listen_notsupp, .pru_peeraddr = pru_peeraddr_notsupp, .pru_rcvd = pru_rcvd_notsupp, .pru_rcvoob = pru_rcvoob_notsupp, .pru_send = pru_send_notsupp, .pru_sense = pru_sense_null, .pru_shutdown = pru_shutdown_notsupp, .pru_sockaddr = pru_sockaddr_notsupp, .pru_sosend = pru_sosend_notsupp, .pru_soreceive = pru_soreceive_notsupp, .pru_sopoll = pru_sopoll_notsupp, }; static void protosw_init(struct protosw *pr) { struct pr_usrreqs *pu; pu = pr->pr_usrreqs; KASSERT(pu != NULL, ("protosw_init: %ssw[%d] has no usrreqs!", pr->pr_domain->dom_name, (int)(pr - pr->pr_domain->dom_protosw))); #define DEFAULT(foo, bar) if ((foo) == NULL) (foo) = (bar) DEFAULT(pu->pru_accept, pru_accept_notsupp); DEFAULT(pu->pru_connect, pru_connect_notsupp); DEFAULT(pu->pru_connect2, pru_connect2_notsupp); DEFAULT(pu->pru_control, pru_control_notsupp); DEFAULT(pu->pru_listen, pru_listen_notsupp); DEFAULT(pu->pru_rcvd, pru_rcvd_notsupp); DEFAULT(pu->pru_rcvoob, pru_rcvoob_notsupp); DEFAULT(pu->pru_sense, pru_sense_null); DEFAULT(pu->pru_sosend, sosend_generic); DEFAULT(pu->pru_soreceive, soreceive_generic); DEFAULT(pu->pru_sopoll, sopoll_generic); #undef DEFAULT if (pr->pr_init) (*pr->pr_init)(); } /* * Add a new protocol domain to the list of supported domains * Note: you cant unload it again because a socket may be using it. * XXX can't fail at this time. */ static void net_init_domain(struct domain *dp) { struct protosw *pr; if (dp->dom_init) (*dp->dom_init)(); for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) protosw_init(pr); /* * update global information about maximums */ max_hdr = max_linkhdr + max_protohdr; max_datalen = MHLEN - max_hdr; if (max_datalen < 1) panic("%s: max_datalen < 1", __func__); } /* * Add a new protocol domain to the list of supported domains * Note: you cant unload it again because a socket may be using it. * XXX can't fail at this time. */ void net_add_domain(void *data) { struct domain *dp; dp = (struct domain *)data; mtx_lock(&dom_mtx); dp->dom_next = domains; domains = dp; KASSERT(domain_init_status >= 1, ("attempt to net_add_domain(%s) before domaininit()", dp->dom_name)); #ifndef INVARIANTS if (domain_init_status < 1) printf("WARNING: attempt to net_add_domain(%s) before " "domaininit()\n", dp->dom_name); #endif #ifdef notyet KASSERT(domain_init_status < 2, ("attempt to net_add_domain(%s) after domainfinalize()", dp->dom_name)); #else if (domain_init_status >= 2) printf("WARNING: attempt to net_add_domain(%s) after " "domainfinalize()\n", dp->dom_name); #endif mtx_unlock(&dom_mtx); net_init_domain(dp); } static void socket_zone_change(void *tag) { uma_zone_set_max(socket_zone, maxsockets); } /* ARGSUSED*/ static void domaininit(void *dummy) { /* * Before we do any setup, make sure to initialize the * zone allocator we get struct sockets from. */ socket_zone = uma_zcreate("socket", sizeof(struct socket), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); uma_zone_set_max(socket_zone, maxsockets); EVENTHANDLER_REGISTER(maxsockets_change, socket_zone_change, NULL, EVENTHANDLER_PRI_FIRST); if (max_linkhdr < 16) /* XXX */ max_linkhdr = 16; - if (debug_mpsafenet) { - callout_init(&pffast_callout, CALLOUT_MPSAFE); - callout_init(&pfslow_callout, CALLOUT_MPSAFE); - } else { - callout_init(&pffast_callout, 0); - callout_init(&pfslow_callout, 0); - } + callout_init(&pffast_callout, CALLOUT_MPSAFE); + callout_init(&pfslow_callout, CALLOUT_MPSAFE); mtx_lock(&dom_mtx); KASSERT(domain_init_status == 0, ("domaininit called too late!")); domain_init_status = 1; mtx_unlock(&dom_mtx); } /* ARGSUSED*/ static void domainfinalize(void *dummy) { mtx_lock(&dom_mtx); KASSERT(domain_init_status == 1, ("domainfinalize called too late!")); domain_init_status = 2; mtx_unlock(&dom_mtx); callout_reset(&pffast_callout, 1, pffasttimo, NULL); callout_reset(&pfslow_callout, 1, pfslowtimo, NULL); } struct protosw * pffindtype(int family, int type) { struct domain *dp; struct protosw *pr; for (dp = domains; dp; dp = dp->dom_next) if (dp->dom_family == family) goto found; return (0); found: for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) if (pr->pr_type && pr->pr_type == type) return (pr); return (0); } struct protosw * pffindproto(int family, int protocol, int type) { struct domain *dp; struct protosw *pr; struct protosw *maybe = 0; if (family == 0) return (0); for (dp = domains; dp; dp = dp->dom_next) if (dp->dom_family == family) goto found; return (0); found: for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) { if ((pr->pr_protocol == protocol) && (pr->pr_type == type)) return (pr); if (type == SOCK_RAW && pr->pr_type == SOCK_RAW && pr->pr_protocol == 0 && maybe == (struct protosw *)0) maybe = pr; } return (maybe); } /* * The caller must make sure that the new protocol is fully set up and ready to * accept requests before it is registered. */ int pf_proto_register(int family, struct protosw *npr) { struct domain *dp; struct protosw *pr, *fpr; /* Sanity checks. */ if (family == 0) return (EPFNOSUPPORT); if (npr->pr_type == 0) return (EPROTOTYPE); if (npr->pr_protocol == 0) return (EPROTONOSUPPORT); if (npr->pr_usrreqs == NULL) return (ENXIO); /* Try to find the specified domain based on the family. */ for (dp = domains; dp; dp = dp->dom_next) if (dp->dom_family == family) goto found; return (EPFNOSUPPORT); found: /* Initialize backpointer to struct domain. */ npr->pr_domain = dp; fpr = NULL; /* * Protect us against races when two protocol registrations for * the same protocol happen at the same time. */ mtx_lock(&Giant); /* The new protocol must not yet exist. */ for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) { if ((pr->pr_type == npr->pr_type) && (pr->pr_protocol == npr->pr_protocol)) { mtx_unlock(&Giant); return (EEXIST); /* XXX: Check only protocol? */ } /* While here, remember the first free spacer. */ if ((fpr == NULL) && (pr->pr_protocol == PROTO_SPACER)) fpr = pr; } /* If no free spacer is found we can't add the new protocol. */ if (fpr == NULL) { mtx_unlock(&Giant); return (ENOMEM); } /* Copy the new struct protosw over the spacer. */ bcopy(npr, fpr, sizeof(*fpr)); /* Job is done, no more protection required. */ mtx_unlock(&Giant); /* Initialize and activate the protocol. */ protosw_init(fpr); return (0); } /* * The caller must make sure the protocol and its functions correctly shut down * all sockets and release all locks and memory references. */ int pf_proto_unregister(int family, int protocol, int type) { struct domain *dp; struct protosw *pr, *dpr; /* Sanity checks. */ if (family == 0) return (EPFNOSUPPORT); if (protocol == 0) return (EPROTONOSUPPORT); if (type == 0) return (EPROTOTYPE); /* Try to find the specified domain based on the family type. */ for (dp = domains; dp; dp = dp->dom_next) if (dp->dom_family == family) goto found; return (EPFNOSUPPORT); found: dpr = NULL; /* Lock out everyone else while we are manipulating the protosw. */ mtx_lock(&Giant); /* The protocol must exist and only once. */ for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) { if ((pr->pr_type == type) && (pr->pr_protocol == protocol)) { if (dpr != NULL) { mtx_unlock(&Giant); return (EMLINK); /* Should not happen! */ } else dpr = pr; } } /* Protocol does not exist. */ if (dpr == NULL) { mtx_unlock(&Giant); return (EPROTONOSUPPORT); } /* De-orbit the protocol and make the slot available again. */ dpr->pr_type = 0; dpr->pr_domain = dp; dpr->pr_protocol = PROTO_SPACER; dpr->pr_flags = 0; dpr->pr_input = NULL; dpr->pr_output = NULL; dpr->pr_ctlinput = NULL; dpr->pr_ctloutput = NULL; dpr->pr_ousrreq = NULL; dpr->pr_init = NULL; dpr->pr_fasttimo = NULL; dpr->pr_slowtimo = NULL; dpr->pr_drain = NULL; dpr->pr_usrreqs = &nousrreqs; /* Job is done, not more protection required. */ mtx_unlock(&Giant); return (0); } void pfctlinput(int cmd, struct sockaddr *sa) { struct domain *dp; struct protosw *pr; for (dp = domains; dp; dp = dp->dom_next) for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) if (pr->pr_ctlinput) (*pr->pr_ctlinput)(cmd, sa, (void *)0); } void pfctlinput2(int cmd, struct sockaddr *sa, void *ctlparam) { struct domain *dp; struct protosw *pr; if (!sa) return; for (dp = domains; dp; dp = dp->dom_next) { /* * the check must be made by xx_ctlinput() anyways, to * make sure we use data item pointed to by ctlparam in * correct way. the following check is made just for safety. */ if (dp->dom_family != sa->sa_family) continue; for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) if (pr->pr_ctlinput) (*pr->pr_ctlinput)(cmd, sa, ctlparam); } } static void pfslowtimo(void *arg) { struct domain *dp; struct protosw *pr; NET_ASSERT_GIANT(); for (dp = domains; dp; dp = dp->dom_next) for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) if (pr->pr_slowtimo) (*pr->pr_slowtimo)(); callout_reset(&pfslow_callout, hz/2, pfslowtimo, NULL); } static void pffasttimo(void *arg) { struct domain *dp; struct protosw *pr; NET_ASSERT_GIANT(); for (dp = domains; dp; dp = dp->dom_next) for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) if (pr->pr_fasttimo) (*pr->pr_fasttimo)(); callout_reset(&pffast_callout, hz/5, pffasttimo, NULL); } Index: head/sys/net/if.c =================================================================== --- head/sys/net/if.c (revision 171612) +++ head/sys/net/if.c (revision 171613) @@ -1,2774 +1,2767 @@ /*- * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)if.c 8.5 (Berkeley) 1/9/95 * $FreeBSD$ */ #include "opt_compat.h" #include "opt_inet6.h" #include "opt_inet.h" #include "opt_mac.h" #include "opt_carp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(INET) || defined(INET6) /*XXX*/ #include #include #ifdef INET6 #include #include #endif #endif #ifdef INET #include #endif #ifdef DEV_CARP #include #endif #include SYSCTL_NODE(_net, PF_LINK, link, CTLFLAG_RW, 0, "Link layers"); SYSCTL_NODE(_net_link, 0, generic, CTLFLAG_RW, 0, "Generic link-management"); /* Log link state change events */ static int log_link_state_change = 1; SYSCTL_INT(_net_link, OID_AUTO, log_link_state_change, CTLFLAG_RW, &log_link_state_change, 0, "log interface link state change events"); void (*bstp_linkstate_p)(struct ifnet *ifp, int state); void (*ng_ether_link_state_p)(struct ifnet *ifp, int state); void (*lagg_linkstate_p)(struct ifnet *ifp, int state); struct mbuf *(*tbr_dequeue_ptr)(struct ifaltq *, int) = NULL; /* * XXX: Style; these should be sorted alphabetically, and unprototyped * static functions should be prototyped. Currently they are sorted by * declaration order. */ static void if_attachdomain(void *); static void if_attachdomain1(struct ifnet *); static void if_purgemaddrs(struct ifnet *); static int ifconf(u_long, caddr_t); static void if_freemulti(struct ifmultiaddr *); static void if_grow(void); static void if_init(void *); static void if_check(void *); static void if_qflush(struct ifaltq *); static void if_route(struct ifnet *, int flag, int fam); static int if_setflag(struct ifnet *, int, int, int *, int); static void if_slowtimo(void *); static void if_unroute(struct ifnet *, int flag, int fam); static void link_rtrequest(int, struct rtentry *, struct rt_addrinfo *); static int if_rtdel(struct radix_node *, void *); static int ifhwioctl(u_long, struct ifnet *, caddr_t, struct thread *); static int if_delmulti_locked(struct ifnet *, struct ifmultiaddr *, int); static void if_start_deferred(void *context, int pending); static void do_link_state_change(void *, int); static int if_getgroup(struct ifgroupreq *, struct ifnet *); static int if_getgroupmembers(struct ifgroupreq *); #ifdef INET6 /* * XXX: declare here to avoid to include many inet6 related files.. * should be more generalized? */ extern void nd6_setmtu(struct ifnet *); #endif int if_index = 0; struct ifindex_entry *ifindex_table = NULL; int ifqmaxlen = IFQ_MAXLEN; struct ifnethead ifnet; /* depend on static init XXX */ struct ifgrouphead ifg_head; struct mtx ifnet_lock; static if_com_alloc_t *if_com_alloc[256]; static if_com_free_t *if_com_free[256]; static int if_indexlim = 8; static struct knlist ifklist; static void filt_netdetach(struct knote *kn); static int filt_netdev(struct knote *kn, long hint); static struct filterops netdev_filtops = { 1, NULL, filt_netdetach, filt_netdev }; /* * System initialization */ SYSINIT(interfaces, SI_SUB_INIT_IF, SI_ORDER_FIRST, if_init, NULL) SYSINIT(interface_check, SI_SUB_PROTO_IF, SI_ORDER_FIRST, if_check, NULL) MALLOC_DEFINE(M_IFNET, "ifnet", "interface internals"); MALLOC_DEFINE(M_IFADDR, "ifaddr", "interface address"); MALLOC_DEFINE(M_IFMADDR, "ether_multi", "link-level multicast address"); static d_open_t netopen; static d_close_t netclose; static d_ioctl_t netioctl; static d_kqfilter_t netkqfilter; static struct cdevsw net_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = netopen, .d_close = netclose, .d_ioctl = netioctl, .d_name = "net", .d_kqfilter = netkqfilter, }; static int netopen(struct cdev *dev, int flag, int mode, struct thread *td) { return (0); } static int netclose(struct cdev *dev, int flags, int fmt, struct thread *td) { return (0); } static int netioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct ifnet *ifp; int error, idx; /* only support interface specific ioctls */ if (IOCGROUP(cmd) != 'i') return (EOPNOTSUPP); idx = minor(dev); if (idx == 0) { /* * special network device, not interface. */ if (cmd == SIOCGIFCONF) return (ifconf(cmd, data)); /* XXX remove cmd */ #ifdef __amd64__ if (cmd == SIOCGIFCONF32) return (ifconf(cmd, data)); /* XXX remove cmd */ #endif return (EOPNOTSUPP); } ifp = ifnet_byindex(idx); if (ifp == NULL) return (ENXIO); error = ifhwioctl(cmd, ifp, data, td); if (error == ENOIOCTL) error = EOPNOTSUPP; return (error); } static int netkqfilter(struct cdev *dev, struct knote *kn) { struct knlist *klist; struct ifnet *ifp; int idx; switch (kn->kn_filter) { case EVFILT_NETDEV: kn->kn_fop = &netdev_filtops; break; default: return (EINVAL); } idx = minor(dev); if (idx == 0) { klist = &ifklist; } else { ifp = ifnet_byindex(idx); if (ifp == NULL) return (1); klist = &ifp->if_klist; } kn->kn_hook = (caddr_t)klist; knlist_add(klist, kn, 0); return (0); } static void filt_netdetach(struct knote *kn) { struct knlist *klist = (struct knlist *)kn->kn_hook; knlist_remove(klist, kn, 0); } static int filt_netdev(struct knote *kn, long hint) { struct knlist *klist = (struct knlist *)kn->kn_hook; /* * Currently NOTE_EXIT is abused to indicate device detach. */ if (hint == NOTE_EXIT) { kn->kn_data = NOTE_LINKINV; kn->kn_flags |= (EV_EOF | EV_ONESHOT); knlist_remove_inevent(klist, kn); return (1); } if (hint != 0) kn->kn_data = hint; /* current status */ if (kn->kn_sfflags & hint) kn->kn_fflags |= hint; return (kn->kn_fflags != 0); } /* * Network interface utility routines. * * Routines with ifa_ifwith* names take sockaddr *'s as * parameters. */ /* ARGSUSED*/ static void if_init(void *dummy __unused) { IFNET_LOCK_INIT(); TAILQ_INIT(&ifnet); TAILQ_INIT(&ifg_head); knlist_init(&ifklist, NULL, NULL, NULL, NULL); if_grow(); /* create initial table */ ifdev_byindex(0) = make_dev(&net_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "network"); if_clone_init(); } static void if_grow(void) { u_int n; struct ifindex_entry *e; if_indexlim <<= 1; n = if_indexlim * sizeof(*e); e = malloc(n, M_IFNET, M_WAITOK | M_ZERO); if (ifindex_table != NULL) { memcpy((caddr_t)e, (caddr_t)ifindex_table, n/2); free((caddr_t)ifindex_table, M_IFNET); } ifindex_table = e; } /* ARGSUSED*/ static void if_check(void *dummy __unused) { struct ifnet *ifp; int s; s = splimp(); IFNET_RLOCK(); /* could sleep on rare error; mostly okay XXX */ TAILQ_FOREACH(ifp, &ifnet, if_link) { if (ifp->if_snd.ifq_maxlen == 0) { if_printf(ifp, "XXX: driver didn't set ifq_maxlen\n"); ifp->if_snd.ifq_maxlen = ifqmaxlen; } if (!mtx_initialized(&ifp->if_snd.ifq_mtx)) { if_printf(ifp, "XXX: driver didn't initialize queue mtx\n"); mtx_init(&ifp->if_snd.ifq_mtx, "unknown", MTX_NETWORK_LOCK, MTX_DEF); } } IFNET_RUNLOCK(); splx(s); if_slowtimo(0); } /* * Allocate a struct ifnet and an index for an interface. A layer 2 * common structure will also be allocated if an allocation routine is * registered for the passed type. */ struct ifnet* if_alloc(u_char type) { struct ifnet *ifp; ifp = malloc(sizeof(struct ifnet), M_IFNET, M_WAITOK|M_ZERO); /* * Try to find an empty slot below if_index. If we fail, take * the next slot. * * XXX: should be locked! */ for (ifp->if_index = 1; ifp->if_index <= if_index; ifp->if_index++) { if (ifnet_byindex(ifp->if_index) == NULL) break; } /* Catch if_index overflow. */ if (ifp->if_index < 1) { free(ifp, M_IFNET); return (NULL); } if (ifp->if_index > if_index) if_index = ifp->if_index; if (if_index >= if_indexlim) if_grow(); ifnet_byindex(ifp->if_index) = ifp; ifp->if_type = type; if (if_com_alloc[type] != NULL) { ifp->if_l2com = if_com_alloc[type](type, ifp); if (ifp->if_l2com == NULL) { free(ifp, M_IFNET); return (NULL); } } IF_ADDR_LOCK_INIT(ifp); return (ifp); } /* * Free the struct ifnet, the associated index, and the layer 2 common * structure if needed. All the work is done in if_free_type(). * * Do not add code to this function! Add it to if_free_type(). */ void if_free(struct ifnet *ifp) { if_free_type(ifp, ifp->if_type); } /* * Do the actual work of freeing a struct ifnet, associated index, and * layer 2 common structure. This version should only be called by * intefaces that switch their type after calling if_alloc(). */ void if_free_type(struct ifnet *ifp, u_char type) { if (ifp != ifnet_byindex(ifp->if_index)) { if_printf(ifp, "%s: value was not if_alloced, skipping\n", __func__); return; } IF_ADDR_LOCK_DESTROY(ifp); ifnet_byindex(ifp->if_index) = NULL; /* XXX: should be locked with if_findindex() */ while (if_index > 0 && ifnet_byindex(if_index) == NULL) if_index--; if (if_com_free[type] != NULL) if_com_free[type](ifp->if_l2com, type); free(ifp, M_IFNET); }; /* * Perform generic interface initalization tasks and attach the interface * to the list of "active" interfaces. * * XXX: * - The decision to return void and thus require this function to * succeed is questionable. * - We do more initialization here then is probably a good idea. * Some of this should probably move to if_alloc(). * - We should probably do more sanity checking. For instance we don't * do anything to insure if_xname is unique or non-empty. */ void if_attach(struct ifnet *ifp) { unsigned socksize, ifasize; int namelen, masklen; struct sockaddr_dl *sdl; struct ifaddr *ifa; if (ifp->if_index == 0 || ifp != ifnet_byindex(ifp->if_index)) panic ("%s: BUG: if_attach called without if_alloc'd input()\n", ifp->if_xname); TASK_INIT(&ifp->if_starttask, 0, if_start_deferred, ifp); TASK_INIT(&ifp->if_linktask, 0, do_link_state_change, ifp); IF_AFDATA_LOCK_INIT(ifp); ifp->if_afdata_initialized = 0; TAILQ_INIT(&ifp->if_addrhead); TAILQ_INIT(&ifp->if_prefixhead); TAILQ_INIT(&ifp->if_multiaddrs); TAILQ_INIT(&ifp->if_groups); if_addgroup(ifp, IFG_ALL); knlist_init(&ifp->if_klist, NULL, NULL, NULL, NULL); getmicrotime(&ifp->if_lastchange); ifp->if_data.ifi_epoch = time_uptime; ifp->if_data.ifi_datalen = sizeof(struct if_data); #ifdef MAC mac_init_ifnet(ifp); mac_create_ifnet(ifp); #endif ifdev_byindex(ifp->if_index) = make_dev(&net_cdevsw, unit2minor(ifp->if_index), UID_ROOT, GID_WHEEL, 0600, "%s/%s", net_cdevsw.d_name, ifp->if_xname); make_dev_alias(ifdev_byindex(ifp->if_index), "%s%d", net_cdevsw.d_name, ifp->if_index); mtx_init(&ifp->if_snd.ifq_mtx, ifp->if_xname, "if send queue", MTX_DEF); /* * create a Link Level name for this device */ namelen = strlen(ifp->if_xname); /* * Always save enough space for any possiable name so we can do * a rename in place later. */ masklen = offsetof(struct sockaddr_dl, sdl_data[0]) + IFNAMSIZ; socksize = masklen + ifp->if_addrlen; if (socksize < sizeof(*sdl)) socksize = sizeof(*sdl); socksize = roundup2(socksize, sizeof(long)); ifasize = sizeof(*ifa) + 2 * socksize; ifa = malloc(ifasize, M_IFADDR, M_WAITOK | M_ZERO); IFA_LOCK_INIT(ifa); sdl = (struct sockaddr_dl *)(ifa + 1); sdl->sdl_len = socksize; sdl->sdl_family = AF_LINK; bcopy(ifp->if_xname, sdl->sdl_data, namelen); sdl->sdl_nlen = namelen; sdl->sdl_index = ifp->if_index; sdl->sdl_type = ifp->if_type; ifp->if_addr = ifa; ifa->ifa_ifp = ifp; ifa->ifa_rtrequest = link_rtrequest; ifa->ifa_addr = (struct sockaddr *)sdl; sdl = (struct sockaddr_dl *)(socksize + (caddr_t)sdl); ifa->ifa_netmask = (struct sockaddr *)sdl; sdl->sdl_len = masklen; while (namelen != 0) sdl->sdl_data[--namelen] = 0xff; ifa->ifa_refcnt = 1; TAILQ_INSERT_HEAD(&ifp->if_addrhead, ifa, ifa_link); ifp->if_broadcastaddr = NULL; /* reliably crash if used uninitialized */ ifp->if_snd.altq_type = 0; ifp->if_snd.altq_disc = NULL; ifp->if_snd.altq_flags &= ALTQF_CANTCHANGE; ifp->if_snd.altq_tbr = NULL; ifp->if_snd.altq_ifp = ifp; IFNET_WLOCK(); TAILQ_INSERT_TAIL(&ifnet, ifp, if_link); IFNET_WUNLOCK(); if (domain_init_status >= 2) if_attachdomain1(ifp); EVENTHANDLER_INVOKE(ifnet_arrival_event, ifp); devctl_notify("IFNET", ifp->if_xname, "ATTACH", NULL); /* Announce the interface. */ rt_ifannouncemsg(ifp, IFAN_ARRIVAL); if (ifp->if_watchdog != NULL) if_printf(ifp, "using obsoleted if_watchdog interface\n"); } static void if_attachdomain(void *dummy) { struct ifnet *ifp; int s; s = splnet(); TAILQ_FOREACH(ifp, &ifnet, if_link) if_attachdomain1(ifp); splx(s); } SYSINIT(domainifattach, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_SECOND, if_attachdomain, NULL); static void if_attachdomain1(struct ifnet *ifp) { struct domain *dp; int s; s = splnet(); /* * Since dp->dom_ifattach calls malloc() with M_WAITOK, we * cannot lock ifp->if_afdata initialization, entirely. */ if (IF_AFDATA_TRYLOCK(ifp) == 0) { splx(s); return; } if (ifp->if_afdata_initialized >= domain_init_status) { IF_AFDATA_UNLOCK(ifp); splx(s); printf("if_attachdomain called more than once on %s\n", ifp->if_xname); return; } ifp->if_afdata_initialized = domain_init_status; IF_AFDATA_UNLOCK(ifp); /* address family dependent data region */ bzero(ifp->if_afdata, sizeof(ifp->if_afdata)); for (dp = domains; dp; dp = dp->dom_next) { if (dp->dom_ifattach) ifp->if_afdata[dp->dom_family] = (*dp->dom_ifattach)(ifp); } splx(s); } /* * Remove any unicast or broadcast network addresses from an interface. */ void if_purgeaddrs(struct ifnet *ifp) { struct ifaddr *ifa, *next; TAILQ_FOREACH_SAFE(ifa, &ifp->if_addrhead, ifa_link, next) { if (ifa->ifa_addr->sa_family == AF_LINK) continue; #ifdef INET /* XXX: Ugly!! ad hoc just for INET */ if (ifa->ifa_addr->sa_family == AF_INET) { struct ifaliasreq ifr; bzero(&ifr, sizeof(ifr)); ifr.ifra_addr = *ifa->ifa_addr; if (ifa->ifa_dstaddr) ifr.ifra_broadaddr = *ifa->ifa_dstaddr; if (in_control(NULL, SIOCDIFADDR, (caddr_t)&ifr, ifp, NULL) == 0) continue; } #endif /* INET */ #ifdef INET6 if (ifa->ifa_addr->sa_family == AF_INET6) { in6_purgeaddr(ifa); /* ifp_addrhead is already updated */ continue; } #endif /* INET6 */ TAILQ_REMOVE(&ifp->if_addrhead, ifa, ifa_link); IFAFREE(ifa); } } /* * Remove any multicast network addresses from an interface. */ static void if_purgemaddrs(struct ifnet *ifp) { struct ifmultiaddr *ifma; struct ifmultiaddr *next; IF_ADDR_LOCK(ifp); TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) if_delmulti_locked(ifp, ifma, 1); IF_ADDR_UNLOCK(ifp); } /* * Detach an interface, removing it from the * list of "active" interfaces. * * XXXRW: There are some significant questions about event ordering, and * how to prevent things from starting to use the interface during detach. */ void if_detach(struct ifnet *ifp) { struct ifaddr *ifa; struct radix_node_head *rnh; int s; int i; struct domain *dp; struct ifnet *iter; int found = 0; IFNET_WLOCK(); TAILQ_FOREACH(iter, &ifnet, if_link) if (iter == ifp) { TAILQ_REMOVE(&ifnet, ifp, if_link); found = 1; break; } IFNET_WUNLOCK(); if (!found) return; /* * Remove/wait for pending events. */ taskqueue_drain(taskqueue_swi, &ifp->if_linktask); /* * Remove routes and flush queues. */ s = splnet(); if_down(ifp); #ifdef ALTQ if (ALTQ_IS_ENABLED(&ifp->if_snd)) altq_disable(&ifp->if_snd); if (ALTQ_IS_ATTACHED(&ifp->if_snd)) altq_detach(&ifp->if_snd); #endif if_purgeaddrs(ifp); #ifdef INET in_ifdetach(ifp); #endif #ifdef INET6 /* * Remove all IPv6 kernel structs related to ifp. This should be done * before removing routing entries below, since IPv6 interface direct * routes are expected to be removed by the IPv6-specific kernel API. * Otherwise, the kernel will detect some inconsistency and bark it. */ in6_ifdetach(ifp); #endif if_purgemaddrs(ifp); /* * Remove link ifaddr pointer and maybe decrement if_index. * Clean up all addresses. */ ifp->if_addr = NULL; destroy_dev(ifdev_byindex(ifp->if_index)); ifdev_byindex(ifp->if_index) = NULL; /* We can now free link ifaddr. */ if (!TAILQ_EMPTY(&ifp->if_addrhead)) { ifa = TAILQ_FIRST(&ifp->if_addrhead); TAILQ_REMOVE(&ifp->if_addrhead, ifa, ifa_link); IFAFREE(ifa); } /* * Delete all remaining routes using this interface * Unfortuneatly the only way to do this is to slog through * the entire routing table looking for routes which point * to this interface...oh well... */ for (i = 1; i <= AF_MAX; i++) { if ((rnh = rt_tables[i]) == NULL) continue; RADIX_NODE_HEAD_LOCK(rnh); (void) rnh->rnh_walktree(rnh, if_rtdel, ifp); RADIX_NODE_HEAD_UNLOCK(rnh); } /* Announce that the interface is gone. */ rt_ifannouncemsg(ifp, IFAN_DEPARTURE); EVENTHANDLER_INVOKE(ifnet_departure_event, ifp); devctl_notify("IFNET", ifp->if_xname, "DETACH", NULL); IF_AFDATA_LOCK(ifp); for (dp = domains; dp; dp = dp->dom_next) { if (dp->dom_ifdetach && ifp->if_afdata[dp->dom_family]) (*dp->dom_ifdetach)(ifp, ifp->if_afdata[dp->dom_family]); } IF_AFDATA_UNLOCK(ifp); #ifdef MAC mac_destroy_ifnet(ifp); #endif /* MAC */ KNOTE_UNLOCKED(&ifp->if_klist, NOTE_EXIT); knlist_clear(&ifp->if_klist, 0); knlist_destroy(&ifp->if_klist); mtx_destroy(&ifp->if_snd.ifq_mtx); IF_AFDATA_DESTROY(ifp); splx(s); } /* * Add a group to an interface */ int if_addgroup(struct ifnet *ifp, const char *groupname) { struct ifg_list *ifgl; struct ifg_group *ifg = NULL; struct ifg_member *ifgm; if (groupname[0] && groupname[strlen(groupname) - 1] >= '0' && groupname[strlen(groupname) - 1] <= '9') return (EINVAL); IFNET_WLOCK(); TAILQ_FOREACH(ifgl, &ifp->if_groups, ifgl_next) if (!strcmp(ifgl->ifgl_group->ifg_group, groupname)) { IFNET_WUNLOCK(); return (EEXIST); } if ((ifgl = (struct ifg_list *)malloc(sizeof(struct ifg_list), M_TEMP, M_NOWAIT)) == NULL) { IFNET_WUNLOCK(); return (ENOMEM); } if ((ifgm = (struct ifg_member *)malloc(sizeof(struct ifg_member), M_TEMP, M_NOWAIT)) == NULL) { free(ifgl, M_TEMP); IFNET_WUNLOCK(); return (ENOMEM); } TAILQ_FOREACH(ifg, &ifg_head, ifg_next) if (!strcmp(ifg->ifg_group, groupname)) break; if (ifg == NULL) { if ((ifg = (struct ifg_group *)malloc(sizeof(struct ifg_group), M_TEMP, M_NOWAIT)) == NULL) { free(ifgl, M_TEMP); free(ifgm, M_TEMP); IFNET_WUNLOCK(); return (ENOMEM); } strlcpy(ifg->ifg_group, groupname, sizeof(ifg->ifg_group)); ifg->ifg_refcnt = 0; TAILQ_INIT(&ifg->ifg_members); EVENTHANDLER_INVOKE(group_attach_event, ifg); TAILQ_INSERT_TAIL(&ifg_head, ifg, ifg_next); } ifg->ifg_refcnt++; ifgl->ifgl_group = ifg; ifgm->ifgm_ifp = ifp; IF_ADDR_LOCK(ifp); TAILQ_INSERT_TAIL(&ifg->ifg_members, ifgm, ifgm_next); TAILQ_INSERT_TAIL(&ifp->if_groups, ifgl, ifgl_next); IF_ADDR_UNLOCK(ifp); IFNET_WUNLOCK(); EVENTHANDLER_INVOKE(group_change_event, groupname); return (0); } /* * Remove a group from an interface */ int if_delgroup(struct ifnet *ifp, const char *groupname) { struct ifg_list *ifgl; struct ifg_member *ifgm; IFNET_WLOCK(); TAILQ_FOREACH(ifgl, &ifp->if_groups, ifgl_next) if (!strcmp(ifgl->ifgl_group->ifg_group, groupname)) break; if (ifgl == NULL) { IFNET_WUNLOCK(); return (ENOENT); } IF_ADDR_LOCK(ifp); TAILQ_REMOVE(&ifp->if_groups, ifgl, ifgl_next); IF_ADDR_UNLOCK(ifp); TAILQ_FOREACH(ifgm, &ifgl->ifgl_group->ifg_members, ifgm_next) if (ifgm->ifgm_ifp == ifp) break; if (ifgm != NULL) { TAILQ_REMOVE(&ifgl->ifgl_group->ifg_members, ifgm, ifgm_next); free(ifgm, M_TEMP); } if (--ifgl->ifgl_group->ifg_refcnt == 0) { TAILQ_REMOVE(&ifg_head, ifgl->ifgl_group, ifg_next); EVENTHANDLER_INVOKE(group_detach_event, ifgl->ifgl_group); free(ifgl->ifgl_group, M_TEMP); } IFNET_WUNLOCK(); free(ifgl, M_TEMP); EVENTHANDLER_INVOKE(group_change_event, groupname); return (0); } /* * Stores all groups from an interface in memory pointed * to by data */ static int if_getgroup(struct ifgroupreq *data, struct ifnet *ifp) { int len, error; struct ifg_list *ifgl; struct ifg_req ifgrq, *ifgp; struct ifgroupreq *ifgr = data; if (ifgr->ifgr_len == 0) { IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifgl, &ifp->if_groups, ifgl_next) ifgr->ifgr_len += sizeof(struct ifg_req); IF_ADDR_UNLOCK(ifp); return (0); } len = ifgr->ifgr_len; ifgp = ifgr->ifgr_groups; /* XXX: wire */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifgl, &ifp->if_groups, ifgl_next) { if (len < sizeof(ifgrq)) { IF_ADDR_UNLOCK(ifp); return (EINVAL); } bzero(&ifgrq, sizeof ifgrq); strlcpy(ifgrq.ifgrq_group, ifgl->ifgl_group->ifg_group, sizeof(ifgrq.ifgrq_group)); if ((error = copyout(&ifgrq, ifgp, sizeof(struct ifg_req)))) { IF_ADDR_UNLOCK(ifp); return (error); } len -= sizeof(ifgrq); ifgp++; } IF_ADDR_UNLOCK(ifp); return (0); } /* * Stores all members of a group in memory pointed to by data */ static int if_getgroupmembers(struct ifgroupreq *data) { struct ifgroupreq *ifgr = data; struct ifg_group *ifg; struct ifg_member *ifgm; struct ifg_req ifgrq, *ifgp; int len, error; IFNET_RLOCK(); TAILQ_FOREACH(ifg, &ifg_head, ifg_next) if (!strcmp(ifg->ifg_group, ifgr->ifgr_name)) break; if (ifg == NULL) { IFNET_RUNLOCK(); return (ENOENT); } if (ifgr->ifgr_len == 0) { TAILQ_FOREACH(ifgm, &ifg->ifg_members, ifgm_next) ifgr->ifgr_len += sizeof(ifgrq); IFNET_RUNLOCK(); return (0); } len = ifgr->ifgr_len; ifgp = ifgr->ifgr_groups; TAILQ_FOREACH(ifgm, &ifg->ifg_members, ifgm_next) { if (len < sizeof(ifgrq)) { IFNET_RUNLOCK(); return (EINVAL); } bzero(&ifgrq, sizeof ifgrq); strlcpy(ifgrq.ifgrq_member, ifgm->ifgm_ifp->if_xname, sizeof(ifgrq.ifgrq_member)); if ((error = copyout(&ifgrq, ifgp, sizeof(struct ifg_req)))) { IFNET_RUNLOCK(); return (error); } len -= sizeof(ifgrq); ifgp++; } IFNET_RUNLOCK(); return (0); } /* * Delete Routes for a Network Interface * * Called for each routing entry via the rnh->rnh_walktree() call above * to delete all route entries referencing a detaching network interface. * * Arguments: * rn pointer to node in the routing table * arg argument passed to rnh->rnh_walktree() - detaching interface * * Returns: * 0 successful * errno failed - reason indicated * */ static int if_rtdel(struct radix_node *rn, void *arg) { struct rtentry *rt = (struct rtentry *)rn; struct ifnet *ifp = arg; int err; if (rt->rt_ifp == ifp) { /* * Protect (sorta) against walktree recursion problems * with cloned routes */ if ((rt->rt_flags & RTF_UP) == 0) return (0); err = rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt), rt->rt_flags, (struct rtentry **) NULL); if (err) { log(LOG_WARNING, "if_rtdel: error %d\n", err); } } return (0); } /* * XXX: Because sockaddr_dl has deeper structure than the sockaddr * structs used to represent other address families, it is necessary * to perform a different comparison. */ #define sa_equal(a1, a2) \ (bcmp((a1), (a2), ((a1))->sa_len) == 0) #define sa_dl_equal(a1, a2) \ ((((struct sockaddr_dl *)(a1))->sdl_len == \ ((struct sockaddr_dl *)(a2))->sdl_len) && \ (bcmp(LLADDR((struct sockaddr_dl *)(a1)), \ LLADDR((struct sockaddr_dl *)(a2)), \ ((struct sockaddr_dl *)(a1))->sdl_alen) == 0)) /* * Locate an interface based on a complete address. */ /*ARGSUSED*/ struct ifaddr * ifa_ifwithaddr(struct sockaddr *addr) { struct ifnet *ifp; struct ifaddr *ifa; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != addr->sa_family) continue; if (sa_equal(addr, ifa->ifa_addr)) goto done; /* IP6 doesn't have broadcast */ if ((ifp->if_flags & IFF_BROADCAST) && ifa->ifa_broadaddr && ifa->ifa_broadaddr->sa_len != 0 && sa_equal(ifa->ifa_broadaddr, addr)) goto done; } ifa = NULL; done: IFNET_RUNLOCK(); return (ifa); } /* * Locate an interface based on the broadcast address. */ /* ARGSUSED */ struct ifaddr * ifa_ifwithbroadaddr(struct sockaddr *addr) { struct ifnet *ifp; struct ifaddr *ifa; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != addr->sa_family) continue; if ((ifp->if_flags & IFF_BROADCAST) && ifa->ifa_broadaddr && ifa->ifa_broadaddr->sa_len != 0 && sa_equal(ifa->ifa_broadaddr, addr)) goto done; } ifa = NULL; done: IFNET_RUNLOCK(); return (ifa); } /* * Locate the point to point interface with a given destination address. */ /*ARGSUSED*/ struct ifaddr * ifa_ifwithdstaddr(struct sockaddr *addr) { struct ifnet *ifp; struct ifaddr *ifa; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if ((ifp->if_flags & IFF_POINTOPOINT) == 0) continue; TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != addr->sa_family) continue; if (ifa->ifa_dstaddr && sa_equal(addr, ifa->ifa_dstaddr)) goto done; } } ifa = NULL; done: IFNET_RUNLOCK(); return (ifa); } /* * Find an interface on a specific network. If many, choice * is most specific found. */ struct ifaddr * ifa_ifwithnet(struct sockaddr *addr) { struct ifnet *ifp; struct ifaddr *ifa; struct ifaddr *ifa_maybe = (struct ifaddr *) 0; u_int af = addr->sa_family; char *addr_data = addr->sa_data, *cplim; /* * AF_LINK addresses can be looked up directly by their index number, * so do that if we can. */ if (af == AF_LINK) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)addr; if (sdl->sdl_index && sdl->sdl_index <= if_index) return (ifaddr_byindex(sdl->sdl_index)); } /* * Scan though each interface, looking for ones that have * addresses in this address family. */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { char *cp, *cp2, *cp3; if (ifa->ifa_addr->sa_family != af) next: continue; if (af == AF_INET && ifp->if_flags & IFF_POINTOPOINT) { /* * This is a bit broken as it doesn't * take into account that the remote end may * be a single node in the network we are * looking for. * The trouble is that we don't know the * netmask for the remote end. */ if (ifa->ifa_dstaddr != 0 && sa_equal(addr, ifa->ifa_dstaddr)) goto done; } else { /* * if we have a special address handler, * then use it instead of the generic one. */ if (ifa->ifa_claim_addr) { if ((*ifa->ifa_claim_addr)(ifa, addr)) goto done; continue; } /* * Scan all the bits in the ifa's address. * If a bit dissagrees with what we are * looking for, mask it with the netmask * to see if it really matters. * (A byte at a time) */ if (ifa->ifa_netmask == 0) continue; cp = addr_data; cp2 = ifa->ifa_addr->sa_data; cp3 = ifa->ifa_netmask->sa_data; cplim = ifa->ifa_netmask->sa_len + (char *)ifa->ifa_netmask; while (cp3 < cplim) if ((*cp++ ^ *cp2++) & *cp3++) goto next; /* next address! */ /* * If the netmask of what we just found * is more specific than what we had before * (if we had one) then remember the new one * before continuing to search * for an even better one. */ if (ifa_maybe == 0 || rn_refines((caddr_t)ifa->ifa_netmask, (caddr_t)ifa_maybe->ifa_netmask)) ifa_maybe = ifa; } } } ifa = ifa_maybe; done: IFNET_RUNLOCK(); return (ifa); } /* * Find an interface address specific to an interface best matching * a given address. */ struct ifaddr * ifaof_ifpforaddr(struct sockaddr *addr, struct ifnet *ifp) { struct ifaddr *ifa; char *cp, *cp2, *cp3; char *cplim; struct ifaddr *ifa_maybe = 0; u_int af = addr->sa_family; if (af >= AF_MAX) return (0); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != af) continue; if (ifa_maybe == 0) ifa_maybe = ifa; if (ifa->ifa_netmask == 0) { if (sa_equal(addr, ifa->ifa_addr) || (ifa->ifa_dstaddr && sa_equal(addr, ifa->ifa_dstaddr))) goto done; continue; } if (ifp->if_flags & IFF_POINTOPOINT) { if (sa_equal(addr, ifa->ifa_dstaddr)) goto done; } else { cp = addr->sa_data; cp2 = ifa->ifa_addr->sa_data; cp3 = ifa->ifa_netmask->sa_data; cplim = ifa->ifa_netmask->sa_len + (char *)ifa->ifa_netmask; for (; cp3 < cplim; cp3++) if ((*cp++ ^ *cp2++) & *cp3) break; if (cp3 == cplim) goto done; } } ifa = ifa_maybe; done: return (ifa); } #include /* * Default action when installing a route with a Link Level gateway. * Lookup an appropriate real ifa to point to. * This should be moved to /sys/net/link.c eventually. */ static void link_rtrequest(int cmd, struct rtentry *rt, struct rt_addrinfo *info) { struct ifaddr *ifa, *oifa; struct sockaddr *dst; struct ifnet *ifp; RT_LOCK_ASSERT(rt); if (cmd != RTM_ADD || ((ifa = rt->rt_ifa) == 0) || ((ifp = ifa->ifa_ifp) == 0) || ((dst = rt_key(rt)) == 0)) return; ifa = ifaof_ifpforaddr(dst, ifp); if (ifa) { IFAREF(ifa); /* XXX */ oifa = rt->rt_ifa; rt->rt_ifa = ifa; IFAFREE(oifa); if (ifa->ifa_rtrequest && ifa->ifa_rtrequest != link_rtrequest) ifa->ifa_rtrequest(cmd, rt, info); } } /* * Mark an interface down and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ static void if_unroute(struct ifnet *ifp, int flag, int fam) { struct ifaddr *ifa; KASSERT(flag == IFF_UP, ("if_unroute: flag != IFF_UP")); ifp->if_flags &= ~flag; getmicrotime(&ifp->if_lastchange); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) if (fam == PF_UNSPEC || (fam == ifa->ifa_addr->sa_family)) pfctlinput(PRC_IFDOWN, ifa->ifa_addr); if_qflush(&ifp->if_snd); #ifdef DEV_CARP if (ifp->if_carp) carp_carpdev_state(ifp->if_carp); #endif rt_ifmsg(ifp); } /* * Mark an interface up and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ static void if_route(struct ifnet *ifp, int flag, int fam) { struct ifaddr *ifa; KASSERT(flag == IFF_UP, ("if_route: flag != IFF_UP")); ifp->if_flags |= flag; getmicrotime(&ifp->if_lastchange); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) if (fam == PF_UNSPEC || (fam == ifa->ifa_addr->sa_family)) pfctlinput(PRC_IFUP, ifa->ifa_addr); #ifdef DEV_CARP if (ifp->if_carp) carp_carpdev_state(ifp->if_carp); #endif rt_ifmsg(ifp); #ifdef INET6 in6_if_up(ifp); #endif } void (*vlan_link_state_p)(struct ifnet *, int); /* XXX: private from if_vlan */ void (*vlan_trunk_cap_p)(struct ifnet *); /* XXX: private from if_vlan */ /* * Handle a change in the interface link state. To avoid LORs * between driver lock and upper layer locks, as well as possible * recursions, we post event to taskqueue, and all job * is done in static do_link_state_change(). */ void if_link_state_change(struct ifnet *ifp, int link_state) { /* Return if state hasn't changed. */ if (ifp->if_link_state == link_state) return; ifp->if_link_state = link_state; taskqueue_enqueue(taskqueue_swi, &ifp->if_linktask); } static void do_link_state_change(void *arg, int pending) { struct ifnet *ifp = (struct ifnet *)arg; int link_state = ifp->if_link_state; int link; /* Notify that the link state has changed. */ rt_ifmsg(ifp); if (link_state == LINK_STATE_UP) link = NOTE_LINKUP; else if (link_state == LINK_STATE_DOWN) link = NOTE_LINKDOWN; else link = NOTE_LINKINV; KNOTE_UNLOCKED(&ifp->if_klist, link); if (ifp->if_vlantrunk != NULL) (*vlan_link_state_p)(ifp, link); if ((ifp->if_type == IFT_ETHER || ifp->if_type == IFT_L2VLAN) && IFP2AC(ifp)->ac_netgraph != NULL) (*ng_ether_link_state_p)(ifp, link_state); #ifdef DEV_CARP if (ifp->if_carp) carp_carpdev_state(ifp->if_carp); #endif if (ifp->if_bridge) { KASSERT(bstp_linkstate_p != NULL,("if_bridge bstp not loaded!")); (*bstp_linkstate_p)(ifp, link_state); } if (ifp->if_lagg) { KASSERT(lagg_linkstate_p != NULL,("if_lagg not loaded!")); (*lagg_linkstate_p)(ifp, link_state); } devctl_notify("IFNET", ifp->if_xname, (link_state == LINK_STATE_UP) ? "LINK_UP" : "LINK_DOWN", NULL); if (pending > 1) if_printf(ifp, "%d link states coalesced\n", pending); if (log_link_state_change) log(LOG_NOTICE, "%s: link state changed to %s\n", ifp->if_xname, (link_state == LINK_STATE_UP) ? "UP" : "DOWN" ); } /* * Mark an interface down and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ void if_down(struct ifnet *ifp) { if_unroute(ifp, IFF_UP, AF_UNSPEC); } /* * Mark an interface up and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ void if_up(struct ifnet *ifp) { if_route(ifp, IFF_UP, AF_UNSPEC); } /* * Flush an interface queue. */ static void if_qflush(struct ifaltq *ifq) { struct mbuf *m, *n; IFQ_LOCK(ifq); #ifdef ALTQ if (ALTQ_IS_ENABLED(ifq)) ALTQ_PURGE(ifq); #endif n = ifq->ifq_head; while ((m = n) != 0) { n = m->m_act; m_freem(m); } ifq->ifq_head = 0; ifq->ifq_tail = 0; ifq->ifq_len = 0; IFQ_UNLOCK(ifq); } /* * Handle interface watchdog timer routines. Called * from softclock, we decrement timers (if set) and * call the appropriate interface routine on expiration. * * XXXRW: Note that because timeouts run with Giant, if_watchdog() is called * holding Giant. If we switch to an MPSAFE callout, we likely need to grab * Giant before entering if_watchdog() on an IFF_NEEDSGIANT interface. */ static void if_slowtimo(void *arg) { struct ifnet *ifp; int s = splimp(); IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (ifp->if_timer == 0 || --ifp->if_timer) continue; if (ifp->if_watchdog) (*ifp->if_watchdog)(ifp); } IFNET_RUNLOCK(); splx(s); timeout(if_slowtimo, (void *)0, hz / IFNET_SLOWHZ); } /* * Map interface name to * interface structure pointer. */ struct ifnet * ifunit(const char *name) { struct ifnet *ifp; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (strncmp(name, ifp->if_xname, IFNAMSIZ) == 0) break; } IFNET_RUNLOCK(); return (ifp); } /* * Hardware specific interface ioctls. */ static int ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td) { struct ifreq *ifr; struct ifstat *ifs; int error = 0; int new_flags, temp_flags; size_t namelen, onamelen; char new_name[IFNAMSIZ]; struct ifaddr *ifa; struct sockaddr_dl *sdl; ifr = (struct ifreq *)data; switch (cmd) { case SIOCGIFINDEX: ifr->ifr_index = ifp->if_index; break; case SIOCGIFFLAGS: temp_flags = ifp->if_flags | ifp->if_drv_flags; ifr->ifr_flags = temp_flags & 0xffff; ifr->ifr_flagshigh = temp_flags >> 16; break; case SIOCGIFCAP: ifr->ifr_reqcap = ifp->if_capabilities; ifr->ifr_curcap = ifp->if_capenable; break; #ifdef MAC case SIOCGIFMAC: error = mac_ioctl_ifnet_get(td->td_ucred, ifr, ifp); break; #endif case SIOCGIFMETRIC: ifr->ifr_metric = ifp->if_metric; break; case SIOCGIFMTU: ifr->ifr_mtu = ifp->if_mtu; break; case SIOCGIFPHYS: ifr->ifr_phys = ifp->if_physical; break; case SIOCSIFFLAGS: error = priv_check(td, PRIV_NET_SETIFFLAGS); if (error) return (error); /* * Currently, no driver owned flags pass the IFF_CANTCHANGE * check, so we don't need special handling here yet. */ new_flags = (ifr->ifr_flags & 0xffff) | (ifr->ifr_flagshigh << 16); if (ifp->if_flags & IFF_SMART) { /* Smart drivers twiddle their own routes */ } else if (ifp->if_flags & IFF_UP && (new_flags & IFF_UP) == 0) { int s = splimp(); if_down(ifp); splx(s); } else if (new_flags & IFF_UP && (ifp->if_flags & IFF_UP) == 0) { int s = splimp(); if_up(ifp); splx(s); } /* See if permanently promiscuous mode bit is about to flip */ if ((ifp->if_flags ^ new_flags) & IFF_PPROMISC) { if (new_flags & IFF_PPROMISC) ifp->if_flags |= IFF_PROMISC; else if (ifp->if_pcount == 0) ifp->if_flags &= ~IFF_PROMISC; log(LOG_INFO, "%s: permanently promiscuous mode %s\n", ifp->if_xname, (new_flags & IFF_PPROMISC) ? "enabled" : "disabled"); } ifp->if_flags = (ifp->if_flags & IFF_CANTCHANGE) | (new_flags &~ IFF_CANTCHANGE); if (ifp->if_ioctl) { IFF_LOCKGIANT(ifp); (void) (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); } getmicrotime(&ifp->if_lastchange); break; case SIOCSIFCAP: error = priv_check(td, PRIV_NET_SETIFCAP); if (error) return (error); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); if (ifr->ifr_reqcap & ~ifp->if_capabilities) return (EINVAL); IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); if (error == 0) getmicrotime(&ifp->if_lastchange); break; #ifdef MAC case SIOCSIFMAC: error = mac_ioctl_ifnet_set(td->td_ucred, ifr, ifp); break; #endif case SIOCSIFNAME: error = priv_check(td, PRIV_NET_SETIFNAME); if (error) return (error); error = copyinstr(ifr->ifr_data, new_name, IFNAMSIZ, NULL); if (error != 0) return (error); if (new_name[0] == '\0') return (EINVAL); if (ifunit(new_name) != NULL) return (EEXIST); /* Announce the departure of the interface. */ rt_ifannouncemsg(ifp, IFAN_DEPARTURE); EVENTHANDLER_INVOKE(ifnet_departure_event, ifp); log(LOG_INFO, "%s: changing name to '%s'\n", ifp->if_xname, new_name); strlcpy(ifp->if_xname, new_name, sizeof(ifp->if_xname)); ifa = ifp->if_addr; IFA_LOCK(ifa); sdl = (struct sockaddr_dl *)ifa->ifa_addr; namelen = strlen(new_name); onamelen = sdl->sdl_nlen; /* * Move the address if needed. This is safe because we * allocate space for a name of length IFNAMSIZ when we * create this in if_attach(). */ if (namelen != onamelen) { bcopy(sdl->sdl_data + onamelen, sdl->sdl_data + namelen, sdl->sdl_alen); } bcopy(new_name, sdl->sdl_data, namelen); sdl->sdl_nlen = namelen; sdl = (struct sockaddr_dl *)ifa->ifa_netmask; bzero(sdl->sdl_data, onamelen); while (namelen != 0) sdl->sdl_data[--namelen] = 0xff; IFA_UNLOCK(ifa); EVENTHANDLER_INVOKE(ifnet_arrival_event, ifp); /* Announce the return of the interface. */ rt_ifannouncemsg(ifp, IFAN_ARRIVAL); break; case SIOCSIFMETRIC: error = priv_check(td, PRIV_NET_SETIFMETRIC); if (error) return (error); ifp->if_metric = ifr->ifr_metric; getmicrotime(&ifp->if_lastchange); break; case SIOCSIFPHYS: error = priv_check(td, PRIV_NET_SETIFPHYS); if (error) return (error); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); if (error == 0) getmicrotime(&ifp->if_lastchange); break; case SIOCSIFMTU: { u_long oldmtu = ifp->if_mtu; error = priv_check(td, PRIV_NET_SETIFMTU); if (error) return (error); if (ifr->ifr_mtu < IF_MINMTU || ifr->ifr_mtu > IF_MAXMTU) return (EINVAL); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); if (error == 0) { getmicrotime(&ifp->if_lastchange); rt_ifmsg(ifp); } /* * If the link MTU changed, do network layer specific procedure. */ if (ifp->if_mtu != oldmtu) { #ifdef INET6 nd6_setmtu(ifp); #endif } break; } case SIOCADDMULTI: case SIOCDELMULTI: if (cmd == SIOCADDMULTI) error = priv_check(td, PRIV_NET_ADDMULTI); else error = priv_check(td, PRIV_NET_DELMULTI); if (error) return (error); /* Don't allow group membership on non-multicast interfaces. */ if ((ifp->if_flags & IFF_MULTICAST) == 0) return (EOPNOTSUPP); /* Don't let users screw up protocols' entries. */ if (ifr->ifr_addr.sa_family != AF_LINK) return (EINVAL); if (cmd == SIOCADDMULTI) { struct ifmultiaddr *ifma; /* * Userland is only permitted to join groups once * via the if_addmulti() KPI, because it cannot hold * struct ifmultiaddr * between calls. It may also * lose a race while we check if the membership * already exists. */ IF_ADDR_LOCK(ifp); ifma = if_findmulti(ifp, &ifr->ifr_addr); IF_ADDR_UNLOCK(ifp); if (ifma != NULL) error = EADDRINUSE; else error = if_addmulti(ifp, &ifr->ifr_addr, &ifma); } else { error = if_delmulti(ifp, &ifr->ifr_addr); } if (error == 0) getmicrotime(&ifp->if_lastchange); break; case SIOCSIFPHYADDR: case SIOCDIFPHYADDR: #ifdef INET6 case SIOCSIFPHYADDR_IN6: #endif case SIOCSLIFPHYADDR: case SIOCSIFMEDIA: case SIOCSIFGENERIC: error = priv_check(td, PRIV_NET_HWIOCTL); if (error) return (error); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); if (error == 0) getmicrotime(&ifp->if_lastchange); break; case SIOCGIFSTATUS: ifs = (struct ifstat *)data; ifs->ascii[0] = '\0'; case SIOCGIFPSRCADDR: case SIOCGIFPDSTADDR: case SIOCGLIFPHYADDR: case SIOCGIFMEDIA: case SIOCGIFGENERIC: if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, cmd, data); IFF_UNLOCKGIANT(ifp); break; case SIOCSIFLLADDR: error = priv_check(td, PRIV_NET_SETLLADDR); if (error) return (error); error = if_setlladdr(ifp, ifr->ifr_addr.sa_data, ifr->ifr_addr.sa_len); break; case SIOCAIFGROUP: { struct ifgroupreq *ifgr = (struct ifgroupreq *)ifr; error = priv_check(td, PRIV_NET_ADDIFGROUP); if (error) return (error); if ((error = if_addgroup(ifp, ifgr->ifgr_group))) return (error); break; } case SIOCGIFGROUP: if ((error = if_getgroup((struct ifgroupreq *)ifr, ifp))) return (error); break; case SIOCDIFGROUP: { struct ifgroupreq *ifgr = (struct ifgroupreq *)ifr; error = priv_check(td, PRIV_NET_DELIFGROUP); if (error) return (error); if ((error = if_delgroup(ifp, ifgr->ifgr_group))) return (error); break; } default: error = ENOIOCTL; break; } return (error); } /* * Interface ioctls. */ int ifioctl(struct socket *so, u_long cmd, caddr_t data, struct thread *td) { struct ifnet *ifp; struct ifreq *ifr; int error; int oif_flags; switch (cmd) { case SIOCGIFCONF: case OSIOCGIFCONF: #ifdef __amd64__ case SIOCGIFCONF32: #endif return (ifconf(cmd, data)); } ifr = (struct ifreq *)data; switch (cmd) { case SIOCIFCREATE: case SIOCIFCREATE2: error = priv_check(td, PRIV_NET_IFCREATE); if (error) return (error); return (if_clone_create(ifr->ifr_name, sizeof(ifr->ifr_name), cmd == SIOCIFCREATE2 ? ifr->ifr_data : NULL)); case SIOCIFDESTROY: error = priv_check(td, PRIV_NET_IFDESTROY); if (error) return (error); return if_clone_destroy(ifr->ifr_name); case SIOCIFGCLONERS: return (if_clone_list((struct if_clonereq *)data)); case SIOCGIFGMEMB: return (if_getgroupmembers((struct ifgroupreq *)data)); } ifp = ifunit(ifr->ifr_name); if (ifp == 0) return (ENXIO); error = ifhwioctl(cmd, ifp, data, td); if (error != ENOIOCTL) return (error); oif_flags = ifp->if_flags; if (so->so_proto == 0) return (EOPNOTSUPP); #ifndef COMPAT_43 error = ((*so->so_proto->pr_usrreqs->pru_control)(so, cmd, data, ifp, td)); #else { int ocmd = cmd; switch (cmd) { case SIOCSIFDSTADDR: case SIOCSIFADDR: case SIOCSIFBRDADDR: case SIOCSIFNETMASK: #if BYTE_ORDER != BIG_ENDIAN if (ifr->ifr_addr.sa_family == 0 && ifr->ifr_addr.sa_len < 16) { ifr->ifr_addr.sa_family = ifr->ifr_addr.sa_len; ifr->ifr_addr.sa_len = 16; } #else if (ifr->ifr_addr.sa_len == 0) ifr->ifr_addr.sa_len = 16; #endif break; case OSIOCGIFADDR: cmd = SIOCGIFADDR; break; case OSIOCGIFDSTADDR: cmd = SIOCGIFDSTADDR; break; case OSIOCGIFBRDADDR: cmd = SIOCGIFBRDADDR; break; case OSIOCGIFNETMASK: cmd = SIOCGIFNETMASK; } error = ((*so->so_proto->pr_usrreqs->pru_control)(so, cmd, data, ifp, td)); switch (ocmd) { case OSIOCGIFADDR: case OSIOCGIFDSTADDR: case OSIOCGIFBRDADDR: case OSIOCGIFNETMASK: *(u_short *)&ifr->ifr_addr = ifr->ifr_addr.sa_family; } } #endif /* COMPAT_43 */ if ((oif_flags ^ ifp->if_flags) & IFF_UP) { #ifdef INET6 DELAY(100);/* XXX: temporary workaround for fxp issue*/ if (ifp->if_flags & IFF_UP) { int s = splimp(); in6_if_up(ifp); splx(s); } #endif } return (error); } /* * The code common to handling reference counted flags, * e.g., in ifpromisc() and if_allmulti(). * The "pflag" argument can specify a permanent mode flag to check, * such as IFF_PPROMISC for promiscuous mode; should be 0 if none. * * Only to be used on stack-owned flags, not driver-owned flags. */ static int if_setflag(struct ifnet *ifp, int flag, int pflag, int *refcount, int onswitch) { struct ifreq ifr; int error; int oldflags, oldcount; /* Sanity checks to catch programming errors */ KASSERT((flag & (IFF_DRV_OACTIVE|IFF_DRV_RUNNING)) == 0, ("%s: setting driver-owned flag %d", __func__, flag)); if (onswitch) KASSERT(*refcount >= 0, ("%s: increment negative refcount %d for flag %d", __func__, *refcount, flag)); else KASSERT(*refcount > 0, ("%s: decrement non-positive refcount %d for flag %d", __func__, *refcount, flag)); /* In case this mode is permanent, just touch refcount */ if (ifp->if_flags & pflag) { *refcount += onswitch ? 1 : -1; return (0); } /* Save ifnet parameters for if_ioctl() may fail */ oldcount = *refcount; oldflags = ifp->if_flags; /* * See if we aren't the only and touching refcount is enough. * Actually toggle interface flag if we are the first or last. */ if (onswitch) { if ((*refcount)++) return (0); ifp->if_flags |= flag; } else { if (--(*refcount)) return (0); ifp->if_flags &= ~flag; } /* Call down the driver since we've changed interface flags */ if (ifp->if_ioctl == NULL) { error = EOPNOTSUPP; goto recover; } ifr.ifr_flags = ifp->if_flags & 0xffff; ifr.ifr_flagshigh = ifp->if_flags >> 16; IFF_LOCKGIANT(ifp); error = (*ifp->if_ioctl)(ifp, SIOCSIFFLAGS, (caddr_t)&ifr); IFF_UNLOCKGIANT(ifp); if (error) goto recover; /* Notify userland that interface flags have changed */ rt_ifmsg(ifp); return (0); recover: /* Recover after driver error */ *refcount = oldcount; ifp->if_flags = oldflags; return (error); } /* * Set/clear promiscuous mode on interface ifp based on the truth value * of pswitch. The calls are reference counted so that only the first * "on" request actually has an effect, as does the final "off" request. * Results are undefined if the "off" and "on" requests are not matched. */ int ifpromisc(struct ifnet *ifp, int pswitch) { int error; int oldflags = ifp->if_flags; error = if_setflag(ifp, IFF_PROMISC, IFF_PPROMISC, &ifp->if_pcount, pswitch); /* If promiscuous mode status has changed, log a message */ if (error == 0 && ((ifp->if_flags ^ oldflags) & IFF_PROMISC)) log(LOG_INFO, "%s: promiscuous mode %s\n", ifp->if_xname, (ifp->if_flags & IFF_PROMISC) ? "enabled" : "disabled"); return (error); } /* * Return interface configuration * of system. List may be used * in later ioctl's (above) to get * other information. */ /*ARGSUSED*/ static int ifconf(u_long cmd, caddr_t data) { struct ifconf *ifc = (struct ifconf *)data; #ifdef __amd64__ struct ifconf32 *ifc32 = (struct ifconf32 *)data; struct ifconf ifc_swab; #endif struct ifnet *ifp; struct ifaddr *ifa; struct ifreq ifr; struct sbuf *sb; int error, full = 0, valid_len, max_len; #ifdef __amd64__ if (cmd == SIOCGIFCONF32) { ifc_swab.ifc_len = ifc32->ifc_len; ifc_swab.ifc_buf = (caddr_t)(uintptr_t)ifc32->ifc_buf; ifc = &ifc_swab; } #endif /* Limit initial buffer size to MAXPHYS to avoid DoS from userspace. */ max_len = MAXPHYS - 1; /* Prevent hostile input from being able to crash the system */ if (ifc->ifc_len <= 0) return (EINVAL); again: if (ifc->ifc_len <= max_len) { max_len = ifc->ifc_len; full = 1; } sb = sbuf_new(NULL, NULL, max_len + 1, SBUF_FIXEDLEN); max_len = 0; valid_len = 0; IFNET_RLOCK(); /* could sleep XXX */ TAILQ_FOREACH(ifp, &ifnet, if_link) { int addrs; /* * Zero the ifr_name buffer to make sure we don't * disclose the contents of the stack. */ memset(ifr.ifr_name, 0, sizeof(ifr.ifr_name)); if (strlcpy(ifr.ifr_name, ifp->if_xname, sizeof(ifr.ifr_name)) >= sizeof(ifr.ifr_name)) { sbuf_delete(sb); IFNET_RUNLOCK(); return (ENAMETOOLONG); } addrs = 0; TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (jailed(curthread->td_ucred) && prison_if(curthread->td_ucred, sa)) continue; addrs++; #ifdef COMPAT_43 if (cmd == OSIOCGIFCONF) { struct osockaddr *osa = (struct osockaddr *)&ifr.ifr_addr; ifr.ifr_addr = *sa; osa->sa_family = sa->sa_family; sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); } else #endif if (sa->sa_len <= sizeof(*sa)) { ifr.ifr_addr = *sa; sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); } else { sbuf_bcat(sb, &ifr, offsetof(struct ifreq, ifr_addr)); max_len += offsetof(struct ifreq, ifr_addr); sbuf_bcat(sb, sa, sa->sa_len); max_len += sa->sa_len; } if (!sbuf_overflowed(sb)) valid_len = sbuf_len(sb); } if (addrs == 0) { bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); if (!sbuf_overflowed(sb)) valid_len = sbuf_len(sb); } } IFNET_RUNLOCK(); /* * If we didn't allocate enough space (uncommon), try again. If * we have already allocated as much space as we are allowed, * return what we've got. */ if (valid_len != max_len && !full) { sbuf_delete(sb); goto again; } ifc->ifc_len = valid_len; #ifdef __amd64__ if (cmd == SIOCGIFCONF32) ifc32->ifc_len = valid_len; #endif sbuf_finish(sb); error = copyout(sbuf_data(sb), ifc->ifc_req, ifc->ifc_len); sbuf_delete(sb); return (error); } /* * Just like ifpromisc(), but for all-multicast-reception mode. */ int if_allmulti(struct ifnet *ifp, int onswitch) { return (if_setflag(ifp, IFF_ALLMULTI, 0, &ifp->if_amcount, onswitch)); } struct ifmultiaddr * if_findmulti(struct ifnet *ifp, struct sockaddr *sa) { struct ifmultiaddr *ifma; IF_ADDR_LOCK_ASSERT(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (sa->sa_family == AF_LINK) { if (sa_dl_equal(ifma->ifma_addr, sa)) break; } else { if (sa_equal(ifma->ifma_addr, sa)) break; } } return ifma; } /* * Allocate a new ifmultiaddr and initialize based on passed arguments. We * make copies of passed sockaddrs. The ifmultiaddr will not be added to * the ifnet multicast address list here, so the caller must do that and * other setup work (such as notifying the device driver). The reference * count is initialized to 1. */ static struct ifmultiaddr * if_allocmulti(struct ifnet *ifp, struct sockaddr *sa, struct sockaddr *llsa, int mflags) { struct ifmultiaddr *ifma; struct sockaddr *dupsa; MALLOC(ifma, struct ifmultiaddr *, sizeof *ifma, M_IFMADDR, mflags | M_ZERO); if (ifma == NULL) return (NULL); MALLOC(dupsa, struct sockaddr *, sa->sa_len, M_IFMADDR, mflags); if (dupsa == NULL) { FREE(ifma, M_IFMADDR); return (NULL); } bcopy(sa, dupsa, sa->sa_len); ifma->ifma_addr = dupsa; ifma->ifma_ifp = ifp; ifma->ifma_refcount = 1; ifma->ifma_protospec = NULL; if (llsa == NULL) { ifma->ifma_lladdr = NULL; return (ifma); } MALLOC(dupsa, struct sockaddr *, llsa->sa_len, M_IFMADDR, mflags); if (dupsa == NULL) { FREE(ifma->ifma_addr, M_IFMADDR); FREE(ifma, M_IFMADDR); return (NULL); } bcopy(llsa, dupsa, llsa->sa_len); ifma->ifma_lladdr = dupsa; return (ifma); } /* * if_freemulti: free ifmultiaddr structure and possibly attached related * addresses. The caller is responsible for implementing reference * counting, notifying the driver, handling routing messages, and releasing * any dependent link layer state. */ static void if_freemulti(struct ifmultiaddr *ifma) { KASSERT(ifma->ifma_refcount == 0, ("if_freemulti: refcount %d", ifma->ifma_refcount)); KASSERT(ifma->ifma_protospec == NULL, ("if_freemulti: protospec not NULL")); if (ifma->ifma_lladdr != NULL) FREE(ifma->ifma_lladdr, M_IFMADDR); FREE(ifma->ifma_addr, M_IFMADDR); FREE(ifma, M_IFMADDR); } /* * Register an additional multicast address with a network interface. * * - If the address is already present, bump the reference count on the * address and return. * - If the address is not link-layer, look up a link layer address. * - Allocate address structures for one or both addresses, and attach to the * multicast address list on the interface. If automatically adding a link * layer address, the protocol address will own a reference to the link * layer address, to be freed when it is freed. * - Notify the network device driver of an addition to the multicast address * list. * * 'sa' points to caller-owned memory with the desired multicast address. * * 'retifma' will be used to return a pointer to the resulting multicast * address reference, if desired. */ int if_addmulti(struct ifnet *ifp, struct sockaddr *sa, struct ifmultiaddr **retifma) { struct ifmultiaddr *ifma, *ll_ifma; struct sockaddr *llsa; int error; /* * If the address is already present, return a new reference to it; * otherwise, allocate storage and set up a new address. */ IF_ADDR_LOCK(ifp); ifma = if_findmulti(ifp, sa); if (ifma != NULL) { ifma->ifma_refcount++; if (retifma != NULL) *retifma = ifma; IF_ADDR_UNLOCK(ifp); return (0); } /* * The address isn't already present; resolve the protocol address * into a link layer address, and then look that up, bump its * refcount or allocate an ifma for that also. If 'llsa' was * returned, we will need to free it later. */ llsa = NULL; ll_ifma = NULL; if (ifp->if_resolvemulti != NULL) { error = ifp->if_resolvemulti(ifp, &llsa, sa); if (error) goto unlock_out; } /* * Allocate the new address. Don't hook it up yet, as we may also * need to allocate a link layer multicast address. */ ifma = if_allocmulti(ifp, sa, llsa, M_NOWAIT); if (ifma == NULL) { error = ENOMEM; goto free_llsa_out; } /* * If a link layer address is found, we'll need to see if it's * already present in the address list, or allocate is as well. * When this block finishes, the link layer address will be on the * list. */ if (llsa != NULL) { ll_ifma = if_findmulti(ifp, llsa); if (ll_ifma == NULL) { ll_ifma = if_allocmulti(ifp, llsa, NULL, M_NOWAIT); if (ll_ifma == NULL) { --ifma->ifma_refcount; if_freemulti(ifma); error = ENOMEM; goto free_llsa_out; } TAILQ_INSERT_HEAD(&ifp->if_multiaddrs, ll_ifma, ifma_link); } else ll_ifma->ifma_refcount++; ifma->ifma_llifma = ll_ifma; } /* * We now have a new multicast address, ifma, and possibly a new or * referenced link layer address. Add the primary address to the * ifnet address list. */ TAILQ_INSERT_HEAD(&ifp->if_multiaddrs, ifma, ifma_link); if (retifma != NULL) *retifma = ifma; /* * Must generate the message while holding the lock so that 'ifma' * pointer is still valid. */ rt_newmaddrmsg(RTM_NEWMADDR, ifma); IF_ADDR_UNLOCK(ifp); /* * We are certain we have added something, so call down to the * interface to let them know about it. */ if (ifp->if_ioctl != NULL) { IFF_LOCKGIANT(ifp); (void) (*ifp->if_ioctl)(ifp, SIOCADDMULTI, 0); IFF_UNLOCKGIANT(ifp); } if (llsa != NULL) FREE(llsa, M_IFMADDR); return (0); free_llsa_out: if (llsa != NULL) FREE(llsa, M_IFMADDR); unlock_out: IF_ADDR_UNLOCK(ifp); return (error); } /* * Delete a multicast group membership by network-layer group address. * * Returns ENOENT if the entry could not be found. If ifp no longer * exists, results are undefined. This entry point should only be used * from subsystems which do appropriate locking to hold ifp for the * duration of the call. * Network-layer protocol domains must use if_delmulti_ifma(). */ int if_delmulti(struct ifnet *ifp, struct sockaddr *sa) { struct ifmultiaddr *ifma; int lastref; #ifdef INVARIANTS struct ifnet *oifp; IFNET_RLOCK(); TAILQ_FOREACH(oifp, &ifnet, if_link) if (ifp == oifp) break; if (ifp != oifp) ifp = NULL; IFNET_RUNLOCK(); KASSERT(ifp != NULL, ("%s: ifnet went away", __func__)); #endif if (ifp == NULL) return (ENOENT); IF_ADDR_LOCK(ifp); lastref = 0; ifma = if_findmulti(ifp, sa); if (ifma != NULL) lastref = if_delmulti_locked(ifp, ifma, 0); IF_ADDR_UNLOCK(ifp); if (ifma == NULL) return (ENOENT); if (lastref && ifp->if_ioctl != NULL) { IFF_LOCKGIANT(ifp); (void)(*ifp->if_ioctl)(ifp, SIOCDELMULTI, 0); IFF_UNLOCKGIANT(ifp); } return (0); } /* * Delete a multicast group membership by group membership pointer. * Network-layer protocol domains must use this routine. * * It is safe to call this routine if the ifp disappeared. Callers should * hold IFF_LOCKGIANT() to avoid a LOR in case the hardware needs to be * reconfigured. */ void if_delmulti_ifma(struct ifmultiaddr *ifma) { struct ifnet *ifp; int lastref; ifp = ifma->ifma_ifp; #ifdef DIAGNOSTIC if (ifp == NULL) { printf("%s: ifma_ifp seems to be detached\n", __func__); } else { struct ifnet *oifp; IFNET_RLOCK(); TAILQ_FOREACH(oifp, &ifnet, if_link) if (ifp == oifp) break; if (ifp != oifp) { printf("%s: ifnet %p disappeared\n", __func__, ifp); ifp = NULL; } IFNET_RUNLOCK(); } #endif /* * If and only if the ifnet instance exists: Acquire the address lock. */ if (ifp != NULL) IF_ADDR_LOCK(ifp); lastref = if_delmulti_locked(ifp, ifma, 0); if (ifp != NULL) { /* * If and only if the ifnet instance exists: * Release the address lock. * If the group was left: update the hardware hash filter. */ IF_ADDR_UNLOCK(ifp); if (lastref && ifp->if_ioctl != NULL) { IFF_LOCKGIANT(ifp); (void)(*ifp->if_ioctl)(ifp, SIOCDELMULTI, 0); IFF_UNLOCKGIANT(ifp); } } } /* * Perform deletion of network-layer and/or link-layer multicast address. * * Return 0 if the reference count was decremented. * Return 1 if the final reference was released, indicating that the * hardware hash filter should be reprogrammed. */ static int if_delmulti_locked(struct ifnet *ifp, struct ifmultiaddr *ifma, int detaching) { struct ifmultiaddr *ll_ifma; if (ifp != NULL && ifma->ifma_ifp != NULL) { KASSERT(ifma->ifma_ifp == ifp, ("%s: inconsistent ifp %p", __func__, ifp)); IF_ADDR_LOCK_ASSERT(ifp); } ifp = ifma->ifma_ifp; /* * If the ifnet is detaching, null out references to ifnet, * so that upper protocol layers will notice, and not attempt * to obtain locks for an ifnet which no longer exists. The * routing socket announcement must happen before the ifnet * instance is detached from the system. */ if (detaching) { #ifdef DIAGNOSTIC printf("%s: detaching ifnet instance %p\n", __func__, ifp); #endif /* * ifp may already be nulled out if we are being reentered * to delete the ll_ifma. */ if (ifp != NULL) { rt_newmaddrmsg(RTM_DELMADDR, ifma); ifma->ifma_ifp = NULL; } } if (--ifma->ifma_refcount > 0) return 0; /* * If this ifma is a network-layer ifma, a link-layer ifma may * have been associated with it. Release it first if so. */ ll_ifma = ifma->ifma_llifma; if (ll_ifma != NULL) { KASSERT(ifma->ifma_lladdr != NULL, ("%s: llifma w/o lladdr", __func__)); if (detaching) ll_ifma->ifma_ifp = NULL; /* XXX */ if (--ll_ifma->ifma_refcount == 0) { if (ifp != NULL) { TAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifma_link); } if_freemulti(ll_ifma); } } if (ifp != NULL) TAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifma_link); if_freemulti(ifma); /* * The last reference to this instance of struct ifmultiaddr * was released; the hardware should be notified of this change. */ return 1; } /* * Set the link layer address on an interface. * * At this time we only support certain types of interfaces, * and we don't allow the length of the address to change. */ int if_setlladdr(struct ifnet *ifp, const u_char *lladdr, int len) { struct sockaddr_dl *sdl; struct ifaddr *ifa; struct ifreq ifr; ifa = ifp->if_addr; if (ifa == NULL) return (EINVAL); sdl = (struct sockaddr_dl *)ifa->ifa_addr; if (sdl == NULL) return (EINVAL); if (len != sdl->sdl_alen) /* don't allow length to change */ return (EINVAL); switch (ifp->if_type) { case IFT_ETHER: case IFT_FDDI: case IFT_XETHER: case IFT_ISO88025: case IFT_L2VLAN: case IFT_BRIDGE: case IFT_ARCNET: case IFT_IEEE8023ADLAG: bcopy(lladdr, LLADDR(sdl), len); break; default: return (ENODEV); } /* * If the interface is already up, we need * to re-init it in order to reprogram its * address filter. */ if ((ifp->if_flags & IFF_UP) != 0) { if (ifp->if_ioctl) { IFF_LOCKGIANT(ifp); ifp->if_flags &= ~IFF_UP; ifr.ifr_flags = ifp->if_flags & 0xffff; ifr.ifr_flagshigh = ifp->if_flags >> 16; (*ifp->if_ioctl)(ifp, SIOCSIFFLAGS, (caddr_t)&ifr); ifp->if_flags |= IFF_UP; ifr.ifr_flags = ifp->if_flags & 0xffff; ifr.ifr_flagshigh = ifp->if_flags >> 16; (*ifp->if_ioctl)(ifp, SIOCSIFFLAGS, (caddr_t)&ifr); IFF_UNLOCKGIANT(ifp); } #ifdef INET /* * Also send gratuitous ARPs to notify other nodes about * the address change. */ TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family == AF_INET) arp_ifinit(ifp, ifa); } #endif } return (0); } /* * The name argument must be a pointer to storage which will last as * long as the interface does. For physical devices, the result of * device_get_name(dev) is a good choice and for pseudo-devices a * static string works well. */ void if_initname(struct ifnet *ifp, const char *name, int unit) { ifp->if_dname = name; ifp->if_dunit = unit; if (unit != IF_DUNIT_NONE) snprintf(ifp->if_xname, IFNAMSIZ, "%s%d", name, unit); else strlcpy(ifp->if_xname, name, IFNAMSIZ); } int if_printf(struct ifnet *ifp, const char * fmt, ...) { va_list ap; int retval; retval = printf("%s: ", ifp->if_xname); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } /* * When an interface is marked IFF_NEEDSGIANT, its if_start() routine cannot * be called without Giant. However, we often can't acquire the Giant lock * at those points; instead, we run it via a task queue that holds Giant via * if_start_deferred. * * XXXRW: We need to make sure that the ifnet isn't fully detached until any * outstanding if_start_deferred() tasks that will run after the free. This * probably means waiting in if_detach(). */ void if_start(struct ifnet *ifp) { - NET_ASSERT_GIANT(); - - if ((ifp->if_flags & IFF_NEEDSGIANT) != 0 && debug_mpsafenet != 0) { + if (ifp->if_flags & IFF_NEEDSGIANT) { if (mtx_owned(&Giant)) (*(ifp)->if_start)(ifp); else taskqueue_enqueue(taskqueue_swi_giant, &ifp->if_starttask); } else (*(ifp)->if_start)(ifp); } static void if_start_deferred(void *context, int pending) { struct ifnet *ifp; - /* - * This code must be entered with Giant, and should never run if - * we're not running with debug.mpsafenet. - */ - KASSERT(debug_mpsafenet != 0, ("if_start_deferred: debug.mpsafenet")); GIANT_REQUIRED; ifp = context; (ifp->if_start)(ifp); } int if_handoff(struct ifqueue *ifq, struct mbuf *m, struct ifnet *ifp, int adjust) { int active = 0; IF_LOCK(ifq); if (_IF_QFULL(ifq)) { _IF_DROP(ifq); IF_UNLOCK(ifq); m_freem(m); return (0); } if (ifp != NULL) { ifp->if_obytes += m->m_pkthdr.len + adjust; if (m->m_flags & (M_BCAST|M_MCAST)) ifp->if_omcasts++; active = ifp->if_drv_flags & IFF_DRV_OACTIVE; } _IF_ENQUEUE(ifq, m); IF_UNLOCK(ifq); if (ifp != NULL && !active) if_start(ifp); return (1); } void if_register_com_alloc(u_char type, if_com_alloc_t *a, if_com_free_t *f) { KASSERT(if_com_alloc[type] == NULL, ("if_register_com_alloc: %d already registered", type)); KASSERT(if_com_free[type] == NULL, ("if_register_com_alloc: %d free already registered", type)); if_com_alloc[type] = a; if_com_free[type] = f; } void if_deregister_com_alloc(u_char type) { KASSERT(if_com_alloc[type] != NULL, ("if_deregister_com_alloc: %d not registered", type)); KASSERT(if_com_free[type] != NULL, ("if_deregister_com_alloc: %d free not registered", type)); if_com_alloc[type] = NULL; if_com_free[type] = NULL; } Index: head/sys/net/if_ethersubr.c =================================================================== --- head/sys/net/if_ethersubr.c (revision 171612) +++ head/sys/net/if_ethersubr.c (revision 171613) @@ -1,1267 +1,1267 @@ /*- * Copyright (c) 1982, 1989, 1993 * The Regents of the University of California. 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)if_ethersubr.c 8.1 (Berkeley) 6/10/93 * $FreeBSD$ */ #include "opt_atalk.h" #include "opt_inet.h" #include "opt_inet6.h" #include "opt_ipx.h" #include "opt_mac.h" #include "opt_netgraph.h" #include "opt_carp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(INET) || defined(INET6) #include #include #include #include #include #endif #ifdef INET6 #include #endif #ifdef DEV_CARP #include #endif #ifdef IPX #include #include #endif int (*ef_inputp)(struct ifnet*, struct ether_header *eh, struct mbuf *m); int (*ef_outputp)(struct ifnet *ifp, struct mbuf **mp, struct sockaddr *dst, short *tp, int *hlen); #ifdef NETATALK #include #include #include #define llc_snap_org_code llc_un.type_snap.org_code #define llc_snap_ether_type llc_un.type_snap.ether_type extern u_char at_org_code[3]; extern u_char aarp_org_code[3]; #endif /* NETATALK */ #include /* netgraph node hooks for ng_ether(4) */ void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); void (*ng_ether_input_orphan_p)(struct ifnet *ifp, struct mbuf *m); int (*ng_ether_output_p)(struct ifnet *ifp, struct mbuf **mp); void (*ng_ether_attach_p)(struct ifnet *ifp); void (*ng_ether_detach_p)(struct ifnet *ifp); void (*vlan_input_p)(struct ifnet *, struct mbuf *); /* if_bridge(4) support */ struct mbuf *(*bridge_input_p)(struct ifnet *, struct mbuf *); int (*bridge_output_p)(struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *); void (*bridge_dn_p)(struct mbuf *, struct ifnet *); /* if_lagg(4) support */ struct mbuf *(*lagg_input_p)(struct ifnet *, struct mbuf *); static const u_char etherbroadcastaddr[ETHER_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static int ether_resolvemulti(struct ifnet *, struct sockaddr **, struct sockaddr *); /* XXX: should be in an arp support file, not here */ MALLOC_DEFINE(M_ARPCOM, "arpcom", "802.* interface internals"); #define ETHER_IS_BROADCAST(addr) \ (bcmp(etherbroadcastaddr, (addr), ETHER_ADDR_LEN) == 0) #define senderr(e) do { error = (e); goto bad;} while (0) #if defined(INET) || defined(INET6) int ether_ipfw_chk(struct mbuf **m0, struct ifnet *dst, struct ip_fw **rule, int shared); static int ether_ipfw; #endif /* * Ethernet output routine. * Encapsulate a packet of type family for the local net. * Use trailer local net encapsulation if enough data in first * packet leaves a multiple of 512 bytes of data in remainder. */ int ether_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt0) { short type; int error, hdrcmplt = 0; u_char esrc[ETHER_ADDR_LEN], edst[ETHER_ADDR_LEN]; struct ether_header *eh; struct pf_mtag *t; int loop_copy = 1; int hlen; /* link layer header length */ #ifdef MAC error = mac_check_ifnet_transmit(ifp, m); if (error) senderr(error); #endif if (ifp->if_flags & IFF_MONITOR) senderr(ENETDOWN); if (!((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))) senderr(ENETDOWN); hlen = ETHER_HDR_LEN; switch (dst->sa_family) { #ifdef INET case AF_INET: error = arpresolve(ifp, rt0, m, dst, edst); if (error) return (error == EWOULDBLOCK ? 0 : error); type = htons(ETHERTYPE_IP); break; case AF_ARP: { struct arphdr *ah; ah = mtod(m, struct arphdr *); ah->ar_hrd = htons(ARPHRD_ETHER); loop_copy = 0; /* if this is for us, don't do it */ switch(ntohs(ah->ar_op)) { case ARPOP_REVREQUEST: case ARPOP_REVREPLY: type = htons(ETHERTYPE_REVARP); break; case ARPOP_REQUEST: case ARPOP_REPLY: default: type = htons(ETHERTYPE_ARP); break; } if (m->m_flags & M_BCAST) bcopy(ifp->if_broadcastaddr, edst, ETHER_ADDR_LEN); else bcopy(ar_tha(ah), edst, ETHER_ADDR_LEN); } break; #endif #ifdef INET6 case AF_INET6: error = nd6_storelladdr(ifp, rt0, m, dst, (u_char *)edst); if (error) return error; type = htons(ETHERTYPE_IPV6); break; #endif #ifdef IPX case AF_IPX: if (ef_outputp) { error = ef_outputp(ifp, &m, dst, &type, &hlen); if (error) goto bad; } else type = htons(ETHERTYPE_IPX); bcopy((caddr_t)&(((struct sockaddr_ipx *)dst)->sipx_addr.x_host), (caddr_t)edst, sizeof (edst)); break; #endif #ifdef NETATALK case AF_APPLETALK: { struct at_ifaddr *aa; if ((aa = at_ifawithnet((struct sockaddr_at *)dst)) == NULL) senderr(EHOSTUNREACH); /* XXX */ if (!aarpresolve(ifp, m, (struct sockaddr_at *)dst, edst)) return (0); /* * In the phase 2 case, need to prepend an mbuf for the llc header. */ if ( aa->aa_flags & AFA_PHASE2 ) { struct llc llc; M_PREPEND(m, LLC_SNAPFRAMELEN, M_DONTWAIT); if (m == NULL) senderr(ENOBUFS); llc.llc_dsap = llc.llc_ssap = LLC_SNAP_LSAP; llc.llc_control = LLC_UI; bcopy(at_org_code, llc.llc_snap_org_code, sizeof(at_org_code)); llc.llc_snap_ether_type = htons( ETHERTYPE_AT ); bcopy(&llc, mtod(m, caddr_t), LLC_SNAPFRAMELEN); type = htons(m->m_pkthdr.len); hlen = LLC_SNAPFRAMELEN + ETHER_HDR_LEN; } else { type = htons(ETHERTYPE_AT); } break; } #endif /* NETATALK */ case pseudo_AF_HDRCMPLT: hdrcmplt = 1; eh = (struct ether_header *)dst->sa_data; (void)memcpy(esrc, eh->ether_shost, sizeof (esrc)); /* FALLTHROUGH */ case AF_UNSPEC: loop_copy = 0; /* if this is for us, don't do it */ eh = (struct ether_header *)dst->sa_data; (void)memcpy(edst, eh->ether_dhost, sizeof (edst)); type = eh->ether_type; break; default: if_printf(ifp, "can't handle af%d\n", dst->sa_family); senderr(EAFNOSUPPORT); } /* * Add local net header. If no space in first mbuf, * allocate another. */ M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); if (m == NULL) senderr(ENOBUFS); eh = mtod(m, struct ether_header *); (void)memcpy(&eh->ether_type, &type, sizeof(eh->ether_type)); (void)memcpy(eh->ether_dhost, edst, sizeof (edst)); if (hdrcmplt) (void)memcpy(eh->ether_shost, esrc, sizeof(eh->ether_shost)); else (void)memcpy(eh->ether_shost, IF_LLADDR(ifp), sizeof(eh->ether_shost)); /* * If a simplex interface, and the packet is being sent to our * Ethernet address or a broadcast address, loopback a copy. * XXX To make a simplex device behave exactly like a duplex * device, we should copy in the case of sending to our own * ethernet address (thus letting the original actually appear * on the wire). However, we don't do that here for security * reasons and compatibility with the original behavior. */ if ((ifp->if_flags & IFF_SIMPLEX) && loop_copy && ((t = pf_find_mtag(m)) == NULL || !t->routed)) { int csum_flags = 0; if (m->m_pkthdr.csum_flags & CSUM_IP) csum_flags |= (CSUM_IP_CHECKED|CSUM_IP_VALID); if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) csum_flags |= (CSUM_DATA_VALID|CSUM_PSEUDO_HDR); if (m->m_flags & M_BCAST) { struct mbuf *n; /* * Because if_simloop() modifies the packet, we need a * writable copy through m_dup() instead of a readonly * one as m_copy[m] would give us. The alternative would * be to modify if_simloop() to handle the readonly mbuf, * but performancewise it is mostly equivalent (trading * extra data copying vs. extra locking). * * XXX This is a local workaround. A number of less * often used kernel parts suffer from the same bug. * See PR kern/105943 for a proposed general solution. */ if ((n = m_dup(m, M_DONTWAIT)) != NULL) { n->m_pkthdr.csum_flags |= csum_flags; if (csum_flags & CSUM_DATA_VALID) n->m_pkthdr.csum_data = 0xffff; (void)if_simloop(ifp, n, dst->sa_family, hlen); } else ifp->if_iqdrops++; } else if (bcmp(eh->ether_dhost, eh->ether_shost, ETHER_ADDR_LEN) == 0) { m->m_pkthdr.csum_flags |= csum_flags; if (csum_flags & CSUM_DATA_VALID) m->m_pkthdr.csum_data = 0xffff; (void) if_simloop(ifp, m, dst->sa_family, hlen); return (0); /* XXX */ } } /* * Bridges require special output handling. */ if (ifp->if_bridge) { BRIDGE_OUTPUT(ifp, m, error); return (error); } #ifdef DEV_CARP if (ifp->if_carp && (error = carp_output(ifp, m, dst, NULL))) goto bad; #endif /* Handle ng_ether(4) processing, if any */ if (IFP2AC(ifp)->ac_netgraph != NULL) { KASSERT(ng_ether_output_p != NULL, ("ng_ether_output_p is NULL")); if ((error = (*ng_ether_output_p)(ifp, &m)) != 0) { bad: if (m != NULL) m_freem(m); return (error); } if (m == NULL) return (0); } /* Continue with link-layer output */ return ether_output_frame(ifp, m); } /* * Ethernet link layer output routine to send a raw frame to the device. * * This assumes that the 14 byte Ethernet header is present and contiguous * in the first mbuf (if BRIDGE'ing). */ int ether_output_frame(struct ifnet *ifp, struct mbuf *m) { int error; #if defined(INET) || defined(INET6) struct ip_fw *rule = ip_dn_claim_rule(m); if (IPFW_LOADED && ether_ipfw != 0) { if (ether_ipfw_chk(&m, ifp, &rule, 0) == 0) { if (m) { m_freem(m); return EACCES; /* pkt dropped */ } else return 0; /* consumed e.g. in a pipe */ } } #endif /* * Queue message on interface, update output statistics if * successful, and start output if interface not yet active. */ IFQ_HANDOFF(ifp, m, error); return (error); } #if defined(INET) || defined(INET6) /* * ipfw processing for ethernet packets (in and out). * The second parameter is NULL from ether_demux, and ifp from * ether_output_frame. */ int ether_ipfw_chk(struct mbuf **m0, struct ifnet *dst, struct ip_fw **rule, int shared) { struct ether_header *eh; struct ether_header save_eh; struct mbuf *m; int i; struct ip_fw_args args; if (*rule != NULL && fw_one_pass) return 1; /* dummynet packet, already partially processed */ /* * I need some amt of data to be contiguous, and in case others need * the packet (shared==1) also better be in the first mbuf. */ m = *m0; i = min( m->m_pkthdr.len, max_protohdr); if ( shared || m->m_len < i) { m = m_pullup(m, i); if (m == NULL) { *m0 = m; return 0; } } eh = mtod(m, struct ether_header *); save_eh = *eh; /* save copy for restore below */ m_adj(m, ETHER_HDR_LEN); /* strip ethernet header */ args.m = m; /* the packet we are looking at */ args.oif = dst; /* destination, if any */ args.rule = *rule; /* matching rule to restart */ args.next_hop = NULL; /* we do not support forward yet */ args.eh = &save_eh; /* MAC header for bridged/MAC packets */ args.inp = NULL; /* used by ipfw uid/gid/jail rules */ i = ip_fw_chk_ptr(&args); m = args.m; if (m != NULL) { /* * Restore Ethernet header, as needed, in case the * mbuf chain was replaced by ipfw. */ M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); if (m == NULL) { *m0 = m; return 0; } if (eh != mtod(m, struct ether_header *)) bcopy(&save_eh, mtod(m, struct ether_header *), ETHER_HDR_LEN); } *m0 = m; *rule = args.rule; if (i == IP_FW_DENY) /* drop */ return 0; KASSERT(m != NULL, ("ether_ipfw_chk: m is NULL")); if (i == IP_FW_PASS) /* a PASS rule. */ return 1; if (DUMMYNET_LOADED && (i == IP_FW_DUMMYNET)) { /* * Pass the pkt to dummynet, which consumes it. * If shared, make a copy and keep the original. */ if (shared) { m = m_copypacket(m, M_DONTWAIT); if (m == NULL) return 0; } else { /* * Pass the original to dummynet and * nothing back to the caller */ *m0 = NULL ; } ip_dn_io_ptr(m, dst ? DN_TO_ETH_OUT: DN_TO_ETH_DEMUX, &args); return 0; } /* * XXX at some point add support for divert/forward actions. * If none of the above matches, we have to drop the pkt. */ return 0; } #endif /* * Process a received Ethernet packet; the packet is in the * mbuf chain m with the ethernet header at the front. */ static void ether_input(struct ifnet *ifp, struct mbuf *m) { struct ether_header *eh; u_short etype; if ((ifp->if_flags & IFF_UP) == 0) { m_freem(m); return; } #ifdef DIAGNOSTIC if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { if_printf(ifp, "discard frame at !IFF_DRV_RUNNING\n"); m_freem(m); return; } #endif /* * Do consistency checks to verify assumptions * made by code past this point. */ if ((m->m_flags & M_PKTHDR) == 0) { if_printf(ifp, "discard frame w/o packet header\n"); ifp->if_ierrors++; m_freem(m); return; } if (m->m_len < ETHER_HDR_LEN) { /* XXX maybe should pullup? */ if_printf(ifp, "discard frame w/o leading ethernet " "header (len %u pkt len %u)\n", m->m_len, m->m_pkthdr.len); ifp->if_ierrors++; m_freem(m); return; } eh = mtod(m, struct ether_header *); etype = ntohs(eh->ether_type); #ifdef DIAGNOSTIC if (m->m_pkthdr.len > ETHER_MAX_FRAME(ifp, etype, m->m_flags & M_HASFCS) && (ifp->if_capenable & IFCAP_LRO) == 0) { if_printf(ifp, "discard oversize frame " "(ether type %x flags %x len %u > max %lu)\n", etype, m->m_flags, m->m_pkthdr.len, ETHER_MAX_FRAME(ifp, etype, m->m_flags & M_HASFCS)); ifp->if_ierrors++; m_freem(m); return; } #endif if (m->m_pkthdr.rcvif == NULL) { if_printf(ifp, "discard frame w/o interface pointer\n"); ifp->if_ierrors++; m_freem(m); return; } #ifdef DIAGNOSTIC if (m->m_pkthdr.rcvif != ifp) { if_printf(ifp, "Warning, frame marked as received on %s\n", m->m_pkthdr.rcvif->if_xname); } #endif if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (ETHER_IS_BROADCAST(eh->ether_dhost)) m->m_flags |= M_BCAST; else m->m_flags |= M_MCAST; ifp->if_imcasts++; } #ifdef MAC /* * Tag the mbuf with an appropriate MAC label before any other * consumers can get to it. */ mac_create_mbuf_from_ifnet(ifp, m); #endif /* * Give bpf a chance at the packet. */ ETHER_BPF_MTAP(ifp, m); /* * If the CRC is still on the packet, trim it off. We do this once * and once only in case we are re-entered. Nothing else on the * Ethernet receive path expects to see the FCS. */ if (m->m_flags & M_HASFCS) { m_adj(m, -ETHER_CRC_LEN); m->m_flags &= ~M_HASFCS; } ifp->if_ibytes += m->m_pkthdr.len; /* Allow monitor mode to claim this frame, after stats are updated. */ if (ifp->if_flags & IFF_MONITOR) { m_freem(m); return; } /* Handle input from a lagg(4) port */ if (ifp->if_type == IFT_IEEE8023ADLAG) { KASSERT(lagg_input_p != NULL, ("%s: if_lagg not loaded!", __func__)); m = (*lagg_input_p)(ifp, m); if (m != NULL) ifp = m->m_pkthdr.rcvif; else return; } /* * If the hardware did not process an 802.1Q tag, do this now, * to allow 802.1P priority frames to be passed to the main input * path correctly. * TODO: Deal with Q-in-Q frames, but not arbitrary nesting levels. */ if ((m->m_flags & M_VLANTAG) == 0 && etype == ETHERTYPE_VLAN) { struct ether_vlan_header *evl; if (m->m_len < sizeof(*evl) && (m = m_pullup(m, sizeof(*evl))) == NULL) { #ifdef DIAGNOSTIC if_printf(ifp, "cannot pullup VLAN header\n"); #endif ifp->if_ierrors++; m_freem(m); return; } evl = mtod(m, struct ether_vlan_header *); m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag); m->m_flags |= M_VLANTAG; bcopy((char *)evl, (char *)evl + ETHER_VLAN_ENCAP_LEN, ETHER_HDR_LEN - ETHER_TYPE_LEN); m_adj(m, ETHER_VLAN_ENCAP_LEN); } /* Allow ng_ether(4) to claim this frame. */ if (IFP2AC(ifp)->ac_netgraph != NULL) { KASSERT(ng_ether_input_p != NULL, ("%s: ng_ether_input_p is NULL", __func__)); m->m_flags &= ~M_PROMISC; (*ng_ether_input_p)(ifp, &m); if (m == NULL) return; } /* * Allow if_bridge(4) to claim this frame. * The BRIDGE_INPUT() macro will update ifp if the bridge changed it * and the frame should be delivered locally. */ if (ifp->if_bridge != NULL) { m->m_flags &= ~M_PROMISC; BRIDGE_INPUT(ifp, m); if (m == NULL) return; } #ifdef DEV_CARP /* * Clear M_PROMISC on frame so that carp(4) will see it when the * mbuf flows up to Layer 3. * FreeBSD's implementation of carp(4) uses the inprotosw * to dispatch IPPROTO_CARP. carp(4) also allocates its own * Ethernet addresses of the form 00:00:5e:00:01:xx, which * is outside the scope of the M_PROMISC test below. * TODO: Maintain a hash table of ethernet addresses other than * ether_dhost which may be active on this ifp. */ if (ifp->if_carp && carp_forus(ifp->if_carp, eh->ether_dhost)) { m->m_flags &= ~M_PROMISC; } else #endif { /* * If the frame received was not for our MAC address, set the * M_PROMISC flag on the mbuf chain. The frame may need to * be seen by the rest of the Ethernet input path in case of * re-entry (e.g. bridge, vlan, netgraph) but should not be * seen by upper protocol layers. */ if (!ETHER_IS_MULTICAST(eh->ether_dhost) && bcmp(IF_LLADDR(ifp), eh->ether_dhost, ETHER_ADDR_LEN) != 0) m->m_flags |= M_PROMISC; } /* First chunk of an mbuf contains good entropy */ if (harvest.ethernet) random_harvest(m, 16, 3, 0, RANDOM_NET); ether_demux(ifp, m); } /* * Upper layer processing for a received Ethernet packet. */ void ether_demux(struct ifnet *ifp, struct mbuf *m) { struct ether_header *eh; int isr; u_short ether_type; #if defined(NETATALK) struct llc *l; #endif KASSERT(ifp != NULL, ("%s: NULL interface pointer", __func__)); #if defined(INET) || defined(INET6) /* * Allow dummynet and/or ipfw to claim the frame. * Do not do this for PROMISC frames in case we are re-entered. */ if (IPFW_LOADED && ether_ipfw != 0 && !(m->m_flags & M_PROMISC)) { struct ip_fw *rule = ip_dn_claim_rule(m); if (ether_ipfw_chk(&m, NULL, &rule, 0) == 0) { if (m) m_freem(m); /* dropped; free mbuf chain */ return; /* consumed */ } } #endif eh = mtod(m, struct ether_header *); ether_type = ntohs(eh->ether_type); /* * If this frame has a VLAN tag other than 0, call vlan_input() * if its module is loaded. Otherwise, drop. */ if ((m->m_flags & M_VLANTAG) && EVL_VLANOFTAG(m->m_pkthdr.ether_vtag) != 0) { if (ifp->if_vlantrunk == NULL) { ifp->if_noproto++; m_freem(m); return; } KASSERT(vlan_input_p != NULL,("%s: VLAN not loaded!", __func__)); /* Clear before possibly re-entering ether_input(). */ m->m_flags &= ~M_PROMISC; (*vlan_input_p)(ifp, m); return; } /* * Pass promiscuously received frames to the upper layer if the user * requested this by setting IFF_PPROMISC. Otherwise, drop them. */ if ((ifp->if_flags & IFF_PPROMISC) == 0 && (m->m_flags & M_PROMISC)) { m_freem(m); return; } /* * Reset layer specific mbuf flags to avoid confusing upper layers. * Strip off Ethernet header. */ m->m_flags &= ~M_VLANTAG; m->m_flags &= ~(M_PROTOFLAGS); m_adj(m, ETHER_HDR_LEN); /* * Dispatch frame to upper layer. */ switch (ether_type) { #ifdef INET case ETHERTYPE_IP: if ((m = ip_fastforward(m)) == NULL) return; isr = NETISR_IP; break; case ETHERTYPE_ARP: if (ifp->if_flags & IFF_NOARP) { /* Discard packet if ARP is disabled on interface */ m_freem(m); return; } isr = NETISR_ARP; break; #endif #ifdef IPX case ETHERTYPE_IPX: if (ef_inputp && ef_inputp(ifp, eh, m) == 0) return; isr = NETISR_IPX; break; #endif #ifdef INET6 case ETHERTYPE_IPV6: isr = NETISR_IPV6; break; #endif #ifdef NETATALK case ETHERTYPE_AT: isr = NETISR_ATALK1; break; case ETHERTYPE_AARP: isr = NETISR_AARP; break; #endif /* NETATALK */ default: #ifdef IPX if (ef_inputp && ef_inputp(ifp, eh, m) == 0) return; #endif /* IPX */ #if defined(NETATALK) if (ether_type > ETHERMTU) goto discard; l = mtod(m, struct llc *); if (l->llc_dsap == LLC_SNAP_LSAP && l->llc_ssap == LLC_SNAP_LSAP && l->llc_control == LLC_UI) { if (bcmp(&(l->llc_snap_org_code)[0], at_org_code, sizeof(at_org_code)) == 0 && ntohs(l->llc_snap_ether_type) == ETHERTYPE_AT) { m_adj(m, LLC_SNAPFRAMELEN); isr = NETISR_ATALK2; break; } if (bcmp(&(l->llc_snap_org_code)[0], aarp_org_code, sizeof(aarp_org_code)) == 0 && ntohs(l->llc_snap_ether_type) == ETHERTYPE_AARP) { m_adj(m, LLC_SNAPFRAMELEN); isr = NETISR_AARP; break; } } #endif /* NETATALK */ goto discard; } netisr_dispatch(isr, m); return; discard: /* * Packet is to be discarded. If netgraph is present, * hand the packet to it for last chance processing; * otherwise dispose of it. */ if (IFP2AC(ifp)->ac_netgraph != NULL) { KASSERT(ng_ether_input_orphan_p != NULL, ("ng_ether_input_orphan_p is NULL")); /* * Put back the ethernet header so netgraph has a * consistent view of inbound packets. */ M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); (*ng_ether_input_orphan_p)(ifp, m); return; } m_freem(m); } /* * Convert Ethernet address to printable (loggable) representation. * This routine is for compatibility; it's better to just use * * printf("%6D", , ":"); * * since there's no static buffer involved. */ char * ether_sprintf(const u_char *ap) { static char etherbuf[18]; snprintf(etherbuf, sizeof (etherbuf), "%6D", ap, ":"); return (etherbuf); } /* * Perform common duties while attaching to interface list */ void ether_ifattach(struct ifnet *ifp, const u_int8_t *lla) { int i; struct ifaddr *ifa; struct sockaddr_dl *sdl; ifp->if_addrlen = ETHER_ADDR_LEN; ifp->if_hdrlen = ETHER_HDR_LEN; if_attach(ifp); ifp->if_mtu = ETHERMTU; ifp->if_output = ether_output; ifp->if_input = ether_input; ifp->if_resolvemulti = ether_resolvemulti; if (ifp->if_baudrate == 0) ifp->if_baudrate = IF_Mbps(10); /* just a default */ ifp->if_broadcastaddr = etherbroadcastaddr; ifa = ifp->if_addr; KASSERT(ifa != NULL, ("%s: no lladdr!\n", __func__)); sdl = (struct sockaddr_dl *)ifa->ifa_addr; sdl->sdl_type = IFT_ETHER; sdl->sdl_alen = ifp->if_addrlen; bcopy(lla, LLADDR(sdl), ifp->if_addrlen); bpfattach(ifp, DLT_EN10MB, ETHER_HDR_LEN); if (ng_ether_attach_p != NULL) (*ng_ether_attach_p)(ifp); /* Announce Ethernet MAC address if non-zero. */ for (i = 0; i < ifp->if_addrlen; i++) if (lla[i] != 0) break; if (i != ifp->if_addrlen) if_printf(ifp, "Ethernet address: %6D\n", lla, ":"); - if (debug_mpsafenet && (ifp->if_flags & IFF_NEEDSGIANT) != 0) + if (ifp->if_flags & IFF_NEEDSGIANT) if_printf(ifp, "if_start running deferred for Giant\n"); } /* * Perform common duties while detaching an Ethernet interface */ void ether_ifdetach(struct ifnet *ifp) { if (IFP2AC(ifp)->ac_netgraph != NULL) { KASSERT(ng_ether_detach_p != NULL, ("ng_ether_detach_p is NULL")); (*ng_ether_detach_p)(ifp); } bpfdetach(ifp); if_detach(ifp); } SYSCTL_DECL(_net_link); SYSCTL_NODE(_net_link, IFT_ETHER, ether, CTLFLAG_RW, 0, "Ethernet"); #if defined(INET) || defined(INET6) SYSCTL_INT(_net_link_ether, OID_AUTO, ipfw, CTLFLAG_RW, ðer_ipfw,0,"Pass ether pkts through firewall"); #endif #if 0 /* * This is for reference. We have a table-driven version * of the little-endian crc32 generator, which is faster * than the double-loop. */ uint32_t ether_crc32_le(const uint8_t *buf, size_t len) { size_t i; uint32_t crc; int bit; uint8_t data; crc = 0xffffffff; /* initial value */ for (i = 0; i < len; i++) { for (data = *buf++, bit = 0; bit < 8; bit++, data >>= 1) carry = (crc ^ data) & 1; crc >>= 1; if (carry) crc = (crc ^ ETHER_CRC_POLY_LE); } return (crc); } #else uint32_t ether_crc32_le(const uint8_t *buf, size_t len) { static const uint32_t crctab[] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; size_t i; uint32_t crc; crc = 0xffffffff; /* initial value */ for (i = 0; i < len; i++) { crc ^= buf[i]; crc = (crc >> 4) ^ crctab[crc & 0xf]; crc = (crc >> 4) ^ crctab[crc & 0xf]; } return (crc); } #endif uint32_t ether_crc32_be(const uint8_t *buf, size_t len) { size_t i; uint32_t crc, carry; int bit; uint8_t data; crc = 0xffffffff; /* initial value */ for (i = 0; i < len; i++) { for (data = *buf++, bit = 0; bit < 8; bit++, data >>= 1) { carry = ((crc & 0x80000000) ? 1 : 0) ^ (data & 0x01); crc <<= 1; if (carry) crc = (crc ^ ETHER_CRC_POLY_BE) | carry; } } return (crc); } int ether_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct ifaddr *ifa = (struct ifaddr *) data; struct ifreq *ifr = (struct ifreq *) data; int error = 0; switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: ifp->if_init(ifp->if_softc); /* before arpwhohas */ arp_ifinit(ifp, ifa); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) IF_LLADDR(ifp); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) IF_LLADDR(ifp), ETHER_ADDR_LEN); } /* * Set new address */ ifp->if_init(ifp->if_softc); break; } #endif default: ifp->if_init(ifp->if_softc); break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *) & ifr->ifr_data; bcopy(IF_LLADDR(ifp), (caddr_t) sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else { ifp->if_mtu = ifr->ifr_mtu; } break; default: error = EINVAL; /* XXX netbsd has ENOTTY??? */ break; } return (error); } static int ether_resolvemulti(struct ifnet *ifp, struct sockaddr **llsa, struct sockaddr *sa) { struct sockaddr_dl *sdl; #ifdef INET struct sockaddr_in *sin; #endif #ifdef INET6 struct sockaddr_in6 *sin6; #endif u_char *e_addr; switch(sa->sa_family) { case AF_LINK: /* * No mapping needed. Just check that it's a valid MC address. */ sdl = (struct sockaddr_dl *)sa; e_addr = LLADDR(sdl); if (!ETHER_IS_MULTICAST(e_addr)) return EADDRNOTAVAIL; *llsa = 0; return 0; #ifdef INET case AF_INET: sin = (struct sockaddr_in *)sa; if (!IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) return EADDRNOTAVAIL; MALLOC(sdl, struct sockaddr_dl *, sizeof *sdl, M_IFMADDR, M_NOWAIT|M_ZERO); if (sdl == NULL) return ENOMEM; sdl->sdl_len = sizeof *sdl; sdl->sdl_family = AF_LINK; sdl->sdl_index = ifp->if_index; sdl->sdl_type = IFT_ETHER; sdl->sdl_alen = ETHER_ADDR_LEN; e_addr = LLADDR(sdl); ETHER_MAP_IP_MULTICAST(&sin->sin_addr, e_addr); *llsa = (struct sockaddr *)sdl; return 0; #endif #ifdef INET6 case AF_INET6: sin6 = (struct sockaddr_in6 *)sa; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* * An IP6 address of 0 means listen to all * of the Ethernet multicast address used for IP6. * (This is used for multicast routers.) */ ifp->if_flags |= IFF_ALLMULTI; *llsa = 0; return 0; } if (!IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) return EADDRNOTAVAIL; MALLOC(sdl, struct sockaddr_dl *, sizeof *sdl, M_IFMADDR, M_NOWAIT|M_ZERO); if (sdl == NULL) return (ENOMEM); sdl->sdl_len = sizeof *sdl; sdl->sdl_family = AF_LINK; sdl->sdl_index = ifp->if_index; sdl->sdl_type = IFT_ETHER; sdl->sdl_alen = ETHER_ADDR_LEN; e_addr = LLADDR(sdl); ETHER_MAP_IPV6_MULTICAST(&sin6->sin6_addr, e_addr); *llsa = (struct sockaddr *)sdl; return 0; #endif default: /* * Well, the text isn't quite right, but it's the name * that counts... */ return EAFNOSUPPORT; } } static void* ether_alloc(u_char type, struct ifnet *ifp) { struct arpcom *ac; ac = malloc(sizeof(struct arpcom), M_ARPCOM, M_WAITOK | M_ZERO); ac->ac_ifp = ifp; return (ac); } static void ether_free(void *com, u_char type) { free(com, M_ARPCOM); } static int ether_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: if_register_com_alloc(IFT_ETHER, ether_alloc, ether_free); break; case MOD_UNLOAD: if_deregister_com_alloc(IFT_ETHER); break; default: return EOPNOTSUPP; } return (0); } static moduledata_t ether_mod = { "ether", ether_modevent, 0 }; void ether_vlan_mtap(struct bpf_if *bp, struct mbuf *m, void *data, u_int dlen) { struct ether_vlan_header vlan; struct mbuf mv, mb; KASSERT((m->m_flags & M_VLANTAG) != 0, ("%s: vlan information not present", __func__)); KASSERT(m->m_len >= sizeof(struct ether_header), ("%s: mbuf not large enough for header", __func__)); bcopy(mtod(m, char *), &vlan, sizeof(struct ether_header)); vlan.evl_proto = vlan.evl_encap_proto; vlan.evl_encap_proto = htons(ETHERTYPE_VLAN); vlan.evl_tag = htons(m->m_pkthdr.ether_vtag); m->m_len -= sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); /* * If a data link has been supplied by the caller, then we will need to * re-create a stack allocated mbuf chain with the following structure: * * (1) mbuf #1 will contain the supplied data link * (2) mbuf #2 will contain the vlan header * (3) mbuf #3 will contain the original mbuf's packet data * * Otherwise, submit the packet and vlan header via bpf_mtap2(). */ if (data != NULL) { mv.m_next = m; mv.m_data = (caddr_t)&vlan; mv.m_len = sizeof(vlan); mb.m_next = &mv; mb.m_data = data; mb.m_len = dlen; bpf_mtap(bp, &mb); } else bpf_mtap2(bp, &vlan, sizeof(vlan), m); m->m_len += sizeof(struct ether_header); m->m_data -= sizeof(struct ether_header); } DECLARE_MODULE(ether, ether_mod, SI_SUB_INIT_IF, SI_ORDER_ANY); MODULE_VERSION(ether, 1); Index: head/sys/net/netisr.c =================================================================== --- head/sys/net/netisr.c (revision 171612) +++ head/sys/net/netisr.c (revision 171613) @@ -1,362 +1,270 @@ /*- * Copyright (c) 2001,2002,2003 Jonathan Lemon * Copyright (c) 1997, Stefan Esser * 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. * * $FreeBSD$ */ #include "opt_device_polling.h" #include "opt_net.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -/* - * debug_mpsafenet controls network subsystem-wide use of the Giant lock, - * from system calls down to interrupt handlers. It can be changed only via - * a tunable at boot, not at run-time, due to the complexity of unwinding. - * The compiled default is set via a kernel option; right now, the default - * unless otherwise specified is to run the network stack without Giant. - */ -#ifdef NET_WITH_GIANT -int debug_mpsafenet = 0; -#else -int debug_mpsafenet = 1; -#endif -int debug_mpsafenet_toolatetotwiddle = 0; - -TUNABLE_INT("debug.mpsafenet", &debug_mpsafenet); -SYSCTL_INT(_debug, OID_AUTO, mpsafenet, CTLFLAG_RD, &debug_mpsafenet, 0, - "Enable/disable MPSAFE network support"); - volatile unsigned int netisr; /* scheduling bits for network */ struct netisr { netisr_t *ni_handler; struct ifqueue *ni_queue; int ni_flags; } netisrs[32]; static void *net_ih; -/* - * Not all network code is currently capable of running MPSAFE; however, - * most of it is. Since those sections that are not are generally optional - * components not shipped with default kernels, we provide a basic way to - * determine whether MPSAFE operation is permitted: based on a default of - * yes, we permit non-MPSAFE components to use a registration call to - * identify that they require Giant. If the system is early in the boot - * process still, then we change the debug_mpsafenet setting to choose a - * non-MPSAFE execution mode (degraded). If it's too late for that (since - * the setting cannot be changed at run time), we generate a console warning - * that the configuration may be unsafe. - */ -static int mpsafe_warn_count; - -/* - * Function call implementing registration of a non-MPSAFE network component. - */ void -net_warn_not_mpsafe(const char *component) -{ - - /* - * If we're running with Giant over the network stack, there is no - * problem. - */ - if (!debug_mpsafenet) - return; - - /* - * If it's not too late to change the MPSAFE setting for the network - * stack, do so now. This effectively suppresses warnings by - * components registering later. - */ - if (!debug_mpsafenet_toolatetotwiddle) { - debug_mpsafenet = 0; - printf("WARNING: debug.mpsafenet forced to 0 as %s requires " - "Giant\n", component); - return; - } - - /* - * We must run without Giant, so generate a console warning with some - * information with what to do about it. The system may be operating - * unsafely, however. - */ - printf("WARNING: Network stack Giant-free, but %s requires Giant.\n", - component); - if (mpsafe_warn_count == 0) - printf(" Consider adding 'options NET_WITH_GIANT' or " - "setting debug.mpsafenet=0\n"); - mpsafe_warn_count++; -} - -/* - * This sysinit is run after any pre-loaded or compiled-in components have - * announced that they require Giant, but before any modules loaded at - * run-time. - */ -static void -net_mpsafe_toolate(void *arg) -{ - - debug_mpsafenet_toolatetotwiddle = 1; - - if (!debug_mpsafenet) - printf("WARNING: MPSAFE network stack disabled, expect " - "reduced performance.\n"); -} - -SYSINIT(net_mpsafe_toolate, SI_SUB_SETTINGS, SI_ORDER_ANY, net_mpsafe_toolate, - NULL); - -void legacy_setsoftnet(void) { swi_sched(net_ih, 0); } void netisr_register(int num, netisr_t *handler, struct ifqueue *inq, int flags) { KASSERT(!(num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs))), ("bad isr %d", num)); netisrs[num].ni_handler = handler; netisrs[num].ni_queue = inq; - if ((flags & NETISR_MPSAFE) && !debug_mpsafenet) - flags &= ~NETISR_MPSAFE; netisrs[num].ni_flags = flags; } void netisr_unregister(int num) { struct netisr *ni; KASSERT(!(num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs))), ("bad isr %d", num)); ni = &netisrs[num]; ni->ni_handler = NULL; if (ni->ni_queue != NULL) IF_DRAIN(ni->ni_queue); ni->ni_queue = NULL; } struct isrstat { int isrs_count; /* dispatch count */ int isrs_directed; /* ...directly dispatched */ int isrs_deferred; /* ...queued instead */ int isrs_queued; /* intentionally queueued */ int isrs_drop; /* dropped 'cuz no handler */ int isrs_swi_count; /* swi_net handlers called */ }; static struct isrstat isrstat; SYSCTL_NODE(_net, OID_AUTO, isr, CTLFLAG_RW, 0, "netisr counters"); static int netisr_direct = 1; SYSCTL_INT(_net_isr, OID_AUTO, direct, CTLFLAG_RW, &netisr_direct, 0, "enable direct dispatch"); TUNABLE_INT("net.isr.direct", &netisr_direct); SYSCTL_INT(_net_isr, OID_AUTO, count, CTLFLAG_RD, &isrstat.isrs_count, 0, ""); SYSCTL_INT(_net_isr, OID_AUTO, directed, CTLFLAG_RD, &isrstat.isrs_directed, 0, ""); SYSCTL_INT(_net_isr, OID_AUTO, deferred, CTLFLAG_RD, &isrstat.isrs_deferred, 0, ""); SYSCTL_INT(_net_isr, OID_AUTO, queued, CTLFLAG_RD, &isrstat.isrs_queued, 0, ""); SYSCTL_INT(_net_isr, OID_AUTO, drop, CTLFLAG_RD, &isrstat.isrs_drop, 0, ""); SYSCTL_INT(_net_isr, OID_AUTO, swi_count, CTLFLAG_RD, &isrstat.isrs_swi_count, 0, ""); /* * Process all packets currently present in a netisr queue. Used to * drain an existing set of packets waiting for processing when we * begin direct dispatch, to avoid processing packets out of order. */ static void netisr_processqueue(struct netisr *ni) { struct mbuf *m; for (;;) { IF_DEQUEUE(ni->ni_queue, m); if (m == NULL) break; ni->ni_handler(m); } } /* * Call the netisr directly instead of queueing the packet, if possible. */ void netisr_dispatch(int num, struct mbuf *m) { struct netisr *ni; isrstat.isrs_count++; /* XXX redundant */ KASSERT(!(num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs))), ("bad isr %d", num)); ni = &netisrs[num]; if (ni->ni_queue == NULL) { isrstat.isrs_drop++; m_freem(m); return; } /* * Do direct dispatch only for MPSAFE netisrs (and * only when enabled). Note that when a netisr is * marked MPSAFE we permit multiple concurrent instances * to run. We guarantee only the order in which * packets are processed for each "dispatch point" in * the system (i.e. call to netisr_dispatch or * netisr_queue). This insures ordering of packets * from an interface but does not guarantee ordering * between multiple places in the system (e.g. IP * dispatched from interfaces vs. IP queued from IPSec). */ if (netisr_direct && (ni->ni_flags & NETISR_MPSAFE)) { isrstat.isrs_directed++; /* * NB: We used to drain the queue before handling * the packet but now do not. Doing so here will * not preserve ordering so instead we fallback to * guaranteeing order only from dispatch points * in the system (see above). */ ni->ni_handler(m); } else { isrstat.isrs_deferred++; if (IF_HANDOFF(ni->ni_queue, m, NULL)) schednetisr(num); } } /* * Same as above, but always queue. * This is either used in places where we are not confident that * direct dispatch is possible, or where queueing is required. * It returns (0) on success and ERRNO on failure. On failure the * mbuf has been free'd. */ int netisr_queue(int num, struct mbuf *m) { struct netisr *ni; KASSERT(!(num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs))), ("bad isr %d", num)); ni = &netisrs[num]; if (ni->ni_queue == NULL) { isrstat.isrs_drop++; m_freem(m); return (ENXIO); } isrstat.isrs_queued++; if (!IF_HANDOFF(ni->ni_queue, m, NULL)) return (ENOBUFS); /* IF_HANDOFF has free'd the mbuf */ schednetisr(num); return (0); } static void swi_net(void *dummy) { struct netisr *ni; u_int bits; int i; #ifdef DEVICE_POLLING const int polling = 1; #else const int polling = 0; #endif do { bits = atomic_readandclear_int(&netisr); if (bits == 0) break; while ((i = ffs(bits)) != 0) { isrstat.isrs_swi_count++; i--; bits &= ~(1 << i); ni = &netisrs[i]; if (ni->ni_handler == NULL) { printf("swi_net: unregistered isr %d.\n", i); continue; } if ((ni->ni_flags & NETISR_MPSAFE) == 0) { mtx_lock(&Giant); if (ni->ni_queue == NULL) ni->ni_handler(NULL); else netisr_processqueue(ni); mtx_unlock(&Giant); } else { if (ni->ni_queue == NULL) ni->ni_handler(NULL); else netisr_processqueue(ni); } } } while (polling); } static void start_netisr(void *dummy) { if (swi_add(NULL, "net", swi_net, NULL, SWI_NET, INTR_MPSAFE, &net_ih)) panic("start_netisr"); } SYSINIT(start_netisr, SI_SUB_SOFTINTR, SI_ORDER_FIRST, start_netisr, NULL) Index: head/sys/nfsserver/nfs_srvsubs.c =================================================================== --- head/sys/nfsserver/nfs_srvsubs.c (revision 171612) +++ head/sys/nfsserver/nfs_srvsubs.c (revision 171613) @@ -1,1494 +1,1491 @@ /*- * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)nfs_subs.c 8.8 (Berkeley) 5/22/95 */ #include __FBSDID("$FreeBSD$"); /* * These functions support the macros and help fiddle mbuf chains for * the nfs op functions. They do things like create the rpc header and * copy data between mbuf chains and uio lists. */ #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Data items converted to xdr at startup, since they are constant * This is kinda hokey, but may save a little time doing byte swaps */ u_int32_t nfsrv_nfs_xdrneg1; u_int32_t nfsrv_rpc_call, nfsrv_rpc_vers, nfsrv_rpc_reply, nfsrv_rpc_msgdenied, nfsrv_rpc_autherr, nfsrv_rpc_mismatch, nfsrv_rpc_auth_unix, nfsrv_rpc_msgaccepted; u_int32_t nfsrv_nfs_prog, nfsrv_nfs_true, nfsrv_nfs_false; /* And other global data */ static const nfstype nfsv2_type[9] = { NFNON, NFREG, NFDIR, NFBLK, NFCHR, NFLNK, NFNON, NFCHR, NFNON }; #define vtonfsv2_type(a) txdr_unsigned(nfsv2_type[((int32_t)(a))]) #define vtonfsv3_mode(m) txdr_unsigned((m) & ALLPERMS) int nfsrv_ticks; struct nfssvc_sockhead nfssvc_sockhead; int nfssvc_sockhead_flag; struct nfsd_head nfsd_head; int nfsd_head_flag; static int nfssvc_offset = SYS_nfssvc; static struct sysent nfssvc_prev_sysent; MAKE_SYSENT(nfssvc); struct mtx nfsd_mtx; /* * Mapping of old NFS Version 2 RPC numbers to generic numbers. */ const int nfsrv_nfsv3_procid[NFS_NPROCS] = { NFSPROC_NULL, NFSPROC_GETATTR, NFSPROC_SETATTR, NFSPROC_NOOP, NFSPROC_LOOKUP, NFSPROC_READLINK, NFSPROC_READ, NFSPROC_NOOP, NFSPROC_WRITE, NFSPROC_CREATE, NFSPROC_REMOVE, NFSPROC_RENAME, NFSPROC_LINK, NFSPROC_SYMLINK, NFSPROC_MKDIR, NFSPROC_RMDIR, NFSPROC_READDIR, NFSPROC_FSSTAT, NFSPROC_NOOP, NFSPROC_NOOP, NFSPROC_NOOP, NFSPROC_NOOP, NFSPROC_NOOP, }; /* * and the reverse mapping from generic to Version 2 procedure numbers */ const int nfsrvv2_procid[NFS_NPROCS] = { NFSV2PROC_NULL, NFSV2PROC_GETATTR, NFSV2PROC_SETATTR, NFSV2PROC_LOOKUP, NFSV2PROC_NOOP, NFSV2PROC_READLINK, NFSV2PROC_READ, NFSV2PROC_WRITE, NFSV2PROC_CREATE, NFSV2PROC_MKDIR, NFSV2PROC_SYMLINK, NFSV2PROC_CREATE, NFSV2PROC_REMOVE, NFSV2PROC_RMDIR, NFSV2PROC_RENAME, NFSV2PROC_LINK, NFSV2PROC_READDIR, NFSV2PROC_NOOP, NFSV2PROC_STATFS, NFSV2PROC_NOOP, NFSV2PROC_NOOP, NFSV2PROC_NOOP, NFSV2PROC_NOOP, }; /* * Maps errno values to nfs error numbers. * Use 0 (which gets converted to NFSERR_IO) as the catch all for ones not * specifically defined in RFC 1094. */ static const u_char nfsrv_v2errmap[ELAST] = { NFSERR_PERM, NFSERR_NOENT, 0, 0, 0, NFSERR_NXIO, 0, 0, 0, 0, 0, 0, NFSERR_ACCES, 0, 0, 0, NFSERR_EXIST, 0, NFSERR_NODEV, NFSERR_NOTDIR, NFSERR_ISDIR, 0, 0, 0, 0, 0, NFSERR_FBIG, NFSERR_NOSPC, 0, NFSERR_ROFS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NFSERR_NAMETOL, 0, 0, NFSERR_NOTEMPTY, 0, 0, NFSERR_DQUOT, NFSERR_STALE, 0 }; /* * Maps errno values to nfs error numbers. * Although it is not obvious whether or not NFS clients really care if * a returned error value is in the specified list for the procedure, the * safest thing to do is filter them appropriately. For Version 2, the * X/Open XNFS document is the only specification that defines error values * for each RPC (The RFC simply lists all possible error values for all RPCs), * so I have decided to not do this for Version 2. * The first entry is the default error return and the rest are the valid * errors for that RPC in increasing numeric order. */ static const short nfsv3err_null[] = { 0, 0, }; static const short nfsv3err_getattr[] = { NFSERR_IO, NFSERR_IO, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_setattr[] = { NFSERR_IO, NFSERR_PERM, NFSERR_IO, NFSERR_ACCES, NFSERR_INVAL, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOT_SYNC, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_lookup[] = { NFSERR_IO, NFSERR_NOENT, NFSERR_IO, NFSERR_ACCES, NFSERR_NOTDIR, NFSERR_NAMETOL, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_access[] = { NFSERR_IO, NFSERR_IO, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_readlink[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_INVAL, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_read[] = { NFSERR_IO, NFSERR_IO, NFSERR_NXIO, NFSERR_ACCES, NFSERR_INVAL, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_write[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_INVAL, NFSERR_FBIG, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_create[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_NOTDIR, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_mkdir[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_NOTDIR, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_symlink[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_NOTDIR, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_mknod[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_NOTDIR, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, NFSERR_BADTYPE, 0, }; static const short nfsv3err_remove[] = { NFSERR_IO, NFSERR_NOENT, NFSERR_IO, NFSERR_ACCES, NFSERR_NOTDIR, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_rmdir[] = { NFSERR_IO, NFSERR_NOENT, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_NOTDIR, NFSERR_INVAL, NFSERR_ROFS, NFSERR_NAMETOL, NFSERR_NOTEMPTY, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_rename[] = { NFSERR_IO, NFSERR_NOENT, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_XDEV, NFSERR_NOTDIR, NFSERR_ISDIR, NFSERR_INVAL, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_MLINK, NFSERR_NAMETOL, NFSERR_NOTEMPTY, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_link[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_EXIST, NFSERR_XDEV, NFSERR_NOTDIR, NFSERR_INVAL, NFSERR_NOSPC, NFSERR_ROFS, NFSERR_MLINK, NFSERR_NAMETOL, NFSERR_DQUOT, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_NOTSUPP, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_readdir[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_NOTDIR, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_BAD_COOKIE, NFSERR_TOOSMALL, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_readdirplus[] = { NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_NOTDIR, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_BAD_COOKIE, NFSERR_NOTSUPP, NFSERR_TOOSMALL, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_fsstat[] = { NFSERR_IO, NFSERR_IO, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_fsinfo[] = { NFSERR_STALE, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_pathconf[] = { NFSERR_STALE, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short nfsv3err_commit[] = { NFSERR_IO, NFSERR_IO, NFSERR_STALE, NFSERR_BADHANDLE, NFSERR_SERVERFAULT, 0, }; static const short *nfsrv_v3errmap[] = { nfsv3err_null, nfsv3err_getattr, nfsv3err_setattr, nfsv3err_lookup, nfsv3err_access, nfsv3err_readlink, nfsv3err_read, nfsv3err_write, nfsv3err_create, nfsv3err_mkdir, nfsv3err_symlink, nfsv3err_mknod, nfsv3err_remove, nfsv3err_rmdir, nfsv3err_rename, nfsv3err_link, nfsv3err_readdir, nfsv3err_readdirplus, nfsv3err_fsstat, nfsv3err_fsinfo, nfsv3err_pathconf, nfsv3err_commit, }; /* * Called once to initialize data structures... */ static int nfsrv_modevent(module_t mod, int type, void *data) { static int registered; int error = 0; NET_LOCK_GIANT(); switch (type) { case MOD_LOAD: mtx_init(&nfsd_mtx, "nfsd_mtx", NULL, MTX_DEF); nfsrv_rpc_vers = txdr_unsigned(RPC_VER2); nfsrv_rpc_call = txdr_unsigned(RPC_CALL); nfsrv_rpc_reply = txdr_unsigned(RPC_REPLY); nfsrv_rpc_msgdenied = txdr_unsigned(RPC_MSGDENIED); nfsrv_rpc_msgaccepted = txdr_unsigned(RPC_MSGACCEPTED); nfsrv_rpc_mismatch = txdr_unsigned(RPC_MISMATCH); nfsrv_rpc_autherr = txdr_unsigned(RPC_AUTHERR); nfsrv_rpc_auth_unix = txdr_unsigned(RPCAUTH_UNIX); nfsrv_nfs_prog = txdr_unsigned(NFS_PROG); nfsrv_nfs_true = txdr_unsigned(TRUE); nfsrv_nfs_false = txdr_unsigned(FALSE); nfsrv_nfs_xdrneg1 = txdr_unsigned(-1); nfsrv_ticks = (hz * NFS_TICKINTVL + 500) / 1000; if (nfsrv_ticks < 1) nfsrv_ticks = 1; nfsrv_initcache(); /* Init the server request cache */ NFSD_LOCK(); nfsrv_init(0); /* Init server data structures */ - if (debug_mpsafenet) - callout_init(&nfsrv_callout, CALLOUT_MPSAFE); - else - callout_init(&nfsrv_callout, 0); + callout_init(&nfsrv_callout, CALLOUT_MPSAFE); NFSD_UNLOCK(); nfsrv_timer(0); error = syscall_register(&nfssvc_offset, &nfssvc_sysent, &nfssvc_prev_sysent); if (error) break; registered = 1; break; case MOD_UNLOAD: if (nfsrv_numnfsd != 0) { error = EBUSY; break; } if (registered) syscall_deregister(&nfssvc_offset, &nfssvc_prev_sysent); callout_drain(&nfsrv_callout); nfsrv_destroycache(); /* Free the server request cache */ nfsrv_destroycache(); /* Free the server request cache */ mtx_destroy(&nfsd_mtx); break; default: error = EOPNOTSUPP; break; } NET_UNLOCK_GIANT(); return error; } static moduledata_t nfsserver_mod = { "nfsserver", nfsrv_modevent, NULL, }; DECLARE_MODULE(nfsserver, nfsserver_mod, SI_SUB_VFS, SI_ORDER_ANY); /* So that loader and kldload(2) can find us, wherever we are.. */ MODULE_VERSION(nfsserver, 1); /* * Set up nameidata for a lookup() call and do it. * * If pubflag is set, this call is done for a lookup operation on the * public filehandle. In that case we allow crossing mountpoints and * absolute pathnames. However, the caller is expected to check that * the lookup result is within the public fs, and deny access if * it is not. * * nfs_namei() clears out garbage fields that namei() might leave garbage. * This is mainly ni_vp and ni_dvp when an error occurs, and ni_dvp when no * error occurs but the parent was not requested. * * dirp may be set whether an error is returned or not, and must be * released by the caller. */ int nfs_namei(struct nameidata *ndp, fhandle_t *fhp, int len, struct nfssvc_sock *slp, struct sockaddr *nam, struct mbuf **mdp, caddr_t *dposp, struct vnode **retdirp, int v3, struct vattr *retdirattrp, int *retdirattr_retp, struct thread *td, int pubflag) { int i, rem; struct mbuf *md; char *fromcp, *tocp, *cp; struct iovec aiov; struct uio auio; struct vnode *dp; int error, rdonly, linklen; struct componentname *cnp = &ndp->ni_cnd; int lockleaf = (cnp->cn_flags & LOCKLEAF) != 0; int dvfslocked; int vfslocked; vfslocked = 0; dvfslocked = 0; *retdirp = NULL; cnp->cn_flags |= NOMACCHECK; cnp->cn_pnbuf = uma_zalloc(namei_zone, M_WAITOK); /* * Copy the name from the mbuf list to ndp->ni_pnbuf * and set the various ndp fields appropriately. */ fromcp = *dposp; tocp = cnp->cn_pnbuf; md = *mdp; rem = mtod(md, caddr_t) + md->m_len - fromcp; for (i = 0; i < len; i++) { while (rem == 0) { md = md->m_next; if (md == NULL) { error = EBADRPC; goto out; } fromcp = mtod(md, caddr_t); rem = md->m_len; } if (*fromcp == '\0' || (!pubflag && *fromcp == '/')) { error = EACCES; goto out; } *tocp++ = *fromcp++; rem--; } *tocp = '\0'; *mdp = md; *dposp = fromcp; len = nfsm_rndup(len)-len; if (len > 0) { if (rem >= len) *dposp += len; else if ((error = nfs_adv(mdp, dposp, len, rem)) != 0) goto out; } /* * Extract and set starting directory. */ error = nfsrv_fhtovp(fhp, FALSE, &dp, &dvfslocked, ndp->ni_cnd.cn_cred, slp, nam, &rdonly, pubflag); if (error) goto out; vfslocked = VFS_LOCK_GIANT(dp->v_mount); if (dp->v_type != VDIR) { vrele(dp); error = ENOTDIR; goto out; } if (rdonly) cnp->cn_flags |= RDONLY; /* * Set return directory. Reference to dp is implicitly transfered * to the returned pointer */ *retdirp = dp; if (v3) { vn_lock(dp, LK_EXCLUSIVE | LK_RETRY, td); *retdirattr_retp = VOP_GETATTR(dp, retdirattrp, ndp->ni_cnd.cn_cred, td); VOP_UNLOCK(dp, 0, td); } if (pubflag) { /* * Oh joy. For WebNFS, handle those pesky '%' escapes, * and the 'native path' indicator. */ cp = uma_zalloc(namei_zone, M_WAITOK); fromcp = cnp->cn_pnbuf; tocp = cp; if ((unsigned char)*fromcp >= WEBNFS_SPECCHAR_START) { switch ((unsigned char)*fromcp) { case WEBNFS_NATIVE_CHAR: /* * 'Native' path for us is the same * as a path according to the NFS spec, * just skip the escape char. */ fromcp++; break; /* * More may be added in the future, range 0x80-0xff */ default: error = EIO; uma_zfree(namei_zone, cp); goto out; } } /* * Translate the '%' escapes, URL-style. */ while (*fromcp != '\0') { if (*fromcp == WEBNFS_ESC_CHAR) { if (fromcp[1] != '\0' && fromcp[2] != '\0') { fromcp++; *tocp++ = HEXSTRTOI(fromcp); fromcp += 2; continue; } else { error = ENOENT; uma_zfree(namei_zone, cp); goto out; } } else *tocp++ = *fromcp++; } *tocp = '\0'; uma_zfree(namei_zone, cnp->cn_pnbuf); cnp->cn_pnbuf = cp; } ndp->ni_pathlen = (tocp - cnp->cn_pnbuf) + 1; ndp->ni_segflg = UIO_SYSSPACE; if (pubflag) { ndp->ni_rootdir = rootvnode; ndp->ni_loopcnt = 0; if (cnp->cn_pnbuf[0] == '/') { int tvfslocked; tvfslocked = VFS_LOCK_GIANT(rootvnode->v_mount); VFS_UNLOCK_GIANT(vfslocked); dp = rootvnode; vfslocked = tvfslocked; } } else { cnp->cn_flags |= NOCROSSMOUNT; } /* * Initialize for scan, set ni_startdir and bump ref on dp again * because lookup() will dereference ni_startdir. */ cnp->cn_thread = td; VREF(dp); ndp->ni_startdir = dp; if (!lockleaf) cnp->cn_flags |= LOCKLEAF; for (;;) { cnp->cn_nameptr = cnp->cn_pnbuf; /* * Call lookup() to do the real work. If an error occurs, * ndp->ni_vp and ni_dvp are left uninitialized or NULL and * we do not have to dereference anything before returning. * In either case ni_startdir will be dereferenced and NULLed * out. */ if (vfslocked) ndp->ni_cnd.cn_flags |= GIANTHELD; error = lookup(ndp); vfslocked = (ndp->ni_cnd.cn_flags & GIANTHELD) != 0; ndp->ni_cnd.cn_flags &= ~GIANTHELD; if (error) break; /* * Check for encountering a symbolic link. Trivial * termination occurs if no symlink encountered. * Note: zfree is safe because error is 0, so we will * not zfree it again when we break. */ if ((cnp->cn_flags & ISSYMLINK) == 0) { if (cnp->cn_flags & (SAVENAME | SAVESTART)) cnp->cn_flags |= HASBUF; else uma_zfree(namei_zone, cnp->cn_pnbuf); if (ndp->ni_vp && !lockleaf) VOP_UNLOCK(ndp->ni_vp, 0, td); break; } /* * Validate symlink */ if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1) VOP_UNLOCK(ndp->ni_dvp, 0, td); if (!pubflag) { error = EINVAL; goto badlink2; } if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { error = ELOOP; goto badlink2; } if (ndp->ni_pathlen > 1) cp = uma_zalloc(namei_zone, M_WAITOK); else cp = cnp->cn_pnbuf; aiov.iov_base = cp; aiov.iov_len = MAXPATHLEN; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_offset = 0; auio.uio_rw = UIO_READ; auio.uio_segflg = UIO_SYSSPACE; auio.uio_td = NULL; auio.uio_resid = MAXPATHLEN; error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred); if (error) { badlink1: if (ndp->ni_pathlen > 1) uma_zfree(namei_zone, cp); badlink2: vput(ndp->ni_vp); vrele(ndp->ni_dvp); break; } linklen = MAXPATHLEN - auio.uio_resid; if (linklen == 0) { error = ENOENT; goto badlink1; } if (linklen + ndp->ni_pathlen >= MAXPATHLEN) { error = ENAMETOOLONG; goto badlink1; } /* * Adjust or replace path */ if (ndp->ni_pathlen > 1) { bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen); uma_zfree(namei_zone, cnp->cn_pnbuf); cnp->cn_pnbuf = cp; } else cnp->cn_pnbuf[linklen] = '\0'; ndp->ni_pathlen += linklen; /* * Cleanup refs for next loop and check if root directory * should replace current directory. Normally ni_dvp * becomes the new base directory and is cleaned up when * we loop. Explicitly null pointers after invalidation * to clarify operation. */ vput(ndp->ni_vp); ndp->ni_vp = NULL; if (cnp->cn_pnbuf[0] == '/') { vrele(ndp->ni_dvp); ndp->ni_dvp = ndp->ni_rootdir; VREF(ndp->ni_dvp); } ndp->ni_startdir = ndp->ni_dvp; ndp->ni_dvp = NULL; } if (!lockleaf) cnp->cn_flags &= ~LOCKLEAF; if (cnp->cn_flags & GIANTHELD) { mtx_unlock(&Giant); cnp->cn_flags &= ~GIANTHELD; } /* * nfs_namei() guarentees that fields will not contain garbage * whether an error occurs or not. This allows the caller to track * cleanup state trivially. */ out: if (error) { uma_zfree(namei_zone, cnp->cn_pnbuf); ndp->ni_vp = NULL; ndp->ni_dvp = NULL; ndp->ni_startdir = NULL; cnp->cn_flags &= ~HASBUF; VFS_UNLOCK_GIANT(vfslocked); vfslocked = 0; } else if ((ndp->ni_cnd.cn_flags & (WANTPARENT|LOCKPARENT)) == 0) { ndp->ni_dvp = NULL; } /* * This differs from normal namei() in that even on failure we may * return with Giant held due to the dirp return. Make sure we only * have not recursed however. The calling code only expects to drop * one acquire. */ if (vfslocked || dvfslocked) ndp->ni_cnd.cn_flags |= GIANTHELD; if (vfslocked && dvfslocked) VFS_UNLOCK_GIANT(vfslocked); return (error); } /* * A fiddled version of m_adj() that ensures null fill to a long * boundary and only trims off the back end */ void nfsm_adj(struct mbuf *mp, int len, int nul) { struct mbuf *m; int count, i; char *cp; /* * Trim from tail. Scan the mbuf chain, * calculating its length and finding the last mbuf. * If the adjustment only affects this mbuf, then just * adjust and return. Otherwise, rescan and truncate * after the remaining size. */ count = 0; m = mp; for (;;) { count += m->m_len; if (m->m_next == NULL) break; m = m->m_next; } if (m->m_len > len) { m->m_len -= len; if (nul > 0) { cp = mtod(m, caddr_t)+m->m_len-nul; for (i = 0; i < nul; i++) *cp++ = '\0'; } return; } count -= len; if (count < 0) count = 0; /* * Correct length for chain is "count". * Find the mbuf with last data, adjust its length, * and toss data from remaining mbufs on chain. */ for (m = mp; m; m = m->m_next) { if (m->m_len >= count) { m->m_len = count; if (nul > 0) { cp = mtod(m, caddr_t)+m->m_len-nul; for (i = 0; i < nul; i++) *cp++ = '\0'; } if (m->m_next != NULL) { m_freem(m->m_next); m->m_next = NULL; } break; } count -= m->m_len; } } /* * Make these functions instead of macros, so that the kernel text size * doesn't get too big... */ void nfsm_srvwcc(struct nfsrv_descript *nfsd, int before_ret, struct vattr *before_vap, int after_ret, struct vattr *after_vap, struct mbuf **mbp, char **bposp) { struct mbuf *mb = *mbp; char *bpos = *bposp; u_int32_t *tl; if (before_ret) { tl = nfsm_build(u_int32_t *, NFSX_UNSIGNED); *tl = nfsrv_nfs_false; } else { tl = nfsm_build(u_int32_t *, 7 * NFSX_UNSIGNED); *tl++ = nfsrv_nfs_true; txdr_hyper(before_vap->va_size, tl); tl += 2; txdr_nfsv3time(&(before_vap->va_mtime), tl); tl += 2; txdr_nfsv3time(&(before_vap->va_ctime), tl); } *bposp = bpos; *mbp = mb; nfsm_srvpostopattr(nfsd, after_ret, after_vap, mbp, bposp); } void nfsm_srvpostopattr(struct nfsrv_descript *nfsd, int after_ret, struct vattr *after_vap, struct mbuf **mbp, char **bposp) { struct mbuf *mb = *mbp; char *bpos = *bposp; u_int32_t *tl; struct nfs_fattr *fp; if (after_ret) { tl = nfsm_build(u_int32_t *, NFSX_UNSIGNED); *tl = nfsrv_nfs_false; } else { tl = nfsm_build(u_int32_t *, NFSX_UNSIGNED + NFSX_V3FATTR); *tl++ = nfsrv_nfs_true; fp = (struct nfs_fattr *)tl; nfsm_srvfattr(nfsd, after_vap, fp); } *mbp = mb; *bposp = bpos; } void nfsm_srvfattr(struct nfsrv_descript *nfsd, struct vattr *vap, struct nfs_fattr *fp) { fp->fa_nlink = txdr_unsigned(vap->va_nlink); fp->fa_uid = txdr_unsigned(vap->va_uid); fp->fa_gid = txdr_unsigned(vap->va_gid); if (nfsd->nd_flag & ND_NFSV3) { fp->fa_type = vtonfsv3_type(vap->va_type); fp->fa_mode = vtonfsv3_mode(vap->va_mode); txdr_hyper(vap->va_size, &fp->fa3_size); txdr_hyper(vap->va_bytes, &fp->fa3_used); fp->fa3_rdev.specdata1 = txdr_unsigned(umajor(vap->va_rdev)); fp->fa3_rdev.specdata2 = txdr_unsigned(uminor(vap->va_rdev)); fp->fa3_fsid.nfsuquad[0] = 0; fp->fa3_fsid.nfsuquad[1] = txdr_unsigned(vap->va_fsid); fp->fa3_fileid.nfsuquad[0] = 0; fp->fa3_fileid.nfsuquad[1] = txdr_unsigned(vap->va_fileid); txdr_nfsv3time(&vap->va_atime, &fp->fa3_atime); txdr_nfsv3time(&vap->va_mtime, &fp->fa3_mtime); txdr_nfsv3time(&vap->va_ctime, &fp->fa3_ctime); } else { fp->fa_type = vtonfsv2_type(vap->va_type); fp->fa_mode = vtonfsv2_mode(vap->va_type, vap->va_mode); fp->fa2_size = txdr_unsigned(vap->va_size); fp->fa2_blocksize = txdr_unsigned(vap->va_blocksize); if (vap->va_type == VFIFO) fp->fa2_rdev = 0xffffffff; else fp->fa2_rdev = txdr_unsigned(vap->va_rdev); fp->fa2_blocks = txdr_unsigned(vap->va_bytes / NFS_FABLKSIZE); fp->fa2_fsid = txdr_unsigned(vap->va_fsid); fp->fa2_fileid = txdr_unsigned(vap->va_fileid); txdr_nfsv2time(&vap->va_atime, &fp->fa2_atime); txdr_nfsv2time(&vap->va_mtime, &fp->fa2_mtime); txdr_nfsv2time(&vap->va_ctime, &fp->fa2_ctime); } } /* * nfsrv_fhtovp() - convert a fh to a vnode ptr (optionally locked) * - look up fsid in mount list (if not found ret error) * - get vp and export rights by calling VFS_FHTOVP() * - if cred->cr_uid == 0 or MNT_EXPORTANON set it to credanon * - if not lockflag unlock it with VOP_UNLOCK() */ int nfsrv_fhtovp(fhandle_t *fhp, int lockflag, struct vnode **vpp, int *vfslockedp, struct ucred *cred, struct nfssvc_sock *slp, struct sockaddr *nam, int *rdonlyp, int pubflag) { struct thread *td = curthread; /* XXX */ struct mount *mp; int i; struct ucred *credanon; int error, exflags; #ifdef MNT_EXNORESPORT /* XXX needs mountd and /etc/exports help yet */ struct sockaddr_int *saddr; #endif int vfslocked; *vfslockedp = 0; *vpp = NULL; if (nfs_ispublicfh(fhp)) { if (!pubflag || !nfs_pub.np_valid) return (ESTALE); fhp = &nfs_pub.np_handle; } mp = vfs_getvfs(&fhp->fh_fsid); if (!mp) return (ESTALE); vfslocked = VFS_LOCK_GIANT(mp); error = VFS_CHECKEXP(mp, nam, &exflags, &credanon); if (error) goto out; error = VFS_FHTOVP(mp, &fhp->fh_fid, vpp); if (error) goto out; #ifdef MNT_EXNORESPORT if (!(exflags & (MNT_EXNORESPORT|MNT_EXPUBLIC))) { saddr = (struct sockaddr_in *)nam; if ((saddr->sin_family == AF_INET || saddr->sin_family == AF_INET6) && /* same code for INET and INET6: sin*_port at same offet */ ntohs(saddr->sin_port) >= IPPORT_RESERVED) { vput(*vpp); *vpp = NULL; error = NFSERR_AUTHERR | AUTH_TOOWEAK; } } #endif /* * Check/setup credentials. */ if (cred->cr_uid == 0 || (exflags & MNT_EXPORTANON)) { cred->cr_uid = credanon->cr_uid; for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++) cred->cr_groups[i] = credanon->cr_groups[i]; cred->cr_ngroups = i; } if (exflags & MNT_EXRDONLY) *rdonlyp = 1; else *rdonlyp = 0; if (!lockflag) VOP_UNLOCK(*vpp, 0, td); out: vfs_rel(mp); if (error) { VFS_UNLOCK_GIANT(vfslocked); } else *vfslockedp = vfslocked; return (error); } /* * WebNFS: check if a filehandle is a public filehandle. For v3, this * means a length of 0, for v2 it means all zeroes. nfsm_srvmtofh has * transformed this to all zeroes in both cases, so check for it. */ int nfs_ispublicfh(fhandle_t *fhp) { char *cp = (char *)fhp; int i; NFSD_LOCK_DONTCARE(); for (i = 0; i < NFSX_V3FH; i++) if (*cp++ != 0) return (FALSE); return (TRUE); } /* * This function compares two net addresses by family and returns TRUE * if they are the same host. * If there is any doubt, return FALSE. * The AF_INET family is handled as a special case so that address mbufs * don't need to be saved to store "struct in_addr", which is only 4 bytes. */ int netaddr_match(int family, union nethostaddr *haddr, struct sockaddr *nam) { struct sockaddr_in *inetaddr; NFSD_LOCK_DONTCARE(); switch (family) { case AF_INET: inetaddr = (struct sockaddr_in *)nam; if (inetaddr->sin_family == AF_INET && inetaddr->sin_addr.s_addr == haddr->had_inetaddr) return (1); break; #ifdef INET6 case AF_INET6: { register struct sockaddr_in6 *inet6addr1, *inet6addr2; inet6addr1 = (struct sockaddr_in6 *)nam; inet6addr2 = (struct sockaddr_in6 *)haddr->had_nam; /* XXX - should test sin6_scope_id ? */ if (inet6addr1->sin6_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&inet6addr1->sin6_addr, &inet6addr2->sin6_addr)) return (1); break; } #endif default: break; }; return (0); } /* * Map errnos to NFS error numbers. For Version 3 also filter out error * numbers not specified for the associated procedure. */ int nfsrv_errmap(struct nfsrv_descript *nd, int err) { const short *defaulterrp, *errp; int e; if (nd->nd_flag & ND_NFSV3) { if (nd->nd_procnum <= NFSPROC_COMMIT) { errp = defaulterrp = nfsrv_v3errmap[nd->nd_procnum]; while (*++errp) { if (*errp == err) return (err); else if (*errp > err) break; } return ((int)*defaulterrp); } else return (err & 0xffff); } e = 0; if (err <= ELAST) e = nfsrv_v2errmap[err - 1]; if (e != 0) return (e); return (NFSERR_IO); } /* * Sort the group list in increasing numerical order. * (Insertion sort by Chris Torek, who was grossed out by the bubble sort * that used to be here.) */ void nfsrvw_sort(gid_t *list, int num) { int i, j; gid_t v; /* Insertion sort. */ for (i = 1; i < num; i++) { v = list[i]; /* find correct slot for value v, moving others up */ for (j = i; --j >= 0 && v < list[j];) list[j + 1] = list[j]; list[j + 1] = v; } } /* * copy credentials making sure that the result can be compared with bcmp(). */ void nfsrv_setcred(struct ucred *incred, struct ucred *outcred) { int i; bzero((caddr_t)outcred, sizeof (struct ucred)); refcount_init(&outcred->cr_ref, 1); outcred->cr_uid = incred->cr_uid; outcred->cr_ngroups = incred->cr_ngroups; for (i = 0; i < incred->cr_ngroups; i++) outcred->cr_groups[i] = incred->cr_groups[i]; nfsrvw_sort(outcred->cr_groups, outcred->cr_ngroups); } /* * Helper functions for macros. */ void nfsm_srvfhtom_xx(fhandle_t *f, int v3, struct mbuf **mb, caddr_t *bpos) { u_int32_t *tl; if (v3) { tl = nfsm_build_xx(NFSX_UNSIGNED + NFSX_V3FH, mb, bpos); *tl++ = txdr_unsigned(NFSX_V3FH); bcopy(f, tl, NFSX_V3FH); } else { tl = nfsm_build_xx(NFSX_V2FH, mb, bpos); bcopy(f, tl, NFSX_V2FH); } } void nfsm_srvpostop_fh_xx(fhandle_t *f, struct mbuf **mb, caddr_t *bpos) { u_int32_t *tl; tl = nfsm_build_xx(2 * NFSX_UNSIGNED + NFSX_V3FH, mb, bpos); *tl++ = nfsrv_nfs_true; *tl++ = txdr_unsigned(NFSX_V3FH); bcopy(f, tl, NFSX_V3FH); } int nfsm_srvstrsiz_xx(int *s, int m, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; *s = fxdr_unsigned(int32_t, *tl); if (*s > m || *s <= 0) return EBADRPC; return 0; } int nfsm_srvnamesiz_xx(int *s, int m, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; NFSD_LOCK_DONTCARE(); tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; *s = fxdr_unsigned(int32_t, *tl); if (*s > m) return NFSERR_NAMETOL; if (*s <= 0) return EBADRPC; return 0; } int nfsm_srvnamesiz0_xx(int *s, int m, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; *s = fxdr_unsigned(int32_t, *tl); if (*s > m) return NFSERR_NAMETOL; if (*s < 0) return EBADRPC; return 0; } void nfsm_clget_xx(u_int32_t **tl, struct mbuf *mb, struct mbuf **mp, char **bp, char **be, caddr_t bpos) { struct mbuf *nmp; NFSD_UNLOCK_ASSERT(); if (*bp >= *be) { if (*mp == mb) (*mp)->m_len += *bp - bpos; MGET(nmp, M_TRYWAIT, MT_DATA); MCLGET(nmp, M_TRYWAIT); nmp->m_len = NFSMSIZ(nmp); (*mp)->m_next = nmp; *mp = nmp; *bp = mtod(*mp, caddr_t); *be = *bp + (*mp)->m_len; } *tl = (u_int32_t *)*bp; } int nfsm_srvmtofh_xx(fhandle_t *f, struct nfsrv_descript *nfsd, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; int fhlen; if (nfsd->nd_flag & ND_NFSV3) { tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; fhlen = fxdr_unsigned(int, *tl); if (fhlen != 0 && fhlen != NFSX_V3FH) return EBADRPC; } else { fhlen = NFSX_V2FH; } if (fhlen != 0) { tl = nfsm_dissect_xx_nonblock(fhlen, md, dpos); if (tl == NULL) return EBADRPC; bcopy((caddr_t)tl, (caddr_t)(f), fhlen); } else { bzero((caddr_t)(f), NFSX_V3FH); } return 0; } int nfsm_srvsattr_xx(struct vattr *a, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; int toclient = 0; tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; if (*tl == nfsrv_nfs_true) { tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; (a)->va_mode = nfstov_mode(*tl); } tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; if (*tl == nfsrv_nfs_true) { tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; (a)->va_uid = fxdr_unsigned(uid_t, *tl); } tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; if (*tl == nfsrv_nfs_true) { tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; (a)->va_gid = fxdr_unsigned(gid_t, *tl); } tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; if (*tl == nfsrv_nfs_true) { tl = nfsm_dissect_xx_nonblock(2 * NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; (a)->va_size = fxdr_hyper(tl); } tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; switch (fxdr_unsigned(int, *tl)) { case NFSV3SATTRTIME_TOCLIENT: tl = nfsm_dissect_xx_nonblock(2 * NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; fxdr_nfsv3time(tl, &(a)->va_atime); toclient = 1; break; case NFSV3SATTRTIME_TOSERVER: getnanotime(&(a)->va_atime); a->va_vaflags |= VA_UTIMES_NULL; break; } tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; switch (fxdr_unsigned(int, *tl)) { case NFSV3SATTRTIME_TOCLIENT: tl = nfsm_dissect_xx_nonblock(2 * NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; fxdr_nfsv3time(tl, &(a)->va_mtime); a->va_vaflags &= ~VA_UTIMES_NULL; break; case NFSV3SATTRTIME_TOSERVER: getnanotime(&(a)->va_mtime); if (toclient == 0) a->va_vaflags |= VA_UTIMES_NULL; break; } return 0; } Index: head/sys/nfsserver/nfs_syscalls.c =================================================================== --- head/sys/nfsserver/nfs_syscalls.c (revision 171612) +++ head/sys/nfsserver/nfs_syscalls.c (revision 171613) @@ -1,750 +1,744 @@ /*- * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)nfs_syscalls.c 8.5 (Berkeley) 3/30/95 */ #include __FBSDID("$FreeBSD$"); #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #include #endif #include #include #include #include #include #include static MALLOC_DEFINE(M_NFSSVC, "nfss_srvsock", "Nfs server structure"); MALLOC_DEFINE(M_NFSRVDESC, "nfss_srvdesc", "NFS server socket descriptor"); MALLOC_DEFINE(M_NFSD, "nfss_daemon", "Nfs server daemon structure"); #define TRUE 1 #define FALSE 0 SYSCTL_DECL(_vfs_nfsrv); int nfsd_waiting = 0; int nfsrv_numnfsd = 0; static int notstarted = 1; static int nfs_privport = 0; SYSCTL_INT(_vfs_nfsrv, NFS_NFSPRIVPORT, nfs_privport, CTLFLAG_RW, &nfs_privport, 0, ""); SYSCTL_INT(_vfs_nfsrv, OID_AUTO, gatherdelay, CTLFLAG_RW, &nfsrvw_procrastinate, 0, ""); SYSCTL_INT(_vfs_nfsrv, OID_AUTO, gatherdelay_v3, CTLFLAG_RW, &nfsrvw_procrastinate_v3, 0, ""); static int nfssvc_addsock(struct file *, struct sockaddr *, struct thread *); static void nfsrv_zapsock(struct nfssvc_sock *slp); static int nfssvc_nfsd(struct thread *); /* * NFS server system calls */ /* * Nfs server psuedo system call for the nfsd's * Based on the flag value it either: * - adds a socket to the selection list * - remains in the kernel as an nfsd * - remains in the kernel as an nfsiod * For INET6 we suppose that nfsd provides only IN6P_IPV6_V6ONLY sockets * and that mountd provides * - sockaddr with no IPv4-mapped addresses * - mask for both INET and INET6 families if there is IPv4-mapped overlap */ #ifndef _SYS_SYSPROTO_H_ struct nfssvc_args { int flag; caddr_t argp; }; #endif int nfssvc(struct thread *td, struct nfssvc_args *uap) { struct file *fp; struct sockaddr *nam; struct nfsd_args nfsdarg; int error; KASSERT(!mtx_owned(&Giant), ("nfssvc(): called with Giant")); error = priv_check(td, PRIV_NFS_DAEMON); if (error) return (error); NET_LOCK_GIANT(); NFSD_LOCK(); while (nfssvc_sockhead_flag & SLP_INIT) { nfssvc_sockhead_flag |= SLP_WANTINIT; (void) msleep(&nfssvc_sockhead, &nfsd_mtx, PSOCK, "nfsd init", 0); } NFSD_UNLOCK(); if (uap->flag & NFSSVC_ADDSOCK) { error = copyin(uap->argp, (caddr_t)&nfsdarg, sizeof(nfsdarg)); if (error) goto done2; if ((error = fget(td, nfsdarg.sock, &fp)) != 0) goto done2; if (fp->f_type != DTYPE_SOCKET) { fdrop(fp, td); goto done2; } /* * Get the client address for connected sockets. */ if (nfsdarg.name == NULL || nfsdarg.namelen == 0) nam = NULL; else { error = getsockaddr(&nam, nfsdarg.name, nfsdarg.namelen); if (error) { fdrop(fp, td); goto done2; } } error = nfssvc_addsock(fp, nam, td); fdrop(fp, td); } else if (uap->flag & NFSSVC_NFSD) { error = nfssvc_nfsd(td); } else { error = ENXIO; } if (error == EINTR || error == ERESTART) error = 0; done2: NET_UNLOCK_GIANT(); return (error); } /* * Adds a socket to the list for servicing by nfsds. */ static int nfssvc_addsock(struct file *fp, struct sockaddr *mynam, struct thread *td) { int siz; struct nfssvc_sock *slp; struct socket *so; int error, s; NET_ASSERT_GIANT(); so = fp->f_data; #if 0 /* * XXXRW: If this code is ever enabled, there's a race when running * MPSAFE. */ tslp = NULL; /* * Add it to the list, as required. */ if (so->so_proto->pr_protocol == IPPROTO_UDP) { tslp = nfs_udpsock; if (tslp->ns_flag & SLP_VALID) { if (mynam != NULL) FREE(mynam, M_SONAME); return (EPERM); } } #endif if (so->so_type == SOCK_STREAM) siz = NFS_MAXPACKET + sizeof (u_long); else siz = NFS_MAXPACKET; error = soreserve(so, siz, siz); if (error) { if (mynam != NULL) FREE(mynam, M_SONAME); return (error); } /* * Set protocol specific options { for now TCP only } and * reserve some space. For datagram sockets, this can get called * repeatedly for the same socket, but that isn't harmful. */ if (so->so_type == SOCK_STREAM) { struct sockopt sopt; int val; bzero(&sopt, sizeof sopt); sopt.sopt_dir = SOPT_SET; sopt.sopt_level = SOL_SOCKET; sopt.sopt_name = SO_KEEPALIVE; sopt.sopt_val = &val; sopt.sopt_valsize = sizeof val; val = 1; sosetopt(so, &sopt); } if (so->so_proto->pr_protocol == IPPROTO_TCP) { struct sockopt sopt; int val; bzero(&sopt, sizeof sopt); sopt.sopt_dir = SOPT_SET; sopt.sopt_level = IPPROTO_TCP; sopt.sopt_name = TCP_NODELAY; sopt.sopt_val = &val; sopt.sopt_valsize = sizeof val; val = 1; sosetopt(so, &sopt); } SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_flags &= ~SB_NOINTR; so->so_rcv.sb_timeo = 0; SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_flags &= ~SB_NOINTR; so->so_snd.sb_timeo = 0; SOCKBUF_UNLOCK(&so->so_snd); slp = (struct nfssvc_sock *) malloc(sizeof (struct nfssvc_sock), M_NFSSVC, M_WAITOK | M_ZERO); STAILQ_INIT(&slp->ns_rec); NFSD_LOCK(); TAILQ_INSERT_TAIL(&nfssvc_sockhead, slp, ns_chain); slp->ns_so = so; slp->ns_nam = mynam; fhold(fp); slp->ns_fp = fp; /* * XXXRW: Socket locking here? */ s = splnet(); so->so_upcallarg = (caddr_t)slp; so->so_upcall = nfsrv_rcv; SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_flags |= SB_UPCALL; SOCKBUF_UNLOCK(&so->so_rcv); slp->ns_flag = (SLP_VALID | SLP_NEEDQ); nfsrv_wakenfsd(slp); splx(s); NFSD_UNLOCK(); return (0); } /* * Called by nfssvc() for nfsds. Just loops around servicing rpc requests * until it is killed by a signal. */ static int nfssvc_nfsd(struct thread *td) { int siz; struct nfssvc_sock *slp; struct nfsd *nfsd; struct nfsrv_descript *nd = NULL; struct mbuf *m, *mreq; int error = 0, cacherep, s, sotype, writes_todo; int procrastinate; u_quad_t cur_usec; NET_ASSERT_GIANT(); #ifndef nolint cacherep = RC_DOIT; writes_todo = 0; #endif nfsd = (struct nfsd *) malloc(sizeof (struct nfsd), M_NFSD, M_WAITOK | M_ZERO); s = splnet(); NFSD_LOCK(); nfsd->nfsd_td = td; TAILQ_INSERT_TAIL(&nfsd_head, nfsd, nfsd_chain); nfsrv_numnfsd++; /* * Loop getting rpc requests until SIGKILL. */ for (;;) { if ((nfsd->nfsd_flag & NFSD_REQINPROG) == 0) { while (nfsd->nfsd_slp == NULL && (nfsd_head_flag & NFSD_CHECKSLP) == 0) { nfsd->nfsd_flag |= NFSD_WAITING; nfsd_waiting++; error = msleep(nfsd, &nfsd_mtx, PSOCK | PCATCH, "-", 0); nfsd_waiting--; if (error) goto done; } if (nfsd->nfsd_slp == NULL && (nfsd_head_flag & NFSD_CHECKSLP) != 0) { TAILQ_FOREACH(slp, &nfssvc_sockhead, ns_chain) { if ((slp->ns_flag & (SLP_VALID | SLP_DOREC)) == (SLP_VALID | SLP_DOREC)) { slp->ns_flag &= ~SLP_DOREC; slp->ns_sref++; nfsd->nfsd_slp = slp; break; } } if (slp == NULL) nfsd_head_flag &= ~NFSD_CHECKSLP; } if ((slp = nfsd->nfsd_slp) == NULL) continue; if (slp->ns_flag & SLP_VALID) { if (slp->ns_flag & SLP_DISCONN) nfsrv_zapsock(slp); else if (slp->ns_flag & SLP_NEEDQ) { slp->ns_flag &= ~SLP_NEEDQ; (void) nfs_slplock(slp, 1); NFSD_UNLOCK(); nfsrv_rcv(slp->ns_so, (caddr_t)slp, M_TRYWAIT); NFSD_LOCK(); nfs_slpunlock(slp); } error = nfsrv_dorec(slp, nfsd, &nd); cur_usec = nfs_curusec(); if (error && LIST_FIRST(&slp->ns_tq) && LIST_FIRST(&slp->ns_tq)->nd_time <= cur_usec) { error = 0; cacherep = RC_DOIT; writes_todo = 1; } else writes_todo = 0; nfsd->nfsd_flag |= NFSD_REQINPROG; } } else { error = 0; slp = nfsd->nfsd_slp; } if (error || (slp->ns_flag & SLP_VALID) == 0) { if (nd) { if (nd->nd_cr != NULL) crfree(nd->nd_cr); free((caddr_t)nd, M_NFSRVDESC); nd = NULL; } nfsd->nfsd_slp = NULL; nfsd->nfsd_flag &= ~NFSD_REQINPROG; nfsrv_slpderef(slp); continue; } splx(s); sotype = slp->ns_so->so_type; if (nd) { getmicrotime(&nd->nd_starttime); if (nd->nd_nam2) nd->nd_nam = nd->nd_nam2; else nd->nd_nam = slp->ns_nam; /* * Check to see if authorization is needed. */ cacherep = nfsrv_getcache(nd, &mreq); if (nfs_privport) { /* Check if source port is privileged */ u_short port; struct sockaddr *nam = nd->nd_nam; struct sockaddr_in *sin; sin = (struct sockaddr_in *)nam; /* * INET/INET6 - same code: * sin_port and sin6_port are at same offset */ port = ntohs(sin->sin_port); if (port >= IPPORT_RESERVED && nd->nd_procnum != NFSPROC_NULL) { #ifdef INET6 char b6[INET6_ADDRSTRLEN]; #if defined(KLD_MODULE) /* Do not use ip6_sprintf: the nfs module should work without INET6. */ #define ip6_sprintf(buf, a) \ (sprintf((buf), "%x:%x:%x:%x:%x:%x:%x:%x", \ (a)->s6_addr16[0], (a)->s6_addr16[1], \ (a)->s6_addr16[2], (a)->s6_addr16[3], \ (a)->s6_addr16[4], (a)->s6_addr16[5], \ (a)->s6_addr16[6], (a)->s6_addr16[7]), \ (buf)) #endif #endif nd->nd_procnum = NFSPROC_NOOP; nd->nd_repstat = (NFSERR_AUTHERR | AUTH_TOOWEAK); cacherep = RC_DOIT; printf("NFS request from unprivileged port (%s:%d)\n", #ifdef INET6 sin->sin_family == AF_INET6 ? ip6_sprintf(b6, &satosin6(sin)->sin6_addr) : #if defined(KLD_MODULE) #undef ip6_sprintf #endif #endif inet_ntoa(sin->sin_addr), port); } } } /* * Loop to get all the write rpc relies that have been * gathered together. */ do { switch (cacherep) { case RC_DOIT: if (nd && (nd->nd_flag & ND_NFSV3)) procrastinate = nfsrvw_procrastinate_v3; else procrastinate = nfsrvw_procrastinate; NFSD_UNLOCK(); if (writes_todo || (!(nd->nd_flag & ND_NFSV3) && nd->nd_procnum == NFSPROC_WRITE && procrastinate > 0 && !notstarted)) error = nfsrv_writegather(&nd, slp, nfsd->nfsd_td, &mreq); else error = (*(nfsrv3_procs[nd->nd_procnum]))(nd, slp, nfsd->nfsd_td, &mreq); NFSD_LOCK(); if (mreq == NULL) break; if (error != 0 && error != NFSERR_RETVOID) { nfsrvstats.srv_errs++; nfsrv_updatecache(nd, FALSE, mreq); if (nd->nd_nam2) FREE(nd->nd_nam2, M_SONAME); break; } nfsrvstats.srvrpccnt[nd->nd_procnum]++; nfsrv_updatecache(nd, TRUE, mreq); nd->nd_mrep = NULL; /* FALLTHROUGH */ case RC_REPLY: NFSD_UNLOCK(); siz = m_length(mreq, NULL); if (siz <= 0 || siz > NFS_MAXPACKET) { printf("mbuf siz=%d\n",siz); panic("Bad nfs svc reply"); } m = mreq; m->m_pkthdr.len = siz; m->m_pkthdr.rcvif = NULL; /* * For stream protocols, prepend a Sun RPC * Record Mark. */ if (sotype == SOCK_STREAM) { M_PREPEND(m, NFSX_UNSIGNED, M_TRYWAIT); *mtod(m, u_int32_t *) = htonl(0x80000000 | siz); } NFSD_LOCK(); if (slp->ns_so->so_proto->pr_flags & PR_CONNREQUIRED) (void) nfs_slplock(slp, 1); if (slp->ns_flag & SLP_VALID) { NFSD_UNLOCK(); error = nfsrv_send(slp->ns_so, nd->nd_nam2, m); NFSD_LOCK(); } else { error = EPIPE; m_freem(m); } if (nd->nd_nam2) FREE(nd->nd_nam2, M_SONAME); if (nd->nd_mrep) m_freem(nd->nd_mrep); if (error == EPIPE) nfsrv_zapsock(slp); if (slp->ns_so->so_proto->pr_flags & PR_CONNREQUIRED) nfs_slpunlock(slp); if (error == EINTR || error == ERESTART) { if (nd->nd_cr != NULL) crfree(nd->nd_cr); free((caddr_t)nd, M_NFSRVDESC); nfsrv_slpderef(slp); s = splnet(); goto done; } break; case RC_DROPIT: m_freem(nd->nd_mrep); if (nd->nd_nam2) FREE(nd->nd_nam2, M_SONAME); break; }; if (nd) { if (nd->nd_cr != NULL) crfree(nd->nd_cr); FREE((caddr_t)nd, M_NFSRVDESC); nd = NULL; } /* * Check to see if there are outstanding writes that * need to be serviced. */ cur_usec = nfs_curusec(); s = splsoftclock(); if (LIST_FIRST(&slp->ns_tq) && LIST_FIRST(&slp->ns_tq)->nd_time <= cur_usec) { cacherep = RC_DOIT; writes_todo = 1; } else writes_todo = 0; splx(s); } while (writes_todo); s = splnet(); if (nfsrv_dorec(slp, nfsd, &nd)) { nfsd->nfsd_flag &= ~NFSD_REQINPROG; nfsd->nfsd_slp = NULL; nfsrv_slpderef(slp); } - KASSERT(!(debug_mpsafenet == 0 && !mtx_owned(&Giant)), - ("nfssvc_nfsd(): debug.mpsafenet=0 && !Giant")); - KASSERT(!(debug_mpsafenet == 1 && mtx_owned(&Giant)), - ("nfssvc_nfsd(): debug.mpsafenet=1 && Giant")); + mtx_assert(&Giant, MA_NOTOWNED); } done: - KASSERT(!(debug_mpsafenet == 0 && !mtx_owned(&Giant)), - ("nfssvc_nfsd(): debug.mpsafenet=0 && !Giant")); - KASSERT(!(debug_mpsafenet == 1 && mtx_owned(&Giant)), - ("nfssvc_nfsd(): debug.mpsafenet=1 && Giant")); + mtx_assert(&Giant, MA_NOTOWNED); TAILQ_REMOVE(&nfsd_head, nfsd, nfsd_chain); splx(s); free((caddr_t)nfsd, M_NFSD); if (--nfsrv_numnfsd == 0) nfsrv_init(TRUE); /* Reinitialize everything */ NFSD_UNLOCK(); return (error); } /* * Shut down a socket associated with an nfssvc_sock structure. * Should be called with the send lock set, if required. * The trick here is to increment the sref at the start, so that the nfsds * will stop using it and clear ns_flag at the end so that it will not be * reassigned during cleanup. */ static void nfsrv_zapsock(struct nfssvc_sock *slp) { struct nfsrv_descript *nwp, *nnwp; struct socket *so; struct file *fp; struct nfsrv_rec *rec; int s; NET_ASSERT_GIANT(); NFSD_LOCK_ASSERT(); /* * XXXRW: By clearing all flags, other threads/etc should ignore * this slp and we can safely release nfsd_mtx so we can clean * up the slp safely. */ slp->ns_flag &= ~SLP_ALLFLAGS; fp = slp->ns_fp; if (fp) { NFSD_UNLOCK(); slp->ns_fp = NULL; so = slp->ns_so; SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_flags &= ~SB_UPCALL; SOCKBUF_UNLOCK(&so->so_rcv); so->so_upcall = NULL; so->so_upcallarg = NULL; soshutdown(so, SHUT_RDWR); closef(fp, NULL); NFSD_LOCK(); if (slp->ns_nam) FREE(slp->ns_nam, M_SONAME); m_freem(slp->ns_raw); while ((rec = STAILQ_FIRST(&slp->ns_rec)) != NULL) { STAILQ_REMOVE_HEAD(&slp->ns_rec, nr_link); if (rec->nr_address) FREE(rec->nr_address, M_SONAME); m_freem(rec->nr_packet); free(rec, M_NFSRVDESC); } s = splsoftclock(); for (nwp = LIST_FIRST(&slp->ns_tq); nwp; nwp = nnwp) { nnwp = LIST_NEXT(nwp, nd_tq); LIST_REMOVE(nwp, nd_tq); if (nwp->nd_cr != NULL) crfree(nwp->nd_cr); free((caddr_t)nwp, M_NFSRVDESC); } LIST_INIT(&slp->ns_tq); splx(s); } } /* * Derefence a server socket structure. If it has no more references and * is no longer valid, you can throw it away. */ void nfsrv_slpderef(struct nfssvc_sock *slp) { NFSD_LOCK_ASSERT(); if (--(slp->ns_sref) == 0 && (slp->ns_flag & SLP_VALID) == 0) { TAILQ_REMOVE(&nfssvc_sockhead, slp, ns_chain); free((caddr_t)slp, M_NFSSVC); } } /* * Lock a socket against others. * * XXXRW: Wait argument is always 1 in the caller. Replace with a real * sleep lock? */ int nfs_slplock(struct nfssvc_sock *slp, int wait) { int *statep = &slp->ns_solock; NFSD_LOCK_ASSERT(); if (!wait && (*statep & NFSRV_SNDLOCK)) return(0); /* already locked, fail */ while (*statep & NFSRV_SNDLOCK) { *statep |= NFSRV_WANTSND; (void) msleep(statep, &nfsd_mtx, PZERO - 1, "nfsslplck", 0); } *statep |= NFSRV_SNDLOCK; return (1); } /* * Unlock the stream socket for others. */ void nfs_slpunlock(struct nfssvc_sock *slp) { int *statep = &slp->ns_solock; NFSD_LOCK_ASSERT(); if ((*statep & NFSRV_SNDLOCK) == 0) panic("nfs slpunlock"); *statep &= ~NFSRV_SNDLOCK; if (*statep & NFSRV_WANTSND) { *statep &= ~NFSRV_WANTSND; wakeup(statep); } } /* * Initialize the data structures for the server. * Handshake with any new nfsds starting up to avoid any chance of * corruption. */ void nfsrv_init(int terminating) { struct nfssvc_sock *slp, *nslp; NET_ASSERT_GIANT(); NFSD_LOCK_ASSERT(); if (nfssvc_sockhead_flag & SLP_INIT) panic("nfsd init"); nfssvc_sockhead_flag |= SLP_INIT; if (terminating) { TAILQ_FOREACH_SAFE(slp, &nfssvc_sockhead, ns_chain, nslp) { if (slp->ns_flag & SLP_VALID) nfsrv_zapsock(slp); TAILQ_REMOVE(&nfssvc_sockhead, slp, ns_chain); free((caddr_t)slp, M_NFSSVC); } nfsrv_cleancache(); /* And clear out server cache */ } else nfs_pub.np_valid = 0; TAILQ_INIT(&nfssvc_sockhead); nfssvc_sockhead_flag &= ~SLP_INIT; if (nfssvc_sockhead_flag & SLP_WANTINIT) { nfssvc_sockhead_flag &= ~SLP_WANTINIT; wakeup(&nfssvc_sockhead); } TAILQ_INIT(&nfsd_head); nfsd_head_flag &= ~NFSD_CHECKSLP; #if 0 nfs_udpsock = (struct nfssvc_sock *) malloc(sizeof (struct nfssvc_sock), M_NFSSVC, M_WAITOK | M_ZERO); STAILQ_INIT(&nfs_udpsock->ns_rec); TAILQ_INSERT_HEAD(&nfssvc_sockhead, nfs_udpsock, ns_chain); nfs_cltpsock = (struct nfssvc_sock *) malloc(sizeof (struct nfssvc_sock), M_NFSSVC, M_WAITOK | M_ZERO); STAILQ_INIT(&nfs_cltpsock->ns_rec); TAILQ_INSERT_TAIL(&nfssvc_sockhead, nfs_cltpsock, ns_chain); #endif } Index: head/sys/sys/kernel.h =================================================================== --- head/sys/sys/kernel.h (revision 171612) +++ head/sys/sys/kernel.h (revision 171613) @@ -1,360 +1,355 @@ /*- * Copyright (c) 1995 Terrence R. Lambert * All rights reserved. * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)kernel.h 8.3 (Berkeley) 1/21/94 * $FreeBSD$ */ #ifndef _SYS_KERNEL_H_ #define _SYS_KERNEL_H_ #include #ifdef _KERNEL /* for intrhook below */ #include /* Global variables for the kernel. */ /* 1.1 */ extern unsigned long hostid; extern char hostuuid[64]; extern char hostname[MAXHOSTNAMELEN]; extern char domainname[MAXHOSTNAMELEN]; extern char kernelname[MAXPATHLEN]; extern int tick; /* usec per tick (1000000 / hz) */ extern int hz; /* system clock's frequency */ extern int psratio; /* ratio: prof / stat */ extern int stathz; /* statistics clock's frequency */ extern int profhz; /* profiling clock's frequency */ extern int profprocs; /* number of process's profiling */ extern int ticks; #ifndef _SOLARIS_C_SOURCE extern int lbolt; /* once a second sleep address */ #endif #endif /* _KERNEL */ /* * Enumerated types for known system startup interfaces. * * Startup occurs in ascending numeric order; the list entries are * sorted prior to attempting startup to guarantee order. Items * of the same level are arbitrated for order based on the 'order' * element. * * These numbers are arbitrary and are chosen ONLY for ordering; the * enumeration values are explicit rather than implicit to provide * for binary compatibility with inserted elements. * * The SI_SUB_RUN_SCHEDULER value must have the highest lexical value. * * The SI_SUB_SWAP values represent a value used by * the BSD 4.4Lite but not by FreeBSD; it is maintained in dependent * order to support porting. * * The SI_SUB_PROTO_BEGIN and SI_SUB_PROTO_END bracket a range of * initializations to take place at splimp(). This is a historical * wart that should be removed -- probably running everything at * splimp() until the first init that doesn't want it is the correct * fix. They are currently present to ensure historical behavior. */ enum sysinit_sub_id { SI_SUB_DUMMY = 0x0000000, /* not executed; for linker*/ SI_SUB_DONE = 0x0000001, /* processed*/ SI_SUB_TUNABLES = 0x0700000, /* establish tunable values */ SI_SUB_COPYRIGHT = 0x0800001, /* first use of console*/ SI_SUB_SETTINGS = 0x0880000, /* check and recheck settings */ SI_SUB_MTX_POOL_STATIC = 0x0900000, /* static mutex pool */ SI_SUB_LOCKMGR = 0x0980000, /* lockmgr locks */ SI_SUB_VM = 0x1000000, /* virtual memory system init*/ SI_SUB_KMEM = 0x1800000, /* kernel memory*/ SI_SUB_KVM_RSRC = 0x1A00000, /* kvm operational limits*/ SI_SUB_WITNESS = 0x1A80000, /* witness initialization */ SI_SUB_MTX_POOL_DYNAMIC = 0x1AC0000, /* dynamic mutex pool */ SI_SUB_LOCK = 0x1B00000, /* various locks */ SI_SUB_EVENTHANDLER = 0x1C00000, /* eventhandler init */ SI_SUB_KLD = 0x2000000, /* KLD and module setup */ SI_SUB_CPU = 0x2100000, /* CPU resource(s)*/ SI_SUB_MAC = 0x2180000, /* TrustedBSD MAC subsystem */ SI_SUB_MAC_POLICY = 0x21C0000, /* TrustedBSD MAC policies */ SI_SUB_MAC_LATE = 0x21D0000, /* TrustedBSD MAC subsystem */ SI_SUB_INTRINSIC = 0x2200000, /* proc 0*/ SI_SUB_VM_CONF = 0x2300000, /* config VM, set limits*/ SI_SUB_RUN_QUEUE = 0x2400000, /* set up run queue*/ SI_SUB_KTRACE = 0x2480000, /* ktrace */ SI_SUB_AUDIT = 0x24C0000, /* audit */ SI_SUB_CREATE_INIT = 0x2500000, /* create init process*/ SI_SUB_SCHED_IDLE = 0x2600000, /* required idle procs */ SI_SUB_MBUF = 0x2700000, /* mbuf subsystem */ SI_SUB_INTR = 0x2800000, /* interrupt threads */ SI_SUB_SOFTINTR = 0x2800001, /* start soft interrupt thread */ SI_SUB_ACL = 0x2900000, /* start for filesystem ACLs */ SI_SUB_DEVFS = 0x2F00000, /* devfs ready for devices */ SI_SUB_INIT_IF = 0x3000000, /* prep for net interfaces */ SI_SUB_NETGRAPH = 0x3010000, /* Let Netgraph initialize */ SI_SUB_DRIVERS = 0x3100000, /* Let Drivers initialize */ SI_SUB_CONFIGURE = 0x3800000, /* Configure devices */ SI_SUB_VFS = 0x4000000, /* virtual filesystem*/ SI_SUB_CLOCKS = 0x4800000, /* real time and stat clocks*/ SI_SUB_CLIST = 0x5800000, /* clists*/ SI_SUB_SYSV_SHM = 0x6400000, /* System V shared memory*/ SI_SUB_SYSV_SEM = 0x6800000, /* System V semaphores*/ SI_SUB_SYSV_MSG = 0x6C00000, /* System V message queues*/ SI_SUB_P1003_1B = 0x6E00000, /* P1003.1B realtime */ SI_SUB_PSEUDO = 0x7000000, /* pseudo devices*/ SI_SUB_EXEC = 0x7400000, /* execve() handlers */ SI_SUB_PROTO_BEGIN = 0x8000000, /* XXX: set splimp (kludge)*/ SI_SUB_PROTO_IF = 0x8400000, /* interfaces*/ SI_SUB_PROTO_DOMAIN = 0x8800000, /* domains (address families?)*/ SI_SUB_PROTO_IFATTACHDOMAIN = 0x8800001, /* domain dependent data init*/ SI_SUB_PROTO_END = 0x8ffffff, /* XXX: set splx (kludge)*/ SI_SUB_KPROF = 0x9000000, /* kernel profiling*/ SI_SUB_KICK_SCHEDULER = 0xa000000, /* start the timeout events*/ SI_SUB_INT_CONFIG_HOOKS = 0xa800000, /* Interrupts enabled config */ SI_SUB_ROOT_CONF = 0xb000000, /* Find root devices */ SI_SUB_DUMP_CONF = 0xb200000, /* Find dump devices */ SI_SUB_RAID = 0xb380000, /* Configure GEOM classes */ SI_SUB_MOUNT_ROOT = 0xb400000, /* root mount*/ SI_SUB_SWAP = 0xc000000, /* swap */ SI_SUB_INTRINSIC_POST = 0xd000000, /* proc 0 cleanup*/ SI_SUB_SYSCALLS = 0xd800000, /* register system calls */ SI_SUB_KTHREAD_INIT = 0xe000000, /* init process*/ SI_SUB_KTHREAD_PAGE = 0xe400000, /* pageout daemon*/ SI_SUB_KTHREAD_VM = 0xe800000, /* vm daemon*/ SI_SUB_KTHREAD_BUF = 0xea00000, /* buffer daemon*/ SI_SUB_KTHREAD_UPDATE = 0xec00000, /* update daemon*/ SI_SUB_KTHREAD_IDLE = 0xee00000, /* idle procs*/ SI_SUB_SMP = 0xf000000, /* start the APs*/ SI_SUB_RUN_SCHEDULER = 0xfffffff /* scheduler*/ }; /* * Some enumerated orders; "ANY" sorts last. */ enum sysinit_elem_order { SI_ORDER_FIRST = 0x0000000, /* first*/ SI_ORDER_SECOND = 0x0000001, /* second*/ SI_ORDER_THIRD = 0x0000002, /* third*/ SI_ORDER_MIDDLE = 0x1000000, /* somewhere in the middle */ SI_ORDER_ANY = 0xfffffff /* last*/ }; /* * A system initialization call instance * * At the moment there is one instance of sysinit. We probably do not * want two which is why this code is if'd out, but we definitely want * to discern SYSINIT's which take non-constant data pointers and * SYSINIT's which take constant data pointers, * * The C_* macros take functions expecting const void * arguments * while the non-C_* macros take functions expecting just void * arguments. * * With -Wcast-qual on, the compiler issues warnings: * - if we pass non-const data or functions taking non-const data * to a C_* macro. * * - if we pass const data to the normal macros * * However, no warning is issued if we pass a function taking const data * through a normal non-const macro. This is ok because the function is * saying it won't modify the data so we don't care whether the data is * modifiable or not. */ typedef void (*sysinit_nfunc_t)(void *); typedef void (*sysinit_cfunc_t)(const void *); struct sysinit { enum sysinit_sub_id subsystem; /* subsystem identifier*/ enum sysinit_elem_order order; /* init order within subsystem*/ sysinit_cfunc_t func; /* function */ const void *udata; /* multiplexer/argument */ }; /* * Default: no special processing * * The C_ version of SYSINIT is for data pointers to const * data ( and functions taking data pointers to const data ). * At the moment it is no different from SYSINIT and thus * still results in warnings. * * The casts are necessary to have the compiler produce the * correct warnings when -Wcast-qual is used. * */ #define C_SYSINIT(uniquifier, subsystem, order, func, ident) \ static struct sysinit uniquifier ## _sys_init = { \ subsystem, \ order, \ func, \ (ident) \ }; \ DATA_SET(sysinit_set,uniquifier ## _sys_init); #define SYSINIT(uniquifier, subsystem, order, func, ident) \ C_SYSINIT(uniquifier, subsystem, order, \ (sysinit_cfunc_t)(sysinit_nfunc_t)func, (void *)(ident)) /* * Called on module unload: no special processing */ #define C_SYSUNINIT(uniquifier, subsystem, order, func, ident) \ static struct sysinit uniquifier ## _sys_uninit = { \ subsystem, \ order, \ func, \ (ident) \ }; \ DATA_SET(sysuninit_set,uniquifier ## _sys_uninit) #define SYSUNINIT(uniquifier, subsystem, order, func, ident) \ C_SYSUNINIT(uniquifier, subsystem, order, \ (sysinit_cfunc_t)(sysinit_nfunc_t)func, (void *)(ident)) void sysinit_add(struct sysinit **set, struct sysinit **set_end); /* * Infrastructure for tunable 'constants'. Value may be specified at compile * time or kernel load time. Rules relating tunables together can be placed * in a SYSINIT function at SI_SUB_TUNABLES with SI_ORDER_LAST. * * WARNING: developers should never use the reserved suffixes specified in * loader.conf(5) for any tunables or conflicts will result. */ /* * int * please avoid using for new tunables! */ extern void tunable_int_init(void *); struct tunable_int { const char *path; int *var; }; #define TUNABLE_INT(path, var) \ static struct tunable_int __CONCAT(__tunable_int_, __LINE__) = { \ (path), \ (var), \ }; \ SYSINIT(__CONCAT(__Tunable_init_, __LINE__), \ SI_SUB_TUNABLES, SI_ORDER_MIDDLE, tunable_int_init, \ &__CONCAT(__tunable_int_, __LINE__)) #define TUNABLE_INT_FETCH(path, var) getenv_int((path), (var)) /* * long */ extern void tunable_long_init(void *); struct tunable_long { const char *path; long *var; }; #define TUNABLE_LONG(path, var) \ static struct tunable_long __CONCAT(__tunable_long_, __LINE__) = { \ (path), \ (var), \ }; \ SYSINIT(__CONCAT(__Tunable_init_, __LINE__), \ SI_SUB_TUNABLES, SI_ORDER_MIDDLE, tunable_long_init,\ &__CONCAT(__tunable_long_, __LINE__)) #define TUNABLE_LONG_FETCH(path, var) getenv_long((path), (var)) /* * unsigned long */ extern void tunable_ulong_init(void *); struct tunable_ulong { const char *path; unsigned long *var; }; #define TUNABLE_ULONG(path, var) \ static struct tunable_ulong __CONCAT(__tunable_ulong_, __LINE__) = { \ (path), \ (var), \ }; \ SYSINIT(__CONCAT(__Tunable_init_, __LINE__), \ SI_SUB_TUNABLES, SI_ORDER_MIDDLE, tunable_ulong_init, \ &__CONCAT(__tunable_ulong_, __LINE__)) #define TUNABLE_ULONG_FETCH(path, var) getenv_ulong((path), (var)) extern void tunable_str_init(void *); struct tunable_str { const char *path; char *var; int size; }; #define TUNABLE_STR(path, var, size) \ static struct tunable_str __CONCAT(__tunable_str_, __LINE__) = { \ (path), \ (var), \ (size), \ }; \ SYSINIT(__CONCAT(__Tunable_init_, __LINE__), \ SI_SUB_TUNABLES, SI_ORDER_MIDDLE, tunable_str_init, \ &__CONCAT(__tunable_str_, __LINE__)) #define TUNABLE_STR_FETCH(path, var, size) \ getenv_string((path), (var), (size)) -void net_warn_not_mpsafe(const char *component); -#define NET_NEEDS_GIANT(component) \ - SYSINIT(__CONCAT(__net_warn_not_mpsafe_, __LINE__), \ - SI_SUB_SETTINGS, SI_ORDER_SECOND, net_warn_not_mpsafe, component); - struct intr_config_hook { TAILQ_ENTRY(intr_config_hook) ich_links; void (*ich_func)(void *arg); void *ich_arg; }; int config_intrhook_establish(struct intr_config_hook *hook); void config_intrhook_disestablish(struct intr_config_hook *hook); #endif /* !_SYS_KERNEL_H_*/ Index: head/sys/sys/mutex.h =================================================================== --- head/sys/sys/mutex.h (revision 171612) +++ head/sys/sys/mutex.h (revision 171613) @@ -1,479 +1,462 @@ /*- * Copyright (c) 1997 Berkeley Software Design, Inc. 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. * 3. Berkeley Software Design Inc's name may not be used to endorse or * promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN INC ``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 BERKELEY SOFTWARE DESIGN INC 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. * * from BSDI $Id: mutex.h,v 2.7.2.35 2000/04/27 03:10:26 cp Exp $ * $FreeBSD$ */ #ifndef _SYS_MUTEX_H_ #define _SYS_MUTEX_H_ #ifndef LOCORE #include #include #include #ifdef _KERNEL #include #include #include #include #endif /* _KERNEL_ */ #endif /* !LOCORE */ #include #ifdef _KERNEL /* * Mutex types and options passed to mtx_init(). MTX_QUIET and MTX_DUPOK * can also be passed in. */ #define MTX_DEF 0x00000000 /* DEFAULT (sleep) lock */ #define MTX_SPIN 0x00000001 /* Spin lock (disables interrupts) */ #define MTX_RECURSE 0x00000004 /* Option: lock allowed to recurse */ #define MTX_NOWITNESS 0x00000008 /* Don't do any witness checking. */ #define MTX_NOPROFILE 0x00000020 /* Don't profile this lock */ /* * Option flags passed to certain lock/unlock routines, through the use * of corresponding mtx_{lock,unlock}_flags() interface macros. */ #define MTX_QUIET LOP_QUIET /* Don't log a mutex event */ #define MTX_DUPOK LOP_DUPOK /* Don't log a duplicate acquire */ /* * State bits kept in mutex->mtx_lock, for the DEFAULT lock type. None of this, * with the exception of MTX_UNOWNED, applies to spin locks. */ #define MTX_RECURSED 0x00000001 /* lock recursed (for MTX_DEF only) */ #define MTX_CONTESTED 0x00000002 /* lock contested (for MTX_DEF only) */ #define MTX_UNOWNED 0x00000004 /* Cookie for free mutex */ #define MTX_FLAGMASK (MTX_RECURSED | MTX_CONTESTED | MTX_UNOWNED) /* * Value stored in mutex->mtx_lock to denote a destroyed mutex. */ #define MTX_DESTROYED (MTX_CONTESTED | MTX_UNOWNED) #endif /* _KERNEL */ #ifndef LOCORE /* * XXX: Friendly reminder to fix things in MP code that is presently being * XXX: worked on. */ #define mp_fixme(string) #ifdef _KERNEL /* * Prototypes * * NOTE: Functions prepended with `_' (underscore) are exported to other parts * of the kernel via macros, thus allowing us to use the cpp LOCK_FILE * and LOCK_LINE. These functions should not be called directly by any * code using the API. Their macros cover their functionality. * * [See below for descriptions] * */ void mtx_init(struct mtx *m, const char *name, const char *type, int opts); void mtx_destroy(struct mtx *m); void mtx_sysinit(void *arg); void mutex_init(void); void _mtx_lock_sleep(struct mtx *m, uintptr_t tid, int opts, const char *file, int line); void _mtx_unlock_sleep(struct mtx *m, int opts, const char *file, int line); #ifdef SMP void _mtx_lock_spin(struct mtx *m, uintptr_t tid, int opts, const char *file, int line); #endif void _mtx_unlock_spin(struct mtx *m, int opts, const char *file, int line); int _mtx_trylock(struct mtx *m, int opts, const char *file, int line); void _mtx_lock_flags(struct mtx *m, int opts, const char *file, int line); void _mtx_unlock_flags(struct mtx *m, int opts, const char *file, int line); void _mtx_lock_spin_flags(struct mtx *m, int opts, const char *file, int line); void _mtx_unlock_spin_flags(struct mtx *m, int opts, const char *file, int line); #if defined(INVARIANTS) || defined(INVARIANT_SUPPORT) void _mtx_assert(struct mtx *m, int what, const char *file, int line); #endif void _thread_lock_flags(struct thread *, int, const char *, int); #define thread_lock(tdp) \ _thread_lock_flags((tdp), 0, __FILE__, __LINE__) #define thread_lock_flags(tdp, opt) \ _thread_lock_flags((tdp), (opt), __FILE__, __LINE__) #define thread_unlock(tdp) \ mtx_unlock_spin((tdp)->td_lock) /* * We define our machine-independent (unoptimized) mutex micro-operations * here, if they are not already defined in the machine-dependent mutex.h */ /* Try to obtain mtx_lock once. */ #ifndef _obtain_lock #define _obtain_lock(mp, tid) \ atomic_cmpset_acq_ptr(&(mp)->mtx_lock, MTX_UNOWNED, (tid)) #endif /* Try to release mtx_lock if it is unrecursed and uncontested. */ #ifndef _release_lock #define _release_lock(mp, tid) \ atomic_cmpset_rel_ptr(&(mp)->mtx_lock, (tid), MTX_UNOWNED) #endif /* Release mtx_lock quickly, assuming we own it. */ #ifndef _release_lock_quick #define _release_lock_quick(mp) \ atomic_store_rel_ptr(&(mp)->mtx_lock, MTX_UNOWNED) #endif /* * Obtain a sleep lock inline, or call the "hard" function if we can't get it * easy. */ #ifndef _get_sleep_lock #define _get_sleep_lock(mp, tid, opts, file, line) do { \ uintptr_t _tid = (uintptr_t)(tid); \ if (!_obtain_lock((mp), _tid)) { \ _mtx_lock_sleep((mp), _tid, (opts), (file), (line)); \ } else \ lock_profile_obtain_lock_success(&(mp)->lock_object, 0, \ 0, (file), (line)); \ } while (0) #endif /* * Obtain a spin lock inline, or call the "hard" function if we can't get it * easy. For spinlocks, we handle recursion inline (it turns out that function * calls can be significantly expensive on some architectures). * Since spin locks are not _too_ common, inlining this code is not too big * a deal. */ #ifndef _get_spin_lock #ifdef SMP #define _get_spin_lock(mp, tid, opts, file, line) do { \ uintptr_t _tid = (uintptr_t)(tid); \ spinlock_enter(); \ if (!_obtain_lock((mp), _tid)) { \ if ((mp)->mtx_lock == _tid) \ (mp)->mtx_recurse++; \ else { \ _mtx_lock_spin((mp), _tid, (opts), (file), (line)); \ } \ } else \ lock_profile_obtain_lock_success(&(mp)->lock_object, 0, \ 0, (file), (line)); \ } while (0) #else /* SMP */ #define _get_spin_lock(mp, tid, opts, file, line) do { \ uintptr_t _tid = (uintptr_t)(tid); \ \ spinlock_enter(); \ if ((mp)->mtx_lock == _tid) \ (mp)->mtx_recurse++; \ else { \ KASSERT((mp)->mtx_lock == MTX_UNOWNED, ("corrupt spinlock")); \ (mp)->mtx_lock = _tid; \ } \ } while (0) #endif /* SMP */ #endif /* * Release a sleep lock inline, or call the "hard" function if we can't do it * easy. */ #ifndef _rel_sleep_lock #define _rel_sleep_lock(mp, tid, opts, file, line) do { \ uintptr_t _tid = (uintptr_t)(tid); \ \ if (!_release_lock((mp), _tid)) \ _mtx_unlock_sleep((mp), (opts), (file), (line)); \ } while (0) #endif /* * For spinlocks, we can handle everything inline, as it's pretty simple and * a function call would be too expensive (at least on some architectures). * Since spin locks are not _too_ common, inlining this code is not too big * a deal. * * Since we always perform a spinlock_enter() when attempting to acquire a * spin lock, we need to always perform a matching spinlock_exit() when * releasing a spin lock. This includes the recursion cases. */ #ifndef _rel_spin_lock #ifdef SMP #define _rel_spin_lock(mp) do { \ if (mtx_recursed((mp))) \ (mp)->mtx_recurse--; \ else { \ lock_profile_release_lock(&(mp)->lock_object); \ _release_lock_quick((mp)); \ } \ spinlock_exit(); \ } while (0) #else /* SMP */ #define _rel_spin_lock(mp) do { \ if (mtx_recursed((mp))) \ (mp)->mtx_recurse--; \ else \ (mp)->mtx_lock = MTX_UNOWNED; \ spinlock_exit(); \ } while (0) #endif /* SMP */ #endif /* * Exported lock manipulation interface. * * mtx_lock(m) locks MTX_DEF mutex `m' * * mtx_lock_spin(m) locks MTX_SPIN mutex `m' * * mtx_unlock(m) unlocks MTX_DEF mutex `m' * * mtx_unlock_spin(m) unlocks MTX_SPIN mutex `m' * * mtx_lock_spin_flags(m, opts) and mtx_lock_flags(m, opts) locks mutex `m' * and passes option flags `opts' to the "hard" function, if required. * With these routines, it is possible to pass flags such as MTX_QUIET * to the appropriate lock manipulation routines. * * mtx_trylock(m) attempts to acquire MTX_DEF mutex `m' but doesn't sleep if * it cannot. Rather, it returns 0 on failure and non-zero on success. * It does NOT handle recursion as we assume that if a caller is properly * using this part of the interface, he will know that the lock in question * is _not_ recursed. * * mtx_trylock_flags(m, opts) is used the same way as mtx_trylock() but accepts * relevant option flags `opts.' * * mtx_initialized(m) returns non-zero if the lock `m' has been initialized. * * mtx_owned(m) returns non-zero if the current thread owns the lock `m' * * mtx_recursed(m) returns non-zero if the lock `m' is presently recursed. */ #define mtx_lock(m) mtx_lock_flags((m), 0) #define mtx_lock_spin(m) mtx_lock_spin_flags((m), 0) #define mtx_trylock(m) mtx_trylock_flags((m), 0) #define mtx_unlock(m) mtx_unlock_flags((m), 0) #define mtx_unlock_spin(m) mtx_unlock_spin_flags((m), 0) struct mtx_pool; struct mtx_pool *mtx_pool_create(const char *mtx_name, int pool_size, int opts); void mtx_pool_destroy(struct mtx_pool **poolp); struct mtx *mtx_pool_find(struct mtx_pool *pool, void *ptr); struct mtx *mtx_pool_alloc(struct mtx_pool *pool); #define mtx_pool_lock(pool, ptr) \ mtx_lock(mtx_pool_find((pool), (ptr))) #define mtx_pool_lock_spin(pool, ptr) \ mtx_lock_spin(mtx_pool_find((pool), (ptr))) #define mtx_pool_unlock(pool, ptr) \ mtx_unlock(mtx_pool_find((pool), (ptr))) #define mtx_pool_unlock_spin(pool, ptr) \ mtx_unlock_spin(mtx_pool_find((pool), (ptr))) /* * mtxpool_lockbuilder is a pool of sleep locks that is not witness * checked and should only be used for building higher level locks. * * mtxpool_sleep is a general purpose pool of sleep mutexes. */ extern struct mtx_pool *mtxpool_lockbuilder; extern struct mtx_pool *mtxpool_sleep; #ifndef LOCK_DEBUG #error LOCK_DEBUG not defined, include before #endif #if LOCK_DEBUG > 0 || defined(MUTEX_NOINLINE) #define mtx_lock_flags(m, opts) \ _mtx_lock_flags((m), (opts), LOCK_FILE, LOCK_LINE) #define mtx_unlock_flags(m, opts) \ _mtx_unlock_flags((m), (opts), LOCK_FILE, LOCK_LINE) #define mtx_lock_spin_flags(m, opts) \ _mtx_lock_spin_flags((m), (opts), LOCK_FILE, LOCK_LINE) #define mtx_unlock_spin_flags(m, opts) \ _mtx_unlock_spin_flags((m), (opts), LOCK_FILE, LOCK_LINE) #else /* LOCK_DEBUG == 0 && !MUTEX_NOINLINE */ #define mtx_lock_flags(m, opts) \ _get_sleep_lock((m), curthread, (opts), LOCK_FILE, LOCK_LINE) #define mtx_unlock_flags(m, opts) \ _rel_sleep_lock((m), curthread, (opts), LOCK_FILE, LOCK_LINE) #define mtx_lock_spin_flags(m, opts) \ _get_spin_lock((m), curthread, (opts), LOCK_FILE, LOCK_LINE) #define mtx_unlock_spin_flags(m, opts) \ _rel_spin_lock((m)) #endif /* LOCK_DEBUG > 0 || MUTEX_NOINLINE */ #define mtx_trylock_flags(m, opts) \ _mtx_trylock((m), (opts), LOCK_FILE, LOCK_LINE) #define mtx_sleep(chan, mtx, pri, wmesg, timo) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), (timo)) #define mtx_initialized(m) lock_initalized(&(m)->lock_object) #define mtx_owned(m) (((m)->mtx_lock & ~MTX_FLAGMASK) == (uintptr_t)curthread) #define mtx_recursed(m) ((m)->mtx_recurse != 0) #define mtx_name(m) ((m)->lock_object.lo_name) /* * Global locks. */ extern struct mtx Giant; extern struct mtx blocked_lock; /* * Giant lock manipulation and clean exit macros. * Used to replace return with an exit Giant and return. * * Note that DROP_GIANT*() needs to be paired with PICKUP_GIANT() * The #ifndef is to allow lint-like tools to redefine DROP_GIANT. */ #ifndef DROP_GIANT #define DROP_GIANT() \ do { \ int _giantcnt = 0; \ WITNESS_SAVE_DECL(Giant); \ \ if (mtx_owned(&Giant)) { \ WITNESS_SAVE(&Giant.lock_object, Giant); \ for (_giantcnt = 0; mtx_owned(&Giant); _giantcnt++) \ mtx_unlock(&Giant); \ } #define PICKUP_GIANT() \ PARTIAL_PICKUP_GIANT(); \ } while (0) #define PARTIAL_PICKUP_GIANT() \ mtx_assert(&Giant, MA_NOTOWNED); \ if (_giantcnt > 0) { \ while (_giantcnt--) \ mtx_lock(&Giant); \ WITNESS_RESTORE(&Giant.lock_object, Giant); \ } #endif #define UGAR(rval) do { \ int _val = (rval); \ mtx_unlock(&Giant); \ return (_val); \ } while (0) /* - * Network MPSAFE temporary workarounds. When debug_mpsafenet - * is 1 the network is assumed to operate without Giant on the - * input path and protocols that require Giant must collect it - * on entry. When 0 Giant is grabbed in the network interface - * ISR's and in the netisr path and there is no need to grab - * the Giant lock. Note that, unlike PICKUP_GIANT() and - * DROP_GIANT(), these macros directly wrap mutex operations - * without special recursion handling. - * - * This mechanism is intended as temporary until everything of - * importance is properly locked. Note: the semantics for - * NET_{LOCK,UNLOCK}_GIANT() are not the same as DROP_GIANT() - * and PICKUP_GIANT(), as they are plain mutex operations - * without a recursion counter. + * With the advent of fine-grained locking, the Giant lock is no longer + * required around the network stack. These macros exist for historical + * reasons, allowing conditional acquisition of Giant based on a debugging + * setting, and will be removed. */ -extern int debug_mpsafenet; /* defined in net/netisr.c */ #define NET_LOCK_GIANT() do { \ - if (!debug_mpsafenet) \ - mtx_lock(&Giant); \ } while (0) #define NET_UNLOCK_GIANT() do { \ - if (!debug_mpsafenet) \ - mtx_unlock(&Giant); \ } while (0) #define NET_ASSERT_GIANT() do { \ - if (!debug_mpsafenet) \ - mtx_assert(&Giant, MA_OWNED); \ } while (0) -#define NET_CALLOUT_MPSAFE (debug_mpsafenet ? CALLOUT_MPSAFE : 0) +#define NET_CALLOUT_MPSAFE CALLOUT_MPSAFE struct mtx_args { struct mtx *ma_mtx; const char *ma_desc; int ma_opts; }; #define MTX_SYSINIT(name, mtx, desc, opts) \ static struct mtx_args name##_args = { \ (mtx), \ (desc), \ (opts) \ }; \ SYSINIT(name##_mtx_sysinit, SI_SUB_LOCK, SI_ORDER_MIDDLE, \ mtx_sysinit, &name##_args); \ SYSUNINIT(name##_mtx_sysuninit, SI_SUB_LOCK, SI_ORDER_MIDDLE, \ mtx_destroy, (mtx)) /* * The INVARIANTS-enabled mtx_assert() functionality. * * The constants need to be defined for INVARIANT_SUPPORT infrastructure * support as _mtx_assert() itself uses them and the latter implies that * _mtx_assert() must build. */ #if defined(INVARIANTS) || defined(INVARIANT_SUPPORT) #define MA_OWNED 0x01 #define MA_NOTOWNED 0x02 #define MA_RECURSED 0x04 #define MA_NOTRECURSED 0x08 #endif #ifdef INVARIANTS #define mtx_assert(m, what) \ _mtx_assert((m), (what), __FILE__, __LINE__) #define GIANT_REQUIRED mtx_assert(&Giant, MA_OWNED) #else /* INVARIANTS */ #define mtx_assert(m, what) #define GIANT_REQUIRED #endif /* INVARIANTS */ /* * Common lock type names. */ #define MTX_NETWORK_LOCK "network driver" #endif /* _KERNEL */ #endif /* !LOCORE */ #endif /* _SYS_MUTEX_H_ */