xref: /freebsd/sys/netgraph/ng_tty.c (revision 4a5216a6dc0c3ce4cf5f2d3ee8af0c3ff3402c4f)
1 /*
2  * ng_tty.c
3  */
4 
5 /*-
6  * Copyright (c) 1996-1999 Whistle Communications, Inc.
7  * All rights reserved.
8  *
9  * Subject to the following obligations and disclaimer of warranty, use and
10  * redistribution of this software, in source or object code forms, with or
11  * without modifications are expressly permitted by Whistle Communications;
12  * provided, however, that:
13  * 1. Any and all reproductions of the source or object code must include the
14  *    copyright notice above and the following disclaimer of warranties; and
15  * 2. No rights are granted, in any manner or form, to use Whistle
16  *    Communications, Inc. trademarks, including the mark "WHISTLE
17  *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18  *    such appears in the above copyright notice or in the software.
19  *
20  * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21  * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22  * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23  * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25  * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26  * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27  * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28  * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29  * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30  * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31  * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35  * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36  * OF SUCH DAMAGE.
37  *
38  * Author: Archie Cobbs <archie@freebsd.org>
39  *
40  * Updated by Andrew Thompson <thompsa@FreeBSD.org> for MPSAFE TTY.
41  *
42  * $FreeBSD$
43  * $Whistle: ng_tty.c,v 1.21 1999/11/01 09:24:52 julian Exp $
44  */
45 
46 /*
47  * This file implements TTY hooks to link in to the netgraph system.  The node
48  * is created and then passed the callers opened TTY file descriptor number to
49  * NGM_TTY_SET_TTY, this will hook the tty via ttyhook_register().
50  *
51  * Incoming data is delivered directly to ng_tty via the TTY bypass hook as a
52  * buffer pointer and length, this is converted to a mbuf and passed to the
53  * peer.
54  *
55  * If the TTY device does not support bypass then incoming characters are
56  * delivered to the hook one at a time, each in its own mbuf. You may
57  * optionally define a ``hotchar,'' which causes incoming characters to be
58  * buffered up until either the hotchar is seen or the mbuf is full (MHLEN
59  * bytes). Then all buffered characters are immediately delivered.
60  */
61 
62 #include <sys/param.h>
63 #include <sys/systm.h>
64 #include <sys/conf.h>
65 #include <sys/errno.h>
66 #include <sys/fcntl.h>
67 #include <sys/ioccom.h>
68 #include <sys/kernel.h>
69 #include <sys/malloc.h>
70 #include <sys/mbuf.h>
71 #include <sys/priv.h>
72 #include <sys/socket.h>
73 #include <sys/syslog.h>
74 #include <sys/tty.h>
75 #include <sys/ttycom.h>
76 
77 #include <net/if.h>
78 #include <net/if_var.h>
79 
80 #include <netgraph/ng_message.h>
81 #include <netgraph/netgraph.h>
82 #include <netgraph/ng_tty.h>
83 
84 /* Per-node private info */
85 struct ngt_softc {
86 	struct tty	*tp;		/* Terminal device */
87 	node_p		node;		/* Netgraph node */
88 	hook_p		hook;		/* Netgraph hook */
89 	struct ifqueue	outq;		/* Queue of outgoing data */
90 	size_t		outqlen;	/* Number of bytes in outq */
91 	struct mbuf	*m;		/* Incoming non-bypass data buffer */
92 	short		hotchar;	/* Hotchar, or -1 if none */
93 	u_int		flags;		/* Flags */
94 };
95 typedef struct ngt_softc *sc_p;
96 
97 static int ngt_unit;
98 
99 /* Flags */
100 #define FLG_DEBUG		0x0002
101 
102 /* Netgraph methods */
103 static ng_constructor_t		ngt_constructor;
104 static ng_rcvmsg_t		ngt_rcvmsg;
105 static ng_shutdown_t		ngt_shutdown;
106 static ng_newhook_t		ngt_newhook;
107 static ng_connect_t		ngt_connect;
108 static ng_rcvdata_t		ngt_rcvdata;
109 static ng_disconnect_t		ngt_disconnect;
110 
111 #define ERROUT(x)		do { error = (x); goto done; } while (0)
112 
113 static th_getc_inject_t		ngt_getc_inject;
114 static th_getc_poll_t		ngt_getc_poll;
115 static th_rint_t		ngt_rint;
116 static th_rint_bypass_t		ngt_rint_bypass;
117 static th_rint_poll_t		ngt_rint_poll;
118 static th_close_t		ngt_close;
119 
120 static struct ttyhook ngt_hook = {
121 	.th_getc_inject = ngt_getc_inject,
122 	.th_getc_poll = ngt_getc_poll,
123 	.th_rint = ngt_rint,
124 	.th_rint_bypass = ngt_rint_bypass,
125 	.th_rint_poll = ngt_rint_poll,
126 	.th_close = ngt_close,
127 };
128 
129 /* Netgraph node type descriptor */
130 static struct ng_type typestruct = {
131 	.version =	NG_ABI_VERSION,
132 	.name =		NG_TTY_NODE_TYPE,
133 	.constructor =	ngt_constructor,
134 	.rcvmsg =	ngt_rcvmsg,
135 	.shutdown =	ngt_shutdown,
136 	.newhook =	ngt_newhook,
137 	.connect =	ngt_connect,
138 	.rcvdata =	ngt_rcvdata,
139 	.disconnect =	ngt_disconnect,
140 };
141 NETGRAPH_INIT(tty, &typestruct);
142 
143 #define	NGTLOCK(sc)	IF_LOCK(&sc->outq)
144 #define	NGTUNLOCK(sc)	IF_UNLOCK(&sc->outq)
145 
146 /******************************************************************
147 		    NETGRAPH NODE METHODS
148 ******************************************************************/
149 
150 /*
151  * Initialize a new node of this type.
152  *
153  * We only allow nodes to be created as a result of setting
154  * the line discipline on a tty, so always return an error if not.
155  */
156 static int
157 ngt_constructor(node_p node)
158 {
159 	sc_p sc;
160 	char name[sizeof(NG_TTY_NODE_TYPE) + 8];
161 
162 	/* Allocate private structure */
163 	MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO);
164 	if (sc == NULL)
165 		return (ENOMEM);
166 
167 	NG_NODE_SET_PRIVATE(node, sc);
168 	sc->node = node;
169 
170 	mtx_init(&sc->outq.ifq_mtx, "ng_tty node+queue", NULL, MTX_DEF);
171 	IFQ_SET_MAXLEN(&sc->outq, IFQ_MAXLEN);
172 
173 	atomic_add_int(&ngt_unit, 1);
174 	snprintf(name, sizeof(name), "%s%d", typestruct.name, ngt_unit);
175 
176 	/* Assign node its name */
177 	if (ng_name_node(node, name))
178 		log(LOG_WARNING, "%s: can't name node %s\n",
179 		    __func__, name);
180 	/* Done */
181 	return (0);
182 }
183 
184 /*
185  * Add a new hook. There can only be one.
186  */
187 static int
188 ngt_newhook(node_p node, hook_p hook, const char *name)
189 {
190 	const sc_p sc = NG_NODE_PRIVATE(node);
191 
192 	if (strcmp(name, NG_TTY_HOOK))
193 		return (EINVAL);
194 
195 	if (sc->hook)
196 		return (EISCONN);
197 
198 	NGTLOCK(sc);
199 	sc->hook = hook;
200 	NGTUNLOCK(sc);
201 
202 	return (0);
203 }
204 
205 /*
206  * Set the hook into queueing mode (for outgoing packets),
207  * so that we wont deliver mbuf thru the whole graph holding
208  * tty locks.
209  */
210 static int
211 ngt_connect(hook_p hook)
212 {
213 	NG_HOOK_FORCE_QUEUE(hook);
214 	return (0);
215 }
216 
217 /*
218  * Disconnect the hook
219  */
220 static int
221 ngt_disconnect(hook_p hook)
222 {
223 	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
224 
225 	if (hook != sc->hook)
226 		panic(__func__);
227 
228 	NGTLOCK(sc);
229 	sc->hook = NULL;
230 	NGTUNLOCK(sc);
231 
232 	return (0);
233 }
234 
235 /*
236  * Remove this node. The does the netgraph portion of the shutdown.
237  */
238 static int
239 ngt_shutdown(node_p node)
240 {
241 	const sc_p sc = NG_NODE_PRIVATE(node);
242 	struct tty *tp;
243 
244 	tp = sc->tp;
245 	if (tp != NULL) {
246 		tty_lock(tp);
247 		ttyhook_unregister(tp);
248 	}
249 	/* Free resources */
250 	IF_DRAIN(&sc->outq);
251 	mtx_destroy(&(sc)->outq.ifq_mtx);
252 	NG_NODE_UNREF(sc->node);
253 	FREE(sc, M_NETGRAPH);
254 
255 	return (0);
256 }
257 
258 /*
259  * Receive control message
260  */
261 static int
262 ngt_rcvmsg(node_p node, item_p item, hook_p lasthook)
263 {
264 	struct thread *td = curthread;	/* XXX */
265 	const sc_p sc = NG_NODE_PRIVATE(node);
266 	struct ng_mesg *msg, *resp = NULL;
267 	int error = 0;
268 
269 	NGI_GET_MSG(item, msg);
270 	switch (msg->header.typecookie) {
271 	case NGM_TTY_COOKIE:
272 		switch (msg->header.cmd) {
273 		case NGM_TTY_SET_TTY:
274 			if (sc->tp != NULL)
275 				return (EBUSY);
276 			error = ttyhook_register(&sc->tp, td, *(int *)msg->data,
277 			    &ngt_hook, sc);
278 			if (error != 0)
279 				return (error);
280 			break;
281 		case NGM_TTY_SET_HOTCHAR:
282 		    {
283 			int     hotchar;
284 
285 			if (msg->header.arglen != sizeof(int))
286 				ERROUT(EINVAL);
287 			hotchar = *((int *) msg->data);
288 			if (hotchar != (u_char) hotchar && hotchar != -1)
289 				ERROUT(EINVAL);
290 			sc->hotchar = hotchar;	/* race condition is OK */
291 			break;
292 		    }
293 		case NGM_TTY_GET_HOTCHAR:
294 			NG_MKRESPONSE(resp, msg, sizeof(int), M_NOWAIT);
295 			if (!resp)
296 				ERROUT(ENOMEM);
297 			/* Race condition here is OK */
298 			*((int *) resp->data) = sc->hotchar;
299 			break;
300 		default:
301 			ERROUT(EINVAL);
302 		}
303 		break;
304 	default:
305 		ERROUT(EINVAL);
306 	}
307 done:
308 	NG_RESPOND_MSG(error, node, item, resp);
309 	NG_FREE_MSG(msg);
310 	return (error);
311 }
312 
313 /*
314  * Receive incoming data from netgraph system. Put it on our
315  * output queue and start output if necessary.
316  */
317 static int
318 ngt_rcvdata(hook_p hook, item_p item)
319 {
320 	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
321 	struct tty *tp = sc->tp;
322 	struct mbuf *m;
323 
324 	if (hook != sc->hook)
325 		panic(__func__);
326 
327 	NGI_GET_M(item, m);
328 	NG_FREE_ITEM(item);
329 
330 	if (tp == NULL) {
331 		NG_FREE_M(m);
332 		return (ENXIO);
333 	}
334 
335 	IF_LOCK(&sc->outq);
336 	if (_IF_QFULL(&sc->outq)) {
337 		_IF_DROP(&sc->outq);
338 		IF_UNLOCK(&sc->outq);
339 		NG_FREE_M(m);
340 		return (ENOBUFS);
341 	}
342 
343 	_IF_ENQUEUE(&sc->outq, m);
344 	sc->outqlen += m->m_pkthdr.len;
345 	IF_UNLOCK(&sc->outq);
346 
347 	/* notify the TTY that data is ready */
348 	tty_lock(tp);
349 	if (!tty_gone(tp))
350 		ttydevsw_outwakeup(tp);
351 	tty_unlock(tp);
352 
353 	return (0);
354 }
355 
356 static size_t
357 ngt_getc_inject(struct tty *tp, void *buf, size_t len)
358 {
359 	sc_p sc = ttyhook_softc(tp);
360 	size_t total = 0;
361 	int length;
362 
363 	while (len) {
364 		struct mbuf *m;
365 
366 		/* Remove first mbuf from queue */
367 		IF_DEQUEUE(&sc->outq, m);
368 		if (m == NULL)
369 			break;
370 
371 		/* Send as much of it as possible */
372 		while (m != NULL) {
373 			length = min(m->m_len, len);
374 			memcpy((char *)buf + total, mtod(m, char *), length);
375 
376 			m->m_data += length;
377 			m->m_len -= length;
378 			total += length;
379 			len -= length;
380 
381 			if (m->m_len > 0)
382 				break;	/* device can't take any more */
383 			m = m_free(m);
384 		}
385 
386 		/* Put remainder of mbuf chain (if any) back on queue */
387 		if (m != NULL) {
388 			IF_PREPEND(&sc->outq, m);
389 			break;
390 		}
391 	}
392 	IF_LOCK(&sc->outq);
393 	sc->outqlen -= total;
394 	IF_UNLOCK(&sc->outq);
395 	MPASS(sc->outqlen >= 0);
396 
397 	return (total);
398 }
399 
400 static size_t
401 ngt_getc_poll(struct tty *tp)
402 {
403 	sc_p sc = ttyhook_softc(tp);
404 
405 	return (sc->outqlen);
406 }
407 
408 /*
409  * Optimised TTY input.
410  *
411  * We get a buffer pointer to hopefully a complete data frame. Do not check for
412  * the hotchar, just pass it on.
413  */
414 static size_t
415 ngt_rint_bypass(struct tty *tp, const void *buf, size_t len)
416 {
417 	sc_p sc = ttyhook_softc(tp);
418 	node_p node = sc->node;
419 	struct mbuf *m, *mb;
420 	size_t total = 0;
421 	int error = 0, length;
422 
423 	tty_lock_assert(tp, MA_OWNED);
424 
425 	if (sc->hook == NULL)
426 		return (0);
427 
428 	m = m_getm2(NULL, len, M_DONTWAIT, MT_DATA, M_PKTHDR);
429 	if (m == NULL) {
430 		if (sc->flags & FLG_DEBUG)
431 			log(LOG_ERR,
432 			    "%s: can't get mbuf\n", NG_NODE_NAME(node));
433 		return (0);
434 	}
435 	m->m_pkthdr.rcvif = NULL;
436 
437 	for (mb = m; mb != NULL; mb = mb->m_next) {
438 		length = min(M_TRAILINGSPACE(mb), len - total);
439 
440 		memcpy(mtod(m, char *), (const char *)buf + total, length);
441 		mb->m_len = length;
442 		total += length;
443 		m->m_pkthdr.len += length;
444 	}
445 	if (sc->m != NULL) {
446 		/*
447 		 * Odd, we have changed from non-bypass to bypass. It is
448 		 * unlikely but not impossible, flush the data first.
449 		 */
450 		sc->m->m_data = sc->m->m_pktdat;
451 		NG_SEND_DATA_ONLY(error, sc->hook, sc->m);
452 		sc->m = NULL;
453 	}
454 	NG_SEND_DATA_ONLY(error, sc->hook, m);
455 
456 	return (total);
457 }
458 
459 /*
460  * Receive data coming from the device one char at a time, when it is not in
461  * bypass mode.
462  */
463 static int
464 ngt_rint(struct tty *tp, char c, int flags)
465 {
466 	sc_p sc = ttyhook_softc(tp);
467 	node_p node = sc->node;
468 	struct mbuf *m;
469 	int error = 0;
470 
471 	tty_lock_assert(tp, MA_OWNED);
472 
473 	if (sc->hook == NULL)
474 		return (0);
475 
476 	if (flags != 0) {
477 		/* framing error or overrun on this char */
478 		if (sc->flags & FLG_DEBUG)
479 			log(LOG_DEBUG, "%s: line error %x\n",
480 			    NG_NODE_NAME(node), flags);
481 		return (0);
482 	}
483 
484 	/* Get a new header mbuf if we need one */
485 	if (!(m = sc->m)) {
486 		MGETHDR(m, M_DONTWAIT, MT_DATA);
487 		if (!m) {
488 			if (sc->flags & FLG_DEBUG)
489 				log(LOG_ERR,
490 				    "%s: can't get mbuf\n", NG_NODE_NAME(node));
491 			return (ENOBUFS);
492 		}
493 		m->m_len = m->m_pkthdr.len = 0;
494 		m->m_pkthdr.rcvif = NULL;
495 		sc->m = m;
496 	}
497 
498 	/* Add char to mbuf */
499 	*mtod(m, u_char *) = c;
500 	m->m_data++;
501 	m->m_len++;
502 	m->m_pkthdr.len++;
503 
504 	/* Ship off mbuf if it's time */
505 	if (sc->hotchar == -1 || c == sc->hotchar || m->m_len >= MHLEN) {
506 		m->m_data = m->m_pktdat;
507 		sc->m = NULL;
508 		NG_SEND_DATA_ONLY(error, sc->hook, m);	/* Will queue */
509 	}
510 
511 	return (error);
512 }
513 
514 static size_t
515 ngt_rint_poll(struct tty *tp)
516 {
517 	/* We can always accept input */
518 	return (1);
519 }
520 
521 static void
522 ngt_close(struct tty *tp)
523 {
524 	sc_p sc = ttyhook_softc(tp);
525 
526 	/* Must be queued to drop the tty lock */
527 	ng_rmnode_flags(sc->node, NG_QUEUE);
528 }
529 
530