David van Moolenbroek c5da0dffe2 Retire inet: the previous MINIX TCP/IP service
This commit (temporarily) leaves MINIX 3 without a TCP/IP service.

Thanks go out to Philip Homburg for providing this TCP/IP stack in the
first place.  It has served MINIX well for a long time.

Change-Id: I0e3eb6fe64204081e4e3c2b9d6e6bd642f121973
2017-03-09 23:39:58 +00:00

509 lines
14 KiB
C

/*
* This file contains routines to perform character device operations.
* Character drivers may suspend I/O requests on their devices (read, write,
* ioctl), as well as select requests. These requests will therefore suspend
* their calling process, freeing up the associated VFS worker thread for other
* tasks. The I/O requests may later be cancelled as a result of the suspended
* process receiving a signal (which it either catches or dies from), in which
* case there will be a worker thread associated with the cancellation. Open
* and close requests may not suspend and will thus block the calling thread.
*
* The entry points in this file are:
* cdev_map: map a character device to its actual device number
* cdev_open: open a character device
* cdev_close: close a character device
* cdev_io: initiate a read, write, or ioctl to a character device
* cdev_select: initiate a select call on a device
* cdev_cancel: cancel an I/O request, blocking until it has been cancelled
* cdev_reply: process the result of a character driver request
*/
#include "fs.h"
#include "vnode.h"
#include "file.h"
#include <string.h>
#include <fcntl.h>
#include <sys/ttycom.h>
#include <assert.h>
/*
* Map the given device number to a real device number, remapping /dev/tty to
* the given process's controlling terminal if it has one. Perform a bounds
* check on the resulting device's major number, and return NO_DEV on failure.
* This function is idempotent but not used that way.
*/
dev_t
cdev_map(dev_t dev, struct fproc * rfp)
{
devmajor_t major;
/*
* First cover one special case: /dev/tty, the magic device that
* translates to the controlling TTY.
*/
if ((major = major(dev)) == CTTY_MAJOR) {
/* No controlling terminal? Fail the request. */
if (rfp->fp_tty == NO_DEV) return NO_DEV;
/* Substitute the controlling terminal device. */
dev = rfp->fp_tty;
major = major(dev);
}
if (major < 0 || major >= NR_DEVICES) return NO_DEV;
return dev;
}
/*
* Obtain the dmap structure for the given device, if a valid driver exists for
* the major device. Perform redirection for CTTY_MAJOR.
*/
static struct dmap *
cdev_get(dev_t dev, devminor_t * minor_dev)
{
struct dmap *dp;
int slot;
/*
* Remap /dev/tty as needed. Perform a bounds check on the major
* number.
*/
if ((dev = cdev_map(dev, fp)) == NO_DEV)
return NULL;
/* Determine the driver endpoint. */
dp = &dmap[major(dev)];
/* See if driver is roughly valid. */
if (dp->dmap_driver == NONE) return NULL;
if (isokendpt(dp->dmap_driver, &slot) != OK) {
printf("VFS: cdev_get: old driver for major %x (%d)\n",
major(dev), dp->dmap_driver);
return NULL;
}
/* Also return the (possibly redirected) minor number. */
*minor_dev = minor(dev);
return dp;
}
/*
* A new minor device number has been returned. Request PFS to create a
* temporary device file to hold it.
*/
static int
cdev_clone(int fd, dev_t dev, devminor_t new_minor)
{
struct vnode *vp;
struct node_details res;
int r;
assert(fd != -1);
/* Device number of the new device. */
dev = makedev(major(dev), new_minor);
/* Create a new file system node on PFS for the cloned device. */
r = req_newnode(PFS_PROC_NR, fp->fp_effuid, fp->fp_effgid,
RWX_MODES | I_CHAR_SPECIAL, dev, &res);
if (r != OK) {
(void)cdev_close(dev);
return r;
}
/* Drop the old node and use the new values. */
if ((vp = get_free_vnode()) == NULL) {
req_putnode(PFS_PROC_NR, res.inode_nr, 1); /* is this right? */
(void)cdev_close(dev);
return err_code;
}
lock_vnode(vp, VNODE_OPCL);
assert(fp->fp_filp[fd] != NULL);
unlock_vnode(fp->fp_filp[fd]->filp_vno);
put_vnode(fp->fp_filp[fd]->filp_vno);
vp->v_fs_e = res.fs_e;
vp->v_vmnt = NULL;
vp->v_dev = NO_DEV;
vp->v_inode_nr = res.inode_nr;
vp->v_mode = res.fmode;
vp->v_sdev = dev;
vp->v_fs_count = 1;
vp->v_ref_count = 1;
fp->fp_filp[fd]->filp_vno = vp;
return OK;
}
/*
* Open or close a character device. The given operation must be either
* CDEV_OPEN or CDEV_CLOSE. For CDEV_OPEN, 'fd' must be the file descriptor
* for the file being opened; for CDEV_CLOSE, it is ignored. For CDEV_OPEN,
* 'flags' identifies a bitwise combination of R_BIT, W_BIT, and/or O_NOCTTY;
* for CDEV_CLOSE, it too is ignored.
*/
static int
cdev_opcl(int op, dev_t dev, int fd, int flags)
{
devminor_t minor_dev, new_minor;
struct dmap *dp;
struct fproc *rfp;
message dev_mess;
int r, r2, acc;
/*
* We need the a descriptor for CDEV_OPEN, because if the driver
* returns a cloned device, we need to replace what the fd points to.
* For CDEV_CLOSE however, we may be closing a device for which the
* calling process has no file descriptor, and thus we expect no
* meaningful fd value in that case.
*/
assert(op == CDEV_OPEN || op == CDEV_CLOSE);
assert(fd != -1 || op == CDEV_CLOSE);
/* Determine task dmap. */
if ((dp = cdev_get(dev, &minor_dev)) == NULL)
return ENXIO;
/*
* CTTY exception: do not actually send the open/close request for
* /dev/tty to the driver. This avoids the case that the actual device
* will remain open forever if the process calls setsid() after opening
* /dev/tty.
*/
if (major(dev) == CTTY_MAJOR) return OK;
/*
* Add O_NOCTTY to the access flags if this process is not a session
* leader, or if it already has a controlling tty, or if it is someone
* else's controlling tty. For performance reasons, only search the
* full process table if this driver has set controlling TTYs before.
*/
if (!(fp->fp_flags & FP_SESLDR) || fp->fp_tty != 0) {
flags |= O_NOCTTY;
} else if (!(flags & O_NOCTTY) && dp->dmap_seen_tty) {
for (rfp = &fproc[0]; rfp < &fproc[NR_PROCS]; rfp++)
if (rfp->fp_pid != PID_FREE && rfp->fp_tty == dev)
flags |= O_NOCTTY;
}
/* Prepare the request message. */
memset(&dev_mess, 0, sizeof(dev_mess));
dev_mess.m_type = op;
dev_mess.m_vfs_lchardriver_openclose.minor = minor_dev;
dev_mess.m_vfs_lchardriver_openclose.id = who_e;
if (op == CDEV_OPEN) {
acc = 0;
if (flags & R_BIT) acc |= CDEV_R_BIT;
if (flags & W_BIT) acc |= CDEV_W_BIT;
if (flags & O_NOCTTY) acc |= CDEV_NOCTTY;
dev_mess.m_vfs_lchardriver_openclose.user = who_e;
dev_mess.m_vfs_lchardriver_openclose.access = acc;
}
/* Send the request to the driver. */
if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
panic("VFS: asynsend in cdev_opcl failed: %d", r);
/* Block the thread waiting for a reply. */
self->w_task = dp->dmap_driver;
self->w_drv_sendrec = &dev_mess;
worker_wait();
self->w_task = NONE;
assert(self->w_drv_sendrec == NULL);
/* Process the reply. */
r = dev_mess.m_lchardriver_vfs_reply.status;
if (op == CDEV_OPEN && r >= 0) {
/*
* Some devices need special processing upon open. Such a
* device is "cloned", i.e., on a succesful open it is replaced
* by a new device with a new unique minor device number. This
* new device number identifies a new object that has been
* allocated within a driver.
*/
if (r & CDEV_CLONED) {
new_minor = r & ~(CDEV_CLONED | CDEV_CTTY);
if ((r2 = cdev_clone(fd, dev, new_minor)) < 0)
return r2;
}
/* Did this call make the TTY the controlling TTY? */
if (r & CDEV_CTTY) {
fp->fp_tty = dev;
dp->dmap_seen_tty = TRUE;
}
r = OK;
}
/* Return the result from the driver. */
return r;
}
/*
* Open a character device.
*/
int
cdev_open(int fd, dev_t dev, int flags)
{
return cdev_opcl(CDEV_OPEN, dev, fd, flags);
}
/*
* Close a character device.
*/
int
cdev_close(dev_t dev)
{
return cdev_opcl(CDEV_CLOSE, dev, -1, 0);
}
/*
* Initiate a read, write, or ioctl to a character device. The given operation
* must be CDEV_READ, CDEV_WRITE, or CDEV_IOCTL. The call is made on behalf of
* user process 'proc_e'. For read/write requests, 'bytes' is the number of
* bytes to read into 'buf' at file position 'pos'. For ioctl requests,
* 'bytes' is actually an IOCTL request code, which implies the size of the
* buffer 'buf' if needed for the request at all ('pos' is ignored here). The
* 'flags' field contains file pointer flags, from which O_NONBLOCK is tested.
*/
int
cdev_io(int op, dev_t dev, endpoint_t proc_e, vir_bytes buf, off_t pos,
unsigned long bytes, int flags)
{
devminor_t minor_dev;
struct dmap *dp;
message dev_mess;
cp_grant_id_t gid;
int r;
assert(op == CDEV_READ || op == CDEV_WRITE || op == CDEV_IOCTL);
/* Determine task map. */
if ((dp = cdev_get(dev, &minor_dev)) == NULL)
return EIO;
/*
* Handle TIOCSCTTY ioctl: set controlling TTY. FIXME: this should not
* hardcode major device numbers, and not assume that the IOCTL request
* succeeds!
*/
if (op == CDEV_IOCTL && bytes == TIOCSCTTY &&
(major(dev) == TTY_MAJOR || major(dev) == PTY_MAJOR)) {
fp->fp_tty = dev;
}
/* Create a grant for the buffer provided by the user process. */
if (op != CDEV_IOCTL) {
gid = cpf_grant_magic(dp->dmap_driver, proc_e, buf,
(size_t)bytes, (op == CDEV_READ) ? CPF_WRITE : CPF_READ);
if (!GRANT_VALID(gid))
panic("VFS: cpf_grant_magic failed");
} else
gid = make_ioctl_grant(dp->dmap_driver, proc_e, buf, bytes);
/* Set up the message that will be sent to the driver. */
memset(&dev_mess, 0, sizeof(dev_mess));
dev_mess.m_type = op;
dev_mess.m_vfs_lchardriver_readwrite.minor = minor_dev;
if (op == CDEV_IOCTL) {
dev_mess.m_vfs_lchardriver_readwrite.request = bytes;
dev_mess.m_vfs_lchardriver_readwrite.user = proc_e;
} else {
dev_mess.m_vfs_lchardriver_readwrite.pos = pos;
dev_mess.m_vfs_lchardriver_readwrite.count = bytes;
}
dev_mess.m_vfs_lchardriver_readwrite.id = proc_e;
dev_mess.m_vfs_lchardriver_readwrite.grant = gid;
dev_mess.m_vfs_lchardriver_readwrite.flags = 0;
if (flags & O_NONBLOCK)
dev_mess.m_vfs_lchardriver_readwrite.flags |= CDEV_NONBLOCK;
/* Send the request to the driver. */
if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
panic("VFS: asynsend in cdev_io failed: %d", r);
/* Suspend the calling process until a reply arrives. */
fp->fp_cdev.dev = dev;
fp->fp_cdev.endpt = dp->dmap_driver;
fp->fp_cdev.grant = gid; /* revoke this when unsuspended */
suspend(FP_BLOCKED_ON_CDEV);
return SUSPEND;
}
/*
* Initiate a select call on a device. Return OK iff the request was sent.
* This function explicitly bypasses cdev_get() since it must not do CTTY
* mapping, because a) the caller already has done that, b) "fp" may be wrong.
*/
int
cdev_select(dev_t dev, int ops)
{
devmajor_t major;
message dev_mess;
struct dmap *dp;
int r;
/* Determine task dmap, without CTTY mapping. */
assert(dev != NO_DEV);
major = major(dev);
assert(major >= 0 && major < NR_DEVICES);
assert(major != CTTY_MAJOR);
dp = &dmap[major];
/* Prepare the request message. */
memset(&dev_mess, 0, sizeof(dev_mess));
dev_mess.m_type = CDEV_SELECT;
dev_mess.m_vfs_lchardriver_select.minor = minor(dev);
dev_mess.m_vfs_lchardriver_select.ops = ops;
/* Send the request to the driver. */
if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
panic("VFS: asynsend in cdev_select failed: %d", r);
return OK;
}
/*
* Cancel an I/O request, blocking until it has been cancelled.
*/
int
cdev_cancel(dev_t dev, endpoint_t endpt __unused, cp_grant_id_t grant)
{
devminor_t minor_dev;
message dev_mess;
struct dmap *dp;
int r;
/* Determine task dmap. */
if ((dp = cdev_get(dev, &minor_dev)) == NULL)
return EIO;
/* Prepare the request message. */
memset(&dev_mess, 0, sizeof(dev_mess));
dev_mess.m_type = CDEV_CANCEL;
dev_mess.m_vfs_lchardriver_cancel.minor = minor_dev;
dev_mess.m_vfs_lchardriver_cancel.id = fp->fp_endpoint;
/* Send the request to the driver. */
if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
panic("VFS: asynsend in cdev_cancel failed: %d", r);
/* Suspend this thread until we have received the response. */
self->w_task = dp->dmap_driver;
self->w_drv_sendrec = &dev_mess;
worker_wait();
self->w_task = NONE;
assert(self->w_drv_sendrec == NULL);
/* Clean up. */
if (GRANT_VALID(grant))
(void)cpf_revoke(grant);
/* Return the result. Note that the request may have completed. */
r = dev_mess.m_lchardriver_vfs_reply.status;
return (r == EAGAIN) ? EINTR : r; /* see below regarding error codes */
}
/*
* A character driver has results for an open, close, read, write, or ioctl
* call (i.e., everything except select). There may be a thread waiting for
* these results as part of an ongoing open, close, or (for read/write/ioctl)
* cancel call. If so, wake up that thread; if not, send a reply to the
* requesting process. This function MUST NOT block its calling thread.
*/
static void
cdev_generic_reply(message * m_ptr)
{
struct fproc *rfp;
struct worker_thread *wp;
endpoint_t proc_e;
int r, slot;
proc_e = m_ptr->m_lchardriver_vfs_reply.id;
if (m_ptr->m_lchardriver_vfs_reply.status == SUSPEND) {
printf("VFS: ignoring SUSPEND status from %d\n",
m_ptr->m_source);
return;
}
if (isokendpt(proc_e, &slot) != OK) {
printf("VFS: proc %d from %d not found\n",
proc_e, m_ptr->m_source);
return;
}
rfp = &fproc[slot];
wp = rfp->fp_worker;
if (wp != NULL && wp->w_task == who_e && wp->w_drv_sendrec != NULL) {
assert(!fp_is_blocked(rfp));
*wp->w_drv_sendrec = *m_ptr;
wp->w_drv_sendrec = NULL;
worker_signal(wp); /* continue open/close/cancel */
} else if (rfp->fp_blocked_on != FP_BLOCKED_ON_CDEV ||
rfp->fp_cdev.endpt != m_ptr->m_source) {
/*
* This would typically be caused by a protocol error, i.e., a
* driver not properly following the character driver protocol.
*/
printf("VFS: proc %d not blocked on %d\n",
proc_e, m_ptr->m_source);
} else {
/*
* Some services use the same infrastructure for nonblocking
* and cancelled requests, resulting in one of EINTR or EAGAIN
* when the other is really the appropriate code. Thus,
* cdev_cancel converts EAGAIN into EINTR, and we convert EINTR
* into EAGAIN here. TODO: this may be obsolete by now..?
*/
r = m_ptr->m_lchardriver_vfs_reply.status;
revive(proc_e, (r == EINTR) ? EAGAIN : r);
}
}
/*
* A character driver has results for us.
*/
void
cdev_reply(void)
{
if (get_dmap_by_endpt(who_e) == NULL) {
printf("VFS: ignoring char dev reply from unknown driver %d\n",
who_e);
return;
}
switch (call_nr) {
case CDEV_REPLY:
cdev_generic_reply(&m_in);
break;
case CDEV_SEL1_REPLY:
select_cdev_reply1(m_in.m_source,
m_in.m_lchardriver_vfs_sel1.minor,
m_in.m_lchardriver_vfs_sel1.status);
break;
case CDEV_SEL2_REPLY:
select_cdev_reply2(m_in.m_source,
m_in.m_lchardriver_vfs_sel2.minor,
m_in.m_lchardriver_vfs_sel2.status);
break;
default:
printf("VFS: char driver %u sent unknown reply %x\n",
who_e, call_nr);
}
}