[BACK]Return to if_tpmr.c CVS log [TXT][DIR] Up to [local] / src / sys / net

File: [local] / src / sys / net / if_tpmr.c (download)

Revision 1.35, Sat Dec 23 10:52:54 2023 UTC (5 months, 2 weeks ago) by bluhm
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.34: +2 -1 lines

Backout always allocate per-CPU statistics counters for network
interface descriptor.  It panics during attach of em(4) device at
boot.

/*	$OpenBSD: if_tpmr.c,v 1.35 2023/12/23 10:52:54 bluhm Exp $ */

/*
 * Copyright (c) 2019 The University of Queensland
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This code was written by David Gwynne <dlg@uq.edu.au> as part
 * of the Information Technology Infrastructure Group (ITIG) in the
 * Faculty of Engineering, Architecture and Information Technology
 * (EAIT).
 */

#include "bpfilter.h"
#include "pf.h"
#include "vlan.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include <sys/syslog.h>
#include <sys/rwlock.h>
#include <sys/percpu.h>
#include <sys/smr.h>
#include <sys/task.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net/if_bridge.h>

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#if NPF > 0
#include <net/pfvar.h>
#endif

#if NVLAN > 0
#include <net/if_vlan_var.h>
#endif

/*
 * tpmr interface
 */

#define TPMR_NUM_PORTS		2

struct tpmr_softc;

struct tpmr_port {
	struct ifnet		*p_ifp0;

	int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
	    struct rtentry *);

	struct task		 p_ltask;
	struct task		 p_dtask;

	struct tpmr_softc	*p_tpmr;
	unsigned int		 p_slot;

	int		 	 p_refcnt;

	struct ether_brport	 p_brport;
};

struct tpmr_softc {
	struct ifnet		 sc_if;
	unsigned int		 sc_dead;

	struct tpmr_port	*sc_ports[TPMR_NUM_PORTS];
	unsigned int		 sc_nports;
};

#define DPRINTF(_sc, fmt...)	do { \
	if (ISSET((_sc)->sc_if.if_flags, IFF_DEBUG)) \
		printf(fmt); \
} while (0)

static int	tpmr_clone_create(struct if_clone *, int);
static int	tpmr_clone_destroy(struct ifnet *);

static int	tpmr_ioctl(struct ifnet *, u_long, caddr_t);
static int	tpmr_enqueue(struct ifnet *, struct mbuf *);
static int	tpmr_output(struct ifnet *, struct mbuf *, struct sockaddr *,
		    struct rtentry *);
static void	tpmr_start(struct ifqueue *);

static int	tpmr_up(struct tpmr_softc *);
static int	tpmr_down(struct tpmr_softc *);
static int	tpmr_iff(struct tpmr_softc *);

static void	tpmr_p_linkch(void *);
static void	tpmr_p_detach(void *);
static int	tpmr_p_ioctl(struct ifnet *, u_long, caddr_t);
static int	tpmr_p_output(struct ifnet *, struct mbuf *,
		    struct sockaddr *, struct rtentry *);

static void	tpmr_p_dtor(struct tpmr_softc *, struct tpmr_port *,
		    const char *);
static int	tpmr_add_port(struct tpmr_softc *,
		    const struct ifbreq *);
static int	tpmr_del_port(struct tpmr_softc *,
		    const struct ifbreq *);
static int	tpmr_port_list(struct tpmr_softc *, struct ifbifconf *);
static void	tpmr_p_take(void *);
static void	tpmr_p_rele(void *);

static struct if_clone tpmr_cloner =
    IF_CLONE_INITIALIZER("tpmr", tpmr_clone_create, tpmr_clone_destroy);

void
tpmrattach(int count)
{
	if_clone_attach(&tpmr_cloner);
}

static int
tpmr_clone_create(struct if_clone *ifc, int unit)
{
	struct tpmr_softc *sc;
	struct ifnet *ifp;

	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
	if (sc == NULL)
		return (ENOMEM);

	ifp = &sc->sc_if;

	snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
	    ifc->ifc_name, unit);

	ifp->if_softc = sc;
	ifp->if_type = IFT_BRIDGE;
	ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
	ifp->if_mtu = 0;
	ifp->if_addrlen = ETHER_ADDR_LEN;
	ifp->if_hdrlen = ETHER_HDR_LEN;
	ifp->if_ioctl = tpmr_ioctl;
	ifp->if_output = tpmr_output;
	ifp->if_enqueue = tpmr_enqueue;
	ifp->if_qstart = tpmr_start;
	ifp->if_flags = IFF_POINTOPOINT;
	ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
	ifp->if_link_state = LINK_STATE_DOWN;

	if_counters_alloc(ifp);
	if_attach(ifp);
	if_alloc_sadl(ifp);

#if NBPFILTER > 0
	bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, ETHER_HDR_LEN);
#endif

	ifp->if_llprio = IFQ_MAXPRIO;

	return (0);
}

static int
tpmr_clone_destroy(struct ifnet *ifp)
{
	struct tpmr_softc *sc = ifp->if_softc;
	unsigned int i;

	NET_LOCK();
	sc->sc_dead = 1;

	if (ISSET(ifp->if_flags, IFF_RUNNING))
		tpmr_down(sc);
	NET_UNLOCK();

	if_detach(ifp);

	NET_LOCK();
	for (i = 0; i < nitems(sc->sc_ports); i++) {
		struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
		if (p == NULL)
			continue;
		tpmr_p_dtor(sc, p, "destroy");
	}
	NET_UNLOCK();

	free(sc, M_DEVBUF, sizeof(*sc));

	return (0);
}

static int
tpmr_vlan_filter(const struct mbuf *m)
{
	const struct ether_header *eh;

	eh = mtod(m, struct ether_header *);
	switch (ntohs(eh->ether_type)) {
	case ETHERTYPE_VLAN:
	case ETHERTYPE_QINQ:
		return (1);
	default:
		break;
	}

	return (0);
}

static int
tpmr_8021q_filter(const struct mbuf *m, uint64_t dst)
{
	if (ETH64_IS_8021_RSVD(dst)) {
		switch (dst & 0xf) {
		case 0x01: /* IEEE MAC-specific Control Protocols */
		case 0x02: /* IEEE 802.3 Slow Protocols */
		case 0x04: /* IEEE MAC-specific Control Protocols */
		case 0x0e: /* Individual LAN Scope, Nearest Bridge */
			return (1);
		default:
			break;
		}
	}

	return (0);
}

#if NPF > 0
struct tpmr_pf_ip_family {
	sa_family_t	   af;
	struct mbuf	*(*ip_check)(struct ifnet *, struct mbuf *);
	void		 (*ip_input)(struct ifnet *, struct mbuf *);
};

static const struct tpmr_pf_ip_family tpmr_pf_ipv4 = {
	.af		= AF_INET,
	.ip_check	= ipv4_check,
	.ip_input	= ipv4_input,
};

#ifdef INET6
static const struct tpmr_pf_ip_family tpmr_pf_ipv6 = {
	.af		= AF_INET6,
	.ip_check	= ipv6_check,
	.ip_input	= ipv6_input,
};
#endif

static struct mbuf *
tpmr_pf(struct ifnet *ifp0, int dir, struct mbuf *m)
{
	struct ether_header *eh, copy;
	const struct tpmr_pf_ip_family *fam;

	eh = mtod(m, struct ether_header *);
	switch (ntohs(eh->ether_type)) {
	case ETHERTYPE_IP:
		fam = &tpmr_pf_ipv4;
		break;
#ifdef INET6
	case ETHERTYPE_IPV6:
		fam = &tpmr_pf_ipv6;
		break;
#endif
	default:
		return (m);
	}

	copy = *eh;
	m_adj(m, sizeof(*eh));

	if (dir == PF_IN) {
		m = (*fam->ip_check)(ifp0, m);
		if (m == NULL)
			return (NULL);
	}

	if (pf_test(fam->af, dir, ifp0, &m) != PF_PASS) {
		m_freem(m);
		return (NULL);
	}
	if (m == NULL)
		return (NULL);

	if (dir == PF_IN && ISSET(m->m_pkthdr.pf.flags, PF_TAG_DIVERTED)) {
		pf_mbuf_unlink_state_key(m);
		pf_mbuf_unlink_inpcb(m);
		(*fam->ip_input)(ifp0, m);
		return (NULL);
	}

	m = m_prepend(m, sizeof(*eh), M_DONTWAIT);
	if (m == NULL)
		return (NULL);

	/* checksum? */

	eh = mtod(m, struct ether_header *);
	*eh = copy;

	return (m);
}
#endif /* NPF > 0 */

static struct mbuf *
tpmr_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport)
{
	struct tpmr_port *p = brport;
	struct tpmr_softc *sc = p->p_tpmr;
	struct ifnet *ifp = &sc->sc_if;
	struct ifnet *ifpn;
	unsigned int iff;
	struct tpmr_port *pn;
	int len;
#if NBPFILTER > 0
	caddr_t if_bpf;
#endif

	iff = READ_ONCE(ifp->if_flags);
	if (!ISSET(iff, IFF_RUNNING))
		goto drop;

#if NVLAN > 0
	/*
	 * If the underlying interface removed the VLAN header itself,
	 * add it back.
	 */
	if (ISSET(m->m_flags, M_VLANTAG)) {
		m = vlan_inject(m, ETHERTYPE_VLAN, m->m_pkthdr.ether_vtag);
		if (m == NULL) {
			counters_inc(ifp->if_counters, ifc_ierrors);
			goto drop;
		}
	}
#endif

	if (!ISSET(iff, IFF_LINK2) &&
	    tpmr_vlan_filter(m))
		goto drop;

	if (!ISSET(iff, IFF_LINK0) &&
	    tpmr_8021q_filter(m, dst))
		goto drop;

#if NPF > 0
	if (!ISSET(iff, IFF_LINK1) &&
	    (m = tpmr_pf(ifp0, PF_IN, m)) == NULL)
		return (NULL);
#endif

	len = m->m_pkthdr.len;
	counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes, len);

#if NBPFILTER > 0
	if_bpf = READ_ONCE(ifp->if_bpf);
	if (if_bpf) {
		if (bpf_mtap(if_bpf, m, 0))
			goto drop;
	}
#endif

	smr_read_enter();
	pn = SMR_PTR_GET(&sc->sc_ports[!p->p_slot]);
	if (pn != NULL)
		tpmr_p_take(pn);
	smr_read_leave();
	if (pn == NULL)
		goto drop;

	ifpn = pn->p_ifp0;
#if NPF > 0
	if (!ISSET(iff, IFF_LINK1) &&
	    (m = tpmr_pf(ifpn, PF_OUT, m)) == NULL) {
		tpmr_p_rele(pn);
		return (NULL);
	}
#endif

	if (if_enqueue(ifpn, m))
		counters_inc(ifp->if_counters, ifc_oerrors);
	else {
		counters_pkt(ifp->if_counters,
		    ifc_opackets, ifc_obytes, len);
	}

	tpmr_p_rele(pn);

	return (NULL);

drop:
	m_freem(m);
	return (NULL);
}

static int
tpmr_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
    struct rtentry *rt)
{
	m_freem(m);
	return (ENODEV);
}

static int
tpmr_enqueue(struct ifnet *ifp, struct mbuf *m)
{
	m_freem(m);
	return (ENODEV);
}

static void
tpmr_start(struct ifqueue *ifq)
{
	ifq_purge(ifq);
}

static int
tpmr_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct tpmr_softc *sc = ifp->if_softc;
	int error = 0;

	if (sc->sc_dead)
		return (ENXIO);

	switch (cmd) {
	case SIOCSIFFLAGS:
		if (ISSET(ifp->if_flags, IFF_UP)) {
			if (!ISSET(ifp->if_flags, IFF_RUNNING))
				error = tpmr_up(sc);
		} else {
			if (ISSET(ifp->if_flags, IFF_RUNNING))
				error = tpmr_down(sc);
		}
		break;

	case SIOCBRDGADD:
		error = suser(curproc);
		if (error != 0)
			break;

		error = tpmr_add_port(sc, (struct ifbreq *)data);
		break;
	case SIOCBRDGDEL:
		error = suser(curproc);
		if (error != 0)
			break;

		error = tpmr_del_port(sc, (struct ifbreq *)data);
		break;
	case SIOCBRDGIFS:
		error = tpmr_port_list(sc, (struct ifbifconf *)data);
		break;
	/* stub for ifconfig(8) brconfig.c:bridge_rules() */
	case SIOCBRDGGRL:
		((struct ifbrlconf *)data)->ifbrl_len = 0;
		break;

	default:
		error = ENOTTY;
		break;
	}

	if (error == ENETRESET)
		error = tpmr_iff(sc);

	return (error);
}

static int
tpmr_add_port(struct tpmr_softc *sc, const struct ifbreq *req)
{
	struct ifnet *ifp = &sc->sc_if;
	struct ifnet *ifp0;
	struct tpmr_port **pp;
	struct tpmr_port *p;
	int i;
	int error;

	NET_ASSERT_LOCKED();
	if (sc->sc_nports >= nitems(sc->sc_ports))
		return (ENOSPC);

	ifp0 = if_unit(req->ifbr_ifsname);
	if (ifp0 == NULL)
		return (EINVAL);

	if (ifp0->if_type != IFT_ETHER) {
		error = EPROTONOSUPPORT;
		goto put;
	}

	error = ether_brport_isset(ifp0);
	if (error != 0)
		goto put;

	/* let's try */

	p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
	if (p == NULL) {
		error = ENOMEM;
		goto put;
	}

	ifsetlro(ifp0, 0);

	p->p_ifp0 = ifp0;
	p->p_tpmr = sc;

	p->p_ioctl = ifp0->if_ioctl;
	p->p_output = ifp0->if_output;

	error = ifpromisc(ifp0, 1);
	if (error != 0)
		goto free;

	/* this might have changed if we slept for malloc or ifpromisc */
	error = ether_brport_isset(ifp0);
	if (error != 0)
		goto unpromisc;

	task_set(&p->p_ltask, tpmr_p_linkch, p);
	if_linkstatehook_add(ifp0, &p->p_ltask);

	task_set(&p->p_dtask, tpmr_p_detach, p);
	if_detachhook_add(ifp0, &p->p_dtask);

	p->p_brport.eb_input = tpmr_input;
	p->p_brport.eb_port_take = tpmr_p_take;
	p->p_brport.eb_port_rele = tpmr_p_rele;
	p->p_brport.eb_port = p;

	/* commit */
	DPRINTF(sc, "%s %s trunkport: creating port\n",
	    ifp->if_xname, ifp0->if_xname);

	for (i = 0; i < nitems(sc->sc_ports); i++) {
		pp = &sc->sc_ports[i];
		if (SMR_PTR_GET_LOCKED(pp) == NULL)
			break;
	}
	sc->sc_nports++;

	p->p_slot = i;

	tpmr_p_take(p);
	ether_brport_set(ifp0, &p->p_brport);
	ifp0->if_ioctl = tpmr_p_ioctl;
	ifp0->if_output = tpmr_p_output;

	SMR_PTR_SET_LOCKED(pp, p);

	tpmr_p_linkch(p);

	return (0);

unpromisc:
	ifpromisc(ifp0, 0);
free:
	free(p, M_DEVBUF, sizeof(*p));
put:
	if_put(ifp0);
	return (error);
}

static struct tpmr_port *
tpmr_trunkport(struct tpmr_softc *sc, const char *name)
{
	unsigned int i;

	for (i = 0; i < nitems(sc->sc_ports); i++) {
		struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
		if (p == NULL)
			continue;

		if (strcmp(p->p_ifp0->if_xname, name) == 0)
			return (p);
	}

	return (NULL);
}

static int
tpmr_del_port(struct tpmr_softc *sc, const struct ifbreq *req)
{
	struct tpmr_port *p;

	NET_ASSERT_LOCKED();
	p = tpmr_trunkport(sc, req->ifbr_ifsname);
	if (p == NULL)
		return (EINVAL);

	tpmr_p_dtor(sc, p, "del");

	return (0);
}


static int
tpmr_port_list(struct tpmr_softc *sc, struct ifbifconf *bifc)
{
	struct tpmr_port *p;
	struct ifbreq breq;
	int i = 0, total = nitems(sc->sc_ports), n = 0, error = 0;

	NET_ASSERT_LOCKED();

	if (bifc->ifbic_len == 0) {
		n = total;
		goto done;
	}

	for (i = 0; i < total; i++) {
		memset(&breq, 0, sizeof(breq));

		if (bifc->ifbic_len < sizeof(breq))
			break;

		p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
		if (p == NULL)
			continue;
		strlcpy(breq.ifbr_ifsname, p->p_ifp0->if_xname, IFNAMSIZ);

		/* flag as span port so ifconfig(8)'s brconfig.c:bridge_list()
		 * stays quiet wrt. STP */
		breq.ifbr_ifsflags = IFBIF_SPAN;
		strlcpy(breq.ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
		if ((error = copyout(&breq, bifc->ifbic_req + n,
		    sizeof(breq))) != 0)
			goto done;

		bifc->ifbic_len -= sizeof(breq);
		n++;
	}

done:
	bifc->ifbic_len = n * sizeof(breq);
	return (error);
}

static int
tpmr_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
{
	const struct ether_brport *eb = ether_brport_get_locked(ifp0);
	struct tpmr_port *p;
	int error = 0;

	KASSERTMSG(eb != NULL,
	    "%s: %s called without an ether_brport set",
	    ifp0->if_xname, __func__);
	KASSERTMSG(eb->eb_input == tpmr_input,
	    "%s: %s called, but eb_input seems wrong (%p != tpmr_input())",
	    ifp0->if_xname, __func__, eb->eb_input);

	p = eb->eb_port;

	switch (cmd) {
	case SIOCSIFADDR:
		error = EBUSY;
		break;

	default:
		error = (*p->p_ioctl)(ifp0, cmd, data);
		break;
	}

	return (error);
}

static int
tpmr_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
    struct rtentry *rt)
{
	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
	    struct rtentry *) = NULL;
	const struct ether_brport *eb;

	/* restrict transmission to bpf only */
	if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
		m_freem(m);
		return (EBUSY);
	}

	smr_read_enter();
	eb = ether_brport_get(ifp0);
	if (eb != NULL && eb->eb_input == tpmr_input) {
		struct tpmr_port *p = eb->eb_port;
		p_output = p->p_output; /* code doesn't go away */
	}
	smr_read_leave();

	if (p_output == NULL) {
		m_freem(m);
		return (ENXIO);
	}

	return ((*p_output)(ifp0, m, dst, rt));
}

static void
tpmr_p_take(void *p)
{
	struct tpmr_port *port = p;

	atomic_inc_int(&port->p_refcnt);
}

static void
tpmr_p_rele(void *p)
{
	struct tpmr_port *port = p;
	struct ifnet *ifp0 = port->p_ifp0;

	if (atomic_dec_int_nv(&port->p_refcnt) == 0) {
		if_put(ifp0);
		free(port, M_DEVBUF, sizeof(*port));
	}
}

static void
tpmr_p_dtor(struct tpmr_softc *sc, struct tpmr_port *p, const char *op)
{
	struct ifnet *ifp = &sc->sc_if;
	struct ifnet *ifp0 = p->p_ifp0;

	DPRINTF(sc, "%s %s: destroying port\n",
	    ifp->if_xname, ifp0->if_xname);

	ifp0->if_ioctl = p->p_ioctl;
	ifp0->if_output = p->p_output;

	ether_brport_clr(ifp0);

	sc->sc_nports--;
	SMR_PTR_SET_LOCKED(&sc->sc_ports[p->p_slot], NULL);

	if (ifpromisc(ifp0, 0) != 0) {
		log(LOG_WARNING, "%s %s: unable to disable promisc\n",
		    ifp->if_xname, ifp0->if_xname);
	}

	if_detachhook_del(ifp0, &p->p_dtask);
	if_linkstatehook_del(ifp0, &p->p_ltask);

	tpmr_p_rele(p);

	smr_barrier();

	if (ifp->if_link_state != LINK_STATE_DOWN) {
		ifp->if_link_state = LINK_STATE_DOWN;
		if_link_state_change(ifp);
	}
}

static void
tpmr_p_detach(void *arg)
{
	struct tpmr_port *p = arg;
	struct tpmr_softc *sc = p->p_tpmr;

	tpmr_p_dtor(sc, p, "detach");

	NET_ASSERT_LOCKED();
}

static int
tpmr_p_active(struct tpmr_port *p)
{
	struct ifnet *ifp0 = p->p_ifp0;

	return (ISSET(ifp0->if_flags, IFF_RUNNING) &&
	    LINK_STATE_IS_UP(ifp0->if_link_state));
}

static void
tpmr_p_linkch(void *arg)
{
	struct tpmr_port *p = arg;
	struct tpmr_softc *sc = p->p_tpmr;
	struct ifnet *ifp = &sc->sc_if;
	struct tpmr_port *np;
	u_char link_state = LINK_STATE_FULL_DUPLEX;

	NET_ASSERT_LOCKED();

	if (!tpmr_p_active(p))
		link_state = LINK_STATE_DOWN;

	np = SMR_PTR_GET_LOCKED(&sc->sc_ports[!p->p_slot]);
	if (np == NULL || !tpmr_p_active(np))
		link_state = LINK_STATE_DOWN;

	if (ifp->if_link_state != link_state) {
		ifp->if_link_state = link_state;
		if_link_state_change(ifp);
	}
}

static int
tpmr_up(struct tpmr_softc *sc)
{
	struct ifnet *ifp = &sc->sc_if;

	NET_ASSERT_LOCKED();
	SET(ifp->if_flags, IFF_RUNNING);

	return (0);
}

static int
tpmr_iff(struct tpmr_softc *sc)
{
	return (0);
}

static int
tpmr_down(struct tpmr_softc *sc)
{
	struct ifnet *ifp = &sc->sc_if;

	NET_ASSERT_LOCKED();
	CLR(ifp->if_flags, IFF_RUNNING);

	return (0);
}