1253691Snp/*-
2253691Snp * Copyright (c) 2013 Chelsio Communications, Inc.
3253691Snp * All rights reserved.
4253691Snp * Written by: Navdeep Parhar <np@FreeBSD.org>
5253691Snp *
6253691Snp * Redistribution and use in source and binary forms, with or without
7253691Snp * modification, are permitted provided that the following conditions
8253691Snp * are met:
9253691Snp * 1. Redistributions of source code must retain the above copyright
10253691Snp *    notice, this list of conditions and the following disclaimer.
11253691Snp * 2. Redistributions in binary form must reproduce the above copyright
12253691Snp *    notice, this list of conditions and the following disclaimer in the
13253691Snp *    documentation and/or other materials provided with the distribution.
14253691Snp *
15253691Snp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16253691Snp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17253691Snp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18253691Snp * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19253691Snp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20253691Snp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21253691Snp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22253691Snp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23253691Snp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24253691Snp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25253691Snp * SUCH DAMAGE.
26253691Snp */
27253691Snp
28253691Snp#include <sys/cdefs.h>
29253691Snp__FBSDID("$FreeBSD: stable/11/sys/dev/cxgbe/t4_tracer.c 309560 2016-12-05 20:43:25Z jhb $");
30253691Snp
31253691Snp#include "opt_inet.h"
32253691Snp#include "opt_inet6.h"
33253691Snp
34253691Snp#include <sys/param.h>
35257241Sglebius#include <sys/eventhandler.h>
36253691Snp#include <sys/lock.h>
37253691Snp#include <sys/types.h>
38253691Snp#include <sys/mbuf.h>
39253691Snp#include <sys/socket.h>
40253691Snp#include <sys/sockio.h>
41253691Snp#include <sys/sx.h>
42253691Snp#include <net/bpf.h>
43253691Snp#include <net/ethernet.h>
44253691Snp#include <net/if.h>
45253691Snp#include <net/if_clone.h>
46253691Snp#include <net/if_types.h>
47253691Snp
48253691Snp#include "common/common.h"
49253691Snp#include "common/t4_msg.h"
50253691Snp#include "common/t4_regs.h"
51253691Snp#include "t4_ioctl.h"
52253691Snp
53253691Snp/*
54253691Snp * Locking notes
55253691Snp * =============
56253691Snp *
57253691Snp * An interface cloner is registered during mod_load and it can be used to
58253691Snp * create or destroy the tracing ifnet for an adapter at any time.  It is
59253691Snp * possible for the cloned interface to outlive the adapter (adapter disappears
60253691Snp * in t4_detach but the tracing ifnet may live till mod_unload when removal of
61253691Snp * the cloner finally destroys any remaining cloned interfaces).  When tracing
62253691Snp * filters are active, this ifnet is also receiving data.  There are potential
63253691Snp * bad races between ifnet create, ifnet destroy, ifnet rx, ifnet ioctl,
64253691Snp * cxgbe_detach/t4_detach, mod_unload.
65253691Snp *
66253691Snp * a) The driver selects an iq for tracing (sc->traceq) inside a synch op.  The
67253691Snp *    iq is destroyed inside a synch op too (and sc->traceq updated).
68253691Snp * b) The cloner looks for an adapter that matches the name of the ifnet it's
69253691Snp *    been asked to create, starts a synch op on that adapter, and proceeds only
70253691Snp *    if the adapter has a tracing iq.
71253691Snp * c) The cloned ifnet and the adapter are coupled to each other via
72253691Snp *    ifp->if_softc and sc->ifp.  These can be modified only with the global
73253691Snp *    t4_trace_lock sx as well as the sc->ifp_lock mutex held.  Holding either
74253691Snp *    of these will prevent any change.
75253691Snp *
76253691Snp * The order in which all the locks involved should be acquired are:
77253691Snp * t4_list_lock
78253691Snp * adapter lock
79253691Snp * (begin synch op and let go of the above two)
80253691Snp * t4_trace_lock
81253691Snp * sc->ifp_lock
82253691Snp */
83253691Snp
84253691Snpstatic struct sx t4_trace_lock;
85253691Snpstatic const char *t4_cloner_name = "tXnex";
86253691Snpstatic struct if_clone *t4_cloner;
87253691Snp
88253691Snp/* tracer ifnet routines.  mostly no-ops. */
89253691Snpstatic void tracer_init(void *);
90253691Snpstatic int tracer_ioctl(struct ifnet *, unsigned long, caddr_t);
91253691Snpstatic int tracer_transmit(struct ifnet *, struct mbuf *);
92253691Snpstatic void tracer_qflush(struct ifnet *);
93253691Snpstatic int tracer_media_change(struct ifnet *);
94253691Snpstatic void tracer_media_status(struct ifnet *, struct ifmediareq *);
95253691Snp
96253691Snp/* match name (request/response) */
97253691Snpstruct match_rr {
98253691Snp	const char *name;
99253691Snp	int lock;	/* set to 1 to returned sc locked. */
100253691Snp	struct adapter *sc;
101253691Snp	int rc;
102253691Snp};
103253691Snp
104253691Snpstatic void
105253691Snpmatch_name(struct adapter *sc, void *arg)
106253691Snp{
107253691Snp	struct match_rr *mrr = arg;
108253691Snp
109253691Snp	if (strcmp(device_get_nameunit(sc->dev), mrr->name) != 0)
110253691Snp		return;
111253691Snp
112253691Snp	KASSERT(mrr->sc == NULL, ("%s: multiple matches (%p, %p) for %s",
113253691Snp	    __func__, mrr->sc, sc, mrr->name));
114253691Snp
115253691Snp	mrr->sc = sc;
116253691Snp	if (mrr->lock)
117253691Snp		mrr->rc = begin_synchronized_op(mrr->sc, NULL, 0, "t4clon");
118253691Snp	else
119253691Snp		mrr->rc = 0;
120253691Snp}
121253691Snp
122253691Snpstatic int
123253691Snpt4_cloner_match(struct if_clone *ifc, const char *name)
124253691Snp{
125253691Snp
126255006Snp	if (strncmp(name, "t4nex", 5) != 0 &&
127309560Sjhb	    strncmp(name, "t5nex", 5) != 0 &&
128309560Sjhb	    strncmp(name, "t6nex", 5) != 0)
129255006Snp		return (0);
130255006Snp	if (name[5] < '0' || name[5] > '9')
131255006Snp		return (0);
132255006Snp	return (1);
133253691Snp}
134253691Snp
135253691Snpstatic int
136253691Snpt4_cloner_create(struct if_clone *ifc, char *name, size_t len, caddr_t params)
137253691Snp{
138253691Snp	struct match_rr mrr;
139253691Snp	struct adapter *sc;
140253691Snp	struct ifnet *ifp;
141253691Snp	int rc, unit;
142253691Snp	const uint8_t lla[ETHER_ADDR_LEN] = {0, 0, 0, 0, 0, 0};
143253691Snp
144253691Snp	mrr.name = name;
145253691Snp	mrr.lock = 1;
146253691Snp	mrr.sc = NULL;
147253691Snp	mrr.rc = ENOENT;
148253691Snp	t4_iterate(match_name, &mrr);
149253691Snp
150253691Snp	if (mrr.rc != 0)
151253691Snp		return (mrr.rc);
152253691Snp	sc = mrr.sc;
153253691Snp
154253691Snp	KASSERT(sc != NULL, ("%s: name (%s) matched but softc is NULL",
155253691Snp	    __func__, name));
156253691Snp	ASSERT_SYNCHRONIZED_OP(sc);
157253691Snp
158253691Snp	sx_xlock(&t4_trace_lock);
159253691Snp
160253691Snp	if (sc->ifp != NULL) {
161253691Snp		rc = EEXIST;
162253691Snp		goto done;
163253691Snp	}
164253691Snp	if (sc->traceq < 0) {
165253691Snp		rc = EAGAIN;
166253691Snp		goto done;
167253691Snp	}
168253691Snp
169253691Snp
170253691Snp	unit = -1;
171253691Snp	rc = ifc_alloc_unit(ifc, &unit);
172253691Snp	if (rc != 0)
173253691Snp		goto done;
174253691Snp
175253691Snp	ifp = if_alloc(IFT_ETHER);
176253691Snp	if (ifp == NULL) {
177253691Snp		ifc_free_unit(ifc, unit);
178253691Snp		rc = ENOMEM;
179253691Snp		goto done;
180253691Snp	}
181253691Snp
182253691Snp	/* Note that if_xname is not <if_dname><if_dunit>. */
183253691Snp	strlcpy(ifp->if_xname, name, sizeof(ifp->if_xname));
184253691Snp	ifp->if_dname = t4_cloner_name;
185253691Snp	ifp->if_dunit = unit;
186253691Snp	ifp->if_init = tracer_init;
187253691Snp	ifp->if_flags = IFF_SIMPLEX | IFF_DRV_RUNNING;
188253691Snp	ifp->if_ioctl = tracer_ioctl;
189253691Snp	ifp->if_transmit = tracer_transmit;
190253691Snp	ifp->if_qflush = tracer_qflush;
191253691Snp	ifp->if_capabilities = IFCAP_JUMBO_MTU | IFCAP_VLAN_MTU;
192253691Snp	ifmedia_init(&sc->media, IFM_IMASK, tracer_media_change,
193253691Snp	    tracer_media_status);
194253691Snp	ifmedia_add(&sc->media, IFM_ETHER | IFM_FDX | IFM_NONE, 0, NULL);
195253691Snp	ifmedia_set(&sc->media, IFM_ETHER | IFM_FDX | IFM_NONE);
196253691Snp	ether_ifattach(ifp, lla);
197253691Snp
198253691Snp	mtx_lock(&sc->ifp_lock);
199253691Snp	ifp->if_softc = sc;
200253691Snp	sc->ifp = ifp;
201253691Snp	mtx_unlock(&sc->ifp_lock);
202253691Snpdone:
203253691Snp	sx_xunlock(&t4_trace_lock);
204253691Snp	end_synchronized_op(sc, 0);
205253691Snp	return (rc);
206253691Snp}
207253691Snp
208253691Snpstatic int
209253691Snpt4_cloner_destroy(struct if_clone *ifc, struct ifnet *ifp)
210253691Snp{
211253691Snp	struct adapter *sc;
212253691Snp	int unit = ifp->if_dunit;
213253691Snp
214253691Snp	sx_xlock(&t4_trace_lock);
215253691Snp	sc = ifp->if_softc;
216253691Snp	if (sc != NULL) {
217253691Snp		mtx_lock(&sc->ifp_lock);
218253691Snp		sc->ifp = NULL;
219253691Snp		ifp->if_softc = NULL;
220253691Snp		mtx_unlock(&sc->ifp_lock);
221253691Snp		ifmedia_removeall(&sc->media);
222253691Snp	}
223253691Snp	ether_ifdetach(ifp);
224253691Snp	if_free(ifp);
225253691Snp	ifc_free_unit(ifc, unit);
226253691Snp	sx_xunlock(&t4_trace_lock);
227253691Snp
228253691Snp	return (0);
229253691Snp}
230253691Snp
231253691Snpvoid
232253691Snpt4_tracer_modload()
233253691Snp{
234253691Snp
235253691Snp	sx_init(&t4_trace_lock, "T4/T5 tracer lock");
236253691Snp	t4_cloner = if_clone_advanced(t4_cloner_name, 0, t4_cloner_match,
237253691Snp	    t4_cloner_create, t4_cloner_destroy);
238253691Snp}
239253691Snp
240253691Snpvoid
241253691Snpt4_tracer_modunload()
242253691Snp{
243253691Snp
244253691Snp	if (t4_cloner != NULL) {
245253691Snp		/*
246253691Snp		 * The module is being unloaded so the nexus drivers have
247253691Snp		 * detached.  The tracing interfaces can not outlive the nexus
248253691Snp		 * (ifp->if_softc is the nexus) and must have been destroyed
249253691Snp		 * already.  XXX: but if_clone is opaque to us and we can't
250253691Snp		 * assert LIST_EMPTY(&t4_cloner->ifc_iflist) at this time.
251253691Snp		 */
252253691Snp		if_clone_detach(t4_cloner);
253253691Snp	}
254253691Snp	sx_destroy(&t4_trace_lock);
255253691Snp}
256253691Snp
257253691Snpvoid
258253691Snpt4_tracer_port_detach(struct adapter *sc)
259253691Snp{
260253691Snp
261253691Snp	sx_xlock(&t4_trace_lock);
262253691Snp	if (sc->ifp != NULL) {
263253691Snp		mtx_lock(&sc->ifp_lock);
264253691Snp		sc->ifp->if_softc = NULL;
265253691Snp		sc->ifp = NULL;
266253691Snp		mtx_unlock(&sc->ifp_lock);
267253691Snp	}
268253691Snp	ifmedia_removeall(&sc->media);
269253691Snp	sx_xunlock(&t4_trace_lock);
270253691Snp}
271253691Snp
272253691Snpint
273253691Snpt4_get_tracer(struct adapter *sc, struct t4_tracer *t)
274253691Snp{
275253691Snp	int rc, i, enabled;
276253691Snp	struct trace_params tp;
277253691Snp
278253691Snp	if (t->idx >= NTRACE) {
279253691Snp		t->idx = 0xff;
280253691Snp		t->enabled = 0;
281253691Snp		t->valid = 0;
282253691Snp		return (0);
283253691Snp	}
284253691Snp
285253691Snp	rc = begin_synchronized_op(sc, NULL, HOLD_LOCK | SLEEP_OK | INTR_OK,
286253691Snp	    "t4gett");
287253691Snp	if (rc)
288253691Snp		return (rc);
289253691Snp
290253691Snp	for (i = t->idx; i < NTRACE; i++) {
291253691Snp		if (isset(&sc->tracer_valid, t->idx)) {
292253691Snp			t4_get_trace_filter(sc, &tp, i, &enabled);
293253691Snp			t->idx = i;
294253691Snp			t->enabled = enabled;
295253691Snp			t->valid = 1;
296253691Snp			memcpy(&t->tp.data[0], &tp.data[0], sizeof(t->tp.data));
297253691Snp			memcpy(&t->tp.mask[0], &tp.mask[0], sizeof(t->tp.mask));
298253691Snp			t->tp.snap_len = tp.snap_len;
299253691Snp			t->tp.min_len = tp.min_len;
300253691Snp			t->tp.skip_ofst = tp.skip_ofst;
301253691Snp			t->tp.skip_len = tp.skip_len;
302253691Snp			t->tp.invert = tp.invert;
303253691Snp
304253691Snp			/* convert channel to port iff 0 <= port < 8. */
305253691Snp			if (tp.port < 4)
306253691Snp				t->tp.port = sc->chan_map[tp.port];
307253691Snp			else if (tp.port < 8)
308253691Snp				t->tp.port = sc->chan_map[tp.port - 4] + 4;
309253691Snp			else
310253691Snp				t->tp.port = tp.port;
311253691Snp
312253691Snp			goto done;
313253691Snp		}
314253691Snp	}
315253691Snp
316253691Snp	t->idx = 0xff;
317253691Snp	t->enabled = 0;
318253691Snp	t->valid = 0;
319253691Snpdone:
320253691Snp	end_synchronized_op(sc, LOCK_HELD);
321253691Snp
322253691Snp	return (rc);
323253691Snp}
324253691Snp
325253691Snpint
326253691Snpt4_set_tracer(struct adapter *sc, struct t4_tracer *t)
327253691Snp{
328253691Snp	int rc;
329253691Snp	struct trace_params tp, *tpp;
330253691Snp
331253691Snp	if (t->idx >= NTRACE)
332253691Snp		return (EINVAL);
333253691Snp
334253691Snp	rc = begin_synchronized_op(sc, NULL, HOLD_LOCK | SLEEP_OK | INTR_OK,
335253691Snp	    "t4sett");
336253691Snp	if (rc)
337253691Snp		return (rc);
338253691Snp
339253691Snp	/*
340253691Snp	 * If no tracing filter is specified this time then check if the filter
341253691Snp	 * at the index is valid anyway because it was set previously.  If so
342253691Snp	 * then this is a legitimate enable/disable operation.
343253691Snp	 */
344253691Snp	if (t->valid == 0) {
345253691Snp		if (isset(&sc->tracer_valid, t->idx))
346253691Snp			tpp = NULL;
347253691Snp		else
348253691Snp			rc = EINVAL;
349253691Snp		goto done;
350253691Snp	}
351253691Snp
352253691Snp	if (t->tp.port > 19 || t->tp.snap_len > 9600 ||
353253691Snp	    t->tp.min_len > M_TFMINPKTSIZE || t->tp.skip_len > M_TFLENGTH ||
354253691Snp	    t->tp.skip_ofst > M_TFOFFSET) {
355253691Snp		rc = EINVAL;
356253691Snp		goto done;
357253691Snp	}
358253691Snp
359253691Snp	memcpy(&tp.data[0], &t->tp.data[0], sizeof(tp.data));
360253691Snp	memcpy(&tp.mask[0], &t->tp.mask[0], sizeof(tp.mask));
361253691Snp	tp.snap_len = t->tp.snap_len;
362253691Snp	tp.min_len = t->tp.min_len;
363253691Snp	tp.skip_ofst = t->tp.skip_ofst;
364253691Snp	tp.skip_len = t->tp.skip_len;
365253691Snp	tp.invert = !!t->tp.invert;
366253691Snp
367253691Snp	/* convert port to channel iff 0 <= port < 8. */
368253691Snp	if (t->tp.port < 4) {
369253691Snp		if (sc->port[t->tp.port] == NULL) {
370253691Snp			rc = EINVAL;
371253691Snp			goto done;
372253691Snp		}
373253691Snp		tp.port = sc->port[t->tp.port]->tx_chan;
374253691Snp	} else if (t->tp.port < 8) {
375253691Snp		if (sc->port[t->tp.port - 4] == NULL) {
376253691Snp			rc = EINVAL;
377253691Snp			goto done;
378253691Snp		}
379253691Snp		tp.port = sc->port[t->tp.port - 4]->tx_chan + 4;
380253691Snp	}
381253691Snp	tpp = &tp;
382253691Snpdone:
383253691Snp	if (rc == 0) {
384253691Snp		rc = -t4_set_trace_filter(sc, tpp, t->idx, t->enabled);
385253691Snp		if (rc == 0) {
386253691Snp			if (t->enabled) {
387253691Snp				setbit(&sc->tracer_valid, t->idx);
388253691Snp				if (sc->tracer_enabled == 0) {
389253691Snp					t4_set_reg_field(sc, A_MPS_TRC_CFG,
390253691Snp					    F_TRCEN, F_TRCEN);
391253691Snp				}
392253691Snp				setbit(&sc->tracer_enabled, t->idx);
393253691Snp			} else {
394253691Snp				clrbit(&sc->tracer_enabled, t->idx);
395253691Snp				if (sc->tracer_enabled == 0) {
396253691Snp					t4_set_reg_field(sc, A_MPS_TRC_CFG,
397253691Snp					    F_TRCEN, 0);
398253691Snp				}
399253691Snp			}
400253691Snp		}
401253691Snp	}
402253691Snp	end_synchronized_op(sc, LOCK_HELD);
403253691Snp
404253691Snp	return (rc);
405253691Snp}
406253691Snp
407253691Snpint
408253691Snpt4_trace_pkt(struct sge_iq *iq, const struct rss_header *rss, struct mbuf *m)
409253691Snp{
410253691Snp	struct adapter *sc = iq->adapter;
411253691Snp	struct ifnet *ifp;
412253691Snp
413253691Snp	KASSERT(m != NULL, ("%s: no payload with opcode %02x", __func__,
414253691Snp	    rss->opcode));
415253691Snp
416253691Snp	mtx_lock(&sc->ifp_lock);
417253691Snp	ifp = sc->ifp;
418253691Snp	if (sc->ifp) {
419253691Snp		m_adj(m, sizeof(struct cpl_trace_pkt));
420253691Snp		m->m_pkthdr.rcvif = ifp;
421253691Snp		ETHER_BPF_MTAP(ifp, m);
422253691Snp	}
423253691Snp	mtx_unlock(&sc->ifp_lock);
424253691Snp	m_freem(m);
425253691Snp
426253691Snp	return (0);
427253691Snp}
428253691Snp
429253691Snpint
430253691Snpt5_trace_pkt(struct sge_iq *iq, const struct rss_header *rss, struct mbuf *m)
431253691Snp{
432253691Snp	struct adapter *sc = iq->adapter;
433253691Snp	struct ifnet *ifp;
434253691Snp
435253691Snp	KASSERT(m != NULL, ("%s: no payload with opcode %02x", __func__,
436253691Snp	    rss->opcode));
437253691Snp
438253691Snp	mtx_lock(&sc->ifp_lock);
439253691Snp	ifp = sc->ifp;
440253691Snp	if (ifp != NULL) {
441253691Snp		m_adj(m, sizeof(struct cpl_t5_trace_pkt));
442253691Snp		m->m_pkthdr.rcvif = ifp;
443253691Snp		ETHER_BPF_MTAP(ifp, m);
444253691Snp	}
445253691Snp	mtx_unlock(&sc->ifp_lock);
446253691Snp	m_freem(m);
447253691Snp
448253691Snp	return (0);
449253691Snp}
450253691Snp
451253691Snp
452253691Snpstatic void
453253691Snptracer_init(void *arg)
454253691Snp{
455253691Snp
456253691Snp	return;
457253691Snp}
458253691Snp
459253691Snpstatic int
460253691Snptracer_ioctl(struct ifnet *ifp, unsigned long cmd, caddr_t data)
461253691Snp{
462253691Snp	int rc = 0;
463253691Snp	struct adapter *sc;
464253691Snp	struct ifreq *ifr = (struct ifreq *)data;
465253691Snp
466253691Snp	switch (cmd) {
467253691Snp	case SIOCSIFMTU:
468253691Snp	case SIOCSIFFLAGS:
469255011Snp	case SIOCADDMULTI:
470253691Snp	case SIOCDELMULTI:
471253691Snp	case SIOCSIFCAP:
472253691Snp		break;
473253691Snp	case SIOCSIFMEDIA:
474253691Snp	case SIOCGIFMEDIA:
475309560Sjhb	case SIOCGIFXMEDIA:
476253691Snp		sx_xlock(&t4_trace_lock);
477253691Snp		sc = ifp->if_softc;
478253691Snp		if (sc == NULL)
479253691Snp			rc = EIO;
480253691Snp		else
481253691Snp			rc = ifmedia_ioctl(ifp, ifr, &sc->media, cmd);
482253691Snp		sx_xunlock(&t4_trace_lock);
483253691Snp		break;
484253691Snp	default:
485253691Snp		rc = ether_ioctl(ifp, cmd, data);
486253691Snp	}
487253691Snp
488253691Snp	return (rc);
489253691Snp}
490253691Snp
491253691Snpstatic int
492253691Snptracer_transmit(struct ifnet *ifp, struct mbuf *m)
493253691Snp{
494253691Snp
495253691Snp	m_freem(m);
496253691Snp	return (0);
497253691Snp}
498253691Snp
499253691Snpstatic void
500253691Snptracer_qflush(struct ifnet *ifp)
501253691Snp{
502253691Snp
503253691Snp	return;
504253691Snp}
505253691Snp
506253691Snpstatic int
507253691Snptracer_media_change(struct ifnet *ifp)
508253691Snp{
509253691Snp
510253691Snp	return (EOPNOTSUPP);
511253691Snp}
512253691Snp
513253691Snpstatic void
514253691Snptracer_media_status(struct ifnet *ifp, struct ifmediareq *ifmr)
515253691Snp{
516253691Snp
517253691Snp	ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
518253691Snp
519253691Snp	return;
520253691Snp}
521