//+++2001-11-18 // Copyright (C) 2001, Mike Rieker, Beverly, MA USA // // 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; version 2 of the License. // // 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, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //---2001-11-18 /************************************************************************/ /* */ /* Pseudo console port driver */ /* */ /* User (or kernel) mode programs, like telnetd, use this driver to */ /* create console-like devices. */ /* */ /************************************************************************/ #include "ozone.h" #include "oz_knl_devio.h" #include "oz_knl_hw.h" #include "oz_knl_kmalloc.h" #include "oz_knl_logon.h" #include "oz_knl_sdata.h" #include "oz_knl_status.h" #include "oz_knl_thread.h" #include "oz_io_comport.h" #include "oz_io_conpseudo.h" #include "oz_io_console.h" #include "oz_sys_xprintf.h" #define SENDDATQMAX 5 /* max number of requests to allow in senddatq */ typedef struct Chnex Chnex; typedef struct Devex Devex; typedef struct Eventdat Eventdat; typedef struct Gsreq Gsreq; typedef struct Iopex Iopex; typedef struct Senddat Senddat; struct Chnex { uByte class_area[1]; /* class driver's area */ }; struct Devex { OZ_Iochan *classiochan; /* console class I/O channel (assigned to devunit "conclass") */ OZ_IO_comport_setup comport_setup; /* console port setup parameters */ OZ_Event *lockevent; /* database locking event flag */ OZ_Thread *lockthread; /* thread that has database locked */ int terminated; /* we have gotten a terminate call from the class driver */ Iopex *sendreqqh, **sendreqqt; /* queue of OZ_IO_CONPSEUDO_GETSCRDATA requests waiting for screen data */ Senddat *senddatqh, **senddatqt; /* queue of screen data waiting for OZ_IO_CONPSEUDO_GETSCRDATA requests */ int senddatqs; /* number of requests in senddatq */ Iopex *eventreqqh, **eventreqqt; Eventdat *eventdatqh, **eventdatqt; Gsreq *gsreqh, **gsreqt; /* list of pending GETMODE/SETMODE requests */ OZ_Iochan *masteriochan; /* master I/O channel - allowed to do OZ_IO_CONPSEUDO_... calls */ /* when telnetd (or whoever) deassigns this channel, the device gets shut down */ OZ_Devfunc *port_functab; /* copy of function table (pt_functable) with xxx_exsize's modified for class driver */ }; struct Eventdat { Eventdat *next; /* next in eventdatq */ OZ_Conpseudo_event event; /* event number */ }; struct Gsreq { Gsreq *next; /* next in gsreqh/t */ void *param; /* param to return to class driver on completion */ uLong size; /* size of mode buffer */ int fetched; /* 0: waiting to be fetched; 1: waiting to be posted */ OZ_Console_modebuff *buff; /* address of mode buffer */ }; struct Iopex { Iopex *next; OZ_Ioop *ioop; Devex *devex; union { struct { uLong size; /* size of user's buffer */ char *buff; /* address of user's buffer */ uLong *rlen; /* where to return length to display */ } getscrdata; struct { Eventdat *eventdat; /* corresponding event data */ OZ_Conpseudo_event *event; /* where to return event data */ } getevent; } u; uByte class_area[1]; /* class driver's area */ }; struct Senddat { Senddat *next; /* next in senddatq */ uLong size; /* size of data to display */ char *buff; /* address of data to display */ void *write_param; /* parameter to pass to display complete routine */ }; static uLong pt_clonecre (OZ_Devunit *template_devunit, void *template_devex, int template_cloned, OZ_Procmode procmode, OZ_Devunit **cloned_devunit); static int pt_clonedel (OZ_Devunit *cloned_devunit, void *devexv, int cloned); static uLong pt_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode); static int pt_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv); static void pt_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode); static uLong pt_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap); static const OZ_Devfunc pt_functable = { sizeof (Devex), sizeof (Chnex), sizeof (Iopex), 0, NULL, pt_clonecre, pt_clonedel, pt_assign, pt_deassign, pt_abort, pt_start, NULL }; static int initialized = 0; static OZ_Devclass *devclass = NULL; static OZ_Devdriver *devdriver = NULL; static OZ_Devunit *devunit = NULL; static uLong io_pt_setup (OZ_Ioop *ioop, OZ_Devunit *devunit, Devex *devex, const char *portname, const char *classname); static uLong io_pt_getscrdata (Devex *devex, Iopex *iopex, uLong size, char *buff, uLong *rlen); static uLong io_pt_putkbddata (Devex *devex, uLong size, const char *buff); static void kbd_rah_full (void *devexv, int full); static uLong getsetmode (void *devexv, void *getset_param, uLong size, OZ_Console_modebuff *buff); static uLong io_pt_getevent (Devex *devex, Iopex *iopex, OZ_Conpseudo_event *event); static uLong io_pt_fetchgsmodereq (Devex *devex, OZ_IO_conpseudo_fetchgsmodereq *pt_fetchgsmodereq); static uLong io_pt_postgsmodereq (Devex *devex, OZ_IO_conpseudo_postgsmodereq *pt_postgsmodereq); static void terminate (void *devexv); static void read_start (void *devexv, int start); static uLong disp_start (void *devexv, void *write_param, uLong size, char *buff); static void sendreq_iodone (void *sendreqv, int finok, uLong *status_r); static void disp_suspend (void *devexv, int suspend); static void gotevent (Devex *devex, OZ_Conpseudo_event event); static void event_iodone (void *eventreqv, int finok, uLong *status_r); static uLong lockdb (void *devexv); static void unlkdb (void *devexv, uLong iplsav); /************************************************************************/ /* */ /* Boot-time initialization routine */ /* */ /************************************************************************/ void oz_dev_conpseudo_init () { if (!initialized) { initialized = 1; oz_knl_printk ("oz_dev_conpseudo_init\n"); devclass = oz_knl_devclass_create (OZ_IO_CONPSEUDO_CLASSNAME, OZ_IO_CONPSEUDO_BASE, OZ_IO_CONPSEUDO_MASK, "oz_dev_conpseudo"); devdriver = oz_knl_devdriver_create (devclass, "oz_dev_conpseudo"); devunit = oz_knl_devunit_create (devdriver, OZ_IO_CONPSEUDO_DEV, "pseudo console template", &pt_functable, 0, oz_s_secattr_tempdev); } } /************************************************************************/ /* */ /* A device is to be created. This is done by a program such as */ /* telnetd assigning a channel to the template device. */ /* */ /* This routine runs with the dv smplock set */ /* */ /* We make a copy of the template device and give it a temporary */ /* name. Also, we init the devex area with a functab and lockevent. */ /* */ /************************************************************************/ static uLong pt_clonecre (OZ_Devunit *template_devunit, void *template_devex, int template_cloned, OZ_Procmode procmode, OZ_Devunit **cloned_devunit) { char unitname[OZ_DEVUNIT_NAMESIZE]; Devex *devex; OZ_Devfunc *functab; OZ_Devunit *devunit; OZ_Event *lockevent; OZ_Secattr *secattr; uLong sts; static uLong seq = 0; if (template_cloned) { /* maybe someone is assigning to one of the clones */ *cloned_devunit = template_devunit; /* if so, just use the device as it is */ oz_knl_devunit_increfc (template_devunit, 1); /* ... but increment the unit's ref count */ } else { oz_sys_sprintf (sizeof unitname, unitname, "%s.%u", oz_knl_devunit_devname (template_devunit), ++ seq); functab = OZ_KNL_NPPMALLOQ (sizeof *functab); /* create a copy of the function table for it */ if (functab == NULL) return (OZ_EXQUOTANPP); sts = oz_knl_event_create (strlen (unitname), unitname, NULL, &lockevent); /* get an event flag set up for locking the database */ if (sts != OZ_SUCCESS) { OZ_KNL_NPPFREE (functab); return (sts); } *functab = pt_functable; secattr = oz_knl_thread_getdefcresecattr (NULL); devunit = oz_knl_devunit_create (devdriver, unitname, "not set up", functab, 1, secattr); /* create the cloned device */ oz_knl_secattr_increfc (secattr, -1); devex = oz_knl_devunit_ex (devunit); /* point to its extension */ memset (devex, 0, sizeof *devex); /* clear out the extension */ *cloned_devunit = devunit; /* return pointer to new device */ devex -> port_functab = functab; /* remember where function table is */ devex -> lockevent = lockevent; /* mark database 'unlocked' */ oz_knl_event_set (lockevent, 1); } return (OZ_SUCCESS); } /************************************************************************/ /* */ /* All channels, including the telnetd channel, have been deassigned. */ /* The device will be deleted from the system iff a 1 is returned. */ /* */ /* This routine runs with the dv smplock set */ /* */ /************************************************************************/ static int pt_clonedel (OZ_Devunit *cloned_devunit, void *devexv, int cloned) { Devex *devex; devex = devexv; if (cloned) { OZ_KNL_NPPFREE (devex -> port_functab); /* free off the functab copy */ oz_knl_event_increfc (devex -> lockevent, -1); /* free off the database locking event flag */ devex -> port_functab = NULL; devex -> lockevent = NULL; } return (cloned); /* delete only 'cloned' devices - don't delete the template */ } /************************************************************************/ /* */ /* An channel was just assigned to the 'pt' device */ /* */ /* For us, it is a nop, but the class driver might want to hear about */ /* it. */ /* */ /************************************************************************/ static uLong pt_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode) { Devex *devex; devex = devexv; if (devex -> classiochan == NULL) return (OZ_SUCCESS); return ((*(devex -> comport_setup.class_functab -> assign)) (devunit, devex -> comport_setup.class_devex, iochan, ((Chnex *)chnexv) -> class_area, procmode)); } /************************************************************************/ /* */ /* Channel to device is being deassigned */ /* */ /************************************************************************/ static int pt_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv) { Devex *devex; Eventdat *eventdat; Gsreq *gsreq; Iopex *iopex; uLong db; OZ_Iochan *classiochan; Senddat *senddat; devex = devexv; /* If not set up at all, just return 'kill it now' status */ if (devex -> classiochan == NULL) return (0); /* If it is not the 'master channel' just pass it to the class driver. We don't do the */ /* master channel because the assign routine did not get called for the master channel. */ if (iochan != devex -> masteriochan) { return ((*(devex -> comport_setup.class_functab -> deassign)) (devunit, devex -> comport_setup.class_devex, iochan, ((Chnex *)chnexv) -> class_area)); } /* Master channel, process it internally - we have to undo everything the io_pt_setup call did as well as any I/O's that are going on */ db = lockdb (devex); /* lock the database */ oz_knl_devunit_rename (devunit, NULL, "master channel deassigned"); /* Close the console class device - we must not call any of the class_ setup */ /* routines anymore, and it must not call any of our port_ routines anymore */ classiochan = devex -> classiochan; /* get iochan to 'conclass' device */ if (devex -> classiochan != NULL) { /* see if it was ever set up (via OZ_IO_CONPSEUDO_SETUP) */ devex -> classiochan = NULL; /* this is our indicator to not call anymore class_ setup routines */ oz_knl_iochan_increfc (classiochan, -1); /* close the channel - this aborts all application I/O's to the console device */ memset (&(devex -> comport_setup), 0, sizeof devex -> comport_setup); /* make sure we can't call the class_ setup routines anymore */ } /* Abort all OZ_IO_CONPSEUDO_GETSCRDATA and OZ_IO_CONPSEUDO_GETEVENT requests */ devex -> sendreqqt = NULL; /* anything what tries to queue with this will crash */ while ((iopex = devex -> sendreqqh) != NULL) { /* abort any I/O's remaining in the queue */ devex -> sendreqqh = iopex -> next; oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, NULL, NULL); } devex -> eventreqqt = NULL; /* ditto */ while ((iopex = devex -> eventreqqh) != NULL) { devex -> eventreqqh = iopex -> next; oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, NULL, NULL); } /* Free off any left over data and left over events */ devex -> senddatqt = NULL; while ((senddat = devex -> senddatqh) != NULL) { devex -> senddatqh = senddat -> next; devex -> senddatqs --; OZ_KNL_PGPFREE (senddat); } devex -> eventdatqt = NULL; while ((eventdat = devex -> eventdatqh) != NULL) { devex -> eventdatqh = eventdat -> next; OZ_KNL_PGPFREE (eventdat); } devex -> gsreqt = NULL; while ((gsreq = devex -> gsreqh) != NULL) { devex -> gsreqh = gsreq -> next; OZ_KNL_PGPFREE (gsreq); } /* We don't have an master anymore. Unlock database */ devex -> masteriochan = NULL; unlkdb (devex, db); return (0); } /************************************************************************/ /* */ /* Abort any requests to the device */ /* */ /************************************************************************/ static void pt_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode) { Devex *devex; Iopex *iopex, **liopex; uLong db; devex = devexv; /* get pointer to the devex struct */ db = lockdb (devex); /* lock the database */ /* See if it is the master I/O channel (ie, the one something like telnetd is using) */ if (iochan == devex -> masteriochan) { /* Abort all OZ_IO_CONPSEUDO_GETSCRDATA requests */ for (liopex = &(devex -> sendreqqh); (iopex = *liopex) != NULL;) { if (!oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) liopex = &(iopex -> next); else { *liopex = iopex -> next; oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, NULL, NULL); } } devex -> sendreqqt = liopex; /* Abort all OZ_IO_CONPSEUDO_GETEVENT requests */ for (liopex = &(devex -> eventreqqh); (iopex = *liopex) != NULL;) { if (!oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) liopex = &(iopex -> next); else { *liopex = iopex -> next; oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, NULL, NULL); } } devex -> eventreqqt = liopex; } /* Not master, call the class driver (if any) */ else if (devex -> classiochan != NULL) { iopex = iopexv; (*(devex -> comport_setup.class_functab -> abort)) (devunit, devex -> comport_setup.class_devex, iochan, ((Chnex *)chnexv) -> class_area, ioop, (iopex == NULL) ? NULL : iopex -> class_area, procmode); } unlkdb (devex, db); } /************************************************************************/ /* */ /* An new I/O request is to be processed */ /* */ /************************************************************************/ static uLong pt_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap) { char classname[OZ_DEVUNIT_NAMESIZE], portname[OZ_DEVUNIT_NAMESIZE]; Devex *devex; Iopex *iopex; uLong db, sts; devex = devexv; iopex = iopexv; iopex -> next = NULL; iopex -> ioop = ioop; iopex -> devex = devex; switch (funcode) { /* Set up the device */ case OZ_IO_CONPSEUDO_SETUP: { OZ_IO_conpseudo_setup pt_setup; movc4 (as, ap, sizeof pt_setup, &pt_setup); sts = oz_knl_section_ugetz (procmode, sizeof portname, pt_setup.portname, portname, NULL); if (sts == OZ_SUCCESS) oz_knl_section_ugetz (procmode, sizeof classname, pt_setup.classname, classname, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_setup (ioop, devunit, devex, portname, classname); /* do all the hard work */ if (sts == OZ_SUCCESS) devex -> masteriochan = iochan; /* if successful, remember who set it up */ unlkdb (devex, db); } return (sts); } /* Get some data from the class driver to be displayed on the screen */ case OZ_IO_CONPSEUDO_GETSCRDATA: { OZ_IO_conpseudo_getscrdata pt_getscrdata; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_getscrdata, &pt_getscrdata); sts = oz_knl_ioop_lockw (ioop, pt_getscrdata.size, pt_getscrdata.buff, NULL, NULL, NULL); if (sts == OZ_SUCCESS) sts = oz_knl_ioop_lockw (ioop, sizeof *(pt_getscrdata.rlen), pt_getscrdata.rlen, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_getscrdata (devex, iopex, pt_getscrdata.size, pt_getscrdata.buff, pt_getscrdata.rlen); unlkdb (devex, db); } } return (sts); } /* Put some data to the class driver that came from the keyboard */ case OZ_IO_CONPSEUDO_PUTKBDDATA: { OZ_IO_conpseudo_putkbddata pt_putkbddata; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_putkbddata, &pt_putkbddata); sts = oz_knl_ioop_lockr (ioop, pt_putkbddata.size, pt_putkbddata.buff, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_putkbddata (devex, pt_putkbddata.size, pt_putkbddata.buff); unlkdb (devex, db); } } return (sts); } /* Get an event from the class driver */ case OZ_IO_CONPSEUDO_GETEVENT: { OZ_IO_conpseudo_getevent pt_getevent; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_getevent, &pt_getevent); sts = oz_knl_ioop_lockw (ioop, sizeof *(pt_getevent.event), pt_getevent.event, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_getevent (devex, iopex, pt_getevent.event); unlkdb (devex, db); } } return (sts); } /* Fetch an get/setmode request from the queue */ case OZ_IO_CONPSEUDO_FETCHGSMODEREQ: { OZ_IO_conpseudo_fetchgsmodereq pt_fetchgsmodereq; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_fetchgsmodereq, &pt_fetchgsmodereq); sts = oz_knl_ioop_lockw (ioop, pt_fetchgsmodereq.size, pt_fetchgsmodereq.buff, NULL, NULL, NULL); if (sts == OZ_SUCCESS) sts = oz_knl_ioop_lockw (ioop, sizeof *(pt_fetchgsmodereq.reqid_r), pt_fetchgsmodereq.reqid_r, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_fetchgsmodereq (devex, &pt_fetchgsmodereq); unlkdb (devex, db); } } return (sts); } /* Post an get/setmode request from the queue */ case OZ_IO_CONPSEUDO_POSTGSMODEREQ: { OZ_IO_conpseudo_postgsmodereq pt_postgsmodereq; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_postgsmodereq, &pt_postgsmodereq); sts = oz_knl_ioop_lockr (ioop, pt_postgsmodereq.size, pt_postgsmodereq.buff, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = io_pt_postgsmodereq (devex, &pt_postgsmodereq); unlkdb (devex, db); } } return (sts); } /* Tell class driver what modes we are doing (like maybe the telnet client changed the window size) */ case OZ_IO_CONPSEUDO_SETMODE: { OZ_IO_conpseudo_setmode pt_setmode; sts = OZ_BADIOFUNC; if (iochan == devex -> masteriochan) { /* only accessible to channel that set it up */ movc4 (as, ap, sizeof pt_setmode, &pt_setmode); sts = oz_knl_ioop_lockr (ioop, pt_setmode.size, pt_setmode.buff, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { db = lockdb (devex); sts = (*(devex -> comport_setup.class_setmode)) (devex -> comport_setup.class_param, &pt_setmode); unlkdb (devex, db); } } return (sts); } /* All others go to the class driver - dont let masteriochan channel do these because the */ /* pt_deassign routine won't call the class driver's deassign routine for this channel */ default: { sts = OZ_DEVOFFLINE; if ((iochan != devex -> masteriochan) && (devex -> classiochan != NULL)) { sts = (*(devex -> comport_setup.class_functab -> start)) (devunit, devex -> comport_setup.class_devex, iochan, ((Chnex *)chnexv) -> class_area, procmode, ioop, iopex -> class_area, funcode, as, ap); } return (sts); } } } /************************************************************************/ /* */ /* Process an OZ_IO_CONPSEUDO_SETUP request */ /* This creates the class device and activates it */ /* */ /************************************************************************/ static uLong io_pt_setup (OZ_Ioop *ioop, OZ_Devunit *devunit, Devex *devex, const char *portname, const char *classname) { char unitdesc[OZ_DEVUNIT_DESCSIZE]; OZ_Iochan *classiochan; OZ_Thread *thread; uLong sts; if (devex -> classiochan != NULL) return (OZ_FILEALREADYOPEN); if (devex -> terminated) return (OZ_DEVOFFLINE); if (!oz_knl_devunit_rename (devunit, portname, NULL)) { oz_knl_printk ("oz_dev_conpseudo: failed to rename %s to %s\n", oz_knl_devunit_devname (devunit), portname); } /* Set up application level console class device by assigning a channel to 'conclass' and passing our setup parameters */ sts = oz_knl_iochan_crbynm (classname, OZ_LOCKMODE_CW, OZ_PROCMODE_KNL, NULL, &classiochan); if (sts != OZ_SUCCESS) { oz_knl_printk ("oz_dev_conpseudo: error %u assigning channel to class device %s for %s\n", sts, classname, portname); return (sts); } devex -> comport_setup.port_devunit = devunit; /* port device unit */ devex -> comport_setup.port_param = devex; /* this is what the class driver is to pass back to my port routines */ devex -> comport_setup.port_terminate = terminate; /* class driver calls this routine when last channel is deassigned from the app level device */ devex -> comport_setup.port_read_start = read_start; /* class driver calls this routine when starting/finishing a read request */ devex -> comport_setup.port_disp_start = disp_start; /* class driver calls this when there is data to be displayed on the screen */ devex -> comport_setup.port_disp_suspend = disp_suspend; /* class driver calls this when it wants us to suspend/resume/abort screen output operations */ devex -> comport_setup.port_kbd_rah_full = kbd_rah_full; /* class driver calls this when its keyboard buffer fills/empties */ devex -> comport_setup.port_getsetmode = getsetmode; /* class driver calls this to process get/setmode function */ devex -> comport_setup.port_lkprm = devex; /* this is what the class driver is to pass back to my lockdb/unlkdb routines */ devex -> comport_setup.port_lockdb = lockdb; /* class driver calls this to lock the database */ devex -> comport_setup.port_unlkdb = unlkdb; /* class driver calls this to unlock the database */ sts = oz_knl_io (classiochan, OZ_IO_COMPORT_SETUP, sizeof devex -> comport_setup, &(devex -> comport_setup)); if (sts != OZ_SUCCESS) { oz_knl_iochan_increfc (classiochan, -1); oz_knl_printk ("oz_dev_conpseudo: error %u setting up device %s via %s\n", sts, portname, classname); } else { *(devex -> port_functab) = pt_functable; /* re-init func table and modify extension area sizes for class driver */ devex -> port_functab -> chn_exsize += devex -> comport_setup.class_functab -> chn_exsize; devex -> port_functab -> iop_exsize += devex -> comport_setup.class_functab -> iop_exsize; devex -> port_functab -> sel_exsize += devex -> comport_setup.class_functab -> sel_exsize; devex -> senddatqt = &(devex -> senddatqh); /* it is ok to queue stuff now */ devex -> sendreqqt = &(devex -> sendreqqh); devex -> eventdatqt = &(devex -> eventdatqh); devex -> eventreqqt = &(devex -> eventreqqh); devex -> gsreqt = &(devex -> gsreqh); devex -> classiochan = classiochan; /* turn it 'online' */ thread = oz_knl_ioop_getthread (ioop); if (thread == NULL) strcpy (unitdesc, "online"); else oz_sys_sprintf (sizeof unitdesc, unitdesc, "online (threadid %u)", oz_knl_thread_getid (thread)); oz_knl_devunit_rename (devunit, NULL, unitdesc); } return (sts); } /************************************************************************/ /* */ /* Process OZ_IO_CONPSEUDO_GETSCRDATA request */ /* */ /************************************************************************/ static uLong io_pt_getscrdata (Devex *devex, Iopex *iopex, uLong size, char *buff, uLong *rlen) { Senddat *senddat; /* Don't allow if channel has not been set up yet or has been deassigned */ if (devex -> classiochan == NULL) return (OZ_DEVOFFLINE); /* See if there is any data for it right now */ senddat = devex -> senddatqh; /* If not, queue request to wait for some screen data to arrive from class driver */ if (senddat == NULL) { iopex -> next = NULL; iopex -> u.getscrdata.size = size; /* save caller's buffer size */ iopex -> u.getscrdata.buff = buff; /* save caller's buffer address */ iopex -> u.getscrdata.rlen = rlen; /* save where caller wants length returned */ *(devex -> sendreqqt) = iopex; /* put request on end of queue */ devex -> sendreqqt = &(iopex -> next); return (OZ_STARTED); /* tell caller it will complete later */ } /* If so, dequeue the data, return corresponding values and complete synchronously */ if (senddat -> size > size) { memcpy (buff, senddat -> buff, size); /* have more data than request can take, */ senddat -> size -= size; /* so just get as much as we can return */ senddat -> buff += size; } else { devex -> senddatqh = senddat -> next; /* request can take all the data, dequeue data */ if (devex -> senddatqh == NULL) devex -> senddatqt = &(devex -> senddatqh); devex -> senddatqs --; size = senddat -> size; /* take all the data */ memcpy (buff, senddat -> buff, size); (*(devex -> comport_setup.class_displayed)) (devex -> comport_setup.class_param, senddat -> write_param, OZ_SUCCESS); OZ_KNL_PGPFREE (senddat); } *rlen = size; /* anyway, tell caller how much we returned */ return (OZ_SUCCESS); /* completed synchronously */ } /************************************************************************/ /* */ /* Send data to the pseudo-terminal as keyboard data */ /* */ /* Input: */ /* */ /* size = number of characters in buff */ /* buff = address of characters */ /* smplock = softint delivery inhibited */ /* */ /************************************************************************/ static uLong io_pt_putkbddata (Devex *devex, uLong size, const char *buff) { uLong i; if (devex -> classiochan == NULL) return (OZ_DEVOFFLINE); for (i = 0; i < size; i ++) { (*(devex -> comport_setup.class_kbd_char)) (devex -> comport_setup.class_param, buff[i]); } return (OZ_SUCCESS); } /************************************************************************/ /* */ /* Called by the class driver when too much keyboard data has been */ /* received */ /* */ /************************************************************************/ static void kbd_rah_full (void *devexv, int full) { /* Tell the server program to suspend or resume sending keyboard data */ gotevent (devexv, full ? OZ_CONPSEUDO_KBD_SUSPEND : OZ_CONPSEUDO_KBD_RESUME); } /************************************************************************/ /* */ /* Called by the class driver to process the get/setmode function */ /* */ /************************************************************************/ static uLong getsetmode (void *devexv, void *getset_param, uLong size, OZ_Console_modebuff *buff) { Devex *devex; Gsreq *gsreq; devex = devexv; gsreq = OZ_KNL_PGPMALLOQ (sizeof *gsreq); /* remember about this request */ if (gsreq == NULL) return (OZ_EXQUOTAPGP); *(devex -> gsreqt) = gsreq; gsreq -> next = NULL; gsreq -> param = getset_param; gsreq -> size = size; gsreq -> buff = buff; gsreq -> fetched = 0; devex -> gsreqt = &(gsreq -> next); gotevent (devex, OZ_CONPSEUDO_GETSETMODE); /* tell telnetd that someone is trying to get/set the modes */ return (OZ_STARTED); /* wait for telnetd to get back to us on that */ } /************************************************************************/ /* */ /* Process OZ_IO_CONPSEUDO_GETEVENT request */ /* This function returns the next event to telnetd (or whoever) */ /* */ /************************************************************************/ static uLong io_pt_getevent (Devex *devex, Iopex *iopex, OZ_Conpseudo_event *event) { Eventdat *eventdat; if (devex -> classiochan == NULL) return (OZ_DEVOFFLINE); /* See if there is any event for it right now */ eventdat = devex -> eventdatqh; /* If not, queue request for later event */ if (eventdat == NULL) { iopex -> next = NULL; iopex -> u.getevent.event = event; *(devex -> eventreqqt) = iopex; devex -> eventreqqt = &(iopex -> next); return (OZ_STARTED); } /* If so, dequeue the event, return corresponding value and complete synchronously */ devex -> eventdatqh = eventdat -> next; if (devex -> eventdatqh == NULL) devex -> eventdatqt = &(devex -> eventdatqh); *event = eventdat -> event; OZ_KNL_PGPFREE (eventdat); return (OZ_SUCCESS); } /************************************************************************/ /* */ /* Fetch / Post Get / Set mode request */ /* */ /************************************************************************/ static uLong io_pt_fetchgsmodereq (Devex *devex, OZ_IO_conpseudo_fetchgsmodereq *pt_fetchgsmodereq) { Gsreq *gsreq; if (devex -> classiochan == NULL) return (OZ_DEVOFFLINE); /* Find the first request that hasn't been fetched yet */ for (gsreq = devex -> gsreqh; gsreq != NULL; gsreq = gsreq -> next) if (!(gsreq -> fetched)) break; if (gsreq == NULL) return (OZ_QUEUEMPTY); /* Copy the class driver's data to the daemon's buffer and mark the request as fetched */ movc4 (gsreq -> size, gsreq -> buff, pt_fetchgsmodereq -> size, pt_fetchgsmodereq -> buff); *(pt_fetchgsmodereq -> reqid_r) = gsreq; gsreq -> fetched = 1; /* Successful */ return (OZ_SUCCESS); } static uLong io_pt_postgsmodereq (Devex *devex, OZ_IO_conpseudo_postgsmodereq *pt_postgsmodereq) { Gsreq *gsreq, **lgsreq; /* Scan the queue for the given reqid */ for (lgsreq = &(devex -> gsreqh); (gsreq = *lgsreq) != NULL; lgsreq = &(gsreq -> next)) { if (pt_postgsmodereq -> reqid == gsreq) { /* Request found, it better be marked as 'fetched' or it might be a re-cycle of the same memory address */ if (!(gsreq -> fetched)) return (OZ_NOSUCHREQ); /* Ok, remove from queue */ *lgsreq = gsreq -> next; if (gsreq -> next == NULL) devex -> gsreqt = &(devex -> gsreqh); /* Copy daemon's data back to class driver's buffer and tell class driver request is complete */ movc4 (pt_postgsmodereq -> size, pt_postgsmodereq -> buff, gsreq -> size, gsreq -> buff); (*(devex -> comport_setup.class_gotsetmode)) (devex -> comport_setup.class_param, gsreq -> param, pt_postgsmodereq -> status); /* Clean up and return success status to daemon */ OZ_KNL_PGPFREE (gsreq); return (OZ_SUCCESS); } } return (OZ_NOSUCHREQ); } /************************************************************************/ /* */ /* Called by class driver to terminate operations */ /* */ /* The class driver shall not make any other calls to us afther this */ /* except to unlock the database */ /* */ /* Input: */ /* */ /* chnex = context block pointer */ /* database locked */ /* */ /************************************************************************/ static void terminate (void *devexv) { Devex *devex; devex = devexv; devex -> terminated = 1; /* set flag to remember we got a terminate call */ gotevent (devex, OZ_CONPSEUDO_TERMINATE); /* send a terminate event to the server program */ /* (eg, tell telnetd the user has logged out) */ } /************************************************************************/ /* */ /* Called by the class driver when it starts/finishes a read request */ /* */ /* Input: */ /* */ /* chnex = connection context pointer */ /* smplock = softint delivery inhibited, database locked */ /* */ /************************************************************************/ static void read_start (void *devexv, int start) { gotevent (devexv, start ? OZ_CONPSEUDO_KBD_READSTARTED : OZ_CONPSEUDO_KBD_READFINISHED); } /************************************************************************/ /* */ /* Called by the class driver to start displaying some data */ /* */ /* Input: */ /* */ /* chnex = connection context pointer */ /* write_param = value to pass to completion routine */ /* size = number of characters to write */ /* buff = address of characters to write */ /* smplock = softint delivery inhibited, database locked */ /* */ /* Output: */ /* */ /* disp_start = OZ_STARTED : will complete asynchronously */ /* else : completion status */ /* */ /************************************************************************/ static uLong disp_start (void *devexv, void *write_param, uLong size, char *buff) { Devex *devex; Iopex *sendreq; Senddat *senddat; uLong sts; devex = devexv; if (devex -> terminated) oz_crash ("oz_dev_conpseudo: disp_start called after terminated"); sts = OZ_DEVOFFLINE; if (devex -> classiochan == NULL) goto rtn; /* Don't let the queue get too big - if we return OZ_QUEUEFULL status, the caller will expect us to call its class_displayed routine when we are ready for more */ sts = OZ_QUEUEFULL; if (devex -> senddatqs >= SENDDATQMAX) goto rtn; /* Queue send data */ senddat = OZ_KNL_PGPMALLOQ (sizeof *senddat); /* set up descriptor for screen data */ sts = OZ_EXQUOTAPGP; if (senddat == NULL) goto rtn; senddat -> next = NULL; senddat -> write_param = write_param; senddat -> size = size; senddat -> buff = buff; *(devex -> senddatqt) = senddat; /* queue it for later output */ devex -> senddatqt = &(senddat -> next); devex -> senddatqs ++; sendreq = devex -> sendreqqh; /* see if any I/O request waiting for it */ if (sendreq != NULL) { devex -> sendreqqh = sendreq -> next; /* if so, dequeue I/O request */ if (devex -> sendreqqh == NULL) devex -> sendreqqt = &(devex -> sendreqqh); oz_knl_iodone (sendreq -> ioop, OZ_SUCCESS, NULL, sendreq_iodone, sendreq); } sts = OZ_STARTED; /* either way, it's not done yet (we still have to access the data in the sendreq_iodone routine) */ rtn: return (sts); } /* This routine is called when back in the calling process' address space */ static void sendreq_iodone (void *sendreqv, int finok, uLong *status_r) { Devex *devex; Iopex *sendreq; uLong db; Senddat *senddat; sendreq = sendreqv; devex = sendreq -> devex; if (!finok) return; /* see if we made it back to requestor's process */ *(sendreq -> u.getscrdata.rlen) = 0; /* ok, zero out the length in case there is no data */ db = lockdb (devex); /* lock the database */ if (devex -> classiochan != NULL) { /* see if we're still allowed to call class_displayed routine */ /* (if not, we return a length of zero) */ senddat = devex -> senddatqh; /* see if there is any data ready for us */ /* (if not, we return a length of zero) */ if (senddat != NULL) { if (senddat -> size > sendreq -> u.getscrdata.size) { /* ok, see if bigger than requestor's buffer */ memcpy (sendreq -> u.getscrdata.buff, senddat -> buff, sendreq -> u.getscrdata.size); /* it is, just copy what will fit */ senddat -> size -= sendreq -> u.getscrdata.size; /* ... and reduce data by that much */ senddat -> buff += sendreq -> u.getscrdata.size; /* ... and leave what's left on the queue */ } else { devex -> senddatqh = senddat -> next; /* it will all fit, remove data from queue */ if (devex -> senddatqh == NULL) devex -> senddatqt = &(devex -> senddatqh); devex -> senddatqs --; sendreq -> u.getscrdata.size = senddat -> size; /* set size returned = all the data */ memcpy (sendreq -> u.getscrdata.buff, senddat -> buff, senddat -> size); /* copy all the data out */ (*(devex -> comport_setup.class_displayed)) (devex -> comport_setup.class_param, senddat -> write_param, OZ_SUCCESS); /* tell class driver we finished with data */ OZ_KNL_PGPFREE (senddat); /* free off the data block */ } *(sendreq -> u.getscrdata.rlen) = sendreq -> u.getscrdata.size; /* return how much data we copied out */ } } unlkdb (devex, db); } /************************************************************************/ /* */ /* Called by the class driver to suspend any output operation that */ /* might be in progress */ /* */ /* In this implementation, we just pass an event on to the program, */ /* and hopefully it will stop sending stuff. */ /* */ /************************************************************************/ static void disp_suspend (void *devexv, int suspend) { Devex *devex; OZ_Conpseudo_event event; devex = devexv; if (devex -> terminated) oz_crash ("oz_dev_conpseudo: disp_suspend called after terminated"); event = OZ_CONPSEUDO_SCR_RESUME; /* resume output (ie, got a control-Q from keyboard) */ if (suspend < 0) event = OZ_CONPSEUDO_SCR_ABORT; /* abort output (ie, got a control-O from keyboard) */ if (suspend > 0) event = OZ_CONPSEUDO_SCR_SUSPEND; /* suspend output (ie, got a control-S from keyboard) */ gotevent (devex, event); } /************************************************************************/ /* */ /* We got an event so satisfy a pending event request */ /* (or queue the event for later retrieval if no request pending) */ /* */ /************************************************************************/ static void gotevent (Devex *devex, OZ_Conpseudo_event event) { Eventdat *eventdat; Iopex *eventreq; if (devex -> classiochan == NULL) return; /* ignore event if I/O channel has been deassigned */ eventdat = OZ_KNL_PGPMALLOC (sizeof *eventdat); /* ok, save the event code in a block */ eventdat -> next = NULL; eventdat -> event = event; eventreq = devex -> eventreqqh; /* see if there is a pending I/O request to receive it */ if (eventreq == NULL) { *(devex -> eventdatqt) = eventdat; /* if not, queue for later retrieval */ devex -> eventdatqt = &(eventdat -> next); } else { devex -> eventreqqh = eventreq -> next; /* if so, dequeue the I/O request */ if (devex -> eventreqqh == NULL) devex -> eventreqqt = &(devex -> eventreqqh); eventreq -> u.getevent.eventdat = eventdat; /* post request for completion */ oz_knl_iodone (eventreq -> ioop, OZ_SUCCESS, NULL, event_iodone, eventreq); } } /* This routine executes in the thread context to deliver the event code */ static void event_iodone (void *eventreqv, int finok, uLong *status_r) { Eventdat *eventdat; Iopex *eventreq; eventreq = eventreqv; eventdat = eventreq -> u.getevent.eventdat; if (finok) *(eventreq -> u.getevent.event) = eventdat -> event; OZ_KNL_PGPFREE (eventdat); } /************************************************************************/ /* */ /* Lock access to database */ /* */ /************************************************************************/ static uLong lockdb (void *devexv) { Devex *devex; Long prev; OZ_Thread *thread; devex = devexv; thread = oz_knl_thread_getcur (); tryit: prev = oz_knl_event_set (devex -> lockevent, 0); /* clear flag to mark it locked */ if (prev > 0) devex -> lockthread = thread; /* if not previously locked, say this thread has it now */ if (devex -> lockthread == thread) return (prev); /* if this thread has it locked now, return with prev state */ oz_knl_event_waitone (devex -> lockevent); /* another thread has is locked, wait for it to be released */ goto tryit; /* try again to lock it */ } /************************************************************************/ /* */ /* Unlock access to database */ /* */ /************************************************************************/ static void unlkdb (void *devexv, uLong iplsav) { Devex *devex; devex = devexv; if (iplsav > 0) { /* see if it's really being unlocked */ devex -> lockthread = NULL; /* if so, clear thread pointer so all 'if (lockthread == thread)' compares will fail causing them to wait */ oz_knl_event_set (devex -> lockevent, iplsav); /* unlock by setting the flag */ } }