tty_ioctl.c

/* w tym pliku zdefiniowana jest  funkcja tty_ioctl dla dyscypliny
 * linii N_TTY - funkcja n_tty_ioctl */
/*
 *  linux/drivers/char/tty_ioctl.c
 *
 *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
 * which can be dynamically activated and de-activated by the line
 * discipline handling modules (like SLIP).
 */

#include <linux/types.h>
#include <linux/termios.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/tty.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <linux/mm.h>

#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <asm/system.h>

#undef TTY_DEBUG_WAIT_UNTIL_SENT

#undef  DEBUG
#ifdef DEBUG
# define        PRINTK(x)       printk (x)
#else
# define        PRINTK(x)       /**/
#endif

/*
 * Internal flag options for termios setting behavior
 */
/* wewnetrzne flagi ustawienia struktury termios */
#define TERMIOS_FLUSH   1
#define TERMIOS_WAIT    2
#define TERMIOS_TERMIO  4


void tty_wait_until_sent(struct tty_struct * tty, int timeout)
{
        struct wait_queue wait = { current, NULL };

#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
        printk("%s wait until sent...\n", tty_name(tty));
#endif
   /* jeżeli nie ma nic w buforze sterownika, to nic nie robi */
        if (!tty->driver.chars_in_buffer ||
            !tty->driver.chars_in_buffer(tty))
                return;
   /* proces sam wstawia się do kolejki czekających na pisanie do 
    * terminala */ 
        add_wait_queue(&tty->write_wait, &wait);
        current->counter = 0;   /* make us low-priority */
        if (timeout)
                current->timeout = timeout + jiffies;
        else
                current->timeout = (unsigned) -1;
        do { 
#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
                printk("waiting %s...(%d)\n", tty_name(tty), tty->driver.chars_in_buffer(tty));
#endif
                current->state = TASK_INTERRUPTIBLE;
                if (current->signal & ~current->blocked)
                        break;
                if (!tty->driver.chars_in_buffer(tty))
                        break;
                schedule();
   /* odczekuje odpowiednią ilość czasu (timeout) */
        } while (current->timeout);
   /* zmiana stanu na TASK_RUNNING i usunięcie się z kolejki */
        current->state = TASK_RUNNING;
        remove_wait_queue(&tty->write_wait, &wait);
}

/* ustawienie flag i znaków sterujących w termios tak jak były ustawione
 * w old pod warunkiem, że termios_locked gwarantuje takie ustawienie */
static void unset_locked_termios(struct termios *termios,
                                 struct termios *old,
                                 struct termios *locked)
{
        int     i;
        
#define NOSET_MASK(x,y,z) (x = ((x) & ~(z)) | ((y) & (z)))

        if (!locked) {
                printk("Warning?!? termios_locked is NULL.\n");
                return;
        }
   /* zmiana flag */
        NOSET_MASK(termios->c_iflag, old->c_iflag, locked->c_iflag);
        NOSET_MASK(termios->c_oflag, old->c_oflag, locked->c_oflag);
        NOSET_MASK(termios->c_cflag, old->c_cflag, locked->c_cflag);
        NOSET_MASK(termios->c_lflag, old->c_lflag, locked->c_lflag);
        termios->c_line = locked->c_line ? old->c_line : termios->c_line;
   /* zmiana znaków sterujących */
        for (i=0; i < NCCS; i++)
                termios->c_cc[i] = locked->c_cc[i] ?
                        old->c_cc[i] : termios->c_cc[i];
}

/* zamienie strukturę termios w tty_struct */
static void change_termios(struct tty_struct * tty, struct termios * new_termios)
{
        int canon_change;
        struct termios old_termios = *tty->termios;

   /* wyłączenie przerwań */
        cli();
        *tty->termios = *new_termios;
   /* uniemożliwienie zmian w termios, które popsułyby ich ochronę 
    * polem termios_locked */
        unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
        canon_change = (old_termios.c_lflag ^ tty->termios->c_lflag) & ICANON;
        if (canon_change) {
   /* ustawienie parametrów dla N_TTY w strukturze tty_struct, o ile 
    * został zmieniony z  trybu kanonicznego */
                memset(&tty->read_flags, 0, sizeof tty->read_flags);
                tty->canon_head = tty->read_tail;
                tty->canon_data = 0;
                tty->erasing = 0;
        }
   /* przywrócenie przerwań */
        sti();
   
   /* budzenie procesu czekającego na czytanie, o ile pozostały jakieś 
    * znaki do odczytania z trybu kanonicznego */
        if (canon_change && !L_ICANON(tty) && tty->read_cnt)
                /* Get characters left over from canonical mode. */
                wake_up_interruptible(&tty->read_wait);

        /* see if packet mode change of state */
   /* dla pseudoterminali - czy został zmieniony tryb pakietowy */
        if (tty->link && tty->link->packet) {
                int old_flow = ((old_termios.c_iflag & IXON) &&
                                (old_termios.c_cc[VSTOP] == '\023') &&
                                (old_termios.c_cc[VSTART] == '\021'));
                int new_flow = (I_IXON(tty) &&
                                STOP_CHAR(tty) == '\023' &&
                                START_CHAR(tty) == '\021');
                if (old_flow != new_flow) {
   /* zmieniła się flaga IXON lub znaki VSTOP i VSTART */
                        tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
                        if (new_flow)
                                tty->ctrl_status |= TIOCPKT_DOSTOP;
                        else
                                tty->ctrl_status |= TIOCPKT_NOSTOP;
   /* budzenie procesu czekającego na czytanie w drugim z pary */
                        wake_up_interruptible(&tty->link->read_wait);
                }
        }

   /* wywołanie set_termios ze sterownika i dyscypliny linii */
        if (tty->driver.set_termios)
                (*tty->driver.set_termios)(tty, &old_termios);

        if (tty->ldisc.set_termios)
                (*tty->ldisc.set_termios)(tty, &old_termios);
}

/* zmienia strukturę termios w tty, w zależności od parametru opt robi
 * jeszcze inne rzeczy */ 
static int set_termios(struct tty_struct * tty, unsigned long arg, int opt)
{
        struct termios tmp_termios;
        int retval;

   /* wysyła sygnał SIGTTOU do procesów dla którego tty jest terminalem 
    * sterującym, jeżeli sam jest w tle */
        retval = tty_check_change(tty);
        if (retval)
                return retval;

        if (opt & TERMIOS_TERMIO) {
   /* na podstawie struktury termio w parametrze arg tworzy strukturę 
    * termios w tmp_termios */
                struct termio tmp_termio;
                retval = verify_area(VERIFY_READ, (void *) arg, sizeof(struct termio));
                if (retval)
                        return retval;
                tmp_termios = *tty->termios;
                memcpy_fromfs(&tmp_termio, (struct termio *) arg,
                              sizeof (struct termio));
                trans_from_termio(&tmp_termio, &tmp_termios);
        } else {
   /* w arg jest struktura termios i ją kopiuje do tmp_termios */
                retval = verify_area(VERIFY_READ, (void *) arg, sizeof(struct termios));
                if (retval)
                        return retval;
                memcpy_fromfs(&tmp_termios, (struct termios *) arg,
                              sizeof (struct termios));
        }
   
        if ((opt & TERMIOS_FLUSH) && tty->ldisc.flush_buffer)
   /* opróżnienie bufora dyscypliny linii */
                tty->ldisc.flush_buffer(tty);

        if (opt & TERMIOS_WAIT)
   /* czekanie przed pisaniem */
                tty_wait_until_sent(tty, 0);
   
   /* zmiana struktury termios */  
       change_termios(tty, &tmp_termios);
        return 0;
}

/* wypełnia strukturę termio dla terminala opisanego przez parametr tty */
static int get_termio(struct tty_struct * tty, struct termio * termio)
{
        int i;
        struct termio tmp_termio;

   /* sprawdzenie obszaru zajmowanego przez strukturę termio */
        i = verify_area(VERIFY_WRITE, termio, sizeof (struct termio));
        if (i)
                return i;
   /* przepisanie parametrów ze struktury termios do termio. Te struktury
    * są bardzo podobne, zawierają prawie to samo; funkcja trans_to_termio
    * jest zdefiniowana w pliku termios.h */
        trans_to_termio(tty->termios, &tmp_termio);
        memcpy_tofs(termio, &tmp_termio, sizeof (struct termio));
        return 0;
}

/* zwraca liczbę znaków buforowanych w dyscyplinie linii omijając znaki
 * końca pliku */
static unsigned long inq_canon(struct tty_struct * tty)
{
        int nr, head, tail;

        if (!tty->canon_data || !tty->read_buf)
   /* nie zaalokowany bufor lub - znaków w trybie kanonicznym */
                return 0;
        head = tty->canon_head;
        tail = tty->read_tail;
        nr = (head - tail) & (N_TTY_BUF_SIZE-1);
        /* Skip EOF-chars.. */
   /* przeglądamy wszystkie znaki omijając znaki EOF */
        while (head != tail) {
                if (test_bit(tail, &tty->read_flags) &&
                    tty->read_buf[tail] == __DISABLED_CHAR)
                        nr--;
                tail = (tail+1) & (N_TTY_BUF_SIZE-1);
        }
        return nr;
}

#ifdef TIOCGETP
/*
 * These are deprecated, but there is limited support..
 *
 * The "sg_flags" translation is a joke..
 */
/* zwraca flagi z tty_struct, tutaj tak naprawdę nic specjalnego się nie 
 * dzieje */
static int get_sgflags(struct tty_struct * tty)
{
        int flags = 0;

        if (!(tty->termios->c_lflag & ICANON))
                if (tty->termios->c_lflag & ISIG)
                        flags |= 0x02;          /* cbreak */
                else
                        flags |= 0x20;          /* raw */
        if (tty->termios->c_lflag & ECHO)
                flags |= 0x08;                  /* echo */
        if (tty->termios->c_oflag & OPOST)
                if (tty->termios->c_oflag & ONLCR)
                        flags |= 0x10;          /* crmod */
        return flags;
}

/* wypełnia strukturę sgttyb znakami sterującymi z tty_struct */
static int get_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
{
        int retval;
        struct sgttyb tmp;
   
   /* sprawdzenie obszaru zajmowanego przez sgttyb */
        retval = verify_area(VERIFY_WRITE, sgttyb, sizeof(struct sgttyb));
        if (retval)
                return retval;
   /* wypełnienie struktury znakami sterującymi dla struktury termios 
    * opisującej ten sam terminal co tty */
        tmp.sg_ispeed = 0;
        tmp.sg_ospeed = 0;
        tmp.sg_erase = tty->termios->c_cc[VERASE];
        tmp.sg_kill = tty->termios->c_cc[VKILL];
        tmp.sg_flags = get_sgflags(tty);
        memcpy_tofs(sgttyb, &tmp, sizeof(tmp));
        return 0;
}

/* ustawienie flag w strukturze termios według parametru flagi */
static void set_sgflags(struct termios * termios, int flags)
{
        termios->c_iflag = ICRNL | IXON;
        termios->c_oflag = 0;
        termios->c_lflag = ISIG | ICANON;
        if (flags & 0x02) {     /* cbreak */
                termios->c_iflag = 0;
                termios->c_lflag &= ~ICANON;
        }
        if (flags & 0x08) {             /* echo */
                termios->c_lflag |= ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
        }
        if (flags & 0x10) {             /* crmod */
                termios->c_oflag |= OPOST | ONLCR;
        }
        if (flags & 0x20) {     /* raw */
                termios->c_iflag = 0;
                termios->c_lflag &= ~(ISIG | ICANON);
        }
        if (!(termios->c_lflag & ICANON)) {
                termios->c_cc[VMIN] = 1;
                termios->c_cc[VTIME] = 0;
        }
}

/* wypełnia strukturę sgttyb znakami sterującymi terminala */
static intset_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
{
        int retval;
        struct sgttyb tmp;
        struct termios termios;
   
   /* sprawdzenie obszaru */
        retval = verify_area(VERIFY_READ, sgttyb, sizeof(struct sgttyb));
        if (retval)
                return retval;
   /* wysyła sygnał SIGTTOU do procesu dla którego tty jest terminalem 
    * sterującym */
        retval = tty_check_change(tty);
        if (retval)
                return retval;
        termios =  *tty->termios;
        memcpy_fromfs(&tmp, sgttyb, sizeof(tmp));
        termios.c_cc[VERASE] = tmp.sg_erase;
        termios.c_cc[VKILL] = tmp.sg_kill;
        set_sgflags(&termios, tmp.sg_flags);
        change_termios(tty, &termios);
        return 0;
}
#endif

#ifdef TIOCGETC
/* wypełnienie struktury tchars znakami sterującymi z terminala */
static int get_tchars(struct tty_struct * tty, struct tchars * tchars)
{
        int retval;
        struct tchars tmp;

   /* sprawdzenie obszaru */
        retval = verify_area(VERIFY_WRITE, tchars, sizeof(struct tchars));
        if (retval)
                return retval;
        tmp.t_intrc = tty->termios->c_cc[VINTR];
        tmp.t_quitc = tty->termios->c_cc[VQUIT];
        tmp.t_startc = tty->termios->c_cc[VSTART];
        tmp.t_stopc = tty->termios->c_cc[VSTOP];
        tmp.t_eofc = tty->termios->c_cc[VEOF];
        tmp.t_brkc = tty->termios->c_cc[VEOL2]; /* what is brkc anyway? */
        memcpy_tofs(tchars, &tmp, sizeof(tmp));
        return 0;
}

/* ustawienie znaków sterujących terminala tak, jak to określone w 
 * strukturze tchars */
static int set_tchars(struct tty_struct * tty, struct tchars * tchars)
{
        int retval;
        struct tchars tmp;

        retval = verify_area(VERIFY_READ, tchars, sizeof(struct tchars));
        if (retval)
                return retval;
        memcpy_fromfs(&tmp, tchars, sizeof(tmp));
        tty->termios->c_cc[VINTR] = tmp.t_intrc;
        tty->termios->c_cc[VQUIT] = tmp.t_quitc;
        tty->termios->c_cc[VSTART] = tmp.t_startc;
        tty->termios->c_cc[VSTOP] = tmp.t_stopc;
        tty->termios->c_cc[VEOF] = tmp.t_eofc;
        tty->termios->c_cc[VEOL2] = tmp.t_brkc; /* what is brkc anyway? */
        return 0;
}
#endif

#ifdef TIOCGLTC

/* wypełnienie struktury ltchars znakamie ze struktury termios */
static int get_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
{
        int retval;
        struct ltchars tmp;

        retval = verify_area(VERIFY_WRITE, ltchars, sizeof(struct ltchars));
        if (retval)
                return retval;
        tmp.t_suspc = tty->termios->c_cc[VSUSP];
        tmp.t_dsuspc = tty->termios->c_cc[VSUSP];       /* what is dsuspc anyway? */
        tmp.t_rprntc = tty->termios->c_cc[VREPRINT];
        tmp.t_flushc = tty->termios->c_cc[VEOL2];       /* what is flushc anyway? */
        tmp.t_werasc = tty->termios->c_cc[VWERASE];
        tmp.t_lnextc = tty->termios->c_cc[VLNEXT];
        memcpy_tofs(ltchars, &tmp, sizeof(tmp));
        return 0;
}

/* ustawienie w strukturze termios znaków sterujących tak jak w ltchars */
static int set_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
{
        int retval;
        struct ltchars tmp;

        retval = verify_area(VERIFY_READ, ltchars, sizeof(struct ltchars));
        if (retval)
                return retval;
        memcpy_fromfs(&tmp, ltchars, sizeof(tmp));
        tty->termios->c_cc[VSUSP] = tmp.t_suspc;
        tty->termios->c_cc[VEOL2] = tmp.t_dsuspc;       /* what is dsuspc anyway? */
        tty->termios->c_cc[VREPRINT] = tmp.t_rprntc;
        tty->termios->c_cc[VEOL2] = tmp.t_flushc;       /* what is flushc anyway? */
        tty->termios->c_cc[VWERASE] = tmp.t_werasc;
        tty->termios->c_cc[VLNEXT] = tmp.t_lnextc;
        return 0;
}
#endif

/* funkcja dyscypliny linii N_TTY; w interfejsie tty_ioctl */
int n_tty_ioctl(struct tty_struct * tty, struct file * file,
                       unsigned int cmd, unsigned long arg)
{
        struct tty_struct * real_tty;
        int retval;

   /* w przypadku wywołania tej funkcji dla części nadrzędnej
    * pseudoterminala na real_tty podstawiana jest struktura opisująca
    * część podrzędną */
        if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
            tty->driver.subtype == PTY_TYPE_MASTER)
                real_tty = tty->link;
        else
                real_tty = tty;

        switch (cmd) {
   /* zależnie od parametru cmd - polecenia */
#ifdef TIOCGETP
   /* komendy dotyczące pobrania albo ustawienia znaków sterujących */
                case TIOCGETP:
                        return get_sgttyb(real_tty, (struct sgttyb *) arg);
                case TIOCSETP:
                case TIOCSETN:
                        return set_sgttyb(real_tty, (struct sgttyb *) arg);
#endif
#ifdef TIOCGETC
                case TIOCGETC:
                        return get_tchars(real_tty, (struct tchars *) arg);
                case TIOCSETC:
                        return set_tchars(real_tty, (struct tchars *) arg);
#endif
#ifdef TIOCGLTC
                case TIOCGLTC:
                        return get_ltchars(real_tty, (struct ltchars *) arg);
                case TIOCSLTC:
                        return set_ltchars(real_tty, (struct ltchars *) arg);
#endif
                case TCGETS:
   /* pobranie na arg struktury termios */
                        retval = verify_area(VERIFY_WRITE, (void *) arg,
                                             sizeof (struct termios));
                        if (retval)
                                return retval;
                        memcpy_tofs((struct termios *) arg,
                                    real_tty->termios,
                                    sizeof (struct termios));
                        return 0;
   /* pobranie lub ustawienie flag w strukturze termios */
                case TCSETSF:
                        return set_termios(real_tty, arg, TERMIOS_FLUSH);
                case TCSETSW:
                        return set_termios(real_tty, arg, TERMIOS_WAIT);
                case TCSETS:
                        return set_termios(real_tty, arg, 0);
                case TCGETA:
                        return get_termio(real_tty,(struct termio *) arg);
                case TCSETAF:
                        return set_termios(real_tty, arg, TERMIOS_FLUSH | TERMIOS_TERMIO);
                case TCSETAW:
                        return set_termios(real_tty, arg, TERMIOS_WAIT | TERMIOS_TERMIO);
                case TCSETA:
                        return set_termios(real_tty, arg, TERMIOS_TERMIO);
                case TCXONC:
   /* w zależności od wartości parametru arg zatrzymanie lub wystartowanie
    * terminala lub wypisanie na terminal znaku STOP_CHAR lub START_CHAR */
                        retval = (tty);
                        if (retval)
                                return retval;
                        switch (arg) {
                        case TCOOFF:
                                stop_tty(tty);
                                break;
                        case TCOON:
                                start_tty(tty);
                                break;
                        case TCIOFF:
                                if (STOP_CHAR(tty) != __DISABLED_CHAR)
                                        tty->driver.write(tty, 0,
                                                          &STOP_CHAR(tty), 1);
                                break;
                        case TCION:
                                if (START_CHAR(tty) != __DISABLED_CHAR)
                                        tty->driver.write(tty, 0,
                                                          &START_CHAR(tty), 1);
                                break;
                        default:
                                return -EINVAL;
                        }
                        return 0;
                case TCFLSH:
   /* jeżeli jesteśmy w tle, to wysyłany sygnał SIGTTOU do właściwej grupy
    * terminalowej */
                        retval = tty_check_change(tty);
                        if (retval)
                                return retval;
                        switch (arg) {
   /* opróżnienie bufora dyscypliny linii */
                        case TCIFLUSH:
                                if (tty->ldisc.flush_buffer)
                                        tty->ldisc.flush_buffer(tty);
                                break;
                        case TCIOFLUSH:
                                if (tty->ldisc.flush_buffer)
                                        tty->ldisc.flush_buffer(tty);
                                /* fall through */
                        case TCOFLUSH:
                                if (tty->driver.flush_buffer)
                                        tty->driver.flush_buffer(tty);
                                break;
                        default:
                                return -EINVAL;
                        }
                        return 0;
                case TIOCOUTQ:
   /* na parametr *arg przypisywana jest ilość znaków w buforze sterownika */
                        retval = verify_area(VERIFY_WRITE, (void *) arg,
                                             sizeof (int));
                        if (retval)
                                return retval;
                        if (tty->driver.chars_in_buffer)
                                put_user(tty->driver.chars_in_buffer(tty),
                                         (int *) arg);
                        else
                                put_user(0, (int *) arg);
                        return 0;
                case TIOCINQ:
   /* na parametrze arg zwraca ilość znaków w buforze dyscypliny linii */
                        retval = verify_area(VERIFY_WRITE, (void *) arg,
                                             sizeof (unsigned long));
                        if (retval)
                                return retval;
                        if (L_ICANON(tty))
   /* w trybie kanonicznym */
                                put_fs_long(inq_canon(tty),
                                        (unsigned long *) arg);
                        else
   /* w trybie surowym lub znakowym */
                                put_fs_long(tty->read_cnt,
                                            (unsigned long *) arg);
                        return 0;
                case TIOCGLCKTRMIOS:
   /* na parametr arg kopiuje strukturę termios_locked ze struktury tty */
                        retval = verify_area(VERIFY_WRITE, (void *) arg,
                                             sizeof (struct termios));
                        if (retval)
                                return retval;
                        memcpy_tofs((struct termios *) arg,
                                    real_tty->termios_locked,
                                    sizeof (struct termios));
                        return 0;
                case TIOCSLCKTRMIOS:
   /* zmiana pola termios_locked w strukturze tty */
                        if (!suser())
   /* sprawdzenie uprawnień */
                                return -EPERM;
                        retval = verify_area(VERIFY_READ, (void *) arg,
                                             sizeof (struct termios));
                        if (retval)
                                return retval;
                        memcpy_fromfs(real_tty->termios_locked,
                                      (struct termios *) arg,
                                      sizeof (struct termios));
                        return 0;
                case TIOCPKT:
   /* włączenie/wyłączenie trybu pakietowego dla części nadrzędnej 
    * pseudoterminala */
                        if (tty->driver.type != TTY_DRIVER_TYPE_PTY ||
                            tty->driver.subtype != PTY_TYPE_MASTER)
                                return -ENOTTY;
                        retval = verify_area(VERIFY_READ, (void *) arg,
                                             sizeof (int));
                        if (retval)
                                return retval;
   /* o ile jest ustawiony parametr arg */
                        if (get_user((int*)arg)) {
                                if (!tty->packet) {
                                        tty->packet = 1;
                                        tty->link->ctrl_status = 0;
                                }
                        } else
                                tty->packet = 0;
                        return 0;
                /* These two ioctl's always return success; even if */
                /* the driver doesn't support them. */
   /* te dwie komendy kontrolne zawsze zwracają sukces, nawet wtedy, 
    * gdy sterownik ich nie gwarantuje */
                case TCSBRK: case TCSBRKP:
                        retval = tty_check_change(tty);
                        if (retval)
                                return retval;
   /* czeka trochę zanim coś zapisze */
                        tty_wait_until_sent(tty, 0);
   /* jeżeli jest udostępniona funkcja ioctl w sterowniku, to jej 
    * wywołanie z takimi parametrami, z jakimi wywołano n_tty_ioctl() */
                        if (!tty->driver.ioctl)
                                return 0;
                        tty->driver.ioctl(tty, file, cmd, arg);
                        return 0;
                default:
   /* nieznana komenda */
                        return -ENOIOCTLCMD;
                }
}

Autorka komentarzy: Maja Królikowska