View of xos/drivers/tty.c


XOS | Parent Directory | View | Download

/* Terminal */
/* Copyright (C) 2008  Emmanuel Varoquaux
 
   This file is part of XOS.
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
#include "tty_struct.h"
#include "tty_queue.h"
 
#include <alarm.h>
#include <current.h>
#include <verify_area.h>
#include <proc.h>
#include <mutex.h>
#include <condition.h>
#include <segment.h>
#include <asm.h>
#include <errno.h>
#include <enums.h>
#include <string.h>
#include <ctype.h>
#include <stddef.h>
 
#define NL '\n'
#define CR '\r'
 
/* Utilitaires */
 
static inline int is_line_delimiter(const struct tty_struct *tty, char c)
{
  return c == NL || c == tty->cc[VEOF] || c == tty->cc[VEOL];
}
 
static inline int read_data_available(const struct tty_struct *tty)
{
  return tty->lflags & ICANON ? tty->read_lines || tty_queue_is_full(&tty->read_queue) : !tty_queue_is_empty(&tty->read_queue);
}
 
/* Permissions */
 
/* Dit si le processus courant appartient au groupe d'avant-plan du terminal.
 */
static inline int check_group(const struct tty_struct *tty)
{
  return get_pgid() == tty->pgid;
}
 
/* La lecture sur un terminal requiert d'appartenir au groupe de ce terminal.
 */
static inline int check_read_perm(const struct tty_struct *tty)
{
  return check_group(tty);
}
 
/* L'ecriture sur un terminal requiert d'appartenir au groupe de ce terminal,
 * sauf si TOSTOP n'est pas positionne. */
static inline int check_write_perm(const struct tty_struct *tty)
{
  return !(tty->lflags & TOSTOP) || check_group(tty);
}
 
/* Meme condition que pour write() sauf que TOSTOP est ignore. */
static inline int check_set_parameter_perm(const struct tty_struct *tty)
{
  return check_group(tty);
}
 
static int get_access_read(const struct tty_struct *tty)
{
  if (!check_read_perm(tty)) {
    if (is_orphaned_pg(get_pgid()))
      return -EIO;
    signal_pg(get_pgid(), SIGTTIN);
    return -ERESTARTSYS;
  }
  return 0;
}
 
static int get_access_write(const struct tty_struct *tty)
{
  if (!check_write_perm(tty)) {
    if (is_orphaned_pg(get_pgid()))
      return -EIO;
    signal_pg(get_pgid(), SIGTTOU);
    return -ERESTARTSYS;
  }
  return 0;
}
 
static int get_access_set_parameter(const struct tty_struct *tty)
{
  if (!check_set_parameter_perm(tty)) {
    if (is_orphaned_pg(get_pgid()))
      return -EIO;
    signal_pg(get_pgid(), SIGTTOU);
    return -ERESTARTSYS;
  }
  return 0;
}
 
/* Les fonctions sur les files doivent toutes etre executees avec les
 * interruptions masquees. */
 
/* output_queue */
 
/* Le terminal doit verifier (!tty->stopped). */
static void flush_output_queue(struct tty_struct *tty)
{
  tty->vptr->do_output(tty, &tty->output_queue);
}
 
static int put_output_queue_echo(struct tty_struct *tty, char c)
{
  if (tty_queue_is_full(&tty->output_queue)) {
    if (tty->stopped)
      return 1;
    flush_output_queue(tty);
  }
  tty_queue_push(&tty->output_queue, c);
  return 1;
}
 
static int put_output_queue_write(struct tty_struct *tty, char c)
{
  if (tty_queue_is_full(&tty->output_queue)) {
    while (tty->stopped)
      if (!condition_wait_interruptible(&tty->started_condition))
        return 0;
    flush_output_queue(tty);
  }
  tty_queue_push(&tty->output_queue, c);
  return 1;
}
 
/* Output processing */
 
static int process_output(struct tty_struct *tty, char c, int (*put_output_queue)(struct tty_struct *, char))
{
  /* traitement de sortie */
  if (tty->oflags & OPOST) {
    switch (c) {
    case NL:
      if (tty->oflags & ONLCR) {
        tty->output_column = 0;
        if (!put_output_queue(tty, CR))
          return 0;
        if (!put_output_queue(tty, NL))
          return 0;
        return 1;
      }
      if (tty->oflags & ONLRET)
        tty->output_column = 0;
      break;
    case CR:
      if (tty->oflags & OCRNL) {
        if (tty->oflags & ONLRET)
          tty->output_column = 0;
        if (!put_output_queue(tty, NL))
          return 0;
        return 1;
      }
      if ((tty->oflags & ONOCR) && tty->output_column == 0)
        return 1;
      tty->output_column = 0;
      break;
    case '\t':
      tty->output_column = (tty->output_column | 7) + 1;
      break;
    case '\b':
      if (tty->output_column > 0)
        tty->output_column--;
      break;
    default:
      if (tty->oflags & OLCUC)
        c = toupper(c);
      if (!iscntrl(c))
        tty->output_column++;
    }
  }
 
  return put_output_queue(tty, c);
}
 
#define process_output_echo(tty, c) process_output(tty, c, put_output_queue_echo)
#define process_output_write(tty, c) process_output(tty, c, put_output_queue_write)
 
/* read_queue */
 
/* Tous les acces en ecriture a read_queue (push()/pop()) sont centralises dans
 * cette section, qui maintient tty->read_lines et positionne
 * tty->read_new_line. */
 
static void flush_read_queue(struct tty_struct *tty)
{
  tty_queue_clear(&tty->read_queue);
  tty->read_lines = 0;
  tty->read_new_line = 1;
}
 
static void put_read_queue(struct tty_struct *tty, char c)
{
  if (tty_queue_push(&tty->read_queue, c))
    if (is_line_delimiter(tty, c)) {
      tty->read_lines++;
      tty->read_new_line = 1;
    }
  /* Si la file est pleine, les nouveaux caracteres recus sont perdus, mais la
     lecture peut tout de meme se faire de maniere a liberer de la place dans
     read_queue. */
  if (read_data_available(tty))
    condition_signal(&tty->read_condition);
}
 
static int get_read_queue(struct tty_struct *tty, char *c)
{
  if (!tty_queue_pop(&tty->read_queue, c))
    return 0;
  if (is_line_delimiter(tty, *c))
    tty->read_lines--;
  if (tty_queue_is_empty(&tty->read_queue))
    tty->read_new_line = 1;
  return 1;
}
 
static int del_read_queue(struct tty_struct *tty, char *c)
{
  if (!tty_queue_get_last(&tty->read_queue, c) || is_line_delimiter(tty, *c))
    return 0;
  tty_queue_del(&tty->read_queue);
  if (tty_queue_is_empty(&tty->read_queue))
    tty->read_new_line = 1;
  return 1;
}
 
/* stop()/start() */
 
/* Le terminal doit verifier (!tty->stopped). */
static void stop(struct tty_struct *tty)
{
  tty->stopped = 1;
  tty->vptr->do_stop(tty);
}
 
/* Le terminal doit verifier (tty->stopped). */
static void start(struct tty_struct *tty)
{
  tty->stopped = 0;
  tty->vptr->do_start(tty);
  flush_output_queue(tty);
  condition_signal(&tty->started_condition);
}
 
/* Local processing */
 
/* Echo d'un caractere. */
static void echo(struct tty_struct *tty, char c)
{
  if ((tty->lflags & ECHOCTL) && iscntrl(c) && c != NL && c != '\t') {
    process_output_echo(tty, '^');
    process_output_echo(tty, c ^ 0100);
  }
  else
    process_output_echo(tty, c);
}
 
/* Efface un caractere en sortie. */
static void erase_char(struct tty_struct *tty)
{
  process_output_echo(tty, '\b');
  process_output_echo(tty, ' ');
  process_output_echo(tty, '\b');
}
 
/* Efface des caracteres en sortie de maniere a ramener le point a la position
 * correspondant a l'echo de la ligne courante. */
static void backup(struct tty_struct *tty)
{
  char c;
  unsigned int pos;
  unsigned int col;
 
  col = tty->echo_current_first_column;
  for (pos = tty->read_current_start; tty_queue_get(&tty->read_queue, &pos, &c); ) {
    /* c ne peut pas etre un delimiteur de ligne. */
    if (c == '\t')
      col = (col | 7) + 1;
    else if (iscntrl(c)) {
      if (tty->lflags & ECHOCTL)
        col += 2;
    }
    else
      col++;
  }
  while (tty->output_column > col)
    erase_char(tty);
}
 
/* Efface le nombre de caracteres en sortie correspondant a l'echo du caractere
 * c (mode canonique). Cette fonction est exactement l'inverse de echo(). Elle
 * doit etre appelee apres avoir supprime le caractere de la ligne courante. */
static void echo_erase(struct tty_struct *tty, char c)
{
  /* c ne peut pas etre un delimiteur de ligne. */
  if (c == '\t')
    /* C'est le seul cas ou on est oblige de recalculer le point. */
    backup(tty);
  else if (iscntrl(c)) {
    if (tty->lflags & ECHOCTL) {
      erase_char(tty);
      erase_char(tty);
    }
  }
  else
    erase_char(tty);
}
 
static void process_local(struct tty_struct *tty, char c)
{
  char d;
 
  /* caracteres speciaux */
  if (tty->lflags & ISIG) {
    if (c == tty->cc[VINTR]) {
      signal_pg(tty->pgid, SIGINT);
      goto flush;
    }
    else if (c == tty->cc[VQUIT]) {
      signal_pg(tty->pgid, SIGQUIT);
      goto flush;
    }
    else if (c == tty->cc[VSUSP]) {
      if (!is_orphaned_pg(tty->pgid))
        signal_pg(tty->pgid, SIGTSTP);
      goto flush;
    }
  }
  if (tty->lflags & ICANON) {
    if (c == tty->cc[VERASE]) {
      if (!del_read_queue(tty, &d))
        return;
      if ((tty->lflags & ECHO) && (tty->lflags & ECHOE))
        echo_erase(tty, d);
      else
        echo(tty, c);
      return;
    }
    else if (c == tty->cc[VKILL]) {
      if (!del_read_queue(tty, &d))
        return;
      do
        if ((tty->lflags & ECHO) && (tty->lflags & ECHOK) && (tty->lflags & ECHOKE) && (tty->lflags & ECHOE))
          echo_erase(tty, d); /* effacement des caracteres */
      while (del_read_queue(tty, &d));
      if (tty->lflags & ECHO) {
        if (!((tty->lflags & ECHOK) && (tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
          echo(tty, c); /* si on n'a pas fait l'effacement des caracteres on fait echo de KILL */
        if ((tty->lflags & ECHOK) && !((tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
          echo(tty, NL); /* echo d'un nouvelle ligne avec ECHOK */
      }
      return;
    }
  }
 
  /* Si la file est pleine, les nouveaux caracteres recus sont perdus. Leur
     echo n'est pas fait. */
  if (tty_queue_is_full(&tty->read_queue))
    return;
 
  /* ligne courante */
  if (tty->read_new_line) {
    tty->read_current_start = tty_queue_get_end(&tty->read_queue);
    tty->echo_current_first_column = tty->output_column;
    tty->read_new_line = 0;
  }
 
  /* echo */
  if (((tty->lflags & ECHO) && (!(tty->lflags & ICANON) || c != tty->cc[VEOF]))
      || ((tty->lflags & ECHONL) && (tty->lflags & ICANON) && c == NL))
    echo(tty, c);
 
  put_read_queue(tty, c);
  return;
 
 flush:
  if (!(tty->lflags & NOFLSH)) {
    flush_read_queue(tty);
    if (!tty->stopped)
      flush_output_queue(tty);
  }
}
 
/* input_queue */
 
static void flush_input_queue(struct tty_struct *tty)
{
  char c;
 
  while (tty_queue_pop(&tty->input_queue, &c))
    process_local(tty, c);
  if (!tty->stopped)
    flush_output_queue(tty); /* echo */
}
 
static void put_input_queue(struct tty_struct *tty, char c)
{
  if (tty_queue_is_full(&tty->input_queue))
    flush_input_queue(tty);
  tty_queue_push(&tty->input_queue, c);
}
 
/* Input processing */
 
static void process_input(struct tty_struct *tty, char c)
{
  /* caracteres speciaux */
  if (tty->iflags & IXON) {
    if (c == tty->cc[VSTOP]) {
      if (!tty->stopped)
        stop(tty);
      return;
    }
    else if (c == tty->cc[VSTART]) {
      if (tty->stopped)
        start(tty);
      return;
    }
  }
  if (tty->iflags & IXANY)
    if (tty->stopped)
      start(tty);
 
  /* conversions d'entree */
  switch (c) {
  case NL:
    if (tty->iflags & INLCR)
      c = CR;
    break;
  case CR:
    if (tty->iflags & IGNCR)
      return;
    else if (tty->iflags & ICRNL)
      c = NL;
    break;
  default:
    if (tty->iflags & IUCLC)
      c = tolower(c);
    break;
  }
 
  put_input_queue(tty, c);
}
 
/* Timer */
 
static void read_alarm_handler(struct alarm_struct *alarm)
{
  struct tty_struct *tty;
 
  tty = (void *)alarm - offsetof(struct tty_struct, read_alarm);
  tty->read_timeout = 1;
  condition_signal(&tty->read_condition);
}
 
/* Interface pour les pilotes */
 
int tty_do_ioctl(struct tty_struct *tty, int request, void *fs_arg)
{
  int rv;
 
  switch (request) {
  case TGETIF:
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
      return -EFAULT;
    put_fs_long(tty->iflags, fs_arg);
    return 0;
  case TSETIF:
    if ((rv = get_access_set_parameter(tty)) < 0)
      return rv;
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
      return -EFAULT;
    tty->iflags = get_fs_long(fs_arg);
    return 0;
  case TGETOF:
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
      return -EFAULT;
    put_fs_long(tty->oflags, fs_arg);
    return 0;
  case TSETOF:
    if ((rv = get_access_set_parameter(tty)) < 0)
      return rv;
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
      return -EFAULT;
    tty->oflags = get_fs_long(fs_arg);
    return 0;
  case TGETLF:
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
      return -EFAULT;
    put_fs_long(tty->lflags, fs_arg);
    return 0;
  case TSETLF:
    if ((rv = get_access_set_parameter(tty)) < 0)
      return rv;
    if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
      return -EFAULT;
    tty->lflags = get_fs_long(fs_arg);
    return 0;
  case TGETCC:
    if (!verify_area(fs_arg, NCCS, PF_WRITE))
      return -EFAULT;
    memcpy_tofs(fs_arg, tty->cc, NCCS);
    return 0;
  case TSETCC:
    if ((rv = get_access_set_parameter(tty)) < 0)
      return rv;
    if (!verify_area(fs_arg, NCCS, PF_READ))
      return -EFAULT;
    memcpy_fromfs(tty->cc, fs_arg, NCCS);
    return 0;
  case TGETPGID:
    if (!verify_area(fs_arg, sizeof (int), PF_WRITE))
      return -EFAULT;
    put_fs_long(tty->pgid, fs_arg);
    return 0;
  case TSETPGID:
    /* Pas de controle d'acces ici sinon il est impossible de changer le groupe
       d'avant-plan du terminal ! */
    if (!verify_area(fs_arg, sizeof (int), PF_READ))
      return -EFAULT;
    tty->pgid = get_fs_long(fs_arg);
    return 0;
  default:
    return -EINVAL;
  }
}
 
void tty_init(struct tty_struct *tty, unsigned long iflags, unsigned long oflags, unsigned long lflags, const unsigned char cc[], unsigned int column)
{
  /* parametres */
  tty->iflags = iflags;
  tty->oflags = oflags;
  tty->lflags = lflags;
  memcpy(tty->cc, cc, NCCS);
 
  tty->pgid = 0; /* pas de groupe d'avant plan */
  tty->stopped = 0;
  tty->output_column = column;
  tty->read_lines = 0;
  tty->read_new_line = 1;
  tty->read_timeout = 0;
 
  alarm_init(&tty->read_alarm, read_alarm_handler);
 
  condition_init(&tty->started_condition);
  condition_init(&tty->read_condition);
 
  mutex_init(&tty->read_mutex);
  mutex_init(&tty->write_mutex);
 
  /* files */
  tty_queue_init(&tty->input_queue);
  tty_queue_init(&tty->read_queue);
  tty_queue_init(&tty->output_queue);
}
 
void tty_put(struct tty_struct *tty, char c)
{
  int intr;
 
  disable_intr(intr);
  process_input(tty, c);
  restore_intr(intr);
}
 
void tty_process_input_queue(struct tty_struct *tty)
{
  int intr;
 
  disable_intr(intr);
  flush_input_queue(tty);
  restore_intr(intr);
}
 
void tty_stop(struct tty_struct *tty)
{
  int intr;
 
  disable_intr(intr);
  if (tty->stopped)
    goto end;
  stop(tty);
 end:
  restore_intr(intr);
}
 
void tty_start(struct tty_struct *tty)
{
  int intr;
 
  disable_intr(intr);
  if (!tty->stopped)
    goto end;
  start(tty);
 end:
  restore_intr(intr);
}
 
/* Interface systeme */
 
/* Lecture bloquante d'un caractere. n est le nombre de caracteres deja lus
 * depuis le debut du read().
 * Retourne 1 si un caractere a ete lu, 0 si aucun caractere n'a ete lu, un
 * code d'erreur en cas d'erreur. */
static int read_char(struct tty_struct *tty, unsigned int n, char *c)
{
  int intr;
  int retval;
 
  disable_intr(intr);
  while (!read_data_available(tty)) {
    if (tty->lflags & ICANON)
      goto no_timer;
    else {
      if (tty->cc[VMIN]) {
        if (n >= tty->cc[VMIN])
          goto no_data;
        else {
          if (tty->cc[VTIME] && n)
            goto timer;
          else
            goto no_timer;
        }
      }
      else {
        if (tty->cc[VTIME] && !n)
          goto timer;
        else
          goto no_data;
      }
    }
  timer:
    alarm_set(&tty->read_alarm, tty->cc[VTIME] * 100);
    goto wait;
  no_timer:
    alarm_unset(&tty->read_alarm);
  wait:
    tty->read_timeout = 0;
    if (!condition_wait_interruptible(&tty->read_condition)) {
      if (!n) {
        retval = -ERESTARTSYS;
        goto end;
      }
      goto no_data;
    }
    if (tty->read_timeout)
      goto no_data;
  }
  /* Ici read_data_available() est forcement vraie, ce qui implique que
     get_read_queue() reussit forcement. */
  get_read_queue(tty, c);
  retval = 1;
  goto end;
 no_data:
  retval = 0;
 end:
  restore_intr(intr);
  return retval;
}
 
/* Depot bloquant d'un caractere dans la file d'ecriture. n est le nombre de
 * caracteres deja ecrits depuis le debut du write().
 * Retourne 1 si un caractere a ete ecrit, 0 si aucun caractere n'a ete ecrit,
 * un code d'erreur en cas d'erreur. */
static int put_write_char(struct tty_struct *tty, unsigned int n, char c)
{
  int retval;
  int intr;
 
  disable_intr(intr);
  if (!process_output_write(tty, c)) {
    if (!n) {
      retval = -ERESTARTSYS;
      goto end;
    }
    retval = 0;
    goto end;
  }
  retval = 1;
 end:
  restore_intr(intr);
  return retval;
}
 
/* Vidage bloquant de la file d'ecriture. */
static int flush_write(struct tty_struct *tty)
{
  int retval;
  int intr;
 
  disable_intr(intr);
  while (tty->stopped) {
    if (!tty_queue_is_full(&tty->output_queue)) {
      retval = 1;
      goto end;
    }
    if (!(retval = condition_wait_interruptible(&tty->started_condition)))
      goto end;
  }
  flush_output_queue(tty);
  retval = 1;
 end:
  restore_intr(intr);
  return retval;
}
 
int tty_read(struct tty_struct *tty, char *fs_buf, unsigned int count)
{
  int retval;
  unsigned int n;
  char c;
 
  if ((retval = get_access_read(tty)) < 0)
    return retval;
  if (!count)
    return 0;
  if (!verify_area(fs_buf, count, PF_WRITE))
    return -EFAULT;
  if (!mutex_lock(&tty->read_mutex))
    return -ERESTARTSYS;
  n = 0;
  do {
    if ((retval = read_char(tty, n, &c)) < 0)
      goto unlock;
    if (!retval)
      break;
    if ((tty->lflags & ICANON) && c == tty->cc[VEOF])
      /* mode canonique : fin de fichier - le caractere est abandonne */
      break;
    put_fs_byte(c, fs_buf++);
    n++;
    count--;
    if ((tty->lflags & ICANON) && is_line_delimiter(tty, c))
      /* mode canonique : fin de ligne */
      break;
  }
  while (count);
  retval = n;
 unlock:
  mutex_unlock(&tty->read_mutex);
  return retval;
}
 
/* Cette fonction doit pouvoir fonctionner en phase d'initialisation, lorsque
 * les interruptions ne sont pas encore activees. C'est bien le cas car
 * tty->stopped est toujours faux en phase d'initialisation. */
int tty_write(struct tty_struct *tty, const char *fs_buf, unsigned int count)
{
  int retval;
  unsigned int n;
 
  if ((retval = get_access_write(tty)) < 0)
    return retval;
  if (!count)
    return 0;
  if (!verify_area(fs_buf, count, PF_READ))
    return -EFAULT;
  if (!mutex_lock(&tty->write_mutex))
    return -ERESTARTSYS;
  n = 0;
  do {
    if ((retval = put_write_char(tty, n, get_fs_byte(fs_buf++))) < 0)
      goto unlock;
    if (!retval)
      break;
    n++;
    count--;
  }
  while (count);
  if (n)
    if ((retval = flush_write(tty)) < 0)
      goto unlock;
  retval = n;
 unlock:
  mutex_unlock(&tty->write_mutex);
  return retval;
}
 
int tty_ioctl(struct tty_struct *tty, int request, void *fs_arg)
{
  return tty->vptr->do_ioctl(tty, request, fs_arg);
}