lx_ptm.c revision 9acbbeaf2a1ffe5c14b244867d427714fab43c5c
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This driver attempts to emulate some of the the behaviors of
*
* opens to it.
*
* drivers on Solaris work:
*
*
* - the dev_t minor node value for each open ptm instance corrospondes
* to it's associated slave terminal device number. ie. the path to
* the slave terminal device associated with an open ptm instance
*
* - the ptm driver always allocates the lowest numbered slave terminal
* device possible.
*/
#include <sys/pathname.h>
#include <sys/sysmacros.h>
#define LP_PTM_PATH "/dev/ptmx"
#define LP_PTS_PATH "/dev/pts/"
#define LP_PTS_DRV_NAME "pts"
/*
* this driver is layered on top of the ptm driver. we'd like to
* make this drivers minor name space a mirror of the ptm drivers
* namespace, but we can't actually do this. the reason is that the
* ptm driver is opened via the clone driver. there for no minor nodes
* of the ptm driver are actually accessible via the filesystem.
* since we're not a streams device we can't be opened by the clone
* driver. there for we need to have at least minor node accessible
* via the filesystem so that consumers can open it. we use the device
* node with a minor number of 0 for this purpose. what this means is
* that minor node 0 can't be used to map ptm minor node 0. since this
* minor node is now reserved we need to shift our ptm minor node
* mappings by one. ie. a ptm minor node with a value of 0 will
* corrospond to our minor node with a value of 1. these mappings are
* managed with the following macros.
*/
#define DEVT_TO_INDEX(x) LX_PTM_DEV_TO_PTS(x)
#define INDEX_TO_MINOR(x) ((x) + 1)
/*
* grow our layered handle array by the same size increment that the ptm
* driver uses to grow the pty device space - PTY_MAXDELTA
*/
#define LP_PTY_INC 128
/*
* lx_ptm_ops contains state information about outstanding operations on the
* underlying master terminal device. Currently we only track information
* for read operations.
*
* Note that this data has not been rolled directly into the lx_ptm_handle
* structure because we can't put mutex's of condition variables into
* lx_ptm_handle structure. The reason is that the array of lx_ptm_handle
* structures linked to from the global lx_ptm state can be resized
* dynamically, and when it's resized, the new array is at a different
* memory location and the old array memory is discarded. Mutexs and cvs
* are accessed based off their address, so if this array was re-sized while
* there were outstanding operations on any mutexs or cvs in the array
* then the system would tip over. In the future the lx_ptm_handle structure
* array should probably be replaced with either an array of pointers to
* lx_ptm_handle structures or some other kind of data structure containing
* pointers to lx_ptm_handle structures. Then the lx_ptm_ops structure
* could be folded directly into the lx_ptm_handle structures. (This will
* also require the definition of a new locking mechanism to protect the
* contents of lx_ptm_handle structures.)
*/
typedef struct lx_ptm_ops {
int lpo_rops;
} lx_ptm_ops_t;
/*
* Every open of the master terminal device in a zone results in a new
* lx_ptm_handle handle allocation. These handles are stored in an array
* hanging off the lx_ptm_state structure.
*/
typedef struct lx_ptm_handle {
/* Flag to indicate if TIOCPKT mode has been enabled. */
int lph_pktio;
int lph_eofed;
/* Callback handler in the ptm driver to check if slave is open. */
/* Pointer to state for operations on underlying device. */
/*
* Global state for the lx_ptm driver.
*/
typedef struct lx_ptm_state {
/* lx_ptm device devinfo pointer */
/* pts drivers major number */
/* rw lock used to manage access and growth of lps_lh_array */
/* number of elements in lps_lh_array */
/* Pointer to the lx_ptm global state structure. */
static lx_ptm_state_t lps;
/*
* List of modules to be autopushed onto slave terminal devices when they
* are opened in an lx branded zone.
*/
static char *lx_pts_mods[] = {
"ptem",
"ldterm",
"ttcompat",
};
static void
{
/*
* allocate a new array. we drop the rw lock on the array so that
* readers can still access devices in case our memory allocation
* blocks.
*/
/*
* double check that we still actually need to increase the size
* of the array
*/
/* someone beat us to it so there's nothing more to do */
sizeof (lx_ptm_handle_t) * new_lh_count);
return;
}
/* copy the existing data into the new array */
if (lps.lps_lh_count != 0) {
}
/* save info on the old array */
/* install the new array */
/* free the old array */
if (old_lh_array != NULL) {
sizeof (lx_ptm_handle_t) * old_lh_count);
}
}
static void
{
/* Allocate and initialize the ops structure */
/* check if we need to grow the size of the layered handle array */
}
/* insert the new handle and return */
}
static ldi_handle_t
{
/* free the write handle */
/* remove the handle and return it */
return (lh);
}
static void
{
}
static void
{
}
static ldi_handle_t
{
/* return the handle */
return (lh);
}
static lx_ptm_ops_t *
{
/* return the handle */
return (lpo);
}
static int
{
int pktio;
/* return the pktio state */
return (pktio);
}
static void
{
/* set the pktio state */
}
static int
{
int eofed;
/* return the eofed state */
return (eofed);
}
static void
{
/* set the eofed state */
}
static int
{
/* Wait for other read operations to finish */
return (-1);
}
}
/* Start a read operation */
return (0);
}
static void
{
/* End a read operation */
}
static int
{
}
static void
{
char junk[1];
/*
* We can remove any EOF message from the head of the stream by
* doing a zero byte read from the stream.
*/
uio.uio_offset = 0;
uio.uio_extflg = 0;
}
static int
{
*rvalp = 0;
/*
* Check if there is an EOF message (represented by a zero length
* data message) at the head of the stream. Note that the
* I_NREAD ioctl is a streams framework ioctl so it will succeed
* even if there have been previous write errors on this stream.
*/
return (err);
/* No EOF message found */
return (0);
}
/* Record the fact that the slave device has been closed. */
/* drop the EOF */
*rvalp = 1;
return (0);
}
static int
{
*rvalp = 0;
for (;;) {
return (err);
if (rval == 0)
return (0);
*rvalp = 1;
}
}
static int
{
int err;
*rvalp = 0;
if (ignore_eof) {
return (err);
if (size != 0)
*rvalp = 1;
} else {
return (err);
if (msg_count != 0)
*rvalp = 1;
}
return (0);
}
static int
{
int err;
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
return (DDI_FAILURE);
if (err != 0) {
return (DDI_FAILURE);
}
lps.lps_lh_count = 0;
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
lps.lps_lh_count = 0;
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
/*
* Don't support the FNDELAY flag and FNONBLOCK until we either
* or O_NONBLOCK flags explicitly, or until we create test cases
* to determine how reads of master terminal devices opened with
* these flags behave in different situations on Linux. Supporting
* these flags will involve enhancing our read implementation
* and changing the way it deals with EOF notifications.
*/
return (ENOTSUP);
/*
* we're layered on top of the ptm driver so open that driver
* zone, not ourselves in the Linux zone.)
*/
if (err != 0)
return (err);
/* get the devt returned by the ptmx open */
if (err != 0) {
return (err);
}
/*
* we're a cloning driver so here's well change the devt that we
* return. the ptmx is also a cloning driver so we'll just use
* it's minor number as our minor number (it already manages it's
* minor name space so no reason to duplicate the effort.)
*/
/* Get a callback function to query if the pts device is open. */
return (EIO); /* XXX return something else here? */
}
/*
* now setup autopush for the terminal slave device. this is
* necessary so that when a Linux program opens the device we
* can push required strmod modules onto the stream. in Solaris
* this is normally done by the application that actually
* allocates the terminal.
*/
lastmin = 0;
&anchor, lx_pts_mods);
if (err != 0) {
return (EIO); /* XXX return something else here? */
}
/* save off this layered handle for future accesses */
return (0);
}
/*ARGSUSED*/
static int
{
int err;
/*
* terminal pair before actually closing the ptm master device.
* this is required because once the close of the ptm device is
* re-use in any zone.
*/
/* free up our saved reference for this layered handle */
/* unconfigure autopush for the associated terminal slave device */
lastmin = 0;
do {
/*
* we loop here because we don't want to release this ptm
* node if autopush can't be disabled on the associated
* slave device because then bad things could happen if
* another brand were to get this terminal allocated
* to them.
*
* XXX should we ever give up?
*/
0, NULL);
} while (err != 0);
/*
* note that we don't have to bother with changing the permissions
* on the associated slave device here. the reason is that no one
* can actually open the device untill it's associated master
* device is re-opened, which will result in the permissions on
* it being reset.
*/
return (err);
}
static int
{
*loop = 0;
/*
* Here's another way that Linux master terminals behave differently
* from Solaris master terminals. If you do a read on a Linux
* master terminal (that was opened witout NDELAY and NONBLOCK)
* who's corrosponding slave terminal is currently closed and
* has been opened and closed at least once, Linux return -1 and
* set errno to EIO where as Solaris blocks.
*/
/* Slave has been opened and closed at least once. */
if (lx_ptm_pts_isopen(dev) == 0) {
/*
* Slave is closed. Make sure that data is avaliable
* before attempting a read.
*/
return (err);
/* If there is no data available then return. */
if (rval == 0)
return (EIO);
}
}
/* Actually do the read operation. */
return (err);
/* If read returned actual data then return. */
return (0);
/*
* This was a zero byte read (ie, an EOF). This indicates
* that the slave terinal device has been closed. Record
* the fact that the slave device has been closed and retry
* the read operation.
*/
*loop = 1;
return (0);
}
static int
{
/*
* If packet mode has been enabled (via TIOCPKT) we need to pad
* all read requests with a leading byte that indicates any
* relevant control status information.
*/
if (pktio != 0) {
/*
* We'd like to write the control information into
* the current buffer but we can't yet. We don't
* want to modify userspace memory here only to have
* the read operation fail later. So instead
* what we'll do here is read one character from the
* beginning of the memory pointed to by the uio
* structure. This will advance the output pointer
* by one. Then when the read completes successfully
* we can update the byte that we passed over. Before
* we do the read make a copy of the current uiop and
* iovec structs so we can write to them later.
*/
return (EFAULT);
}
do {
/*
* Serialize all reads. We need to do this so that we can
* properly emulate the behavior of master terminals on Linux.
* In reality this serializaion should not pose any kind of
* performance problem since it would be very strange to have
* multiple threads trying to read from the same master
* terminal device concurrently.
*/
if (lx_ptm_read_start(dev) != 0)
return (EINTR);
if (err != 0)
return (err);
} while (loop != 0);
if (pktio != 0) {
/*
* Note that the control status information we
* pass back is faked up in the sense that we
* don't actually report any events, we always
* report a status of 0.
*/
return (EFAULT);
}
return (0);
}
static int
{
int err;
return (err);
}
static int
int *rvalp)
{
int err;
/*
* here we need to make sure that we never allow the
* I_SETSIG and I_ESETSIG ioctls to pass through. we
* do this because we can't support them.
*
* the native Solaris ptm device supports these ioctls because
* they are streams framework ioctls and all streams devices
* support them by default. these ioctls cause the current
* process to be registered with a stream and receive signals
* when certain stream events occur.
*
* a problem arises with cleanup of these registrations
* for layered drivers.
*
* normally the streams framework is notified whenever a
* process closes any reference to a stream and it goes ahead
* and cleans up these registrations. but actual device drivers
* are not notified when a process performs a close operation
* unless the process is closing the last opened reference to
* the device on the entire system.
*
* so while we could pass these ioctls on and allow processes
* to register for signal delivery, we would never receive
* any notification when those processes exit (or close a
* stream) and we wouldn't be able to unregister them.
*
* luckily these operations are streams specific and Linux
* doesn't support streams devices. so it doesn't actually
* seem like we need to support these ioctls. if it turns
* out that we do need to support them for some reason in
* the future, the current driver model will have to be
* enhanced to better support streams device layering.
*/
return (EINVAL);
/*
* here we fake up support for TIOCPKT. Linux applications expect
* (it is supported on older bsd style ptys.) so we'll fake
* up support for it here.
*
* the reason that this ioctl is emulated here instead of in
* userland is that this ioctl affects the results returned
* from read() operations. if this ioctl was emulated in
* userland the brand library would need to intercept all
* read operations and check to see if pktio was enabled
* for the fd being read from. since this ioctl only needs
* to be supported on the ptmx device it makes more sense
* to support it here where we can easily update the results
* returned for read() operations performed on ourselves.
*/
int pktio;
mode) != DDI_SUCCESS)
return (EFAULT);
if (pktio == 0)
else
return (0);
}
return (err);
}
static int
{
short reventsp2;
*loop = 0;
/*
* If the slave device has been opened and closed at least
* once and the slave device is currently closed, then poll
* always needs to returns immediatly.
*/
(lx_ptm_pts_isopen(dev) == 0)) {
/* In this case always return POLLHUP */
/*
* Check if there really is data on the stream.
* If so set the correct return flags.
*/
/* Something went wrong. */
return (err);
}
if (rval != 0)
/*
* Is the user checking for writability? Note that for ptm
* devices Linux seems to ignore the POLLWRBAND write flag.
*/
if ((events & POLLWRNORM) == 0)
return (0);
/*
* To check if the stream is writable we have to actually
* call poll, but make sure to set anyyet to 1 to prevent
* the streams framework from setting up callbacks.
*/
return (err);
} else {
int lockstate;
/* The slave device is open, do the poll */
return (err);
/*
* Drop any leading EOFs on the stream.
*
* Note that we have to use pollunlock() here to avoid
* recursive mutex enters in the poll framework. The
* reason is that if there is an EOF message on the stream
* then the act of reading from the queue to remove the
* message can cause the ptm drivers event service
* routine to be invoked, and if there is no open
* slave device then the ptm driver may generate
* error messages and put them on the stream. This
* in turn will generate a poll event and the poll
* framework will try to invoke any poll callbacks
* associated with the stream. In the process of
* doing that the poll framework will try to aquire
* locks that we are already holding. So we need to
* drop those locks here before we do our read.
*/
lockstate = pollunlock();
if (err)
return (err);
/* If no EOF was dropped then return */
if (rval == 0)
return (0);
/*
* An EOF was removed from the stream. Retry the entire
* poll operation from the top because polls on the ptm
* device should behave differently now.
*/
*loop = 1;
}
return (0);
}
static int
{
do {
/* Serialize ourself wrt read operations. */
if (lx_ptm_read_start(dev) != 0)
return (EINTR);
if (err != 0)
return (err);
} while (loop != 0);
return (0);
}
static struct cb_ops lx_ptm_cb_ops = {
lx_ptm_open, /* open */
lx_ptm_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
lx_ptm_read, /* read */
lx_ptm_write, /* write */
lx_ptm_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
lx_ptm_poll, /* chpoll */
ddi_prop_op, /* prop_op */
NULL, /* cb_str */
NULL,
};
static struct dev_ops lx_ptm_ops = {
0,
NULL,
};
"Linux master terminal driver 'lx_ptm' %I%",
};
static struct modlinkage modlinkage = {
&modldrv,
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
{
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}