pty.c

/* w tym pliku znajduja sie funkcje dotyczące pseudoterminali */
/*  linux/drivers/char/pty.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */
/*
 *      pty.c
 *
 * This module exports the following pty function:
 * 
 *      int  pty_open(struct tty_struct * tty, struct file * filp);
 * 
 */


#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <linux/major.h>
#include <linux/mm.h>

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

/* struktura do opisu pty_struct */
struct pty_struct {
        int     magic;
        struct wait_queue * open_wait;
};

#define PTY_MAGIC 0x5001

#define PTY_BUF_SIZE PAGE_SIZE/2

/*
 * tmp_buf is used as a temporary buffer by pty_write.  We need to
 * lock it in case the memcpy_fromfs blocks while swapping in a page,
 * and some other program tries to do a pty write at the same time.
 * Since the lock will only come under contention when the system is
 * swapping and available memory is low, it makes sense to share one
 * buffer across all the PTY's, since it significantly saves memory if
 * large numbers of PTY's are open.
 */
/*
 * tmp_buf jest używany jako bufor tymczasowy przez funkcję pty_write.
 * Ten bufor jest wspólny dla obu procesów z pary, nie ma przesyłania 
 * danych.
 */
static unsigned char *tmp_buf;
/*
 * semafor używany do synchronizacji pisania  
 */
static struct semaphore tmp_buf_sem = MUTEX;

struct tty_driver pty_driver, pty_slave_driver;
struct tty_driver old_pty_driver, old_pty_slave_driver;
static int pty_refcount;

static struct tty_struct *pty_table[NR_PTYS];
static struct termios *pty_termios[NR_PTYS];
static struct termios *pty_termios_locked[NR_PTYS];
   /* tablice dla części nadrzędnych pseudoterminali */
static struct tty_struct *ttyp_table[NR_PTYS];
static struct termios *ttyp_termios[NR_PTYS];
static struct termios *ttyp_termios_locked[NR_PTYS];
   /* tablice dla części podrzędnych pseudoterminali */
static struct pty_struct pty_state[NR_PTYS];
   /* tablica stanów pseudoterminali */

#define MIN(a,b)        ((a) < (b) ? (a) : (b))

/* zamykanie pseudoterminala */
static void pty_close(struct tty_struct * tty, struct file * filp)
{
        if (!tty)
                return;
        if (tty->driver.subtype == PTY_TYPE_MASTER) {
   /* część nadrzędna pseudoterminala */
                if (tty->count > 1)
   /* jeszcze ktoś otwierał ten terminal ...*/
                        printk("master pty_close: count = %d!!\n", tty->count);
        } else {
   /* więcej niż dwa razy otwarto ten terminal - wyjście z funkcji*/
                if (tty->count > 2)
                        return;
        }
   /* obudzenie procesów: czekającego na czytanie i pisanie */
        wake_up_interruptible(&tty->read_wait);
        wake_up_interruptible(&tty->write_wait);
        if (!tty->link)
   /* nie ma drugiego z pary */
                return;
   /* budzenie procesu piszącego i czytającego u drugiego członka pary */
        wake_up_interruptible(&tty->link->read_wait);
        wake_up_interruptible(&tty->link->write_wait);
   /* ustawienie flagi */
        set_bit(TTY_OTHER_CLOSED, &tty->link->flags);
        if (tty->driver.subtype == PTY_TYPE_MASTER) {
   /* jeżeli to terminal nadrzędny, to podrzędnemu robi tty_hangup 
    * (wysyła SIGHUP do procesów dla których jest on sterującym)*/
                tty_hangup(tty->link);
                set_bit(TTY_OTHER_CLOSED, &tty->flags);
        }
}

/*
 * The unthrottle routine is called by the line discipline to signal
 * that it can receive more characters.  For PTY's, the TTY_THROTTLED
 * flag is always set, to force the line discipline to always call the
 * unthrottle routine when there are fewer than TTY_THRESHOLD_UNTHROTTLE 
 * characters in the queue.  This is necessary since each time this
 * happens, we need to wake up any sleeping processes that could be
 * (1) trying to send data to the pty, or (2) waiting in wait_until_sent()
 * for the pty buffer to be drained.
 */

/* ta funkcja jest wołana przez dyscyplinę linii dla zasygnalizowania, że
 * może ona przyjąć więcej znaków. Dla pseudoterminali flaga TTY_THROTTLED
 * jest zawsze ustawiona, żeby zmusić dyscyplinę linii do wołania procedury
 * unthrottle jeśli tylko jest mniej  niż TTY_THRESHOLD_UNTHROTTLE znaków
 * w buforze */
static void pty_unthrottle(struct tty_struct * tty)
{
        struct tty_struct *o_tty = tty->link;

        if (!o_tty)
                return;

        if ((o_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
            o_tty->ldisc.write_wakeup)
   /* funkcja write_wakup wołana z dyscypliny linii terminala podrzędnego
    * proces zleca sterownikowi pisanie, a sam zasypia, ale na przykład
    * w dyscyplinie linii N_TTY ta funkcja nie jest zaimplementowana */
                (o_tty->ldisc.write_wakeup)(o_tty);
   /* budzony jest proces czekający na pisanie */
        wake_up_interruptible(&o_tty->write_wait);
        set_bit(TTY_THROTTLED, &tty->flags);
}
/* pisanie do pseudoterminala */
static int pty_write(struct tty_struct * tty, int from_user,
                       const unsigned char *buf, int count)
{
        struct tty_struct *to = tty->link;
        int     c=0, n, r;
        char    *temp_buffer;
   
   /* nie ma drugiego z pary albo zatrzymany */
        if (!to || tty->stopped)
                return 0;
        
        if (from_user) {
   /* wzajemne wykluczanie pisania za pomocą semafora systemowego */
                down(&tmp_buf_sem);
   /* temp_buffer ustawia się na początku strony przydzielonej na tmp_buf
    * w funkcji pty_open i dla części nadrzędnej i w połowie dla części 
    * podrzędnej */
                temp_buffer = tmp_buf +
                        ((tty->driver.subtype-1) * PTY_BUF_SIZE);
   /* w pętli stara się zapisać wszystkie count znaków */
                while (count > 0) {
   /* kopiuje count albo tyle ile się zmieści znaków do temp_buffer */
                        n = MIN(count, PTY_BUF_SIZE);
                        memcpy_fromfs(temp_buffer, buf, n);
   /* pyta dyscyplinę linii ile znaków ta może przyjąć */
                        r = to->ldisc.receive_room(to);
                        if (r <= 0)
                                break;
   /* wysyła tyle znaków do bufora dyscypliny linii ile ta może pomieścić */
                        n = MIN(n, r);
                        to->ldisc.receive_buf(to, temp_buffer, 0, n);
                        buf += n;  c+= n;
                        count -= n;
                } /* while */
                up(&tmp_buf_sem);
        } else {
   /* nie ustawione from_user - na dyscyplinę linii wpisuje tylko tyle
    * znaków ile ona może zmieścić*/
                c = MIN(count, to->ldisc.receive_room(to));
                to->ldisc.receive_buf(to, buf, 0, c);
        }
   
   /* zwraca ilość zapisanych znaków */        
        return c;
}

/* zwraca ile znaków można zapisać */
static int pty_write_room(struct tty_struct *tty)
{
        struct tty_struct *to = tty->link;

        if (!to || tty->stopped)
                return 0;
   /* wywołuje funkcję receive_room z dyscypliny linii drugiego z pary 
    * pseudoterminalowej */
        return to->ldisc.receive_room(to);
}

/*
 * Modified for asymmetric master/slave behavior
 * The chars_in_buffer() value is used by the ldisc select() function 
 * to hold off writing when chars_in_buffer > WAKEUP_CHARS (== 256).
 * To allow typed-ahead commands to accumulate, the master side returns 0
 * until the buffer is half full. The slave side returns the true count.
 */

/* funkcja używana przez select() z dyscypliny linii do wstrzymywania 
 * pisania, inaczej działa dla części podrzędnej, a inaczej do nadrzędnej
 * pseudoterminala */
static int pty_chars_in_buffer(struct tty_struct *tty)
{
        struct tty_struct *to = tty->link;
        int count;

        if (!to || !to->ldisc.chars_in_buffer)
                return 0;

        /* The ldisc must report 0 if no characters available to be read */
   /* count - liczba znaków do przeczytania w dyscyplinie linii*/
        count = to->ldisc.chars_in_buffer(to);
   
   /* dla części podrzędnej zwraca count */
        if (tty->driver.subtype == PTY_TYPE_SLAVE) return count;

        /* 
         * Master side driver ... return 0 if the other side's read buffer 
         * is less than half full.  This allows room for typed-ahead commands
         * with a reasonable margin to avoid overflow. 
         */
   /* dla części nadrzędnej zwraca 0 jeżeli bufor drugiego z pary jest mniej
    * niż w połowie pełny. To zostawia miejsce dla pisania z wyprzedzeniem
    * i zapobiega przepełnieniu */
        return ((count < N_TTY_BUF_SIZE/2) ? 0 : count);
}

/* opróżnianie bufora */
static void pty_flush_buffer(struct tty_struct *tty)
{
        struct tty_struct *to = tty->link;
        
        if (!to)
                return;
   /* opróżnienie bufora dyscypliny linii */        
        if (to->ldisc.flush_buffer)
                to->ldisc.flush_buffer(to);

   /* w trybie pakietowym zmiana stanu kontrolnego i budzenie procesu 
    * czekającego na pisanie */        
        if (to->packet) {
                tty->ctrl_status |= TIOCPKT_FLUSHWRITE;
                wake_up_interruptible(&to->read_wait);
        }
}

/* otwieranie pseudoterminala 
 * w tej funkcji nie widać bezpośrednio tego, że terminale podrzędny i
 * nadrzędny są inaczej otwierane. We wcześniejszej wersji linux-a ta 
 * funkcja wyglądała inaczej, obecnie to rozróżnienie jest realizowane
 * w funkcji init_dev z pliku tty_io.c*/
int pty_open(struct tty_struct *tty, struct file * filp)
{
        int     retval;
        int     line;
        struct  pty_struct *pty;
   
   /* sprawdzenie stanu terminala, znalezienie jego numeru (numeru 
    * pseudoterminala) na podstawie numeru głównego i drugorzędnego */ 
        retval = -ENODEV;
        if (!tty || !tty->link)
                goto out;
        line = MINOR(tty->device) - tty->driver.minor_start;
        if ((line < 0) || (line >= NR_PTYS))
                goto out;
   /* na pty przypisana struktura pty_struct właściwa dla terminala */
        pty = pty_state + line;
        tty->driver_data = pty;

        if (!tmp_buf) {
   /* nie jest zaalokowany bufor */
                unsigned long page = __get_free_page(GFP_KERNEL);
                if (!tmp_buf) {
                        retval = -ENOMEM;
                        if (!page)
                                goto out;
                        tmp_buf = (unsigned char *) page;
                        memset((void *) page, 0, PAGE_SIZE);
                } else
                        free_page(page);
        }
        retval = -EIO;
        if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
   /* jest ustawione TTY_OTHER_CLOSED */
                goto out;
        if (tty->link->count != 1)
   /* na drugim z pary wykonano nie jedną operację open ( w domyśle zero)*/
                goto out;
   
   /* ustawiamy TTY_OTHER_CLOSED w drugim z pary */
        clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
   /* budzimy proces z kolejki open_wait 
    * to jest dziwne, bo nic nie jest do tej kolejki wkładane*/
        wake_up_interruptible(&pty->open_wait);
   /* ustawiamy TTY_THROTTLED */
        set_bit(TTY_THROTTLED, &tty->flags);
        retval = 0;
out:
        return retval;
}

/* ustawienie flag w strukturze termios */
static void pty_set_termios(struct tty_struct *tty, struct termios *old_termios)
{
        tty->termios->c_cflag &= ~(CSIZE | PARENB);
        tty->termios->c_cflag |= (CS8 | CREAD);
}

/* inicjalizacja pseudoterminali */ 
int pty_init(void)
{
   /* uzupełnienie struktury tty_driver dla części nadrzędnej
    * pseudoterminala*/
        memset(&pty_state, 0, sizeof(pty_state));
        memset(&pty_driver, 0, sizeof(struct tty_driver));
        pty_driver.magic = TTY_DRIVER_MAGIC;
        pty_driver.name = "pty";
        pty_driver.major = PTY_MASTER_MAJOR; 
        pty_driver.minor_start = 0;
        pty_driver.num = NR_PTYS;
        pty_driver.type = TTY_DRIVER_TYPE_PTY;
        pty_driver.subtype = PTY_TYPE_MASTER;
        pty_driver.init_termios = tty_std_termios;
        pty_driver.init_termios.c_iflag = 0;
        pty_driver.init_termios.c_oflag = 0;
        pty_driver.init_termios.c_cflag = B38400 | CS8 | CREAD;
        pty_driver.init_termios.c_lflag = 0;
   /* część nadrzędna działa w trybie surowym */
        pty_driver.flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
        pty_driver.refcount = &pty_refcount;
        pty_driver.table = pty_table;
        pty_driver.termios = pty_termios;
        pty_driver.termios_locked = pty_termios_locked;
        pty_driver.other = &pty_slave_driver;
  
   /* uzupełnienie struktury tty_driver dla części podrzędnej */
        pty_driver.open = pty_open;
        pty_driver.close = pty_close;
        pty_driver.write = pty_write;
        pty_driver.write_room = pty_write_room;
        pty_driver.flush_buffer = pty_flush_buffer;
        pty_driver.chars_in_buffer = pty_chars_in_buffer;
        pty_driver.unthrottle = pty_unthrottle;
        pty_driver.set_termios = pty_set_termios;

        pty_slave_driver = pty_driver;
        pty_slave_driver.name = "ttyp";
        pty_slave_driver.subtype = PTY_TYPE_SLAVE;
        pty_slave_driver.major = PTY_SLAVE_MAJOR;
        pty_slave_driver.minor_start = 0;
        pty_slave_driver.init_termios = tty_std_termios;
        pty_slave_driver.init_termios.c_cflag = B38400 | CS8 | CREAD;
        pty_slave_driver.table = ttyp_table;
        pty_slave_driver.termios = ttyp_termios;
        pty_slave_driver.termios_locked = ttyp_termios_locked;
        pty_slave_driver.other = &pty_driver;

   /* wypełnienie struktur old_pty_driver i old_pty_slave_driver ;
    * mają one numer główny normalengo terminala, wskazują na siebie
    * jako członków pary  */
        old_pty_driver = pty_driver;
        old_pty_driver.major = TTY_MAJOR;
        old_pty_driver.minor_start = 128;
        old_pty_driver.num = (NR_PTYS >64) ? 64 : NR_PTYS;
        old_pty_driver.other = &old_pty_slave_driver;
        
        old_pty_slave_driver = pty_slave_driver;
        old_pty_slave_driver.major = TTY_MAJOR;
        old_pty_slave_driver.minor_start = 192;
        old_pty_slave_driver.num = (NR_PTYS >64) ? 64 : NR_PTYS;
        old_pty_slave_driver.other = &old_pty_driver;

        tmp_buf = 0;
   
   /* zarejestrowanie sterowników, "nowych" z numerami głównymi
    * PTY_MASTER_MAJOR i PTY_SLAVE_MAJOR i starych z TTY_MAJOR
    * trudno orzec po co to jest zrobione, wygląda tak, jakby każdy
    * pseudoterminal był umieszczony jednocześnie w dwóch miejscach 
    * tablicy rozdzielczej */
        if (tty_register_driver(&pty_driver))
                panic("Couldn't register pty driver");
        if (tty_register_driver(&pty_slave_driver))
                panic("Couldn't register pty slave driver");
        if (tty_register_driver(&old_pty_driver))
                panic("Couldn't register compat pty driver");
        if (tty_register_driver(&old_pty_slave_driver))
                panic("Couldn't register compat pty slave driver");
        
        return 0;
}

Autorka komentarzy : Maja Królikowska