diff options
-rw-r--r-- | sys/conf/files | 2 | ||||
-rw-r--r-- | sys/dev/cxgbe/adapter.h | 19 | ||||
-rw-r--r-- | sys/dev/cxgbe/common/t4_hw.c | 44 | ||||
-rw-r--r-- | sys/dev/cxgbe/common/t4_hw.h | 1 | ||||
-rw-r--r-- | sys/dev/cxgbe/t4_ioctl.h | 23 | ||||
-rw-r--r-- | sys/dev/cxgbe/t4_main.c | 35 | ||||
-rw-r--r-- | sys/dev/cxgbe/t4_sge.c | 1 | ||||
-rw-r--r-- | sys/dev/cxgbe/t4_tracer.c | 519 | ||||
-rw-r--r-- | sys/modules/cxgbe/if_cxgbe/Makefile | 2 | ||||
-rw-r--r-- | tools/tools/cxgbetool/cxgbetool.c | 202 |
10 files changed, 825 insertions, 23 deletions
diff --git a/sys/conf/files b/sys/conf/files index b5fb11e..c74aa71 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1155,6 +1155,8 @@ dev/cxgbe/t4_sge.c optional cxgbe pci \ compile-with "${NORMAL_C} -I$S/dev/cxgbe" dev/cxgbe/t4_l2t.c optional cxgbe pci \ compile-with "${NORMAL_C} -I$S/dev/cxgbe" +dev/cxgbe/t4_tracer.c optional cxgbe pci \ + compile-with "${NORMAL_C} -I$S/dev/cxgbe" dev/cxgbe/common/t4_hw.c optional cxgbe pci \ compile-with "${NORMAL_C} -I$S/dev/cxgbe" t4fw_cfg.c optional cxgbe \ diff --git a/sys/dev/cxgbe/adapter.h b/sys/dev/cxgbe/adapter.h index c67570f..442baf5 100644 --- a/sys/dev/cxgbe/adapter.h +++ b/sys/dev/cxgbe/adapter.h @@ -172,6 +172,7 @@ enum { DOOMED = (1 << 0), PORT_INIT_DONE = (1 << 1), PORT_SYSCTL_CTX = (1 << 2), + HAS_TRACEQ = (1 << 3), }; #define IS_DOOMED(pi) ((pi)->flags & DOOMED) @@ -577,6 +578,14 @@ struct adapter { #endif int flags; + char ifp_lockname[16]; + struct mtx ifp_lock; + struct ifnet *ifp; /* tracer ifp */ + struct ifmedia media; + int traceq; /* iq used by all tracers, -1 if none */ + int tracer_valid; /* bitmap of valid tracers */ + int tracer_enabled; /* bitmap of enabled tracers */ + char fw_version[32]; char cfg_file[32]; u_int cfcsum; @@ -808,6 +817,16 @@ int t4_eth_tx(struct ifnet *, struct sge_txq *, struct mbuf *); void t4_update_fl_bufsize(struct ifnet *); int can_resume_tx(struct sge_eq *); +/* t4_tracer.c */ +struct t4_tracer; +void t4_tracer_modload(void); +void t4_tracer_modunload(void); +void t4_tracer_port_detach(struct adapter *); +int t4_get_tracer(struct adapter *, struct t4_tracer *); +int t4_set_tracer(struct adapter *, struct t4_tracer *); +int t4_trace_pkt(struct sge_iq *, const struct rss_header *, struct mbuf *); +int t5_trace_pkt(struct sge_iq *, const struct rss_header *, struct mbuf *); + static inline struct wrqe * alloc_wrqe(int wr_len, struct sge_wrq *wrq) { diff --git a/sys/dev/cxgbe/common/t4_hw.c b/sys/dev/cxgbe/common/t4_hw.c index 3005f63..61742d4 100644 --- a/sys/dev/cxgbe/common/t4_hw.c +++ b/sys/dev/cxgbe/common/t4_hw.c @@ -3624,22 +3624,24 @@ void t4_get_chan_txrate(struct adapter *adap, u64 *nic_rate, u64 *ofld_rate) * @idx: which filter to configure * @enable: whether to enable or disable the filter * - * Configures one of the tracing filters available in HW. If @enable is - * %0 @tp is not examined and may be %NULL. The user is responsible to - * set the single/multiple trace mode by writing to A_MPS_TRC_CFG register - * by using "cxgbtool iface reg reg_addr=val" command. See t4_sniffer/ - * docs/readme.txt for a complete description of how to setup traceing on - * T4. + * Configures one of the tracing filters available in HW. If @tp is %NULL + * it indicates that the filter is already written in the register and it + * just needs to be enabled or disabled. */ -int t4_set_trace_filter(struct adapter *adap, const struct trace_params *tp, int idx, - int enable) +int t4_set_trace_filter(struct adapter *adap, const struct trace_params *tp, + int idx, int enable) { int i, ofst = idx * 4; u32 data_reg, mask_reg, cfg; u32 multitrc = F_TRCMULTIFILTER; + u32 en = is_t4(adap) ? F_TFEN : F_T5_TFEN; - if (!enable) { - t4_write_reg(adap, A_MPS_TRC_FILTER_MATCH_CTL_A + ofst, 0); + if (idx < 0 || idx >= NTRACE) + return -EINVAL; + + if (tp == NULL || !enable) { + t4_set_reg_field(adap, A_MPS_TRC_FILTER_MATCH_CTL_A + ofst, en, + enable ? en : 0); return 0; } @@ -3660,8 +3662,7 @@ int t4_set_trace_filter(struct adapter *adap, const struct trace_params *tp, int */ if (tp->snap_len > ((10 * 1024 / 4) - (2 * 8))) return -EINVAL; - } - else { + } else { /* * If multiple tracers are disabled, to avoid deadlocks * maximum packet capture size of 9600 bytes is recommended. @@ -3672,12 +3673,13 @@ int t4_set_trace_filter(struct adapter *adap, const struct trace_params *tp, int return -EINVAL; } - if (tp->port > 11 || tp->invert > 1 || tp->skip_len > M_TFLENGTH || - tp->skip_ofst > M_TFOFFSET || tp->min_len > M_TFMINPKTSIZE) + if (tp->port > (is_t4(adap) ? 11 : 19) || tp->invert > 1 || + tp->skip_len > M_TFLENGTH || tp->skip_ofst > M_TFOFFSET || + tp->min_len > M_TFMINPKTSIZE) return -EINVAL; /* stop the tracer we'll be changing */ - t4_write_reg(adap, A_MPS_TRC_FILTER_MATCH_CTL_A + ofst, 0); + t4_set_reg_field(adap, A_MPS_TRC_FILTER_MATCH_CTL_A + ofst, en, 0); idx *= (A_MPS_TRC_FILTER1_MATCH - A_MPS_TRC_FILTER0_MATCH); data_reg = A_MPS_TRC_FILTER0_MATCH + idx; @@ -3691,11 +3693,10 @@ int t4_set_trace_filter(struct adapter *adap, const struct trace_params *tp, int V_TFCAPTUREMAX(tp->snap_len) | V_TFMINPKTSIZE(tp->min_len)); t4_write_reg(adap, A_MPS_TRC_FILTER_MATCH_CTL_A + ofst, - V_TFOFFSET(tp->skip_ofst) | V_TFLENGTH(tp->skip_len) | - is_t4(adap) ? - V_TFPORT(tp->port) | F_TFEN | V_TFINVERTMATCH(tp->invert) : - V_T5_TFPORT(tp->port) | F_T5_TFEN | - V_T5_TFINVERTMATCH(tp->invert)); + V_TFOFFSET(tp->skip_ofst) | V_TFLENGTH(tp->skip_len) | en | + (is_t4(adap) ? + V_TFPORT(tp->port) | V_TFINVERTMATCH(tp->invert) : + V_T5_TFPORT(tp->port) | V_T5_TFINVERTMATCH(tp->invert))); return 0; } @@ -3722,15 +3723,16 @@ void t4_get_trace_filter(struct adapter *adap, struct trace_params *tp, int idx, if (is_t4(adap)) { *enabled = !!(ctla & F_TFEN); tp->port = G_TFPORT(ctla); + tp->invert = !!(ctla & F_TFINVERTMATCH); } else { *enabled = !!(ctla & F_T5_TFEN); tp->port = G_T5_TFPORT(ctla); + tp->invert = !!(ctla & F_T5_TFINVERTMATCH); } tp->snap_len = G_TFCAPTUREMAX(ctlb); tp->min_len = G_TFMINPKTSIZE(ctlb); tp->skip_ofst = G_TFOFFSET(ctla); tp->skip_len = G_TFLENGTH(ctla); - tp->invert = !!(ctla & F_TFINVERTMATCH); ofst = (A_MPS_TRC_FILTER1_MATCH - A_MPS_TRC_FILTER0_MATCH) * idx; data_reg = A_MPS_TRC_FILTER0_MATCH + ofst; diff --git a/sys/dev/cxgbe/common/t4_hw.h b/sys/dev/cxgbe/common/t4_hw.h index 3bc2096..b0b82bd 100644 --- a/sys/dev/cxgbe/common/t4_hw.h +++ b/sys/dev/cxgbe/common/t4_hw.h @@ -45,6 +45,7 @@ enum { NTX_SCHED = 8, /* # of HW Tx scheduling queues */ PM_NSTATS = 5, /* # of PM stats */ MBOX_LEN = 64, /* mailbox size in bytes */ + NTRACE = 4, /* # of tracing filters */ TRACE_LEN = 112, /* length of trace data and mask */ FILTER_OPT_LEN = 36, /* filter tuple width of optional components */ NWOL_PAT = 8, /* # of WoL patterns */ diff --git a/sys/dev/cxgbe/t4_ioctl.h b/sys/dev/cxgbe/t4_ioctl.h index 5ad0597..a080f64 100644 --- a/sys/dev/cxgbe/t4_ioctl.h +++ b/sys/dev/cxgbe/t4_ioctl.h @@ -54,6 +54,8 @@ enum { T4_SET_OFLD_POLICY, /* Set offload policy */ T4_SET_SCHED_CLASS, /* set sched class */ T4_SET_SCHED_QUEUE, /* set queue class */ + T4_GET_TRACER, /* get information about a tracer */ + T4_SET_TRACER, /* program a tracer */ }; struct t4_reg { @@ -226,6 +228,25 @@ struct t4_mem_range { uint32_t *data; }; +#define T4_TRACE_LEN 112 +struct t4_trace_params { + uint32_t data[T4_TRACE_LEN / 4]; + uint32_t mask[T4_TRACE_LEN / 4]; + uint16_t snap_len; + uint16_t min_len; + uint8_t skip_ofst; + uint8_t skip_len; + uint8_t invert; + uint8_t port; +}; + +struct t4_tracer { + uint8_t idx; + uint8_t enabled; + uint8_t valid; + struct t4_trace_params tp; +}; + #define CHELSIO_T4_GETREG _IOWR('f', T4_GETREG, struct t4_reg) #define CHELSIO_T4_SETREG _IOW('f', T4_SETREG, struct t4_reg) #define CHELSIO_T4_REGDUMP _IOWR('f', T4_REGDUMP, struct t4_regdump) @@ -240,4 +261,6 @@ struct t4_mem_range { #define CHELSIO_T4_GET_MEM _IOW('f', T4_GET_MEM, struct t4_mem_range) #define CHELSIO_T4_GET_I2C _IOWR('f', T4_GET_I2C, struct t4_i2c_data) #define CHELSIO_T4_CLEAR_STATS _IOW('f', T4_CLEAR_STATS, uint32_t) +#define CHELSIO_T4_GET_TRACER _IOWR('f', T4_GET_TRACER, struct t4_tracer) +#define CHELSIO_T4_SET_TRACER _IOW('f', T4_SET_TRACER, struct t4_tracer) #endif diff --git a/sys/dev/cxgbe/t4_main.c b/sys/dev/cxgbe/t4_main.c index b0c5581..56438f0 100644 --- a/sys/dev/cxgbe/t4_main.c +++ b/sys/dev/cxgbe/t4_main.c @@ -557,6 +557,11 @@ t4_attach(device_t dev) pci_write_config(dev, i + PCIER_DEVICE_CTL, v, 2); } + sc->traceq = -1; + mtx_init(&sc->ifp_lock, sc->ifp_lockname, 0, MTX_DEF); + snprintf(sc->ifp_lockname, sizeof(sc->ifp_lockname), "%s tracer", + device_get_nameunit(dev)); + snprintf(sc->lockname, sizeof(sc->lockname), "%s", device_get_nameunit(dev)); mtx_init(&sc->sc_lock, sc->lockname, 0, MTX_DEF); @@ -588,8 +593,11 @@ t4_attach(device_t dev) for (i = 0; i < nitems(sc->fw_msg_handler); i++) sc->fw_msg_handler[i] = fw_msg_not_handled; t4_register_cpl_handler(sc, CPL_SET_TCB_RPL, t4_filter_rpl); + t4_register_cpl_handler(sc, CPL_TRACE_PKT, t4_trace_pkt); + t4_register_cpl_handler(sc, CPL_TRACE_PKT_T5, t5_trace_pkt); t4_init_sge_cpl_handlers(sc); + /* Prepare the adapter for operation */ rc = -t4_prep_adapter(sc); if (rc != 0) { @@ -668,6 +676,7 @@ t4_attach(device_t dev) snprintf(pi->lockname, sizeof(pi->lockname), "%sp%d", device_get_nameunit(dev), i); mtx_init(&pi->pi_lock, pi->lockname, 0, MTX_DEF); + sc->chan_map[pi->tx_chan] = i; if (is_10G_port(pi) || is_40G_port(pi)) { n10g++; @@ -916,6 +925,8 @@ t4_detach(device_t dev) mtx_destroy(&sc->tids.ftid_lock); if (mtx_initialized(&sc->sfl_lock)) mtx_destroy(&sc->sfl_lock); + if (mtx_initialized(&sc->ifp_lock)) + mtx_destroy(&sc->ifp_lock); bzero(sc, sizeof(*sc)); @@ -1018,6 +1029,11 @@ cxgbe_detach(device_t dev) #endif ADAPTER_UNLOCK(sc); + if (pi->flags & HAS_TRACEQ) { + sc->traceq = -1; /* cloner should not create ifnet */ + t4_tracer_port_detach(sc); + } + if (pi->vlan_c) EVENTHANDLER_DEREGISTER(vlan_config, pi->vlan_c); @@ -2887,6 +2903,17 @@ cxgbe_init_synchronized(struct port_info *pi) goto done; } + /* + * The first iq of the first port to come up is used for tracing. + */ + if (sc->traceq < 0) { + sc->traceq = sc->sge.rxq[pi->first_rxq].iq.abs_id; + t4_write_reg(sc, is_t4(sc) ? A_MPS_TRC_RSS_CONTROL : + A_MPS_T5_TRC_RSS_CONTROL, V_RSSCONTROL(pi->tx_chan) | + V_QUEUENUMBER(sc->traceq)); + pi->flags |= HAS_TRACEQ; + } + /* all ok */ setbit(&sc->open_device_map, pi->port_id); PORT_LOCK(pi); @@ -7414,6 +7441,12 @@ t4_ioctl(struct cdev *dev, unsigned long cmd, caddr_t data, int fflag, } break; } + case CHELSIO_T4_GET_TRACER: + rc = t4_get_tracer(sc, (struct t4_tracer *)data); + break; + case CHELSIO_T4_SET_TRACER: + rc = t4_set_tracer(sc, (struct t4_tracer *)data); + break; default: rc = EINVAL; } @@ -7650,12 +7683,14 @@ mod_event(module_t mod, int cmd, void *arg) mtx_init(&t4_uld_list_lock, "T4 ULDs", 0, MTX_DEF); SLIST_INIT(&t4_uld_list); #endif + t4_tracer_modload(); tweak_tunables(); break; case MOD_UNLOAD: if (atomic_fetchadd_int(&loaded, -1) > 1) break; + t4_tracer_modunload(); #ifdef TCP_OFFLOAD mtx_lock(&t4_uld_list_lock); if (!SLIST_EMPTY(&t4_uld_list)) { diff --git a/sys/dev/cxgbe/t4_sge.c b/sys/dev/cxgbe/t4_sge.c index 586137a..f78a170 100644 --- a/sys/dev/cxgbe/t4_sge.c +++ b/sys/dev/cxgbe/t4_sge.c @@ -276,7 +276,6 @@ t4_init_sge_cpl_handlers(struct adapter *sc) t4_register_cpl_handler(sc, CPL_FW6_MSG, handle_fw_msg); t4_register_cpl_handler(sc, CPL_SGE_EGR_UPDATE, handle_sge_egr_update); t4_register_cpl_handler(sc, CPL_RX_PKT, t4_eth_rx); - t4_register_fw_msg_handler(sc, FW6_TYPE_CMD_RPL, t4_handle_fw_rpl); } diff --git a/sys/dev/cxgbe/t4_tracer.c b/sys/dev/cxgbe/t4_tracer.c new file mode 100644 index 0000000..04f64e3 --- /dev/null +++ b/sys/dev/cxgbe/t4_tracer.c @@ -0,0 +1,519 @@ +/*- + * Copyright (c) 2013 Chelsio Communications, Inc. + * All rights reserved. + * Written by: Navdeep Parhar <np@FreeBSD.org> + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/lock.h> +#include <sys/types.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sx.h> +#include <net/bpf.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_clone.h> +#include <net/if_types.h> + +#include "common/common.h" +#include "common/t4_msg.h" +#include "common/t4_regs.h" +#include "t4_ioctl.h" + +/* + * Locking notes + * ============= + * + * An interface cloner is registered during mod_load and it can be used to + * create or destroy the tracing ifnet for an adapter at any time. It is + * possible for the cloned interface to outlive the adapter (adapter disappears + * in t4_detach but the tracing ifnet may live till mod_unload when removal of + * the cloner finally destroys any remaining cloned interfaces). When tracing + * filters are active, this ifnet is also receiving data. There are potential + * bad races between ifnet create, ifnet destroy, ifnet rx, ifnet ioctl, + * cxgbe_detach/t4_detach, mod_unload. + * + * a) The driver selects an iq for tracing (sc->traceq) inside a synch op. The + * iq is destroyed inside a synch op too (and sc->traceq updated). + * b) The cloner looks for an adapter that matches the name of the ifnet it's + * been asked to create, starts a synch op on that adapter, and proceeds only + * if the adapter has a tracing iq. + * c) The cloned ifnet and the adapter are coupled to each other via + * ifp->if_softc and sc->ifp. These can be modified only with the global + * t4_trace_lock sx as well as the sc->ifp_lock mutex held. Holding either + * of these will prevent any change. + * + * The order in which all the locks involved should be acquired are: + * t4_list_lock + * adapter lock + * (begin synch op and let go of the above two) + * t4_trace_lock + * sc->ifp_lock + */ + +static struct sx t4_trace_lock; +static const char *t4_cloner_name = "tXnex"; +static struct if_clone *t4_cloner; + +/* tracer ifnet routines. mostly no-ops. */ +static void tracer_init(void *); +static int tracer_ioctl(struct ifnet *, unsigned long, caddr_t); +static int tracer_transmit(struct ifnet *, struct mbuf *); +static void tracer_qflush(struct ifnet *); +static int tracer_media_change(struct ifnet *); +static void tracer_media_status(struct ifnet *, struct ifmediareq *); + +/* match name (request/response) */ +struct match_rr { + const char *name; + int lock; /* set to 1 to returned sc locked. */ + struct adapter *sc; + int rc; +}; + +static void +match_name(struct adapter *sc, void *arg) +{ + struct match_rr *mrr = arg; + + if (strcmp(device_get_nameunit(sc->dev), mrr->name) != 0) + return; + + KASSERT(mrr->sc == NULL, ("%s: multiple matches (%p, %p) for %s", + __func__, mrr->sc, sc, mrr->name)); + + mrr->sc = sc; + if (mrr->lock) + mrr->rc = begin_synchronized_op(mrr->sc, NULL, 0, "t4clon"); + else + mrr->rc = 0; +} + +static int +t4_cloner_match(struct if_clone *ifc, const char *name) +{ + struct match_rr mrr; + + mrr.name = name; + mrr.lock = 0; + mrr.sc = NULL; + t4_iterate(match_name, &mrr); + + return (mrr.sc != NULL); +} + +static int +t4_cloner_create(struct if_clone *ifc, char *name, size_t len, caddr_t params) +{ + struct match_rr mrr; + struct adapter *sc; + struct ifnet *ifp; + int rc, unit; + const uint8_t lla[ETHER_ADDR_LEN] = {0, 0, 0, 0, 0, 0}; + + mrr.name = name; + mrr.lock = 1; + mrr.sc = NULL; + mrr.rc = ENOENT; + t4_iterate(match_name, &mrr); + + if (mrr.rc != 0) + return (mrr.rc); + sc = mrr.sc; + + KASSERT(sc != NULL, ("%s: name (%s) matched but softc is NULL", + __func__, name)); + ASSERT_SYNCHRONIZED_OP(sc); + + sx_xlock(&t4_trace_lock); + + if (sc->ifp != NULL) { + rc = EEXIST; + goto done; + } + if (sc->traceq < 0) { + rc = EAGAIN; + goto done; + } + + + unit = -1; + rc = ifc_alloc_unit(ifc, &unit); + if (rc != 0) + goto done; + + ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + ifc_free_unit(ifc, unit); + rc = ENOMEM; + goto done; + } + + /* Note that if_xname is not <if_dname><if_dunit>. */ + strlcpy(ifp->if_xname, name, sizeof(ifp->if_xname)); + ifp->if_dname = t4_cloner_name; + ifp->if_dunit = unit; + ifp->if_init = tracer_init; + ifp->if_flags = IFF_SIMPLEX | IFF_DRV_RUNNING; + ifp->if_ioctl = tracer_ioctl; + ifp->if_transmit = tracer_transmit; + ifp->if_qflush = tracer_qflush; + ifp->if_capabilities = IFCAP_JUMBO_MTU | IFCAP_VLAN_MTU; + ifmedia_init(&sc->media, IFM_IMASK, tracer_media_change, + tracer_media_status); + ifmedia_add(&sc->media, IFM_ETHER | IFM_FDX | IFM_NONE, 0, NULL); + ifmedia_set(&sc->media, IFM_ETHER | IFM_FDX | IFM_NONE); + ether_ifattach(ifp, lla); + if_up(ifp); + + mtx_lock(&sc->ifp_lock); + ifp->if_softc = sc; + sc->ifp = ifp; + mtx_unlock(&sc->ifp_lock); +done: + sx_xunlock(&t4_trace_lock); + end_synchronized_op(sc, 0); + return (rc); +} + +static int +t4_cloner_destroy(struct if_clone *ifc, struct ifnet *ifp) +{ + struct adapter *sc; + int unit = ifp->if_dunit; + + sx_xlock(&t4_trace_lock); + sc = ifp->if_softc; + if (sc != NULL) { + mtx_lock(&sc->ifp_lock); + sc->ifp = NULL; + ifp->if_softc = NULL; + mtx_unlock(&sc->ifp_lock); + ifmedia_removeall(&sc->media); + } + ether_ifdetach(ifp); + if_free(ifp); + ifc_free_unit(ifc, unit); + sx_xunlock(&t4_trace_lock); + + return (0); +} + +void +t4_tracer_modload() +{ + + sx_init(&t4_trace_lock, "T4/T5 tracer lock"); + t4_cloner = if_clone_advanced(t4_cloner_name, 0, t4_cloner_match, + t4_cloner_create, t4_cloner_destroy); +} + +void +t4_tracer_modunload() +{ + + if (t4_cloner != NULL) { + /* + * The module is being unloaded so the nexus drivers have + * detached. The tracing interfaces can not outlive the nexus + * (ifp->if_softc is the nexus) and must have been destroyed + * already. XXX: but if_clone is opaque to us and we can't + * assert LIST_EMPTY(&t4_cloner->ifc_iflist) at this time. + */ + if_clone_detach(t4_cloner); + } + sx_destroy(&t4_trace_lock); +} + +void +t4_tracer_port_detach(struct adapter *sc) +{ + + sx_xlock(&t4_trace_lock); + if (sc->ifp != NULL) { + mtx_lock(&sc->ifp_lock); + sc->ifp->if_softc = NULL; + sc->ifp = NULL; + mtx_unlock(&sc->ifp_lock); + } + ifmedia_removeall(&sc->media); + sx_xunlock(&t4_trace_lock); +} + +int +t4_get_tracer(struct adapter *sc, struct t4_tracer *t) +{ + int rc, i, enabled; + struct trace_params tp; + + if (t->idx >= NTRACE) { + t->idx = 0xff; + t->enabled = 0; + t->valid = 0; + return (0); + } + + rc = begin_synchronized_op(sc, NULL, HOLD_LOCK | SLEEP_OK | INTR_OK, + "t4gett"); + if (rc) + return (rc); + + for (i = t->idx; i < NTRACE; i++) { + if (isset(&sc->tracer_valid, t->idx)) { + t4_get_trace_filter(sc, &tp, i, &enabled); + t->idx = i; + t->enabled = enabled; + t->valid = 1; + memcpy(&t->tp.data[0], &tp.data[0], sizeof(t->tp.data)); + memcpy(&t->tp.mask[0], &tp.mask[0], sizeof(t->tp.mask)); + t->tp.snap_len = tp.snap_len; + t->tp.min_len = tp.min_len; + t->tp.skip_ofst = tp.skip_ofst; + t->tp.skip_len = tp.skip_len; + t->tp.invert = tp.invert; + + /* convert channel to port iff 0 <= port < 8. */ + if (tp.port < 4) + t->tp.port = sc->chan_map[tp.port]; + else if (tp.port < 8) + t->tp.port = sc->chan_map[tp.port - 4] + 4; + else + t->tp.port = tp.port; + + goto done; + } + } + + t->idx = 0xff; + t->enabled = 0; + t->valid = 0; +done: + end_synchronized_op(sc, LOCK_HELD); + + return (rc); +} + +int +t4_set_tracer(struct adapter *sc, struct t4_tracer *t) +{ + int rc; + struct trace_params tp, *tpp; + + if (t->idx >= NTRACE) + return (EINVAL); + + rc = begin_synchronized_op(sc, NULL, HOLD_LOCK | SLEEP_OK | INTR_OK, + "t4sett"); + if (rc) + return (rc); + + /* + * If no tracing filter is specified this time then check if the filter + * at the index is valid anyway because it was set previously. If so + * then this is a legitimate enable/disable operation. + */ + if (t->valid == 0) { + if (isset(&sc->tracer_valid, t->idx)) + tpp = NULL; + else + rc = EINVAL; + goto done; + } + + if (t->tp.port > 19 || t->tp.snap_len > 9600 || + t->tp.min_len > M_TFMINPKTSIZE || t->tp.skip_len > M_TFLENGTH || + t->tp.skip_ofst > M_TFOFFSET) { + rc = EINVAL; + goto done; + } + + memcpy(&tp.data[0], &t->tp.data[0], sizeof(tp.data)); + memcpy(&tp.mask[0], &t->tp.mask[0], sizeof(tp.mask)); + tp.snap_len = t->tp.snap_len; + tp.min_len = t->tp.min_len; + tp.skip_ofst = t->tp.skip_ofst; + tp.skip_len = t->tp.skip_len; + tp.invert = !!t->tp.invert; + + /* convert port to channel iff 0 <= port < 8. */ + if (t->tp.port < 4) { + if (sc->port[t->tp.port] == NULL) { + rc = EINVAL; + goto done; + } + tp.port = sc->port[t->tp.port]->tx_chan; + } else if (t->tp.port < 8) { + if (sc->port[t->tp.port - 4] == NULL) { + rc = EINVAL; + goto done; + } + tp.port = sc->port[t->tp.port - 4]->tx_chan + 4; + } + tpp = &tp; +done: + if (rc == 0) { + rc = -t4_set_trace_filter(sc, tpp, t->idx, t->enabled); + if (rc == 0) { + if (t->enabled) { + setbit(&sc->tracer_valid, t->idx); + if (sc->tracer_enabled == 0) { + t4_set_reg_field(sc, A_MPS_TRC_CFG, + F_TRCEN, F_TRCEN); + } + setbit(&sc->tracer_enabled, t->idx); + } else { + clrbit(&sc->tracer_enabled, t->idx); + if (sc->tracer_enabled == 0) { + t4_set_reg_field(sc, A_MPS_TRC_CFG, + F_TRCEN, 0); + } + } + } + } + end_synchronized_op(sc, LOCK_HELD); + + return (rc); +} + +int +t4_trace_pkt(struct sge_iq *iq, const struct rss_header *rss, struct mbuf *m) +{ + struct adapter *sc = iq->adapter; + struct ifnet *ifp; + + KASSERT(m != NULL, ("%s: no payload with opcode %02x", __func__, + rss->opcode)); + + mtx_lock(&sc->ifp_lock); + ifp = sc->ifp; + if (sc->ifp) { + m_adj(m, sizeof(struct cpl_trace_pkt)); + m->m_pkthdr.rcvif = ifp; + ETHER_BPF_MTAP(ifp, m); + } + mtx_unlock(&sc->ifp_lock); + m_freem(m); + + return (0); +} + +int +t5_trace_pkt(struct sge_iq *iq, const struct rss_header *rss, struct mbuf *m) +{ + struct adapter *sc = iq->adapter; + struct ifnet *ifp; + + KASSERT(m != NULL, ("%s: no payload with opcode %02x", __func__, + rss->opcode)); + + mtx_lock(&sc->ifp_lock); + ifp = sc->ifp; + if (ifp != NULL) { + m_adj(m, sizeof(struct cpl_t5_trace_pkt)); + m->m_pkthdr.rcvif = ifp; + ETHER_BPF_MTAP(ifp, m); + } + mtx_unlock(&sc->ifp_lock); + m_freem(m); + + return (0); +} + + +static void +tracer_init(void *arg) +{ + + return; +} + +static int +tracer_ioctl(struct ifnet *ifp, unsigned long cmd, caddr_t data) +{ + int rc = 0; + struct adapter *sc; + struct ifreq *ifr = (struct ifreq *)data; + + switch (cmd) { + case SIOCSIFMTU: + case SIOCSIFFLAGS: + case SIOCADDMULTI: + case SIOCDELMULTI: + case SIOCSIFCAP: + break; + case SIOCSIFMEDIA: + case SIOCGIFMEDIA: + sx_xlock(&t4_trace_lock); + sc = ifp->if_softc; + if (sc == NULL) + rc = EIO; + else + rc = ifmedia_ioctl(ifp, ifr, &sc->media, cmd); + sx_xunlock(&t4_trace_lock); + break; + default: + rc = ether_ioctl(ifp, cmd, data); + } + + return (rc); +} + +static int +tracer_transmit(struct ifnet *ifp, struct mbuf *m) +{ + + m_freem(m); + return (0); +} + +static void +tracer_qflush(struct ifnet *ifp) +{ + + return; +} + +static int +tracer_media_change(struct ifnet *ifp) +{ + + return (EOPNOTSUPP); +} + +static void +tracer_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + + ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; + + return; +} diff --git a/sys/modules/cxgbe/if_cxgbe/Makefile b/sys/modules/cxgbe/if_cxgbe/Makefile index 1e0eb9d..f4ebcdd 100644 --- a/sys/modules/cxgbe/if_cxgbe/Makefile +++ b/sys/modules/cxgbe/if_cxgbe/Makefile @@ -8,7 +8,7 @@ CXGBE = ${.CURDIR}/../../../dev/cxgbe .PATH: ${CXGBE} ${CXGBE}/common KMOD = if_cxgbe -SRCS = t4_main.c t4_sge.c t4_l2t.c +SRCS = t4_main.c t4_sge.c t4_l2t.c t4_tracer.c SRCS+= t4_hw.c SRCS+= device_if.h bus_if.h pci_if.h SRCS+= opt_inet.h opt_inet6.h diff --git a/tools/tools/cxgbetool/cxgbetool.c b/tools/tools/cxgbetool/cxgbetool.c index a97cf1f..44c4b2d 100644 --- a/tools/tools/cxgbetool/cxgbetool.c +++ b/tools/tools/cxgbetool/cxgbetool.c @@ -98,6 +98,9 @@ usage(FILE *fp) "\tregdump [<module>] ... dump registers\n" "\tstdio interactive mode\n" "\ttcb <tid> read TCB\n" + "\ttracer <idx> tx<n>|rx<n> set and enable a tracer)\n" + "\ttracer <idx> disable|enable disable or enable a tracer\n" + "\ttracer list list all tracers\n" ); } @@ -1658,6 +1661,203 @@ clearstats(int argc, const char *argv[]) } static int +show_tracers(void) +{ + struct t4_tracer t; + char *s; + int rc, port_idx, i; + long long val; + + /* Magic values: MPS_TRC_CFG = 0x9800. MPS_TRC_CFG[1:1] = TrcEn */ + rc = read_reg(0x9800, 4, &val); + if (rc != 0) + return (rc); + printf("tracing is %s\n", val & 2 ? "ENABLED" : "DISABLED"); + + t.idx = 0; + for (t.idx = 0; ; t.idx++) { + rc = doit(CHELSIO_T4_GET_TRACER, &t); + if (rc != 0 || t.idx == 0xff) + break; + + if (t.tp.port < 4) { + s = "Rx"; + port_idx = t.tp.port; + } else if (t.tp.port < 8) { + s = "Tx"; + port_idx = t.tp.port - 4; + } else if (t.tp.port < 12) { + s = "loopback"; + port_idx = t.tp.port - 8; + } else if (t.tp.port < 16) { + s = "MPS Rx"; + port_idx = t.tp.port - 12; + } else if (t.tp.port < 20) { + s = "MPS Tx"; + port_idx = t.tp.port - 16; + } else { + s = "unknown"; + port_idx = t.tp.port; + } + + printf("\ntracer %u (currently %s) captures ", t.idx, + t.enabled ? "ENABLED" : "DISABLED"); + if (t.tp.port < 8) + printf("port %u %s, ", port_idx, s); + else + printf("%s %u, ", s, port_idx); + printf("snap length: %u, min length: %u\n", t.tp.snap_len, + t.tp.min_len); + printf("packets captured %smatch filter\n", + t.tp.invert ? "do not " : ""); + if (t.tp.skip_ofst) { + printf("filter pattern: "); + for (i = 0; i < t.tp.skip_ofst * 2; i += 2) + printf("%08x%08x", t.tp.data[i], + t.tp.data[i + 1]); + printf("/"); + for (i = 0; i < t.tp.skip_ofst * 2; i += 2) + printf("%08x%08x", t.tp.mask[i], + t.tp.mask[i + 1]); + printf("@0\n"); + } + printf("filter pattern: "); + for (i = t.tp.skip_ofst * 2; i < T4_TRACE_LEN / 4; i += 2) + printf("%08x%08x", t.tp.data[i], t.tp.data[i + 1]); + printf("/"); + for (i = t.tp.skip_ofst * 2; i < T4_TRACE_LEN / 4; i += 2) + printf("%08x%08x", t.tp.mask[i], t.tp.mask[i + 1]); + printf("@%u\n", (t.tp.skip_ofst + t.tp.skip_len) * 8); + } + + return (rc); +} + +static int +tracer_onoff(uint8_t idx, int enabled) +{ + struct t4_tracer t; + + t.idx = idx; + t.enabled = enabled; + t.valid = 0; + + return doit(CHELSIO_T4_SET_TRACER, &t); +} + +static void +create_tracing_ifnet() +{ + char *cmd[] = { + "/sbin/ifconfig", __DECONST(char *, nexus), "create", NULL + }; + char *env[] = {NULL}; + + if (vfork() == 0) { + close(STDERR_FILENO); + execve(cmd[0], cmd, env); + _exit(0); + } +} + +/* + * XXX: Allow user to specify snaplen, minlen, and pattern (including inverted + * matching). Right now this is a quick-n-dirty implementation that traces the + * first 128B of all tx or rx on a port + */ +static int +set_tracer(uint8_t idx, int argc, const char *argv[]) +{ + struct t4_tracer t; + int len, port; + + bzero(&t, sizeof (t)); + t.idx = idx; + t.enabled = 1; + t.valid = 1; + + if (argc != 1) { + warnx("must specify tx<n> or rx<n>."); + return (EINVAL); + } + + len = strlen(argv[0]); + if (len != 3) { + warnx("argument must be 3 characters (tx<n> or rx<n>)"); + return (EINVAL); + } + + if (strncmp(argv[0], "tx", 2) == 0) { + port = argv[0][2] - '0'; + if (port < 0 || port > 3) { + warnx("'%c' in %s is invalid", argv[0][2], argv[0]); + return (EINVAL); + } + port += 4; + } else if (strncmp(argv[0], "rx", 2) == 0) { + port = argv[0][2] - '0'; + if (port < 0 || port > 3) { + warnx("'%c' in %s is invalid", argv[0][2], argv[0]); + return (EINVAL); + } + } else { + warnx("argument '%s' isn't tx<n> or rx<n>", argv[0]); + return (EINVAL); + } + + t.tp.snap_len = 128; + t.tp.min_len = 0; + t.tp.skip_ofst = 0; + t.tp.skip_len = 0; + t.tp.invert = 0; + t.tp.port = port; + + create_tracing_ifnet(); + return doit(CHELSIO_T4_SET_TRACER, &t); +} + +static int +tracer_cmd(int argc, const char *argv[]) +{ + long long val; + uint8_t idx; + char *s; + + if (argc == 0) { + warnx("tracer: no arguments."); + return (EINVAL); + }; + + /* list */ + if (strcmp(argv[0], "list") == 0) { + if (argc != 1) + warnx("trailing arguments after \"list\" ignored."); + + return show_tracers(); + } + + /* <idx> ... */ + s = str_to_number(argv[0], NULL, &val); + if (*s || val > 0xff) { + warnx("\"%s\" is neither an index nor a tracer subcommand.", + argv[0]); + return (EINVAL); + } + idx = (int8_t)val; + + /* <idx> disable */ + if (argc == 2 && strcmp(argv[1], "disable") == 0) + return tracer_onoff(idx, 0); + + /* <idx> enable */ + if (argc == 2 && strcmp(argv[1], "enable") == 0) + return tracer_onoff(idx, 1); + + /* <idx> ... */ + return set_tracer(idx, argc - 1, argv + 1); +} + +static int run_cmd(int argc, const char *argv[]) { int rc = -1; @@ -1687,6 +1887,8 @@ run_cmd(int argc, const char *argv[]) rc = read_i2c(argc, argv); else if (!strcmp(cmd, "clearstats")) rc = clearstats(argc, argv); + else if (!strcmp(cmd, "tracer")) + rc = tracer_cmd(argc, argv); else { rc = EINVAL; warnx("invalid command \"%s\"", cmd); |