xref: /freebsd/sys/kern/tty_outq.c (revision 6356dba0b403daa023dec24559ab1f8e602e4f14)
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$");
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. */
56 static long ttyoutq_nfast = 0;
57 SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nfast, CTLFLAG_RD,
58 	&ttyoutq_nfast, 0, "Unbuffered reads to userspace on output");
59 static long ttyoutq_nslow = 0;
60 SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nslow, CTLFLAG_RD,
61 	&ttyoutq_nslow, 0, "Buffered reads to userspace on output");
62 
63 struct ttyoutq_block {
64 	STAILQ_ENTRY(ttyoutq_block) tob_list;
65 	char	tob_data[TTYOUTQ_DATASIZE];
66 };
67 
68 static uma_zone_t ttyoutq_zone;
69 
70 void
71 ttyoutq_flush(struct ttyoutq *to)
72 {
73 
74 	to->to_begin = 0;
75 	to->to_end = 0;
76 }
77 
78 void
79 ttyoutq_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 
137 size_t
138 ttyoutq_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  */
198 int
199 ttyoutq_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 
287 size_t
288 ttyoutq_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 
331 int
332 ttyoutq_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 
346 static void
347 ttyoutq_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 
354 SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL);
355