freebsd-nq/sys/kern/tty_subr.c

698 lines
16 KiB
C

/*-
* Copyright (c) 1994, David Greenman
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* clist support routines
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/clist.h>
static void clist_init(void *);
SYSINIT(clist, SI_SUB_CLIST, SI_ORDER_FIRST, clist_init, NULL)
static struct cblock *cfreelist = 0;
int cfreecount = 0;
static int cslushcount;
static int ctotcount;
#ifndef INITIAL_CBLOCKS
#define INITIAL_CBLOCKS 50
#endif
static struct cblock *cblock_alloc(void);
static void cblock_alloc_cblocks(int number);
static void cblock_free(struct cblock *cblockp);
static void cblock_free_cblocks(int number);
#include "opt_ddb.h"
#ifdef DDB
#include <ddb/ddb.h>
DB_SHOW_COMMAND(cbstat, cbstat)
{
int cbsize = CBSIZE;
printf(
"tot = %d (active = %d, free = %d (reserved = %d, slush = %d))\n",
ctotcount * cbsize, ctotcount * cbsize - cfreecount, cfreecount,
cfreecount - cslushcount * cbsize, cslushcount * cbsize);
}
#endif /* DDB */
/*
* Called from init_main.c
*/
/* ARGSUSED*/
static void
clist_init(dummy)
void *dummy;
{
/*
* Allocate an initial base set of cblocks as a 'slush'.
* We allocate non-slush cblocks with each initial tty_open() and
* deallocate them with each tty_close().
* We should adjust the slush allocation. This can't be done in
* the i/o routines because they are sometimes called from
* interrupt handlers when it may be unsafe to call malloc().
*/
cblock_alloc_cblocks(cslushcount = INITIAL_CBLOCKS);
}
/*
* Remove a cblock from the cfreelist queue and return a pointer
* to it.
*/
static __inline struct cblock *
cblock_alloc()
{
struct cblock *cblockp;
cblockp = cfreelist;
if (cblockp == NULL)
panic("clist reservation botch");
cfreelist = cblockp->c_next;
cblockp->c_next = NULL;
cfreecount -= CBSIZE;
return (cblockp);
}
/*
* Add a cblock to the cfreelist queue.
*/
static __inline void
cblock_free(cblockp)
struct cblock *cblockp;
{
if (isset(cblockp->c_quote, CBQSIZE * NBBY - 1))
bzero(cblockp->c_quote, sizeof cblockp->c_quote);
cblockp->c_next = cfreelist;
cfreelist = cblockp;
cfreecount += CBSIZE;
}
/*
* Allocate some cblocks for the cfreelist queue.
*/
static void
cblock_alloc_cblocks(number)
int number;
{
int i;
struct cblock *cbp;
for (i = 0; i < number; ++i) {
cbp = malloc(sizeof *cbp, M_TTYS, M_NOWAIT);
if (cbp == NULL) {
printf(
"cblock_alloc_cblocks: M_NOWAIT malloc failed, trying M_WAITOK\n");
cbp = malloc(sizeof *cbp, M_TTYS, M_WAITOK);
}
/*
* Freed cblocks have zero quotes and garbage elsewhere.
* Set the may-have-quote bit to force zeroing the quotes.
*/
setbit(cbp->c_quote, CBQSIZE * NBBY - 1);
cblock_free(cbp);
}
ctotcount += number;
}
/*
* Set the cblock allocation policy for a clist.
* Must be called in process context at spltty().
*/
void
clist_alloc_cblocks(clistp, ccmax, ccreserved)
struct clist *clistp;
int ccmax;
int ccreserved;
{
int dcbr;
/*
* Allow for wasted space at the head.
*/
if (ccmax != 0)
ccmax += CBSIZE - 1;
if (ccreserved != 0)
ccreserved += CBSIZE - 1;
clistp->c_cbmax = roundup(ccmax, CBSIZE) / CBSIZE;
dcbr = roundup(ccreserved, CBSIZE) / CBSIZE - clistp->c_cbreserved;
if (dcbr >= 0)
cblock_alloc_cblocks(dcbr);
else {
if (clistp->c_cbreserved + dcbr < clistp->c_cbcount)
dcbr = clistp->c_cbcount - clistp->c_cbreserved;
cblock_free_cblocks(-dcbr);
}
clistp->c_cbreserved += dcbr;
}
/*
* Free some cblocks from the cfreelist queue back to the
* system malloc pool.
*/
static void
cblock_free_cblocks(number)
int number;
{
int i;
for (i = 0; i < number; ++i)
free(cblock_alloc(), M_TTYS);
ctotcount -= number;
}
/*
* Free the cblocks reserved for a clist.
* Must be called at spltty().
*/
void
clist_free_cblocks(clistp)
struct clist *clistp;
{
if (clistp->c_cbcount != 0)
panic("freeing active clist cblocks");
cblock_free_cblocks(clistp->c_cbreserved);
clistp->c_cbmax = 0;
clistp->c_cbreserved = 0;
}
/*
* Get a character from the head of a clist.
*/
int
getc(clistp)
struct clist *clistp;
{
int chr = -1;
int s;
struct cblock *cblockp;
s = spltty();
/* If there are characters in the list, get one */
if (clistp->c_cc) {
cblockp = (struct cblock *)((intptr_t)clistp->c_cf & ~CROUND);
chr = (u_char)*clistp->c_cf;
/*
* If this char is quoted, set the flag.
*/
if (isset(cblockp->c_quote, clistp->c_cf - (char *)cblockp->c_info))
chr |= TTY_QUOTE;
/*
* Advance to next character.
*/
clistp->c_cf++;
clistp->c_cc--;
/*
* If we have advanced the 'first' character pointer
* past the end of this cblock, advance to the next one.
* If there are no more characters, set the first and
* last pointers to NULL. In either case, free the
* current cblock.
*/
if ((clistp->c_cf >= (char *)(cblockp+1)) || (clistp->c_cc == 0)) {
if (clistp->c_cc > 0) {
clistp->c_cf = cblockp->c_next->c_info;
} else {
clistp->c_cf = clistp->c_cl = NULL;
}
cblock_free(cblockp);
if (--clistp->c_cbcount >= clistp->c_cbreserved)
++cslushcount;
}
}
splx(s);
return (chr);
}
/*
* Copy 'amount' of chars, beginning at head of clist 'clistp' to
* destination linear buffer 'dest'. Return number of characters
* actually copied.
*/
int
q_to_b(clistp, dest, amount)
struct clist *clistp;
char *dest;
int amount;
{
struct cblock *cblockp;
struct cblock *cblockn;
char *dest_orig = dest;
int numc;
int s;
s = spltty();
while (clistp && amount && (clistp->c_cc > 0)) {
cblockp = (struct cblock *)((intptr_t)clistp->c_cf & ~CROUND);
cblockn = cblockp + 1; /* pointer arithmetic! */
numc = min(amount, (char *)cblockn - clistp->c_cf);
numc = min(numc, clistp->c_cc);
bcopy(clistp->c_cf, dest, numc);
amount -= numc;
clistp->c_cf += numc;
clistp->c_cc -= numc;
dest += numc;
/*
* If this cblock has been emptied, advance to the next
* one. If there are no more characters, set the first
* and last pointer to NULL. In either case, free the
* current cblock.
*/
if ((clistp->c_cf >= (char *)cblockn) || (clistp->c_cc == 0)) {
if (clistp->c_cc > 0) {
clistp->c_cf = cblockp->c_next->c_info;
} else {
clistp->c_cf = clistp->c_cl = NULL;
}
cblock_free(cblockp);
if (--clistp->c_cbcount >= clistp->c_cbreserved)
++cslushcount;
}
}
splx(s);
return (dest - dest_orig);
}
/*
* Flush 'amount' of chars, beginning at head of clist 'clistp'.
*/
void
ndflush(clistp, amount)
struct clist *clistp;
int amount;
{
struct cblock *cblockp;
struct cblock *cblockn;
int numc;
int s;
s = spltty();
while (amount && (clistp->c_cc > 0)) {
cblockp = (struct cblock *)((intptr_t)clistp->c_cf & ~CROUND);
cblockn = cblockp + 1; /* pointer arithmetic! */
numc = min(amount, (char *)cblockn - clistp->c_cf);
numc = min(numc, clistp->c_cc);
amount -= numc;
clistp->c_cf += numc;
clistp->c_cc -= numc;
/*
* If this cblock has been emptied, advance to the next
* one. If there are no more characters, set the first
* and last pointer to NULL. In either case, free the
* current cblock.
*/
if ((clistp->c_cf >= (char *)cblockn) || (clistp->c_cc == 0)) {
if (clistp->c_cc > 0) {
clistp->c_cf = cblockp->c_next->c_info;
} else {
clistp->c_cf = clistp->c_cl = NULL;
}
cblock_free(cblockp);
if (--clistp->c_cbcount >= clistp->c_cbreserved)
++cslushcount;
}
}
splx(s);
}
/*
* Add a character to the end of a clist. Return -1 is no
* more clists, or 0 for success.
*/
int
putc(chr, clistp)
int chr;
struct clist *clistp;
{
struct cblock *cblockp;
int s;
s = spltty();
if (clistp->c_cl == NULL) {
if (clistp->c_cbreserved < 1) {
splx(s);
printf("putc to a clist with no reserved cblocks\n");
return (-1); /* nothing done */
}
cblockp = cblock_alloc();
clistp->c_cbcount = 1;
clistp->c_cf = clistp->c_cl = cblockp->c_info;
clistp->c_cc = 0;
} else {
cblockp = (struct cblock *)((intptr_t)clistp->c_cl & ~CROUND);
if (((intptr_t)clistp->c_cl & CROUND) == 0) {
struct cblock *prev = (cblockp - 1);
if (clistp->c_cbcount >= clistp->c_cbreserved) {
if (clistp->c_cbcount >= clistp->c_cbmax
|| cslushcount <= 0) {
splx(s);
return (-1);
}
--cslushcount;
}
cblockp = cblock_alloc();
clistp->c_cbcount++;
prev->c_next = cblockp;
clistp->c_cl = cblockp->c_info;
}
}
/*
* If this character is quoted, set the quote bit, if not, clear it.
*/
if (chr & TTY_QUOTE) {
setbit(cblockp->c_quote, clistp->c_cl - (char *)cblockp->c_info);
/*
* Use one of the spare quote bits to record that something
* may be quoted.
*/
setbit(cblockp->c_quote, CBQSIZE * NBBY - 1);
} else
clrbit(cblockp->c_quote, clistp->c_cl - (char *)cblockp->c_info);
*clistp->c_cl++ = chr;
clistp->c_cc++;
splx(s);
return (0);
}
/*
* Copy data from linear buffer to clist chain. Return the
* number of characters not copied.
*/
int
b_to_q(src, amount, clistp)
char *src;
int amount;
struct clist *clistp;
{
struct cblock *cblockp;
char *firstbyte, *lastbyte;
u_char startmask, endmask;
int startbit, endbit, num_between, numc;
int s;
/*
* Avoid allocating an initial cblock and then not using it.
* c_cc == 0 must imply c_cbount == 0.
*/
if (amount <= 0)
return (amount);
s = spltty();
/*
* If there are no cblocks assigned to this clist yet,
* then get one.
*/
if (clistp->c_cl == NULL) {
if (clistp->c_cbreserved < 1) {
splx(s);
printf("b_to_q to a clist with no reserved cblocks.\n");
return (amount); /* nothing done */
}
cblockp = cblock_alloc();
clistp->c_cbcount = 1;
clistp->c_cf = clistp->c_cl = cblockp->c_info;
clistp->c_cc = 0;
} else {
cblockp = (struct cblock *)((intptr_t)clistp->c_cl & ~CROUND);
}
while (amount) {
/*
* Get another cblock if needed.
*/
if (((intptr_t)clistp->c_cl & CROUND) == 0) {
struct cblock *prev = cblockp - 1;
if (clistp->c_cbcount >= clistp->c_cbreserved) {
if (clistp->c_cbcount >= clistp->c_cbmax
|| cslushcount <= 0) {
splx(s);
return (amount);
}
--cslushcount;
}
cblockp = cblock_alloc();
clistp->c_cbcount++;
prev->c_next = cblockp;
clistp->c_cl = cblockp->c_info;
}
/*
* Copy a chunk of the linear buffer up to the end
* of this cblock.
*/
numc = min(amount, (char *)(cblockp + 1) - clistp->c_cl);
bcopy(src, clistp->c_cl, numc);
/*
* Clear quote bits if they aren't known to be clear.
* The following could probably be made into a separate
* "bitzero()" routine, but why bother?
*/
if (isset(cblockp->c_quote, CBQSIZE * NBBY - 1)) {
startbit = clistp->c_cl - (char *)cblockp->c_info;
endbit = startbit + numc - 1;
firstbyte = (u_char *)cblockp->c_quote + (startbit / NBBY);
lastbyte = (u_char *)cblockp->c_quote + (endbit / NBBY);
/*
* Calculate mask of bits to preserve in first and
* last bytes.
*/
startmask = NBBY - (startbit % NBBY);
startmask = 0xff >> startmask;
endmask = (endbit % NBBY);
endmask = 0xff << (endmask + 1);
if (firstbyte != lastbyte) {
*firstbyte &= startmask;
*lastbyte &= endmask;
num_between = lastbyte - firstbyte - 1;
if (num_between)
bzero(firstbyte + 1, num_between);
} else {
*firstbyte &= (startmask | endmask);
}
}
/*
* ...and update pointer for the next chunk.
*/
src += numc;
clistp->c_cl += numc;
clistp->c_cc += numc;
amount -= numc;
/*
* If we go through the loop again, it's always
* for data in the next cblock, so by adding one (cblock),
* (which makes the pointer 1 beyond the end of this
* cblock) we prepare for the assignment of 'prev'
* above.
*/
cblockp += 1;
}
splx(s);
return (amount);
}
/*
* Get the next character in the clist. Store it at dst. Don't
* advance any clist pointers, but return a pointer to the next
* character position.
*/
char *
nextc(clistp, cp, dst)
struct clist *clistp;
char *cp;
int *dst;
{
struct cblock *cblockp;
++cp;
/*
* See if the next character is beyond the end of
* the clist.
*/
if (clistp->c_cc && (cp != clistp->c_cl)) {
/*
* If the next character is beyond the end of this
* cblock, advance to the next cblock.
*/
if (((intptr_t)cp & CROUND) == 0)
cp = ((struct cblock *)cp - 1)->c_next->c_info;
cblockp = (struct cblock *)((intptr_t)cp & ~CROUND);
/*
* Get the character. Set the quote flag if this character
* is quoted.
*/
*dst = (u_char)*cp | (isset(cblockp->c_quote, cp - (char *)cblockp->c_info) ? TTY_QUOTE : 0);
return (cp);
}
return (NULL);
}
/*
* "Unput" a character from a clist.
*/
int
unputc(clistp)
struct clist *clistp;
{
struct cblock *cblockp = 0, *cbp = 0;
int s;
int chr = -1;
s = spltty();
if (clistp->c_cc) {
--clistp->c_cc;
--clistp->c_cl;
chr = (u_char)*clistp->c_cl;
cblockp = (struct cblock *)((intptr_t)clistp->c_cl & ~CROUND);
/*
* Set quote flag if this character was quoted.
*/
if (isset(cblockp->c_quote, (u_char *)clistp->c_cl - cblockp->c_info))
chr |= TTY_QUOTE;
/*
* If all of the characters have been unput in this
* cblock, then find the previous one and free this
* one.
*/
if (clistp->c_cc && (clistp->c_cl <= (char *)cblockp->c_info)) {
cbp = (struct cblock *)((intptr_t)clistp->c_cf & ~CROUND);
while (cbp->c_next != cblockp)
cbp = cbp->c_next;
/*
* When the previous cblock is at the end, the 'last'
* pointer always points (invalidly) one past.
*/
clistp->c_cl = (char *)(cbp+1);
cblock_free(cblockp);
if (--clistp->c_cbcount >= clistp->c_cbreserved)
++cslushcount;
cbp->c_next = NULL;
}
}
/*
* If there are no more characters on the list, then
* free the last cblock.
*/
if ((clistp->c_cc == 0) && clistp->c_cl) {
cblockp = (struct cblock *)((intptr_t)clistp->c_cl & ~CROUND);
cblock_free(cblockp);
if (--clistp->c_cbcount >= clistp->c_cbreserved)
++cslushcount;
clistp->c_cf = clistp->c_cl = NULL;
}
splx(s);
return (chr);
}
/*
* Move characters in source clist to destination clist,
* preserving quote bits.
*/
void
catq(src_clistp, dest_clistp)
struct clist *src_clistp, *dest_clistp;
{
int chr, s;
s = spltty();
/*
* If the destination clist is empty (has no cblocks atttached),
* and there are no possible complications with the resource counters,
* then we simply assign the current clist to the destination.
*/
if (!dest_clistp->c_cf
&& src_clistp->c_cbcount <= src_clistp->c_cbmax
&& src_clistp->c_cbcount <= dest_clistp->c_cbmax) {
dest_clistp->c_cf = src_clistp->c_cf;
dest_clistp->c_cl = src_clistp->c_cl;
src_clistp->c_cf = src_clistp->c_cl = NULL;
dest_clistp->c_cc = src_clistp->c_cc;
src_clistp->c_cc = 0;
dest_clistp->c_cbcount = src_clistp->c_cbcount;
src_clistp->c_cbcount = 0;
splx(s);
return;
}
splx(s);
/*
* XXX This should probably be optimized to more than one
* character at a time.
*/
while ((chr = getc(src_clistp)) != -1)
putc(chr, dest_clistp);
}