tty_outq.c revision 182053
1/*-
2 * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Portions of this software were developed under sponsorship from Snow
6 * B.V., the Netherlands.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: head/sys/kern/tty_outq.c 182053 2008-08-23 13:32:21Z ed $");
32
33#include <sys/param.h>
34#include <sys/kernel.h>
35#include <sys/lock.h>
36#include <sys/queue.h>
37#include <sys/sysctl.h>
38#include <sys/systm.h>
39#include <sys/tty.h>
40#include <sys/uio.h>
41
42#include <vm/uma.h>
43
44/*
45 * TTY output queue buffering.
46 *
47 * The previous design of the TTY layer offered the so-called clists.
48 * These clists were used for both the input queues and the output
49 * queue. We don't use certain features on the output side, like quoting
50 * bits for parity marking and such. This mechanism is similar to the
51 * old clists, but only contains the features we need to buffer the
52 * output.
53 */
54
55/* Statistics. */
56static long ttyoutq_nfast = 0;
57SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nfast, CTLFLAG_RD,
58	&ttyoutq_nfast, 0, "Unbuffered reads to userspace on output");
59static long ttyoutq_nslow = 0;
60SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nslow, CTLFLAG_RD,
61	&ttyoutq_nslow, 0, "Buffered reads to userspace on output");
62
63struct ttyoutq_block {
64	STAILQ_ENTRY(ttyoutq_block) tob_list;
65	char	tob_data[TTYOUTQ_DATASIZE];
66};
67
68static uma_zone_t ttyoutq_zone;
69
70void
71ttyoutq_flush(struct ttyoutq *to)
72{
73
74	to->to_begin = 0;
75	to->to_end = 0;
76}
77
78void
79ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size)
80{
81	unsigned int nblocks;
82	struct ttyoutq_block *tob;
83
84	nblocks = howmany(size, TTYOUTQ_DATASIZE);
85
86	while (nblocks > to->to_nblocks) {
87		/*
88		 * List is getting bigger.
89		 * Add new blocks to the tail of the list.
90		 *
91		 * We must unlock the TTY temporarily, because we need
92		 * to allocate memory. This won't be a problem, because
93		 * in the worst case, another thread ends up here, which
94		 * may cause us to allocate too many blocks, but this
95		 * will be caught by the loop below.
96		 */
97		tty_unlock(tp);
98		tob = uma_zalloc(ttyoutq_zone, M_WAITOK);
99		tty_lock(tp);
100
101		if (tty_gone(tp))
102			return;
103
104		STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
105		to->to_nblocks++;
106	}
107
108	while (nblocks < to->to_nblocks) {
109		/*
110		 * List is getting smaller. Remove unused blocks at the
111		 * end. This means we cannot guarantee this routine
112		 * shrinks buffers properly, when we need to reclaim
113		 * more space than there is available.
114		 *
115		 * XXX TODO: Two solutions here:
116		 * - Throw data away
117		 * - Temporarily hit the watermark until enough data has
118		 *   been flushed, so we can remove the blocks.
119		 */
120
121		if (to->to_end == 0) {
122			tob = STAILQ_FIRST(&to->to_list);
123			if (tob == NULL)
124				break;
125			STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
126		} else {
127			tob = STAILQ_NEXT(to->to_lastblock, tob_list);
128			if (tob == NULL)
129				break;
130			STAILQ_REMOVE_NEXT(&to->to_list, to->to_lastblock, tob_list);
131		}
132		uma_zfree(ttyoutq_zone, tob);
133		to->to_nblocks--;
134	}
135}
136
137size_t
138ttyoutq_read(struct ttyoutq *to, void *buf, size_t len)
139{
140	char *cbuf = buf;
141
142	while (len > 0) {
143		struct ttyoutq_block *tob;
144		size_t cbegin, cend, clen;
145
146		/* See if there still is data. */
147		if (to->to_begin == to->to_end)
148			break;
149		tob = STAILQ_FIRST(&to->to_list);
150		if (tob == NULL)
151			break;
152
153		/*
154		 * The end address should be the lowest of these three:
155		 * - The write pointer
156		 * - The blocksize - we can't read beyond the block
157		 * - The end address if we could perform the full read
158		 */
159		cbegin = to->to_begin;
160		cend = MIN(MIN(to->to_end, to->to_begin + len),
161		    TTYOUTQ_DATASIZE);
162		clen = cend - cbegin;
163
164		if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) {
165			/* Read the block until the end. */
166			STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
167			STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
168			to->to_begin = 0;
169			if (to->to_end <= TTYOUTQ_DATASIZE) {
170				to->to_end = 0;
171			} else {
172				to->to_end -= TTYOUTQ_DATASIZE;
173			}
174		} else {
175			/* Read the block partially. */
176			to->to_begin += clen;
177		}
178
179		/* Copy the data out of the buffers. */
180		memcpy(cbuf, tob->tob_data + cbegin, clen);
181		cbuf += clen;
182		len -= clen;
183	}
184
185	return (cbuf - (char *)buf);
186}
187
188/*
189 * An optimized version of ttyoutq_read() which can be used in pseudo
190 * TTY drivers to directly copy data from the outq to userspace, instead
191 * of buffering it.
192 *
193 * We can only copy data directly if we need to read the entire block
194 * back to the user, because we temporarily remove the block from the
195 * queue. Otherwise we need to copy it to a temporary buffer first, to
196 * make sure data remains in the correct order.
197 */
198int
199ttyoutq_read_uio(struct ttyoutq *to, struct tty *tp, struct uio *uio)
200{
201
202	while (uio->uio_resid > 0) {
203		int error;
204		struct ttyoutq_block *tob;
205		size_t cbegin, cend, clen;
206
207		/* See if there still is data. */
208		if (to->to_begin == to->to_end)
209			return (0);
210		tob = STAILQ_FIRST(&to->to_list);
211		if (tob == NULL)
212			return (0);
213
214		/*
215		 * The end address should be the lowest of these three:
216		 * - The write pointer
217		 * - The blocksize - we can't read beyond the block
218		 * - The end address if we could perform the full read
219		 */
220		cbegin = to->to_begin;
221		cend = MIN(MIN(to->to_end, to->to_begin + uio->uio_resid),
222		    TTYOUTQ_DATASIZE);
223		clen = cend - cbegin;
224
225		/*
226		 * We can prevent buffering in some cases:
227		 * - We need to read the block until the end.
228		 * - We don't need to read the block until the end, but
229		 *   there is no data beyond it, which allows us to move
230		 *   the write pointer to a new block.
231		 */
232		if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) {
233			atomic_add_long(&ttyoutq_nfast, 1);
234
235			/*
236			 * Fast path: zero copy. Remove the first block,
237			 * so we can unlock the TTY temporarily.
238			 */
239			STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
240			to->to_nblocks--;
241			to->to_begin = 0;
242			if (to->to_end <= TTYOUTQ_DATASIZE) {
243				to->to_end = 0;
244			} else {
245				to->to_end -= TTYOUTQ_DATASIZE;
246			}
247
248			/* Temporary unlock and copy the data to userspace. */
249			tty_unlock(tp);
250			error = uiomove(tob->tob_data + cbegin, clen, uio);
251			tty_lock(tp);
252
253			/* Block can now be readded to the list. */
254			/*
255			 * XXX: we could remove the blocks here when the
256			 * queue was shrunk, but still in use. See
257			 * ttyoutq_setsize().
258			 */
259			STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
260			to->to_nblocks++;
261			if (error != 0)
262				return (error);
263		} else {
264			char ob[TTYOUTQ_DATASIZE - 1];
265			atomic_add_long(&ttyoutq_nslow, 1);
266
267			/*
268			 * Slow path: store data in a temporary buffer.
269			 */
270			memcpy(ob, tob->tob_data + cbegin, clen);
271			to->to_begin += clen;
272			MPASS(to->to_begin < TTYOUTQ_DATASIZE);
273
274			/* Temporary unlock and copy the data to userspace. */
275			tty_unlock(tp);
276			error = uiomove(ob, clen, uio);
277			tty_lock(tp);
278
279			if (error != 0)
280				return (error);
281		}
282	}
283
284	return (0);
285}
286
287size_t
288ttyoutq_write(struct ttyoutq *to, const void *buf, size_t nbytes)
289{
290	const char *cbuf = buf;
291	struct ttyoutq_block *tob;
292	unsigned int boff;
293	size_t l;
294
295	while (nbytes > 0) {
296		/* Offset in current block. */
297		tob = to->to_lastblock;
298		boff = to->to_end % TTYOUTQ_DATASIZE;
299
300		if (to->to_end == 0) {
301			/* First time we're being used or drained. */
302			MPASS(to->to_begin == 0);
303			tob = to->to_lastblock = STAILQ_FIRST(&to->to_list);
304			if (tob == NULL) {
305				/* Queue has no blocks. */
306				break;
307			}
308		} else if (boff == 0) {
309			/* We reached the end of this block on last write. */
310			tob = STAILQ_NEXT(tob, tob_list);
311			if (tob == NULL) {
312				/* We've reached the watermark. */
313				break;
314			}
315			to->to_lastblock = tob;
316		}
317
318		/* Don't copy more than was requested. */
319		l = MIN(nbytes, TTYOUTQ_DATASIZE - boff);
320		MPASS(l > 0);
321		memcpy(tob->tob_data + boff, cbuf, l);
322
323		cbuf += l;
324		nbytes -= l;
325		to->to_end += l;
326	}
327
328	return (cbuf - (const char *)buf);
329}
330
331int
332ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t nbytes)
333{
334	size_t ret;
335
336	if (ttyoutq_bytesleft(to) < nbytes)
337		return (-1);
338
339	/* We should always be able to write it back. */
340	ret = ttyoutq_write(to, buf, nbytes);
341	MPASS(ret == nbytes);
342
343	return (0);
344}
345
346static void
347ttyoutq_startup(void *dummy)
348{
349
350	ttyoutq_zone = uma_zcreate("ttyoutq", sizeof(struct ttyoutq_block),
351	    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
352}
353
354SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL);
355