xref: /freebsd/sys/netgraph/ng_tty.c (revision 52267f7411adcc76ede961420e08c0e42f42d415)
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 #include <sys/proc.h>
77 
78 #include <net/if.h>
79 #include <net/if_var.h>
80 
81 #include <netgraph/ng_message.h>
82 #include <netgraph/netgraph.h>
83 #include <netgraph/ng_tty.h>
84 
85 /* Per-node private info */
86 struct ngt_softc {
87 	struct tty	*tp;		/* Terminal device */
88 	node_p		node;		/* Netgraph node */
89 	hook_p		hook;		/* Netgraph hook */
90 	struct ifqueue	outq;		/* Queue of outgoing data */
91 	size_t		outqlen;	/* Number of bytes in outq */
92 	struct mbuf	*m;		/* Incoming non-bypass data buffer */
93 	short		hotchar;	/* Hotchar, or -1 if none */
94 	u_int		flags;		/* Flags */
95 };
96 typedef struct ngt_softc *sc_p;
97 
98 /* Flags */
99 #define FLG_DEBUG		0x0002
100 
101 /* Netgraph methods */
102 static ng_constructor_t		ngt_constructor;
103 static ng_rcvmsg_t		ngt_rcvmsg;
104 static ng_shutdown_t		ngt_shutdown;
105 static ng_newhook_t		ngt_newhook;
106 static ng_connect_t		ngt_connect;
107 static ng_rcvdata_t		ngt_rcvdata;
108 static ng_disconnect_t		ngt_disconnect;
109 
110 #define ERROUT(x)		do { error = (x); goto done; } while (0)
111 
112 static th_getc_inject_t		ngt_getc_inject;
113 static th_getc_poll_t		ngt_getc_poll;
114 static th_rint_t		ngt_rint;
115 static th_rint_bypass_t		ngt_rint_bypass;
116 static th_rint_poll_t		ngt_rint_poll;
117 static th_close_t		ngt_close;
118 
119 static struct ttyhook ngt_hook = {
120 	.th_getc_inject = ngt_getc_inject,
121 	.th_getc_poll = ngt_getc_poll,
122 	.th_rint = ngt_rint,
123 	.th_rint_bypass = ngt_rint_bypass,
124 	.th_rint_poll = ngt_rint_poll,
125 	.th_close = ngt_close,
126 };
127 
128 /* Netgraph node type descriptor */
129 static struct ng_type typestruct = {
130 	.version =	NG_ABI_VERSION,
131 	.name =		NG_TTY_NODE_TYPE,
132 	.constructor =	ngt_constructor,
133 	.rcvmsg =	ngt_rcvmsg,
134 	.shutdown =	ngt_shutdown,
135 	.newhook =	ngt_newhook,
136 	.connect =	ngt_connect,
137 	.rcvdata =	ngt_rcvdata,
138 	.disconnect =	ngt_disconnect,
139 };
140 NETGRAPH_INIT(tty, &typestruct);
141 
142 #define	NGTLOCK(sc)	IF_LOCK(&sc->outq)
143 #define	NGTUNLOCK(sc)	IF_UNLOCK(&sc->outq)
144 
145 /******************************************************************
146 		    NETGRAPH NODE METHODS
147 ******************************************************************/
148 
149 /*
150  * Initialize a new node of this type.
151  *
152  * We only allow nodes to be created as a result of setting
153  * the line discipline on a tty, so always return an error if not.
154  */
155 static int
156 ngt_constructor(node_p node)
157 {
158 	sc_p sc;
159 
160 	/* Allocate private structure */
161 	sc = malloc(sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO);
162 	if (sc == NULL)
163 		return (ENOMEM);
164 
165 	NG_NODE_SET_PRIVATE(node, sc);
166 	sc->node = node;
167 
168 	mtx_init(&sc->outq.ifq_mtx, "ng_tty node+queue", NULL, MTX_DEF);
169 	IFQ_SET_MAXLEN(&sc->outq, IFQ_MAXLEN);
170 
171 	return (0);
172 }
173 
174 /*
175  * Add a new hook. There can only be one.
176  */
177 static int
178 ngt_newhook(node_p node, hook_p hook, const char *name)
179 {
180 	const sc_p sc = NG_NODE_PRIVATE(node);
181 
182 	if (strcmp(name, NG_TTY_HOOK))
183 		return (EINVAL);
184 
185 	if (sc->hook)
186 		return (EISCONN);
187 
188 	NGTLOCK(sc);
189 	sc->hook = hook;
190 	NGTUNLOCK(sc);
191 
192 	return (0);
193 }
194 
195 /*
196  * Set the hook into queueing mode (for outgoing packets),
197  * so that we wont deliver mbuf thru the whole graph holding
198  * tty locks.
199  */
200 static int
201 ngt_connect(hook_p hook)
202 {
203 	NG_HOOK_FORCE_QUEUE(hook);
204 	return (0);
205 }
206 
207 /*
208  * Disconnect the hook
209  */
210 static int
211 ngt_disconnect(hook_p hook)
212 {
213 	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
214 
215 	if (hook != sc->hook)
216 		panic(__func__);
217 
218 	NGTLOCK(sc);
219 	sc->hook = NULL;
220 	NGTUNLOCK(sc);
221 
222 	return (0);
223 }
224 
225 /*
226  * Remove this node. The does the netgraph portion of the shutdown.
227  */
228 static int
229 ngt_shutdown(node_p node)
230 {
231 	const sc_p sc = NG_NODE_PRIVATE(node);
232 	struct tty *tp;
233 
234 	tp = sc->tp;
235 	if (tp != NULL) {
236 		tty_lock(tp);
237 		ttyhook_unregister(tp);
238 	}
239 	/* Free resources */
240 	IF_DRAIN(&sc->outq);
241 	mtx_destroy(&(sc)->outq.ifq_mtx);
242 	NG_NODE_UNREF(sc->node);
243 	free(sc, M_NETGRAPH);
244 
245 	return (0);
246 }
247 
248 /*
249  * Receive control message
250  */
251 static int
252 ngt_rcvmsg(node_p node, item_p item, hook_p lasthook)
253 {
254 	struct proc *p;
255 	struct thread *td;
256 	const sc_p sc = NG_NODE_PRIVATE(node);
257 	struct ng_mesg *msg, *resp = NULL;
258 	int error = 0;
259 
260 	NGI_GET_MSG(item, msg);
261 	switch (msg->header.typecookie) {
262 	case NGM_TTY_COOKIE:
263 		switch (msg->header.cmd) {
264 		case NGM_TTY_SET_TTY:
265 			if (sc->tp != NULL)
266 				return (EBUSY);
267 
268 			p = pfind(((int *)msg->data)[0]);
269 			if (p == NULL)
270 				return (ESRCH);
271 			td = FIRST_THREAD_IN_PROC(p);
272 			error = ttyhook_register(&sc->tp, td, ((int *)msg->data)[1],
273 			    &ngt_hook, sc);
274 			PROC_UNLOCK(p);
275 			if (error != 0)
276 				return (error);
277 			break;
278 		case NGM_TTY_SET_HOTCHAR:
279 		    {
280 			int     hotchar;
281 
282 			if (msg->header.arglen != sizeof(int))
283 				ERROUT(EINVAL);
284 			hotchar = *((int *) msg->data);
285 			if (hotchar != (u_char) hotchar && hotchar != -1)
286 				ERROUT(EINVAL);
287 			sc->hotchar = hotchar;	/* race condition is OK */
288 			break;
289 		    }
290 		case NGM_TTY_GET_HOTCHAR:
291 			NG_MKRESPONSE(resp, msg, sizeof(int), M_NOWAIT);
292 			if (!resp)
293 				ERROUT(ENOMEM);
294 			/* Race condition here is OK */
295 			*((int *) resp->data) = sc->hotchar;
296 			break;
297 		default:
298 			ERROUT(EINVAL);
299 		}
300 		break;
301 	default:
302 		ERROUT(EINVAL);
303 	}
304 done:
305 	NG_RESPOND_MSG(error, node, item, resp);
306 	NG_FREE_MSG(msg);
307 	return (error);
308 }
309 
310 /*
311  * Receive incoming data from netgraph system. Put it on our
312  * output queue and start output if necessary.
313  */
314 static int
315 ngt_rcvdata(hook_p hook, item_p item)
316 {
317 	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
318 	struct tty *tp = sc->tp;
319 	struct mbuf *m;
320 
321 	if (hook != sc->hook)
322 		panic(__func__);
323 
324 	NGI_GET_M(item, m);
325 	NG_FREE_ITEM(item);
326 
327 	if (tp == NULL) {
328 		NG_FREE_M(m);
329 		return (ENXIO);
330 	}
331 
332 	IF_LOCK(&sc->outq);
333 	if (_IF_QFULL(&sc->outq)) {
334 		_IF_DROP(&sc->outq);
335 		IF_UNLOCK(&sc->outq);
336 		NG_FREE_M(m);
337 		return (ENOBUFS);
338 	}
339 
340 	_IF_ENQUEUE(&sc->outq, m);
341 	sc->outqlen += m->m_pkthdr.len;
342 	IF_UNLOCK(&sc->outq);
343 
344 	/* notify the TTY that data is ready */
345 	tty_lock(tp);
346 	if (!tty_gone(tp))
347 		ttydevsw_outwakeup(tp);
348 	tty_unlock(tp);
349 
350 	return (0);
351 }
352 
353 static size_t
354 ngt_getc_inject(struct tty *tp, void *buf, size_t len)
355 {
356 	sc_p sc = ttyhook_softc(tp);
357 	size_t total = 0;
358 	int length;
359 
360 	while (len) {
361 		struct mbuf *m;
362 
363 		/* Remove first mbuf from queue */
364 		IF_DEQUEUE(&sc->outq, m);
365 		if (m == NULL)
366 			break;
367 
368 		/* Send as much of it as possible */
369 		while (m != NULL) {
370 			length = min(m->m_len, len);
371 			memcpy((char *)buf + total, mtod(m, char *), length);
372 
373 			m->m_data += length;
374 			m->m_len -= length;
375 			total += length;
376 			len -= length;
377 
378 			if (m->m_len > 0)
379 				break;	/* device can't take any more */
380 			m = m_free(m);
381 		}
382 
383 		/* Put remainder of mbuf chain (if any) back on queue */
384 		if (m != NULL) {
385 			IF_PREPEND(&sc->outq, m);
386 			break;
387 		}
388 	}
389 	IF_LOCK(&sc->outq);
390 	sc->outqlen -= total;
391 	IF_UNLOCK(&sc->outq);
392 	MPASS(sc->outqlen >= 0);
393 
394 	return (total);
395 }
396 
397 static size_t
398 ngt_getc_poll(struct tty *tp)
399 {
400 	sc_p sc = ttyhook_softc(tp);
401 
402 	return (sc->outqlen);
403 }
404 
405 /*
406  * Optimised TTY input.
407  *
408  * We get a buffer pointer to hopefully a complete data frame. Do not check for
409  * the hotchar, just pass it on.
410  */
411 static size_t
412 ngt_rint_bypass(struct tty *tp, const void *buf, size_t len)
413 {
414 	sc_p sc = ttyhook_softc(tp);
415 	node_p node = sc->node;
416 	struct mbuf *m, *mb;
417 	size_t total = 0;
418 	int error = 0, length;
419 
420 	tty_lock_assert(tp, MA_OWNED);
421 
422 	if (sc->hook == NULL)
423 		return (0);
424 
425 	m = m_getm2(NULL, len, M_DONTWAIT, MT_DATA, M_PKTHDR);
426 	if (m == NULL) {
427 		if (sc->flags & FLG_DEBUG)
428 			log(LOG_ERR,
429 			    "%s: can't get mbuf\n", NG_NODE_NAME(node));
430 		return (0);
431 	}
432 	m->m_pkthdr.rcvif = NULL;
433 
434 	for (mb = m; mb != NULL; mb = mb->m_next) {
435 		length = min(M_TRAILINGSPACE(mb), len - total);
436 
437 		memcpy(mtod(m, char *), (const char *)buf + total, length);
438 		mb->m_len = length;
439 		total += length;
440 		m->m_pkthdr.len += length;
441 	}
442 	if (sc->m != NULL) {
443 		/*
444 		 * Odd, we have changed from non-bypass to bypass. It is
445 		 * unlikely but not impossible, flush the data first.
446 		 */
447 		sc->m->m_data = sc->m->m_pktdat;
448 		NG_SEND_DATA_ONLY(error, sc->hook, sc->m);
449 		sc->m = NULL;
450 	}
451 	NG_SEND_DATA_ONLY(error, sc->hook, m);
452 
453 	return (total);
454 }
455 
456 /*
457  * Receive data coming from the device one char at a time, when it is not in
458  * bypass mode.
459  */
460 static int
461 ngt_rint(struct tty *tp, char c, int flags)
462 {
463 	sc_p sc = ttyhook_softc(tp);
464 	node_p node = sc->node;
465 	struct mbuf *m;
466 	int error = 0;
467 
468 	tty_lock_assert(tp, MA_OWNED);
469 
470 	if (sc->hook == NULL)
471 		return (0);
472 
473 	if (flags != 0) {
474 		/* framing error or overrun on this char */
475 		if (sc->flags & FLG_DEBUG)
476 			log(LOG_DEBUG, "%s: line error %x\n",
477 			    NG_NODE_NAME(node), flags);
478 		return (0);
479 	}
480 
481 	/* Get a new header mbuf if we need one */
482 	if (!(m = sc->m)) {
483 		MGETHDR(m, M_DONTWAIT, MT_DATA);
484 		if (!m) {
485 			if (sc->flags & FLG_DEBUG)
486 				log(LOG_ERR,
487 				    "%s: can't get mbuf\n", NG_NODE_NAME(node));
488 			return (ENOBUFS);
489 		}
490 		m->m_len = m->m_pkthdr.len = 0;
491 		m->m_pkthdr.rcvif = NULL;
492 		sc->m = m;
493 	}
494 
495 	/* Add char to mbuf */
496 	*mtod(m, u_char *) = c;
497 	m->m_data++;
498 	m->m_len++;
499 	m->m_pkthdr.len++;
500 
501 	/* Ship off mbuf if it's time */
502 	if (sc->hotchar == -1 || c == sc->hotchar || m->m_len >= MHLEN) {
503 		m->m_data = m->m_pktdat;
504 		sc->m = NULL;
505 		NG_SEND_DATA_ONLY(error, sc->hook, m);	/* Will queue */
506 	}
507 
508 	return (error);
509 }
510 
511 static size_t
512 ngt_rint_poll(struct tty *tp)
513 {
514 	/* We can always accept input */
515 	return (1);
516 }
517 
518 static void
519 ngt_close(struct tty *tp)
520 {
521 	sc_p sc = ttyhook_softc(tp);
522 
523 	/* Must be queued to drop the tty lock */
524 	ng_rmnode_flags(sc->node, NG_QUEUE);
525 }
526 
527