/* Copyright (C) 1995 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. */ #include "term.h" #include #include #include #include #include #include #include #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 EXTA #undef EXTB #include /* 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; /* This flag is set if there is an outstanding device_open. */ static int 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; /* Forward */ static void devio_desert_dtr (); static void init_devio (void) __attribute__ ((constructor)); static void init_devio () { mach_port_t host_priv; errno = get_privileged_ports (&host_priv, &device_master); if (errno) { perror ("Getting priviliged ports"); exit (1); } 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 28400: *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++) { flush = input_character (data[i]); 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; 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 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; } static void devio_desert_dtr () { /* This will work, because we set the TF_HUPCLS bit earlier. */ device_close (phys_device); mach_port_deallocate (mach_task_self (), phys_device); phys_device = MACH_PORT_NULL; mach_port_deallocate (mach_task_self (), phys_reply); mach_port_deallocate (mach_task_self (), phys_reply_writes); phys_reply = phys_reply_writes = MACH_PORT_NULL; ports_port_deref (phys_reply_pi); ports_port_deref (phys_reply_writes_pi); phys_reply_pi = phys_reply_writes_pi = 0; report_carrier_off (); } static error_t devio_assert_dtr () { error_t err; if (open_pending || (phys_device != MACH_PORT_NULL)) return 0; assert (phys_reply == MACH_PORT_NULL); assert (phys_reply_pi == 0); phys_reply_pi = ports_allocate_port (term_bucket, sizeof (struct port_info), phys_reply_class); 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; } return err; } 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; if (replyport != phys_reply) return EOPNOTSUPP; mutex_lock (&global_lock); open_pending = 0; if (returncode != 0) { /* Bogus. */ 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; mutex_unlock (&global_lock); return 0; } assert (phys_device == MACH_PORT_NULL); assert (phys_reply_writes == MACH_PORT_NULL); assert (phys_reply_writes_pi == 0); phys_device = device; phys_reply_writes_pi = ports_allocate_port (term_bucket, sizeof (struct port_info), phys_reply_class); 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_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); err = device_read_request_inband (phys_device, phys_reply, D_NOWAIT, 0, vm_page_size); input_pending = 1; report_carrier_on (); if (err) devio_desert_dtr (); 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 () { struct tty_status ttystat; int cnt = TTY_STATUS_COUNT; assert (!(termstate.c_cflag & CIGNORE)); if (phys_device == MACH_PORT_NULL) return; /* 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; /* Mach only supports 8 bit channels. So wark the CSIZE to conform. */ termstate.c_cflag = ((termstate.c_cflag & ~CSIZE) | ((termstate.c_cflag & PARENB) ? CS7 : CS8)); } 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) { open_pending = 0; 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_assert_dtr, devio_desert_dtr, devio_set_bits, devio_mdmctl, devio_mdmstate, };