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