xref: /freebsd/sys/kern/tty_outq.c (revision fdafd315ad0d0f28a11b9fb4476a9ab059c62b92)
1bc093719SEd Schouten /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
38a36da99SPedro F. Giffuni  *
4bc093719SEd Schouten  * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
5bc093719SEd Schouten  * All rights reserved.
6bc093719SEd Schouten  *
7bc093719SEd Schouten  * Portions of this software were developed under sponsorship from Snow
8bc093719SEd Schouten  * B.V., the Netherlands.
9bc093719SEd Schouten  *
10bc093719SEd Schouten  * Redistribution and use in source and binary forms, with or without
11bc093719SEd Schouten  * modification, are permitted provided that the following conditions
12bc093719SEd Schouten  * are met:
13bc093719SEd Schouten  * 1. Redistributions of source code must retain the above copyright
14bc093719SEd Schouten  *    notice, this list of conditions and the following disclaimer.
15bc093719SEd Schouten  * 2. Redistributions in binary form must reproduce the above copyright
16bc093719SEd Schouten  *    notice, this list of conditions and the following disclaimer in the
17bc093719SEd Schouten  *    documentation and/or other materials provided with the distribution.
18bc093719SEd Schouten  *
19bc093719SEd Schouten  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20bc093719SEd Schouten  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21bc093719SEd Schouten  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22bc093719SEd Schouten  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23bc093719SEd Schouten  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24bc093719SEd Schouten  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25bc093719SEd Schouten  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26bc093719SEd Schouten  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27bc093719SEd Schouten  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28bc093719SEd Schouten  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29bc093719SEd Schouten  * SUCH DAMAGE.
30bc093719SEd Schouten  */
31bc093719SEd Schouten 
32bc093719SEd Schouten #include <sys/param.h>
33bc093719SEd Schouten #include <sys/kernel.h>
34bc093719SEd Schouten #include <sys/lock.h>
35bc093719SEd Schouten #include <sys/queue.h>
36bc093719SEd Schouten #include <sys/systm.h>
37bc093719SEd Schouten #include <sys/tty.h>
38bc093719SEd Schouten #include <sys/uio.h>
39bc093719SEd Schouten 
40bc093719SEd Schouten #include <vm/uma.h>
41bc093719SEd Schouten 
42bc093719SEd Schouten /*
43bc093719SEd Schouten  * TTY output queue buffering.
44bc093719SEd Schouten  *
45bc093719SEd Schouten  * The previous design of the TTY layer offered the so-called clists.
46bc093719SEd Schouten  * These clists were used for both the input queues and the output
47bc093719SEd Schouten  * queue. We don't use certain features on the output side, like quoting
48bc093719SEd Schouten  * bits for parity marking and such. This mechanism is similar to the
49bc093719SEd Schouten  * old clists, but only contains the features we need to buffer the
50bc093719SEd Schouten  * output.
51bc093719SEd Schouten  */
52bc093719SEd Schouten 
53bc093719SEd Schouten struct ttyoutq_block {
5441ba7e9bSEd Schouten 	struct ttyoutq_block	*tob_next;
55bc093719SEd Schouten 	char			tob_data[TTYOUTQ_DATASIZE];
56bc093719SEd Schouten };
57bc093719SEd Schouten 
58bc093719SEd Schouten static uma_zone_t ttyoutq_zone;
59bc093719SEd Schouten 
6041ba7e9bSEd Schouten #define	TTYOUTQ_INSERT_TAIL(to, tob) do {				\
6141ba7e9bSEd Schouten 	if (to->to_end == 0) {						\
6241ba7e9bSEd Schouten 		tob->tob_next = to->to_firstblock;			\
6341ba7e9bSEd Schouten 		to->to_firstblock = tob;				\
6441ba7e9bSEd Schouten 	} else {							\
6541ba7e9bSEd Schouten 		tob->tob_next = to->to_lastblock->tob_next;		\
6641ba7e9bSEd Schouten 		to->to_lastblock->tob_next = tob;			\
6741ba7e9bSEd Schouten 	}								\
6841ba7e9bSEd Schouten 	to->to_nblocks++;						\
6941ba7e9bSEd Schouten } while (0)
7041ba7e9bSEd Schouten 
7141ba7e9bSEd Schouten #define	TTYOUTQ_REMOVE_HEAD(to) do {					\
7241ba7e9bSEd Schouten 	to->to_firstblock = to->to_firstblock->tob_next;		\
7341ba7e9bSEd Schouten 	to->to_nblocks--;						\
7441ba7e9bSEd Schouten } while (0)
7541ba7e9bSEd Schouten 
7641ba7e9bSEd Schouten #define	TTYOUTQ_RECYCLE(to, tob) do {					\
7741ba7e9bSEd Schouten 	if (to->to_quota <= to->to_nblocks)				\
7841ba7e9bSEd Schouten 		uma_zfree(ttyoutq_zone, tob);				\
7941ba7e9bSEd Schouten 	else								\
8041ba7e9bSEd Schouten 		TTYOUTQ_INSERT_TAIL(to, tob);				\
8141ba7e9bSEd Schouten } while (0)
8241ba7e9bSEd Schouten 
83bc093719SEd Schouten void
ttyoutq_flush(struct ttyoutq * to)84bc093719SEd Schouten ttyoutq_flush(struct ttyoutq *to)
85bc093719SEd Schouten {
86bc093719SEd Schouten 
87bc093719SEd Schouten 	to->to_begin = 0;
88bc093719SEd Schouten 	to->to_end = 0;
89bc093719SEd Schouten }
90bc093719SEd Schouten 
91a6f63533SIan Lepore int
ttyoutq_setsize(struct ttyoutq * to,struct tty * tp,size_t size)92bc093719SEd Schouten ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size)
93bc093719SEd Schouten {
94bc093719SEd Schouten 	struct ttyoutq_block *tob;
95bc093719SEd Schouten 
9674bb9e3aSEd Schouten 	to->to_quota = howmany(size, TTYOUTQ_DATASIZE);
97bc093719SEd Schouten 
9874bb9e3aSEd Schouten 	while (to->to_quota > to->to_nblocks) {
99bc093719SEd Schouten 		/*
100bc093719SEd Schouten 		 * List is getting bigger.
101bc093719SEd Schouten 		 * Add new blocks to the tail of the list.
102bc093719SEd Schouten 		 *
103bc093719SEd Schouten 		 * We must unlock the TTY temporarily, because we need
104bc093719SEd Schouten 		 * to allocate memory. This won't be a problem, because
105bc093719SEd Schouten 		 * in the worst case, another thread ends up here, which
106bc093719SEd Schouten 		 * may cause us to allocate too many blocks, but this
107bc093719SEd Schouten 		 * will be caught by the loop below.
108bc093719SEd Schouten 		 */
109bc093719SEd Schouten 		tty_unlock(tp);
110bc093719SEd Schouten 		tob = uma_zalloc(ttyoutq_zone, M_WAITOK);
111bc093719SEd Schouten 		tty_lock(tp);
112bc093719SEd Schouten 
113a6f63533SIan Lepore 		if (tty_gone(tp)) {
114a6f63533SIan Lepore 			uma_zfree(ttyoutq_zone, tob);
115a6f63533SIan Lepore 			return (ENXIO);
116a6f63533SIan Lepore 		}
117a6f63533SIan Lepore 
11841ba7e9bSEd Schouten 		TTYOUTQ_INSERT_TAIL(to, tob);
119bc093719SEd Schouten 	}
120a6f63533SIan Lepore 	return (0);
121bc093719SEd Schouten }
12274bb9e3aSEd Schouten 
12374bb9e3aSEd Schouten void
ttyoutq_free(struct ttyoutq * to)12474bb9e3aSEd Schouten ttyoutq_free(struct ttyoutq *to)
12574bb9e3aSEd Schouten {
12674bb9e3aSEd Schouten 	struct ttyoutq_block *tob;
12774bb9e3aSEd Schouten 
12874bb9e3aSEd Schouten 	ttyoutq_flush(to);
12974bb9e3aSEd Schouten 	to->to_quota = 0;
13074bb9e3aSEd Schouten 
13141ba7e9bSEd Schouten 	while ((tob = to->to_firstblock) != NULL) {
13241ba7e9bSEd Schouten 		TTYOUTQ_REMOVE_HEAD(to);
133bc093719SEd Schouten 		uma_zfree(ttyoutq_zone, tob);
134bc093719SEd Schouten 	}
13574bb9e3aSEd Schouten 
13674bb9e3aSEd Schouten 	MPASS(to->to_nblocks == 0);
137bc093719SEd Schouten }
138bc093719SEd Schouten 
139bc093719SEd Schouten size_t
ttyoutq_read(struct ttyoutq * to,void * buf,size_t len)140bc093719SEd Schouten ttyoutq_read(struct ttyoutq *to, void *buf, size_t len)
141bc093719SEd Schouten {
142bc093719SEd Schouten 	char *cbuf = buf;
143bc093719SEd Schouten 
144bc093719SEd Schouten 	while (len > 0) {
145bc093719SEd Schouten 		struct ttyoutq_block *tob;
146bc093719SEd Schouten 		size_t cbegin, cend, clen;
147bc093719SEd Schouten 
148bc093719SEd Schouten 		/* See if there still is data. */
149bc093719SEd Schouten 		if (to->to_begin == to->to_end)
150bc093719SEd Schouten 			break;
15141ba7e9bSEd Schouten 		tob = to->to_firstblock;
152bc093719SEd Schouten 		if (tob == NULL)
153bc093719SEd Schouten 			break;
154bc093719SEd Schouten 
155bc093719SEd Schouten 		/*
156bc093719SEd Schouten 		 * The end address should be the lowest of these three:
157bc093719SEd Schouten 		 * - The write pointer
158bc093719SEd Schouten 		 * - The blocksize - we can't read beyond the block
159bc093719SEd Schouten 		 * - The end address if we could perform the full read
160bc093719SEd Schouten 		 */
161bc093719SEd Schouten 		cbegin = to->to_begin;
162bc093719SEd Schouten 		cend = MIN(MIN(to->to_end, to->to_begin + len),
163bc093719SEd Schouten 		    TTYOUTQ_DATASIZE);
164bc093719SEd Schouten 		clen = cend - cbegin;
165bc093719SEd Schouten 
166bc093719SEd Schouten 		/* Copy the data out of the buffers. */
167bc093719SEd Schouten 		memcpy(cbuf, tob->tob_data + cbegin, clen);
168bc093719SEd Schouten 		cbuf += clen;
169bc093719SEd Schouten 		len -= clen;
17041ba7e9bSEd Schouten 
17141ba7e9bSEd Schouten 		if (cend == to->to_end) {
17241ba7e9bSEd Schouten 			/* Read the complete queue. */
17341ba7e9bSEd Schouten 			to->to_begin = 0;
17441ba7e9bSEd Schouten 			to->to_end = 0;
17541ba7e9bSEd Schouten 		} else if (cend == TTYOUTQ_DATASIZE) {
17641ba7e9bSEd Schouten 			/* Read the block until the end. */
17741ba7e9bSEd Schouten 			TTYOUTQ_REMOVE_HEAD(to);
17841ba7e9bSEd Schouten 			to->to_begin = 0;
17941ba7e9bSEd Schouten 			to->to_end -= TTYOUTQ_DATASIZE;
18041ba7e9bSEd Schouten 			TTYOUTQ_RECYCLE(to, tob);
18141ba7e9bSEd Schouten 		} else {
18241ba7e9bSEd Schouten 			/* Read the block partially. */
18341ba7e9bSEd Schouten 			to->to_begin += clen;
18441ba7e9bSEd Schouten 		}
185bc093719SEd Schouten 	}
186bc093719SEd Schouten 
187bc093719SEd Schouten 	return (cbuf - (char *)buf);
188bc093719SEd Schouten }
189bc093719SEd Schouten 
190bc093719SEd Schouten /*
191bc093719SEd Schouten  * An optimized version of ttyoutq_read() which can be used in pseudo
192bc093719SEd Schouten  * TTY drivers to directly copy data from the outq to userspace, instead
193bc093719SEd Schouten  * of buffering it.
194bc093719SEd Schouten  *
195bc093719SEd Schouten  * We can only copy data directly if we need to read the entire block
196bc093719SEd Schouten  * back to the user, because we temporarily remove the block from the
197bc093719SEd Schouten  * queue. Otherwise we need to copy it to a temporary buffer first, to
198bc093719SEd Schouten  * make sure data remains in the correct order.
199bc093719SEd Schouten  */
200bc093719SEd Schouten int
ttyoutq_read_uio(struct ttyoutq * to,struct tty * tp,struct uio * uio)201bc093719SEd Schouten ttyoutq_read_uio(struct ttyoutq *to, struct tty *tp, struct uio *uio)
202bc093719SEd Schouten {
203bc093719SEd Schouten 
204bc093719SEd Schouten 	while (uio->uio_resid > 0) {
205bc093719SEd Schouten 		int error;
206bc093719SEd Schouten 		struct ttyoutq_block *tob;
207bc093719SEd Schouten 		size_t cbegin, cend, clen;
208bc093719SEd Schouten 
209bc093719SEd Schouten 		/* See if there still is data. */
210bc093719SEd Schouten 		if (to->to_begin == to->to_end)
211bc093719SEd Schouten 			return (0);
21241ba7e9bSEd Schouten 		tob = to->to_firstblock;
213bc093719SEd Schouten 		if (tob == NULL)
214bc093719SEd Schouten 			return (0);
215bc093719SEd Schouten 
216bc093719SEd Schouten 		/*
217bc093719SEd Schouten 		 * The end address should be the lowest of these three:
218bc093719SEd Schouten 		 * - The write pointer
219bc093719SEd Schouten 		 * - The blocksize - we can't read beyond the block
220bc093719SEd Schouten 		 * - The end address if we could perform the full read
221bc093719SEd Schouten 		 */
222bc093719SEd Schouten 		cbegin = to->to_begin;
223bc093719SEd Schouten 		cend = MIN(MIN(to->to_end, to->to_begin + uio->uio_resid),
224bc093719SEd Schouten 		    TTYOUTQ_DATASIZE);
225bc093719SEd Schouten 		clen = cend - cbegin;
226bc093719SEd Schouten 
227bc093719SEd Schouten 		/*
228bc093719SEd Schouten 		 * We can prevent buffering in some cases:
229bc093719SEd Schouten 		 * - We need to read the block until the end.
230bc093719SEd Schouten 		 * - We don't need to read the block until the end, but
231bc093719SEd Schouten 		 *   there is no data beyond it, which allows us to move
232bc093719SEd Schouten 		 *   the write pointer to a new block.
233bc093719SEd Schouten 		 */
234bc093719SEd Schouten 		if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) {
235bc093719SEd Schouten 			/*
236bc093719SEd Schouten 			 * Fast path: zero copy. Remove the first block,
237bc093719SEd Schouten 			 * so we can unlock the TTY temporarily.
238bc093719SEd Schouten 			 */
23941ba7e9bSEd Schouten 			TTYOUTQ_REMOVE_HEAD(to);
240bc093719SEd Schouten 			to->to_begin = 0;
24141ba7e9bSEd Schouten 			if (to->to_end <= TTYOUTQ_DATASIZE)
242bc093719SEd Schouten 				to->to_end = 0;
24341ba7e9bSEd Schouten 			else
244bc093719SEd Schouten 				to->to_end -= TTYOUTQ_DATASIZE;
245bc093719SEd Schouten 
246bc093719SEd Schouten 			/* Temporary unlock and copy the data to userspace. */
247bc093719SEd Schouten 			tty_unlock(tp);
248bc093719SEd Schouten 			error = uiomove(tob->tob_data + cbegin, clen, uio);
249bc093719SEd Schouten 			tty_lock(tp);
250bc093719SEd Schouten 
251bc093719SEd Schouten 			/* Block can now be readded to the list. */
25241ba7e9bSEd Schouten 			TTYOUTQ_RECYCLE(to, tob);
253bc093719SEd Schouten 		} else {
254bc093719SEd Schouten 			char ob[TTYOUTQ_DATASIZE - 1];
255bc093719SEd Schouten 
256bc093719SEd Schouten 			/*
257bc093719SEd Schouten 			 * Slow path: store data in a temporary buffer.
258bc093719SEd Schouten 			 */
259bc093719SEd Schouten 			memcpy(ob, tob->tob_data + cbegin, clen);
260bc093719SEd Schouten 			to->to_begin += clen;
261bc093719SEd Schouten 			MPASS(to->to_begin < TTYOUTQ_DATASIZE);
262bc093719SEd Schouten 
263bc093719SEd Schouten 			/* Temporary unlock and copy the data to userspace. */
264bc093719SEd Schouten 			tty_unlock(tp);
265bc093719SEd Schouten 			error = uiomove(ob, clen, uio);
266bc093719SEd Schouten 			tty_lock(tp);
26774bb9e3aSEd Schouten 		}
268bc093719SEd Schouten 
269bc093719SEd Schouten 		if (error != 0)
270bc093719SEd Schouten 			return (error);
271bc093719SEd Schouten 	}
272bc093719SEd Schouten 
273bc093719SEd Schouten 	return (0);
274bc093719SEd Schouten }
275bc093719SEd Schouten 
276bc093719SEd Schouten size_t
ttyoutq_write(struct ttyoutq * to,const void * buf,size_t nbytes)277bc093719SEd Schouten ttyoutq_write(struct ttyoutq *to, const void *buf, size_t nbytes)
278bc093719SEd Schouten {
279bc093719SEd Schouten 	const char *cbuf = buf;
280bc093719SEd Schouten 	struct ttyoutq_block *tob;
281bc093719SEd Schouten 	unsigned int boff;
282bc093719SEd Schouten 	size_t l;
283bc093719SEd Schouten 
284bc093719SEd Schouten 	while (nbytes > 0) {
285bc093719SEd Schouten 		boff = to->to_end % TTYOUTQ_DATASIZE;
286bc093719SEd Schouten 
287bc093719SEd Schouten 		if (to->to_end == 0) {
288bc093719SEd Schouten 			/* First time we're being used or drained. */
289bc093719SEd Schouten 			MPASS(to->to_begin == 0);
29041ba7e9bSEd Schouten 			tob = to->to_firstblock;
291bc093719SEd Schouten 			if (tob == NULL) {
292bc093719SEd Schouten 				/* Queue has no blocks. */
293bc093719SEd Schouten 				break;
294bc093719SEd Schouten 			}
29541ba7e9bSEd Schouten 			to->to_lastblock = tob;
296bc093719SEd Schouten 		} else if (boff == 0) {
297bc093719SEd Schouten 			/* We reached the end of this block on last write. */
29841ba7e9bSEd Schouten 			tob = to->to_lastblock->tob_next;
299bc093719SEd Schouten 			if (tob == NULL) {
300bc093719SEd Schouten 				/* We've reached the watermark. */
301bc093719SEd Schouten 				break;
302bc093719SEd Schouten 			}
303bc093719SEd Schouten 			to->to_lastblock = tob;
30441ba7e9bSEd Schouten 		} else {
30541ba7e9bSEd Schouten 			tob = to->to_lastblock;
306bc093719SEd Schouten 		}
307bc093719SEd Schouten 
308bc093719SEd Schouten 		/* Don't copy more than was requested. */
309bc093719SEd Schouten 		l = MIN(nbytes, TTYOUTQ_DATASIZE - boff);
310bc093719SEd Schouten 		MPASS(l > 0);
311bc093719SEd Schouten 		memcpy(tob->tob_data + boff, cbuf, l);
312bc093719SEd Schouten 
313bc093719SEd Schouten 		cbuf += l;
314bc093719SEd Schouten 		nbytes -= l;
315bc093719SEd Schouten 		to->to_end += l;
316bc093719SEd Schouten 	}
317bc093719SEd Schouten 
318bc093719SEd Schouten 	return (cbuf - (const char *)buf);
319bc093719SEd Schouten }
320bc093719SEd Schouten 
321bc093719SEd Schouten int
ttyoutq_write_nofrag(struct ttyoutq * to,const void * buf,size_t nbytes)322bc093719SEd Schouten ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t nbytes)
323bc093719SEd Schouten {
324a2bb4e08SMatt Macy 	size_t ret __unused;
325bc093719SEd Schouten 
326bc093719SEd Schouten 	if (ttyoutq_bytesleft(to) < nbytes)
327bc093719SEd Schouten 		return (-1);
328bc093719SEd Schouten 
329bc093719SEd Schouten 	/* We should always be able to write it back. */
330a2bb4e08SMatt Macy 	ret = ttyoutq_write(to, buf, nbytes);
331bc093719SEd Schouten 	MPASS(ret == nbytes);
332bc093719SEd Schouten 
333bc093719SEd Schouten 	return (0);
334bc093719SEd Schouten }
335bc093719SEd Schouten 
336bc093719SEd Schouten static void
ttyoutq_startup(void * dummy)337bc093719SEd Schouten ttyoutq_startup(void *dummy)
338bc093719SEd Schouten {
339bc093719SEd Schouten 
340bc093719SEd Schouten 	ttyoutq_zone = uma_zcreate("ttyoutq", sizeof(struct ttyoutq_block),
341bc093719SEd Schouten 	    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
342bc093719SEd Schouten }
343bc093719SEd Schouten 
344bc093719SEd Schouten SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL);
345