diff options
Diffstat (limited to 'term/devio.c')
-rw-r--r-- | term/devio.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/term/devio.c b/term/devio.c new file mode 100644 index 00000000..2306fa3b --- /dev/null +++ b/term/devio.c @@ -0,0 +1,752 @@ +/* + Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + 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. */ + +/* Avoid defenition of the baud rates from <ternios.h> at a later time. */ +#include <termios.h> + +/* And undefine the baud rates to avoid warnings from + <device/tty_status.h>. */ +#undef B50 +#undef B75 +#undef B110 +#undef B134 +#undef B150 +#undef B200 +#undef B300 +#undef B600 +#undef B1200 +#undef B1800 +#undef B2400 +#undef B4800 +#undef B9600 +#undef B19200 +#undef B38400 +#undef B57600 +#undef B115200 +#undef EXTA +#undef EXTB + +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <string.h> + +#include <device/device.h> +#include <device/device_request.h> +#include <device/tty_status.h> +#include <cthreads.h> + +#include <hurd.h> +#include <hurd/ports.h> + +#include "term.h" + + +/* This flag is set if there is an outstanding device_write. */ +static int output_pending; + +/* This flag is set if there is an outstanding device_read. */ +static int input_pending; + +/* Tell the status of any pending open */ +static enum +{ + NOTPENDING, /* no open is pending */ + INITIAL, /* initial open of device pending */ + FAKE, /* open pending to block on dtr */ +} open_pending; + +static char pending_output[IO_INBAND_MAX]; +static int npending_output; + +static struct port_class *phys_reply_class; + +/* The Mach device_t representing the terminal. */ +static device_t phys_device = MACH_PORT_NULL; + +/* The ports we get replies on for device calls. */ +static mach_port_t phys_reply_writes = MACH_PORT_NULL; +static mach_port_t phys_reply = MACH_PORT_NULL; + +/* The port-info structs. */ +static struct port_info *phys_reply_writes_pi; +static struct port_info *phys_reply_pi; + +static device_t device_master; + +static int output_stopped; + +/* XXX Mask that omits high bits we are currently not supposed to pass + through. */ +static int char_size_mask_xxx = 0xff; + +/* Forward */ +static void devio_desert_dtr (); + +static void init_devio (void) __attribute__ ((constructor)); +static void +init_devio () +{ + mach_port_t host_priv; + error_t err; + + err = get_privileged_ports (&host_priv, &device_master); + if (err) + error (1, err, "Getting priviliged ports"); + mach_port_deallocate (mach_task_self (), host_priv); + phys_reply_class = ports_create_class (0, 0); +} + +/* XXX Convert a real speed to a bogus Mach speed. Return + -1 if the real speed was bogus, else 0. */ +static int +real_speed_to_bogus_speed (int rspeed, int *bspeed) +{ + switch (rspeed) + { + case 0: + *bspeed = B0; + break; + case 50: + *bspeed = B50; + break; + case 75: + *bspeed = B75; + break; + case 110: + *bspeed = B110; + break; + case 134: + *bspeed = B134; + break; + case 150: + *bspeed = B150; + break; + case 200: + *bspeed = B200; + break; + case 300: + *bspeed = B300; + break; + case 600: + *bspeed = B600; + break; + case 1200: + *bspeed = B1200; + break; + case 1800: + *bspeed = B1800; + break; + case 2400: + *bspeed = B2400; + break; + case 4800: + *bspeed = B4800; + break; + case 9600: + *bspeed = B9600; + break; + case 19200: + *bspeed = EXTA; + break; + case 38400: + *bspeed = EXTB; + break; + default: + return -1; + } + return 0; +} + +/* XXX Convert a bogus speed to a real speed. */ +static int +bogus_speed_to_real_speed (int bspeed) +{ + switch (bspeed) + { + case B0: + default: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case EXTA: + return 19200; + case EXTB: + return 38400; + } +} + +/* If there are characters on the output queue and no + pending output requests, then send them. */ +static void +devio_start_output () +{ + char *cp; + int size; + error_t err; + + size = qsize (outputq); + + if (!size || output_pending || (termflags & USER_OUTPUT_SUSP)) + return; + + if (output_stopped) + { + device_set_status (phys_device, TTY_START, 0, 0); + output_stopped = 0; + } + + /* Copy characters onto PENDING_OUTPUT, not bothering + those already there. */ + + if (size + npending_output > IO_INBAND_MAX) + size = IO_INBAND_MAX - npending_output; + + cp = pending_output + npending_output; + npending_output += size; + + while (size--) + *cp++ = dequeue (outputq); + + /* Submit all the outstanding characters to the device. */ + /* The D_NOWAIT flag does not, in fact, prevent blocks. Instead, + it merely causes D_WOULD_BLOCK errors when carrier is down... + whee.... */ + err = device_write_request_inband (phys_device, phys_reply_writes, D_NOWAIT, + 0, pending_output, npending_output); + + if (err == MACH_SEND_INVALID_DEST) + devio_desert_dtr (); + else if (!err) + output_pending = 1; +} + +error_t +device_write_reply_inband (mach_port_t replypt, + error_t return_code, + int amount) +{ + if (replypt != phys_reply_writes) + return EOPNOTSUPP; + + mutex_lock (&global_lock); + + output_pending = 0; + + if (return_code == 0) + { + 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); + } + devio_start_output (); + } + else if (return_code == D_WOULD_BLOCK) + /* Carrier has dropped. */ + devio_desert_dtr (); + else + devio_start_output (); + + mutex_unlock (&global_lock); + return 0; +} + +error_t +device_read_reply_inband (mach_port_t replypt, + error_t error_code, + char *data, + u_int datalen) +{ + int i, flush; + error_t err; + + if (replypt != phys_reply) + return EOPNOTSUPP; + + mutex_lock (&global_lock); + + input_pending = 0; + + if (!error_code && (termstate.c_cflag & CREAD)) + for (i = 0; i < datalen; i++) + { + int c = data[i]; + + /* XXX Mach only supports 8-bit channels; this munges things + to account for the reality. */ + c &= char_size_mask_xxx; + + flush = input_character (c); + if (flush) + break; + } + else if (error_code == D_WOULD_BLOCK) + { + devio_desert_dtr (); + mutex_unlock (&global_lock); + return 0; + } + + /* D_NOWAIT does not actually prevent blocks; it merely causes + D_WOULD_BLOCK errors when carrier drops. */ + err = device_read_request_inband (phys_device, phys_reply, D_NOWAIT, + 0, vm_page_size); + + if (err) + devio_desert_dtr (); + else + input_pending = 1; + + mutex_unlock (&global_lock); + return 0; +} + +static void +devio_set_break () +{ + device_set_status (phys_device, TTY_SET_BREAK, 0, 0); +} + +static void +devio_clear_break () +{ + device_set_status (phys_device, TTY_CLEAR_BREAK, 0, 0); +} + +static void +devio_abandon_physical_output () +{ + int val = D_WRITE; + + /* If this variable is clear, then carrier is gone, so we + have nothing to do. */ + if (!phys_reply_writes_pi) + return; + + mach_port_deallocate (mach_task_self (), phys_reply_writes); + ports_reallocate_port (phys_reply_writes_pi); + phys_reply_writes = ports_get_right (phys_reply_writes_pi); + mach_port_insert_right (mach_task_self (), phys_reply_writes, + phys_reply_writes, MACH_MSG_TYPE_MAKE_SEND); + + device_set_status (phys_device, TTY_FLUSH, &val, TTY_FLUSH_COUNT); + npending_output = 0; + output_pending = 0; +} + +static void +devio_suspend_physical_output () +{ + if (!output_stopped) + { + device_set_status (phys_device, TTY_STOP, 0, 0); + output_stopped = 1; + } +} + +static void +devio_notice_input_flushed () +{ +} + +static int +devio_pending_output_size () +{ + /* Unfortunately, there's no way to get the amount back from Mach + that has actually been written from this... */ + return npending_output; +} + +/* Do this the first time the device is to be opened */ +static error_t +initial_open () +{ + error_t err; + + assert (open_pending != FAKE); + + /* Nothing to do */ + if (open_pending == INITIAL) + return 0; + + assert (phys_device == MACH_PORT_NULL); + assert (phys_reply == MACH_PORT_NULL); + assert (phys_reply_pi == 0); + + err = ports_create_port (phys_reply_class, term_bucket, + sizeof (struct port_info), &phys_reply_pi); + if (err) + return err; + + phys_reply = ports_get_right (phys_reply_pi); + mach_port_insert_right (mach_task_self (), phys_reply, phys_reply, + MACH_MSG_TYPE_MAKE_SEND); + + err = device_open_request (device_master, phys_reply, + D_READ|D_WRITE, pterm_name); + if (err) + { + mach_port_deallocate (mach_task_self (), phys_reply); + phys_reply = MACH_PORT_NULL; + ports_port_deref (phys_reply_pi); + phys_reply_pi = 0; + } + else + open_pending = INITIAL; + + return err; +} + +static void +devio_desert_dtr () +{ + int bits; + + /* Turn off DTR. */ + bits = TM_HUP; + device_set_status (phys_device, TTY_MODEM, + (dev_status_t) &bits, TTY_MODEM_COUNT); + + report_carrier_off (); +} + +static error_t +devio_assert_dtr () +{ + error_t err; + + /* The first time is special. */ + if (phys_device == MACH_PORT_NULL) + return initial_open (); + + /* Schedule a fake open to wait for DTR, unless one is already + happening. */ + assert (open_pending != INITIAL); + if (open_pending == FAKE) + return 0; + + err = device_open_request (device_master, phys_reply, + D_READ|D_WRITE, pterm_name); + + if (err) + return err; + + open_pending = FAKE; + return 0; +} + +kern_return_t +device_open_reply (mach_port_t replyport, + int returncode, + mach_port_t device) +{ + struct tty_status ttystat; + int count = TTY_STATUS_COUNT; + error_t err = 0; + + if (replyport != phys_reply) + return EOPNOTSUPP; + + mutex_lock (&global_lock); + + assert (open_pending != NOTPENDING); + + if (returncode != 0) + { + report_carrier_error (returncode); + + mach_port_deallocate (mach_task_self (), phys_reply); + phys_reply = MACH_PORT_NULL; + ports_port_deref (phys_reply_pi); + phys_reply_pi = 0; + + open_pending = NOTPENDING; + mutex_unlock (&global_lock); + return 0; + } + + if (open_pending == INITIAL) + { + /* Special handling for the first open */ + + assert (phys_device == MACH_PORT_NULL); + assert (phys_reply_writes == MACH_PORT_NULL); + assert (phys_reply_writes_pi == 0); + phys_device = device; + err = ports_create_port (phys_reply_class, term_bucket, + sizeof (struct port_info), + &phys_reply_writes_pi); + if (err) + { + open_pending = NOTPENDING; + mutex_unlock (&global_lock); + return err; + } + phys_reply_writes = ports_get_right (phys_reply_writes_pi); + mach_port_insert_right (mach_task_self (), phys_reply_writes, + phys_reply_writes, MACH_MSG_TYPE_MAKE_SEND); + + /* Schedule our first read */ + err = device_read_request_inband (phys_device, phys_reply, D_NOWAIT, + 0, vm_page_size); + + input_pending = 1; + } + else + { + /* This was a fake open, only for the sake of assert DTR. */ + device_close (device); + mach_port_deallocate (mach_task_self (), device); + } + + device_get_status (phys_device, TTY_STATUS, + (dev_status_t)&ttystat, &count); + ttystat.tt_breakc = 0; + ttystat.tt_flags = TF_ANYP | TF_LITOUT | TF_NOHANG | TF_HUPCLS; + device_set_status (phys_device, TTY_STATUS, + (dev_status_t)&ttystat, TTY_STATUS_COUNT); + + report_carrier_on (); + if (err) + devio_desert_dtr (); + + open_pending = NOTPENDING; + mutex_unlock (&global_lock); + + return 0; +} + +/* Adjust physical state on the basis of the terminal state. + Where it isn't possible, mutate terminal state to match + reality. */ +static void +devio_set_bits () +{ + if (!(termstate.c_cflag & CIGNORE) && phys_device != MACH_PORT_NULL) + { + struct tty_status ttystat; + int cnt = TTY_STATUS_COUNT; + + /* Find the current state. */ + device_get_status (phys_device, TTY_STATUS, (dev_status_t) &ttystat, &cnt); + if (termstate.__ispeed) + real_speed_to_bogus_speed (termstate.__ispeed, &ttystat.tt_ispeed); + if (termstate.__ospeed) + real_speed_to_bogus_speed (termstate.__ospeed, &ttystat.tt_ospeed); + + /* Try and set it. */ + device_set_status (phys_device, TTY_STATUS, + (dev_status_t) &ttystat, TTY_STATUS_COUNT); + + /* And now make termstate match reality. */ + cnt = TTY_STATUS_COUNT; + device_get_status (phys_device, TTY_STATUS, (dev_status_t) &ttystat, &cnt); + termstate.__ispeed = bogus_speed_to_real_speed (ttystat.tt_ispeed); + termstate.__ospeed = bogus_speed_to_real_speed (ttystat.tt_ospeed); + + /* Mach forces us to use the normal stop bit convention: + two bits at 110 bps; 1 bit otherwise. */ + if (termstate.__ispeed == 110) + termstate.c_cflag |= CSTOPB; + else + termstate.c_cflag &= ~CSTOPB; + + /* Figure out how to munge input, since we are unable to actually + affect what the hardware does. */ + switch (termstate.c_cflag & CSIZE) + { + case CS5: + char_size_mask_xxx = 0x1f; + break; + + case CS6: + char_size_mask_xxx = 0x3f; + break; + + case CS7: + char_size_mask_xxx = 0x7f; + break; + + case CS8: + default: + char_size_mask_xxx = 0xff; + break; + } + if (termstate.c_cflag & PARENB) + char_size_mask_xxx |= 0x80; + } +} + +static void +devio_mdmctl (int how, int bits) +{ + int oldbits, newbits; + int cnt; + if ((how == MDMCTL_BIS) || (how == MDMCTL_BIC)) + { + cnt = TTY_MODEM_COUNT; + device_get_status (phys_device, TTY_MODEM, + (dev_status_t) &oldbits, &cnt); + if (cnt < TTY_MODEM_COUNT) + oldbits = 0; /* what else can we do? */ + } + + if (how == MDMCTL_BIS) + newbits = (oldbits | bits); + else if (how == MDMCTL_BIC) + newbits = (oldbits &= ~bits); + else + newbits = bits; + + device_set_status (phys_device, TTY_MODEM, + (dev_status_t) &newbits, TTY_MODEM_COUNT); +} + +static int +devio_mdmstate () +{ + int bits, cnt; + + cnt = TTY_MODEM_COUNT; + device_get_status (phys_device, TTY_MODEM, (dev_status_t) &bits, &cnt); + if (cnt != TTY_MODEM_COUNT) + return 0; + else + return bits; +} + +/* Unused stubs */ +kern_return_t +device_read_reply (mach_port_t port, + kern_return_t retcode, + io_buf_ptr_t data, + mach_msg_type_number_t cnt) +{ + return EOPNOTSUPP; +} + +kern_return_t +device_write_reply (mach_port_t replyport, + kern_return_t retcode, + int written) +{ + return EOPNOTSUPP; +} + +error_t +ports_do_mach_notify_send_once (mach_port_t notify) +{ + error_t err; + + mutex_lock (&global_lock); + + if (notify == phys_reply_writes) + { + err = 0; + devio_start_output (); + } + else if (notify == phys_reply) + { + if (input_pending) + { + /* xxx */ + char msg[] = "Term input check happened\r\n"; + int foo; + device_write_inband (phys_device, 0, 0, msg, sizeof msg, &foo); + /* end xxx */ + + input_pending = 0; + + err = device_read_request_inband (phys_device, phys_reply, + D_NOWAIT, 0, vm_page_size); + if (err) + devio_desert_dtr (); + else + input_pending = 1; + } + else if (open_pending != NOTPENDING) + { + open_pending = NOTPENDING; + + report_carrier_on (); + report_carrier_off (); + + mach_port_deallocate (mach_task_self (), phys_reply); + phys_reply = MACH_PORT_NULL; + ports_port_deref (phys_reply_pi); + phys_reply_pi = 0; + } + err = 0; + } + else + err = EOPNOTSUPP; + + mutex_unlock (&global_lock); + return err; +} + + +struct bottomhalf devio_bottom = +{ + devio_start_output, + devio_set_break, + devio_clear_break, + devio_abandon_physical_output, + devio_suspend_physical_output, + devio_pending_output_size, + devio_notice_input_flushed, + devio_assert_dtr, + devio_desert_dtr, + devio_set_bits, + devio_mdmctl, + devio_mdmstate, +}; |