tty_outq.c revision 182471
1265879Sdes/*-
2265879Sdes * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
3265879Sdes * All rights reserved.
4265879Sdes *
543092Smarkm * Portions of this software were developed under sponsorship from Snow
651462Smarkm * B.V., the Netherlands.
751462Smarkm *
851462Smarkm * Redistribution and use in source and binary forms, with or without
951462Smarkm * modification, are permitted provided that the following conditions
1051462Smarkm * are met:
1151462Smarkm * 1. Redistributions of source code must retain the above copyright
1251462Smarkm *    notice, this list of conditions and the following disclaimer.
1351462Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1451462Smarkm *    notice, this list of conditions and the following disclaimer in the
1551462Smarkm *    documentation and/or other materials provided with the distribution.
1651462Smarkm *
1751462Smarkm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1851462Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1951462Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2051462Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2151462Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2251462Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2351462Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2451462Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2551462Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2642981Sbrandon * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2742981Sbrandon * SUCH DAMAGE.
2883551Sdillon */
2983551Sdillon
3042981Sbrandon#include <sys/cdefs.h>
3170419Speter__FBSDID("$FreeBSD: head/sys/kern/tty_outq.c 182471 2008-08-30 09:18:27Z ed $");
32265879Sdes
33265879Sdes#include <sys/param.h>
3417141Sjkh#include <sys/kernel.h>
3591754Smarkm#include <sys/lock.h>
36265879Sdes#include <sys/queue.h>
3751462Smarkm#include <sys/sysctl.h>
381984Scsgr#include <sys/systm.h>
39265879Sdes#include <sys/tty.h>
40273043Sdes#include <sys/uio.h>
41273043Sdes
42273043Sdes#include <vm/uma.h>
43273043Sdes
44273043Sdes/*
45273043Sdes * TTY output queue buffering.
46273043Sdes *
47265879Sdes * The previous design of the TTY layer offered the so-called clists.
48265879Sdes * These clists were used for both the input queues and the output
4964918Sgreen * queue. We don't use certain features on the output side, like quoting
5064918Sgreen * bits for parity marking and such. This mechanism is similar to the
5164918Sgreen * old clists, but only contains the features we need to buffer the
52265879Sdes * output.
53265879Sdes */
5474106Smarkm
55265879Sdes/* Statistics. */
5674106Smarkmstatic long ttyoutq_nfast = 0;
57265879SdesSYSCTL_LONG(_kern, OID_AUTO, tty_outq_nfast, CTLFLAG_RD,
58265879Sdes	&ttyoutq_nfast, 0, "Unbuffered reads to userspace on output");
59273043Sdesstatic long ttyoutq_nslow = 0;
60236967SdesSYSCTL_LONG(_kern, OID_AUTO, tty_outq_nslow, CTLFLAG_RD,
61265879Sdes	&ttyoutq_nslow, 0, "Buffered reads to userspace on output");
62236967Sdes
63236967Sdesstruct ttyoutq_block {
64265879Sdes	STAILQ_ENTRY(ttyoutq_block) tob_list;
65265879Sdes	char	tob_data[TTYOUTQ_DATASIZE];
66265879Sdes};
6764918Sgreen
68273043Sdesstatic uma_zone_t ttyoutq_zone;
69273043Sdes
7070419Spetervoid
71265879Sdesttyoutq_flush(struct ttyoutq *to)
72265879Sdes{
7370419Speter
74265879Sdes	to->to_begin = 0;
75265879Sdes	to->to_end = 0;
76265879Sdes}
7764918Sgreen
7870419Spetervoid
7970419Speterttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size)
8064918Sgreen{
81265879Sdes	struct ttyoutq_block *tob;
8264918Sgreen
8364918Sgreen	to->to_quota = howmany(size, TTYOUTQ_DATASIZE);
84265879Sdes
85265879Sdes	while (to->to_quota > to->to_nblocks) {
86265879Sdes		/*
8764918Sgreen		 * List is getting bigger.
88265879Sdes		 * Add new blocks to the tail of the list.
8970419Speter		 *
90265879Sdes		 * We must unlock the TTY temporarily, because we need
9164918Sgreen		 * to allocate memory. This won't be a problem, because
92265879Sdes		 * in the worst case, another thread ends up here, which
93265879Sdes		 * may cause us to allocate too many blocks, but this
94265879Sdes		 * will be caught by the loop below.
9564918Sgreen		 */
9664918Sgreen		tty_unlock(tp);
9764918Sgreen		tob = uma_zalloc(ttyoutq_zone, M_WAITOK);
9864918Sgreen		tty_lock(tp);
9964918Sgreen
10064918Sgreen		STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
101265879Sdes		to->to_nblocks++;
102265879Sdes	}
103265879Sdes}
104265879Sdes
105265879Sdesvoid
10643092Smarkmttyoutq_free(struct ttyoutq *to)
10791754Smarkm{
1081984Scsgr	struct ttyoutq_block *tob;
109265879Sdes
110267111Sume	ttyoutq_flush(to);
111267111Sume	to->to_quota = 0;
112267111Sume
11364918Sgreen	while ((tob = STAILQ_FIRST(&to->to_list)) != NULL) {
114265879Sdes		STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
115265879Sdes		uma_zfree(ttyoutq_zone, tob);
116265879Sdes		to->to_nblocks--;
117265879Sdes	}
118267111Sume
119267111Sume	MPASS(to->to_nblocks == 0);
120265879Sdes}
121265879Sdes
122265879Sdessize_t
1231984Scsgrttyoutq_read(struct ttyoutq *to, void *buf, size_t len)
124{
125	char *cbuf = buf;
126
127	while (len > 0) {
128		struct ttyoutq_block *tob;
129		size_t cbegin, cend, clen;
130
131		/* See if there still is data. */
132		if (to->to_begin == to->to_end)
133			break;
134		tob = STAILQ_FIRST(&to->to_list);
135		if (tob == NULL)
136			break;
137
138		/*
139		 * The end address should be the lowest of these three:
140		 * - The write pointer
141		 * - The blocksize - we can't read beyond the block
142		 * - The end address if we could perform the full read
143		 */
144		cbegin = to->to_begin;
145		cend = MIN(MIN(to->to_end, to->to_begin + len),
146		    TTYOUTQ_DATASIZE);
147		clen = cend - cbegin;
148
149		if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) {
150			/* Read the block until the end. */
151			STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
152			if (to->to_quota < to->to_nblocks) {
153				uma_zfree(ttyoutq_zone, tob);
154				to->to_nblocks--;
155			} else {
156				STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
157			}
158			to->to_begin = 0;
159			if (to->to_end <= TTYOUTQ_DATASIZE) {
160				to->to_end = 0;
161			} else {
162				to->to_end -= TTYOUTQ_DATASIZE;
163			}
164		} else {
165			/* Read the block partially. */
166			to->to_begin += clen;
167		}
168
169		/* Copy the data out of the buffers. */
170		memcpy(cbuf, tob->tob_data + cbegin, clen);
171		cbuf += clen;
172		len -= clen;
173	}
174
175	return (cbuf - (char *)buf);
176}
177
178/*
179 * An optimized version of ttyoutq_read() which can be used in pseudo
180 * TTY drivers to directly copy data from the outq to userspace, instead
181 * of buffering it.
182 *
183 * We can only copy data directly if we need to read the entire block
184 * back to the user, because we temporarily remove the block from the
185 * queue. Otherwise we need to copy it to a temporary buffer first, to
186 * make sure data remains in the correct order.
187 */
188int
189ttyoutq_read_uio(struct ttyoutq *to, struct tty *tp, struct uio *uio)
190{
191
192	while (uio->uio_resid > 0) {
193		int error;
194		struct ttyoutq_block *tob;
195		size_t cbegin, cend, clen;
196
197		/* See if there still is data. */
198		if (to->to_begin == to->to_end)
199			return (0);
200		tob = STAILQ_FIRST(&to->to_list);
201		if (tob == NULL)
202			return (0);
203
204		/*
205		 * The end address should be the lowest of these three:
206		 * - The write pointer
207		 * - The blocksize - we can't read beyond the block
208		 * - The end address if we could perform the full read
209		 */
210		cbegin = to->to_begin;
211		cend = MIN(MIN(to->to_end, to->to_begin + uio->uio_resid),
212		    TTYOUTQ_DATASIZE);
213		clen = cend - cbegin;
214
215		/*
216		 * We can prevent buffering in some cases:
217		 * - We need to read the block until the end.
218		 * - We don't need to read the block until the end, but
219		 *   there is no data beyond it, which allows us to move
220		 *   the write pointer to a new block.
221		 */
222		if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) {
223			atomic_add_long(&ttyoutq_nfast, 1);
224
225			/*
226			 * Fast path: zero copy. Remove the first block,
227			 * so we can unlock the TTY temporarily.
228			 */
229			STAILQ_REMOVE_HEAD(&to->to_list, tob_list);
230			to->to_nblocks--;
231			to->to_begin = 0;
232			if (to->to_end <= TTYOUTQ_DATASIZE) {
233				to->to_end = 0;
234			} else {
235				to->to_end -= TTYOUTQ_DATASIZE;
236			}
237
238			/* Temporary unlock and copy the data to userspace. */
239			tty_unlock(tp);
240			error = uiomove(tob->tob_data + cbegin, clen, uio);
241			tty_lock(tp);
242
243			/* Block can now be readded to the list. */
244			if (to->to_quota <= to->to_nblocks) {
245				uma_zfree(ttyoutq_zone, tob);
246			} else {
247				STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list);
248				to->to_nblocks++;
249			}
250		} else {
251			char ob[TTYOUTQ_DATASIZE - 1];
252			atomic_add_long(&ttyoutq_nslow, 1);
253
254			/*
255			 * Slow path: store data in a temporary buffer.
256			 */
257			memcpy(ob, tob->tob_data + cbegin, clen);
258			to->to_begin += clen;
259			MPASS(to->to_begin < TTYOUTQ_DATASIZE);
260
261			/* Temporary unlock and copy the data to userspace. */
262			tty_unlock(tp);
263			error = uiomove(ob, clen, uio);
264			tty_lock(tp);
265		}
266
267		if (error != 0)
268			return (error);
269	}
270
271	return (0);
272}
273
274size_t
275ttyoutq_write(struct ttyoutq *to, const void *buf, size_t nbytes)
276{
277	const char *cbuf = buf;
278	struct ttyoutq_block *tob;
279	unsigned int boff;
280	size_t l;
281
282	while (nbytes > 0) {
283		/* Offset in current block. */
284		tob = to->to_lastblock;
285		boff = to->to_end % TTYOUTQ_DATASIZE;
286
287		if (to->to_end == 0) {
288			/* First time we're being used or drained. */
289			MPASS(to->to_begin == 0);
290			tob = to->to_lastblock = STAILQ_FIRST(&to->to_list);
291			if (tob == NULL) {
292				/* Queue has no blocks. */
293				break;
294			}
295		} else if (boff == 0) {
296			/* We reached the end of this block on last write. */
297			tob = STAILQ_NEXT(tob, tob_list);
298			if (tob == NULL) {
299				/* We've reached the watermark. */
300				break;
301			}
302			to->to_lastblock = tob;
303		}
304
305		/* Don't copy more than was requested. */
306		l = MIN(nbytes, TTYOUTQ_DATASIZE - boff);
307		MPASS(l > 0);
308		memcpy(tob->tob_data + boff, cbuf, l);
309
310		cbuf += l;
311		nbytes -= l;
312		to->to_end += l;
313	}
314
315	return (cbuf - (const char *)buf);
316}
317
318int
319ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t nbytes)
320{
321	size_t ret;
322
323	if (ttyoutq_bytesleft(to) < nbytes)
324		return (-1);
325
326	/* We should always be able to write it back. */
327	ret = ttyoutq_write(to, buf, nbytes);
328	MPASS(ret == nbytes);
329
330	return (0);
331}
332
333static void
334ttyoutq_startup(void *dummy)
335{
336
337	ttyoutq_zone = uma_zcreate("ttyoutq", sizeof(struct ttyoutq_block),
338	    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
339}
340
341SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL);
342