From 15395557e2aaf3ec9c86ba15d14e0c08a9a86405 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Sun, 10 Feb 2002 18:05:56 +0000 Subject: 2002-02-10 Marcus Brinkmann * hurdio.c: New file. * Makefile (SRCS): Add hurdio.c. * term.h: Add hurdio_bottom declaration. * main.c: Add T_HURDIO to enum of tty_type. (parse_opt): Add case for hurdio bottom handler. (term_argp): Likewise. (main): Likewise. --- term/ChangeLog | 10 + term/Makefile | 2 +- term/hurdio.c | 590 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ term/main.c | 15 +- term/term.h | 2 +- 5 files changed, 616 insertions(+), 3 deletions(-) create mode 100644 term/hurdio.c (limited to 'term') diff --git a/term/ChangeLog b/term/ChangeLog index 84431c6a..2ef3d89b 100644 --- a/term/ChangeLog +++ b/term/ChangeLog @@ -1,3 +1,13 @@ +2002-02-10 Marcus Brinkmann + + * hurdio.c: New file. + * Makefile (SRCS): Add hurdio.c. + * term.h: Add hurdio_bottom declaration. + * main.c: Add T_HURDIO to enum of tty_type. + (parse_opt): Add case for hurdio bottom handler. + (term_argp): Likewise. + (main): Likewise. + 2002-02-10 Marcus Brinkmann * main.c: Include `argp.h' and `version.h'. diff --git a/term/Makefile b/term/Makefile index 63a75d82..ff842036 100644 --- a/term/Makefile +++ b/term/Makefile @@ -22,7 +22,7 @@ dir := term makemode := server target = term -SRCS = devio.c munge.c users.c main.c ptyio.c +SRCS = devio.c munge.c users.c main.c ptyio.c hurdio.c LCLHDRS = term.h DIST_FILES = ourmsg.defs diff --git a/term/hurdio.c b/term/hurdio.c new file mode 100644 index 00000000..682b03bc --- /dev/null +++ b/term/hurdio.c @@ -0,0 +1,590 @@ +/* + Copyright (C) 1995,96,98,99,2000,01,02 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG and Marcus Brinkmann. + + This file is part of the GNU Hurd. + + The GNU Hurd 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 2, or (at + your option) any later version. + + The GNU Hurd 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +/* Handle carrier dropped (at least EIO errors in read, write) correctly. */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "term.h" + + +/* The thread asserting the DTR and performing all reads. Only + different from MACH_PORT_NULL if thread is live and blocked. */ +thread_t reader_thread = MACH_PORT_NULL; + +/* The Hurd file_t representing the terminal. If this is not + MACH_PORT_NULL, it has the additional meaning that the DTR is + asserted. */ +static file_t ioport = MACH_PORT_NULL; + +/* Each bit represents a supported tioctl call in the underlying node. + If we detect that a tioctl is not supported, we clear the bit in + tioc_caps (which is initialized at every open). */ +#define TIOC_CAP_OUTQ 0x001 +#define TIOC_CAP_START 0x002 +#define TIOC_CAP_STOP 0x004 +#define TIOC_CAP_FLUSH 0x008 +#define TIOC_CAP_CBRK 0x010 +#define TIOC_CAP_SBRK 0x020 +#define TIOC_CAP_MODG 0x040 +#define TIOC_CAP_MODS 0x080 +#define TIOC_CAP_GETA 0x100 +#define TIOC_CAP_SETA 0x200 +int tioc_caps; + +/* The thread performing all writes. Only different from + MACH_PORT_NULL if thread is live and blocked. */ +thread_t writer_thread = MACH_PORT_NULL; + +/* This flag is set if the output was suspended. */ +static int output_stopped; +static struct condition hurdio_writer_condition; + +/* Hold the amount of bytes that are currently in the progress of + being written. May be set to zero while you hold the global lock + to drain the pending output buffer. */ +size_t npending_output; + +/* True if we should assert the dtr. */ +int assert_dtr; +static struct condition hurdio_assert_dtr_condition; + + +/* Forward */ +static error_t hurdio_desert_dtr (); +static any_t hurdio_reader_loop (any_t arg); +static any_t hurdio_writer_loop (any_t arg); +static error_t hurdio_set_bits (struct termios *state); + + +static error_t +hurdio_init (void) +{ + condition_init (&hurdio_writer_condition); + condition_init (&hurdio_assert_dtr_condition); + + cthread_detach (cthread_fork (hurdio_reader_loop, 0)); + cthread_detach (cthread_fork (hurdio_writer_loop, 0)); + return 0; +} + + +/* Assert the DTR if necessary. Must be called with global lock held. */ +static void +wait_for_dtr (void) +{ + while (!assert_dtr) + hurd_condition_wait (&hurdio_assert_dtr_condition, &global_lock); + assert_dtr = 0; + + /* Open the file in blocking mode, so that the carrier is + established as well. */ + ioport = file_name_lookup (tty_arg, O_READ|O_WRITE, 0); + if (ioport == MACH_PORT_NULL) + report_carrier_error (errno); + else + { + error_t err; + struct termios state = termstate; + + /* Assume that we have a full blown terminal initially. */ + tioc_caps = ~0; + + /* Set terminal in raw mode etc. */ + err = hurdio_set_bits (&state); + if (err) + report_carrier_error (err); + else + { + termstate = state; + + /* Signal that we have a carrier. */ + report_carrier_on (); + + /* Signal that the writer thread should resume its work. */ + condition_broadcast (&hurdio_writer_condition); + } + } +} + + +/* Read and enqueue input characters. Is also responsible to assert + the DTR if necessary. */ +static any_t +hurdio_reader_loop (any_t arg) +{ + /* XXX The input buffer has 256 bytes. */ +#define BUFFER_SIZE 256 + char buffer[BUFFER_SIZE]; + char *data; + size_t datalen; + error_t err; + + mutex_lock (&global_lock); + reader_thread = mach_thread_self (); + + while (1) + { + /* We can only start when the DTR has been asserted. */ + while (!ioport) + wait_for_dtr (); + mutex_unlock (&global_lock); + + data = buffer; + datalen = BUFFER_SIZE; + + err = io_read (ioport, &data, &datalen, -1, BUFFER_SIZE); + + mutex_lock (&global_lock); + /* EIO or EOF can mean the carrier has been dropped. */ + if (err == EIO || !datalen) + hurdio_desert_dtr (); + else if (!err) + { + if (termstate.c_cflag & CREAD) + { + int i; + + for (i = 0; i < datalen; i++) + if (input_character (data[i])) + break; + } + + if (data != buffer) + vm_deallocate (mach_task_self(), (vm_address_t) data, datalen); + } + } +#undef BUFFER_SIZE +} + + +/* Output characters. */ +static any_t +hurdio_writer_loop (any_t arg) +{ + /* XXX The output buffer has 256 bytes. */ +#define BUFFER_SIZE 256 + char *bufp; + char pending_output[BUFFER_SIZE]; + size_t amount; + error_t err; + int size; + int npending_output_copy; + mach_port_t ioport_copy; + + mutex_lock (&global_lock); + writer_thread = mach_thread_self (); + + while (1) + { + while (!ioport || !qsize (outputq) + || (termflags & USER_OUTPUT_SUSP)) + hurd_condition_wait (&hurdio_writer_condition, &global_lock); + + /* If the output was suspended earlier, we have to tell the + underlying port to resume it. */ + if (output_stopped) + { + if (tioc_caps & TIOC_CAP_START) + { + err = tioctl_tiocstart (ioport); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_START; + /* XXX Handle the error. */ + err = 0; + } + output_stopped = 0; + } + + /* Copy characters onto PENDING_OUTPUT, not bothering + those already there. */ + size = qsize (outputq); + + if (size + npending_output > BUFFER_SIZE) + size = BUFFER_SIZE - npending_output; + + bufp = pending_output + npending_output; + npending_output += size; + /* We need to save these values, as otherwise there are races + with hurdio_abandon_physical_output or hurdio_desert_dtr, + which might overwrite the static variables. */ + npending_output_copy = npending_output; + ioport_copy = ioport; + mach_port_mod_refs (mach_task_self (), ioport_copy, + MACH_PORT_RIGHT_SEND, 1); + + while (size--) + *bufp++ = dequeue (outputq); + + /* Submit all the outstanding characters to the I/O port. */ + mutex_unlock (&global_lock); + err = io_write (ioport_copy, pending_output, npending_output_copy, + -1, &amount); + mutex_lock (&global_lock); + + mach_port_mod_refs (mach_task_self (), ioport_copy, + MACH_PORT_RIGHT_SEND, -1); + /* XXX Handle errors correctly. */ + if (err == EIO) + hurdio_desert_dtr (); + else if (!err) + { + /* Note that npending_output might be set to null in the + meantime by hurdio_abandon_physical_output. */ + if (amount >= npending_output) + { + npending_output = 0; + condition_broadcast (outputq->wait); + } + else + { + /* Copy the characters that didn't get output + to the front of the array. */ + npending_output -= amount; + memmove (pending_output, pending_output + amount, + npending_output); + } + } + } +#undef BUFFER_SIZE +} + + +/* If there are characters on the output queue, then send them. Is + called with global lock held. */ +static error_t +hurdio_start_output () +{ + condition_broadcast (&hurdio_writer_condition); + return 0; +} + + +/* Stop carrier on the line. Is called with global lock held. */ +static error_t +hurdio_set_break () +{ + if (tioc_caps & TIOC_CAP_SBRK) + { + error_t err = tioctl_tiocsbrk (ioport); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_SBRK; + else if (err) + return err; + } + return 0; +} + + +/* Reassert carrier on the line. Is called with global lock held. */ +static error_t +hurdio_clear_break () +{ + if (tioc_caps & TIOC_CAP_CBRK) + { + error_t err = tioctl_tioccbrk (ioport); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_CBRK; + else if (err) + return err; + } + return 0; +} + + +/* This is called when output queues are being flushed. But there may + be pending output which is sitting in a device buffer or other + place lower down than the terminal's output queue; so this is + called to flush whatever other such things may be going on. Is + called with global lock held. */ +static error_t +hurdio_abandon_physical_output () +{ + if (tioc_caps & TIOC_CAP_FLUSH) + { + error_t err = tioctl_tiocflush (ioport, O_WRITE); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_FLUSH; + else if (err) + return err; + } + + /* Make sure that an incomplete write will not be finished. + hurdio_writer_loop must take care that meddling with + npending_output here does not introduce any races. */ + npending_output = 0; + return 0; +} + + +/* Tell the underlying port to suspend all pending output, and stop + output in the bottom handler as well. Is called with the global + lock held. */ +static error_t +hurdio_suspend_physical_output () +{ + if (!output_stopped) + { + if (tioc_caps & TIOC_CAP_STOP) + { + error_t err = tioctl_tiocstop (ioport); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_STOP; + else if (err) + return err; + } + output_stopped = 1; + } + return 0; +} + +/* This is called to notify the bottom half when an input flush has + occurred. It is necessary to support pty packet mode. */ +static error_t +hurdio_notice_input_flushed () +{ + if (tioc_caps & TIOC_CAP_FLUSH) + { + error_t err = tioctl_tiocflush (ioport, O_READ); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_FLUSH; + else if (err) + return err; + } + return 0; +} + + +/* Determine the number of bytes of output pending. */ +static int +hurdio_pending_output_size () +{ + int queue_size = 0; + + if (tioc_caps & TIOC_CAP_OUTQ) + { + error_t err = tioctl_tiocoutq (ioport, &queue_size); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_OUTQ; + else if (err) + queue_size = 0; + } + /* We can not get the correct number, so let's try a guess. */ + return queue_size + npending_output; +} + + +/* Desert the DTR. Is called with global lock held. */ +static error_t +hurdio_desert_dtr () +{ + if (ioport) + { + mach_port_deallocate (mach_task_self (), ioport); + ioport = MACH_PORT_NULL; + if (writer_thread != MACH_PORT_NULL) + hurd_thread_cancel (writer_thread); + if (reader_thread != MACH_PORT_NULL) + hurd_thread_cancel (reader_thread); + } + /* If we are called after hurdio_assert_dtr before the reader thread + had a chance to wake up and open the port, we can prevent it from + doing so by clearing this flag. */ + assert_dtr = 0; + report_carrier_off (); + return 0; +} + + +static error_t +hurdio_assert_dtr () +{ + if (ioport == MACH_PORT_NULL) + { + assert_dtr = 1; + condition_signal (&hurdio_assert_dtr_condition); + } + + return 0; +} + + +/* Adjust physical state on the basis of the terminal state. + Where it isn't possible, mutate terminal state to match + reality. */ +static error_t +hurdio_set_bits (struct termios *state) +{ + error_t err; + struct termios ttystat; + /* This structure equals how the Hurd tioctl_tiocgeta/seta split up + a termios structure into RPC arguments. */ + struct hurd_termios + { + modes_t modes; + ccs_t ccs; + speeds_t speeds; + } *hurd_ttystat = (struct hurd_termios *) &ttystat; + + if (!(state->c_cflag & CIGNORE) && ioport != MACH_PORT_NULL) + { + + /* If we can not get the terminal state, it doesn't make sense + to attempt to change it. Even if we could change it we + wouldn't know what changes took effect. */ + if (!(tioc_caps & TIOC_CAP_GETA)) + /* XXX Maybe return an error here, but then we must do the + right thing in users.c. */ + return 0; + + err = tioctl_tiocgeta (ioport, hurd_ttystat->modes, + hurd_ttystat->ccs, hurd_ttystat->speeds); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + { + tioc_caps &= ~TIOC_CAP_GETA; + /* XXX Maybe return an error here, but then we must do the + right thing in users.c. */ + return 0; + } + else if (err) + return err; + + /* If possible, change the state. Otherwise we will just make + termstate match reality below. */ + if (tioc_caps & TIOC_CAP_SETA) + { + if (state->__ispeed) + hurd_ttystat->speeds[0] = state->__ispeed; + if (state->__ospeed) + hurd_ttystat->speeds[1] = state->__ospeed; + cfmakeraw (&ttystat); + ttystat.c_cflag = state->c_cflag &~ HUPCL; + + err = tioctl_tiocseta (ioport, hurd_ttystat->modes, + hurd_ttystat->ccs, hurd_ttystat->speeds); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_SETA; + else if (err) + return err; + + /* Refetch the terminal state. */ + err = tioctl_tiocgeta (ioport, hurd_ttystat->modes, + hurd_ttystat->ccs, hurd_ttystat->speeds); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_GETA; + else if (err) + return err; + } + + /* And now make termstate match reality. */ + *state = ttystat; + } + + return 0; +} + +/* Diddle the modem control bits. If HOW is MDMCTL_BIC, the bits set + in BITS should be cleared. If HOW is MDMCTL_BIS, the bits in BITS + should be set. Otherwise, bits that are set in BITS should be set, + and the others cleared. */ +static error_t +hurdio_mdmctl (int how, int bits) +{ + error_t err; + int oldbits, newbits; + + if (tioc_caps & TIOC_CAP_MODS) + { + if ((how == MDMCTL_BIS) || (how == MDMCTL_BIC)) + { + if (tioc_caps & TIOC_CAP_MODG) + { + error_t err = tioctl_tiocmodg (ioport, &oldbits); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_MODG; + else if (err) + return err; + } + } + + if (how == MDMCTL_BIS) + newbits = (oldbits | bits); + else if (how == MDMCTL_BIC) + newbits = (oldbits &= ~bits); + else + newbits = bits; + + err = tioctl_tiocmods (ioport, oldbits); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_MODS; + else if (err) + return err; + } + return 0; +} + + +static int +hurdio_mdmstate () +{ + int oldbits; + + if (tioc_caps & TIOC_CAP_MODG) + { + error_t err = tioctl_tiocmodg (ioport, &oldbits); + if (err && (err == EMIG_BAD_ID || err == EOPNOTSUPP)) + tioc_caps &= ~TIOC_CAP_MODG; + else if (err) + return 0; /* XXX What else can we do? */ + } + return 0; +} + + + +const struct bottomhalf hurdio_bottom = +{ + TERM_ON_HURDIO, + hurdio_init, + hurdio_start_output, + hurdio_set_break, + hurdio_clear_break, + hurdio_abandon_physical_output, + hurdio_suspend_physical_output, + hurdio_pending_output_size, + hurdio_notice_input_flushed, + hurdio_assert_dtr, + hurdio_desert_dtr, + hurdio_set_bits, + hurdio_mdmctl, + hurdio_mdmstate, +}; diff --git a/term/main.c b/term/main.c index 5e90e28e..b0a80658 100644 --- a/term/main.c +++ b/term/main.c @@ -41,7 +41,7 @@ int trivfs_allow_open = O_READ|O_WRITE; /* The argument line options. */ char *tty_name; -enum { T_NONE = 0, T_DEVICE, T_PTYMASTER, T_PTYSLAVE } tty_type; +enum { T_NONE = 0, T_DEVICE, T_HURDIO, T_PTYMASTER, T_PTYSLAVE } tty_type; char *tty_arg; int @@ -76,6 +76,8 @@ parse_opt (int opt, char *arg, struct argp_state *state) { if (!strcmp (arg, "device")) tty_type = T_DEVICE; + else if (!strcmp (arg, "hurdio")) + tty_type = T_HURDIO; else if (!strcmp (arg, "pty-master")) tty_type = T_PTYMASTER; else if (!strcmp (arg, "pty-slave")) @@ -100,6 +102,7 @@ static struct argp term_argp = { 0, parse_opt, "NAME TYPE ARG", "A translator that emulates a terminal.\v"\ "Possible values for TYPE:\n"\ " device Use Mach device ARG as bottom handler.\n"\ + " hurdio Use file port ARG as bottom handler.\n"\ " pty-master Master for slave at ARG.\n"\ " pty-slave Slave for master at ARG.\n"\ "\n"\ @@ -140,6 +143,16 @@ main (int argc, char **argv) peercntl = 0; break; + case T_HURDIO: + bottom = &hurdio_bottom; + ourclass = tty_class; + ourcntlclass = tty_cntl_class; + ourcntl = &termctl; + peerclass = 0; + peercntlclass = 0; + peercntl = 0; + break; + case T_PTYMASTER: bottom = &ptyio_bottom; ourclass = pty_class; diff --git a/term/term.h b/term/term.h index 694c598e..cb22e159 100644 --- a/term/term.h +++ b/term/term.h @@ -155,7 +155,7 @@ struct bottomhalf }; const struct bottomhalf *bottom; -extern const struct bottomhalf devio_bottom, ptyio_bottom; +extern const struct bottomhalf devio_bottom, hurdio_bottom, ptyio_bottom; /* Character queues */ -- cgit v1.2.3