etc/services - sync with NetBSD-8
[minix.git] / minix / servers / vfs / cdev.c
blob08f1aa9344d499bdbf9ee69246b7e78b793d4e4c
1 /*
2 * This file contains routines to perform character device operations.
3 * Character drivers may suspend I/O requests on their devices (read, write,
4 * ioctl), as well as select requests. These requests will therefore suspend
5 * their calling process, freeing up the associated VFS worker thread for other
6 * tasks. The I/O requests may later be cancelled as a result of the suspended
7 * process receiving a signal (which it either catches or dies from), in which
8 * case there will be a worker thread associated with the cancellation. Open
9 * and close requests may not suspend and will thus block the calling thread.
11 * The entry points in this file are:
12 * cdev_map: map a character device to its actual device number
13 * cdev_open: open a character device
14 * cdev_close: close a character device
15 * cdev_io: initiate a read, write, or ioctl to a character device
16 * cdev_select: initiate a select call on a device
17 * cdev_cancel: cancel an I/O request, blocking until it has been cancelled
18 * cdev_reply: process the result of a character driver request
21 #include "fs.h"
22 #include "vnode.h"
23 #include "file.h"
24 #include <string.h>
25 #include <fcntl.h>
26 #include <sys/ttycom.h>
27 #include <assert.h>
30 * Map the given device number to a real device number, remapping /dev/tty to
31 * the given process's controlling terminal if it has one. Perform a bounds
32 * check on the resulting device's major number, and return NO_DEV on failure.
33 * This function is idempotent but not used that way.
35 dev_t
36 cdev_map(dev_t dev, struct fproc * rfp)
38 devmajor_t major;
41 * First cover one special case: /dev/tty, the magic device that
42 * translates to the controlling TTY.
44 if ((major = major(dev)) == CTTY_MAJOR) {
45 /* No controlling terminal? Fail the request. */
46 if (rfp->fp_tty == NO_DEV) return NO_DEV;
48 /* Substitute the controlling terminal device. */
49 dev = rfp->fp_tty;
50 major = major(dev);
53 if (major < 0 || major >= NR_DEVICES) return NO_DEV;
55 return dev;
59 * Obtain the dmap structure for the given device, if a valid driver exists for
60 * the major device. Perform redirection for CTTY_MAJOR.
62 static struct dmap *
63 cdev_get(dev_t dev, devminor_t * minor_dev)
65 struct dmap *dp;
66 int slot;
69 * Remap /dev/tty as needed. Perform a bounds check on the major
70 * number.
72 if ((dev = cdev_map(dev, fp)) == NO_DEV)
73 return NULL;
75 /* Determine the driver endpoint. */
76 dp = &dmap[major(dev)];
78 /* See if driver is roughly valid. */
79 if (dp->dmap_driver == NONE) return NULL;
81 if (isokendpt(dp->dmap_driver, &slot) != OK) {
82 printf("VFS: cdev_get: old driver for major %x (%d)\n",
83 major(dev), dp->dmap_driver);
84 return NULL;
87 /* Also return the (possibly redirected) minor number. */
88 *minor_dev = minor(dev);
89 return dp;
93 * A new minor device number has been returned. Request PFS to create a
94 * temporary device file to hold it.
96 static int
97 cdev_clone(int fd, dev_t dev, devminor_t new_minor)
99 struct vnode *vp;
100 struct node_details res;
101 int r;
103 assert(fd != -1);
105 /* Device number of the new device. */
106 dev = makedev(major(dev), new_minor);
108 /* Create a new file system node on PFS for the cloned device. */
109 r = req_newnode(PFS_PROC_NR, fp->fp_effuid, fp->fp_effgid,
110 RWX_MODES | I_CHAR_SPECIAL, dev, &res);
111 if (r != OK) {
112 (void)cdev_close(dev);
113 return r;
116 /* Drop the old node and use the new values. */
117 if ((vp = get_free_vnode()) == NULL) {
118 req_putnode(PFS_PROC_NR, res.inode_nr, 1); /* is this right? */
119 (void)cdev_close(dev);
120 return err_code;
122 lock_vnode(vp, VNODE_OPCL);
124 assert(fp->fp_filp[fd] != NULL);
125 unlock_vnode(fp->fp_filp[fd]->filp_vno);
126 put_vnode(fp->fp_filp[fd]->filp_vno);
128 vp->v_fs_e = res.fs_e;
129 vp->v_vmnt = NULL;
130 vp->v_dev = NO_DEV;
131 vp->v_inode_nr = res.inode_nr;
132 vp->v_mode = res.fmode;
133 vp->v_sdev = dev;
134 vp->v_fs_count = 1;
135 vp->v_ref_count = 1;
136 fp->fp_filp[fd]->filp_vno = vp;
138 return OK;
142 * Open or close a character device. The given operation must be either
143 * CDEV_OPEN or CDEV_CLOSE. For CDEV_OPEN, 'fd' must be the file descriptor
144 * for the file being opened; for CDEV_CLOSE, it is ignored. For CDEV_OPEN,
145 * 'flags' identifies a bitwise combination of R_BIT, W_BIT, and/or O_NOCTTY;
146 * for CDEV_CLOSE, it too is ignored.
148 static int
149 cdev_opcl(int op, dev_t dev, int fd, int flags)
151 devminor_t minor_dev, new_minor;
152 struct dmap *dp;
153 struct fproc *rfp;
154 message dev_mess;
155 int r, r2, acc;
158 * We need the a descriptor for CDEV_OPEN, because if the driver
159 * returns a cloned device, we need to replace what the fd points to.
160 * For CDEV_CLOSE however, we may be closing a device for which the
161 * calling process has no file descriptor, and thus we expect no
162 * meaningful fd value in that case.
164 assert(op == CDEV_OPEN || op == CDEV_CLOSE);
165 assert(fd != -1 || op == CDEV_CLOSE);
167 /* Determine task dmap. */
168 if ((dp = cdev_get(dev, &minor_dev)) == NULL)
169 return ENXIO;
172 * CTTY exception: do not actually send the open/close request for
173 * /dev/tty to the driver. This avoids the case that the actual device
174 * will remain open forever if the process calls setsid() after opening
175 * /dev/tty.
177 if (major(dev) == CTTY_MAJOR) return OK;
180 * Add O_NOCTTY to the access flags if this process is not a session
181 * leader, or if it already has a controlling tty, or if it is someone
182 * else's controlling tty. For performance reasons, only search the
183 * full process table if this driver has set controlling TTYs before.
185 if (!(fp->fp_flags & FP_SESLDR) || fp->fp_tty != 0) {
186 flags |= O_NOCTTY;
187 } else if (!(flags & O_NOCTTY) && dp->dmap_seen_tty) {
188 for (rfp = &fproc[0]; rfp < &fproc[NR_PROCS]; rfp++)
189 if (rfp->fp_pid != PID_FREE && rfp->fp_tty == dev)
190 flags |= O_NOCTTY;
193 /* Prepare the request message. */
194 memset(&dev_mess, 0, sizeof(dev_mess));
195 dev_mess.m_type = op;
196 dev_mess.m_vfs_lchardriver_openclose.minor = minor_dev;
197 dev_mess.m_vfs_lchardriver_openclose.id = who_e;
198 if (op == CDEV_OPEN) {
199 acc = 0;
200 if (flags & R_BIT) acc |= CDEV_R_BIT;
201 if (flags & W_BIT) acc |= CDEV_W_BIT;
202 if (flags & O_NOCTTY) acc |= CDEV_NOCTTY;
203 dev_mess.m_vfs_lchardriver_openclose.user = who_e;
204 dev_mess.m_vfs_lchardriver_openclose.access = acc;
207 /* Send the request to the driver. */
208 if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
209 panic("VFS: asynsend in cdev_opcl failed: %d", r);
211 /* Block the thread waiting for a reply. */
212 self->w_task = dp->dmap_driver;
213 self->w_drv_sendrec = &dev_mess;
215 worker_wait();
217 self->w_task = NONE;
218 assert(self->w_drv_sendrec == NULL);
220 /* Process the reply. */
221 r = dev_mess.m_lchardriver_vfs_reply.status;
223 if (op == CDEV_OPEN && r >= 0) {
225 * Some devices need special processing upon open. Such a
226 * device is "cloned", i.e., on a succesful open it is replaced
227 * by a new device with a new unique minor device number. This
228 * new device number identifies a new object that has been
229 * allocated within a driver.
231 if (r & CDEV_CLONED) {
232 new_minor = r & ~(CDEV_CLONED | CDEV_CTTY);
233 if ((r2 = cdev_clone(fd, dev, new_minor)) < 0)
234 return r2;
237 /* Did this call make the TTY the controlling TTY? */
238 if (r & CDEV_CTTY) {
239 fp->fp_tty = dev;
240 dp->dmap_seen_tty = TRUE;
243 r = OK;
246 /* Return the result from the driver. */
247 return r;
251 * Open a character device.
254 cdev_open(int fd, dev_t dev, int flags)
257 return cdev_opcl(CDEV_OPEN, dev, fd, flags);
261 * Close a character device.
264 cdev_close(dev_t dev)
267 return cdev_opcl(CDEV_CLOSE, dev, -1, 0);
271 * Initiate a read, write, or ioctl to a character device. The given operation
272 * must be CDEV_READ, CDEV_WRITE, or CDEV_IOCTL. The call is made on behalf of
273 * user process 'proc_e'. For read/write requests, 'bytes' is the number of
274 * bytes to read into 'buf' at file position 'pos'. For ioctl requests,
275 * 'bytes' is actually an IOCTL request code, which implies the size of the
276 * buffer 'buf' if needed for the request at all ('pos' is ignored here). The
277 * 'flags' field contains file pointer flags, from which O_NONBLOCK is tested.
280 cdev_io(int op, dev_t dev, endpoint_t proc_e, vir_bytes buf, off_t pos,
281 unsigned long bytes, int flags)
283 devminor_t minor_dev;
284 struct dmap *dp;
285 message dev_mess;
286 cp_grant_id_t gid;
287 int r;
289 assert(op == CDEV_READ || op == CDEV_WRITE || op == CDEV_IOCTL);
291 /* Determine task map. */
292 if ((dp = cdev_get(dev, &minor_dev)) == NULL)
293 return EIO;
296 * Handle TIOCSCTTY ioctl: set controlling TTY. FIXME: this should not
297 * hardcode major device numbers, and not assume that the IOCTL request
298 * succeeds!
300 if (op == CDEV_IOCTL && bytes == TIOCSCTTY &&
301 (major(dev) == TTY_MAJOR || major(dev) == PTY_MAJOR)) {
302 fp->fp_tty = dev;
305 /* Create a grant for the buffer provided by the user process. */
306 if (op != CDEV_IOCTL) {
307 gid = cpf_grant_magic(dp->dmap_driver, proc_e, buf,
308 (size_t)bytes, (op == CDEV_READ) ? CPF_WRITE : CPF_READ);
309 if (!GRANT_VALID(gid))
310 panic("VFS: cpf_grant_magic failed");
311 } else
312 gid = make_ioctl_grant(dp->dmap_driver, proc_e, buf, bytes);
314 /* Set up the message that will be sent to the driver. */
315 memset(&dev_mess, 0, sizeof(dev_mess));
316 dev_mess.m_type = op;
317 dev_mess.m_vfs_lchardriver_readwrite.minor = minor_dev;
318 if (op == CDEV_IOCTL) {
319 dev_mess.m_vfs_lchardriver_readwrite.request = bytes;
320 dev_mess.m_vfs_lchardriver_readwrite.user = proc_e;
321 } else {
322 dev_mess.m_vfs_lchardriver_readwrite.pos = pos;
323 dev_mess.m_vfs_lchardriver_readwrite.count = bytes;
325 dev_mess.m_vfs_lchardriver_readwrite.id = proc_e;
326 dev_mess.m_vfs_lchardriver_readwrite.grant = gid;
327 dev_mess.m_vfs_lchardriver_readwrite.flags = 0;
328 if (flags & O_NONBLOCK)
329 dev_mess.m_vfs_lchardriver_readwrite.flags |= CDEV_NONBLOCK;
331 /* Send the request to the driver. */
332 if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
333 panic("VFS: asynsend in cdev_io failed: %d", r);
335 /* Suspend the calling process until a reply arrives. */
336 fp->fp_cdev.dev = dev;
337 fp->fp_cdev.endpt = dp->dmap_driver;
338 fp->fp_cdev.grant = gid; /* revoke this when unsuspended */
339 suspend(FP_BLOCKED_ON_CDEV);
341 return SUSPEND;
345 * Initiate a select call on a device. Return OK iff the request was sent.
346 * This function explicitly bypasses cdev_get() since it must not do CTTY
347 * mapping, because a) the caller already has done that, b) "fp" may be wrong.
350 cdev_select(dev_t dev, int ops)
352 devmajor_t major;
353 message dev_mess;
354 struct dmap *dp;
355 int r;
357 /* Determine task dmap, without CTTY mapping. */
358 assert(dev != NO_DEV);
359 major = major(dev);
360 assert(major >= 0 && major < NR_DEVICES);
361 assert(major != CTTY_MAJOR);
362 dp = &dmap[major];
364 /* Prepare the request message. */
365 memset(&dev_mess, 0, sizeof(dev_mess));
366 dev_mess.m_type = CDEV_SELECT;
367 dev_mess.m_vfs_lchardriver_select.minor = minor(dev);
368 dev_mess.m_vfs_lchardriver_select.ops = ops;
370 /* Send the request to the driver. */
371 if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
372 panic("VFS: asynsend in cdev_select failed: %d", r);
374 return OK;
378 * Cancel an I/O request, blocking until it has been cancelled.
381 cdev_cancel(dev_t dev, endpoint_t endpt __unused, cp_grant_id_t grant)
383 devminor_t minor_dev;
384 message dev_mess;
385 struct dmap *dp;
386 int r;
388 /* Determine task dmap. */
389 if ((dp = cdev_get(dev, &minor_dev)) == NULL)
390 return EIO;
392 /* Prepare the request message. */
393 memset(&dev_mess, 0, sizeof(dev_mess));
394 dev_mess.m_type = CDEV_CANCEL;
395 dev_mess.m_vfs_lchardriver_cancel.minor = minor_dev;
396 dev_mess.m_vfs_lchardriver_cancel.id = fp->fp_endpoint;
398 /* Send the request to the driver. */
399 if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
400 panic("VFS: asynsend in cdev_cancel failed: %d", r);
402 /* Suspend this thread until we have received the response. */
403 self->w_task = dp->dmap_driver;
404 self->w_drv_sendrec = &dev_mess;
406 worker_wait();
408 self->w_task = NONE;
409 assert(self->w_drv_sendrec == NULL);
411 /* Clean up. */
412 if (GRANT_VALID(grant))
413 (void)cpf_revoke(grant);
415 /* Return the result. Note that the request may have completed. */
416 r = dev_mess.m_lchardriver_vfs_reply.status;
418 return (r == EAGAIN) ? EINTR : r; /* see below regarding error codes */
422 * A character driver has results for an open, close, read, write, or ioctl
423 * call (i.e., everything except select). There may be a thread waiting for
424 * these results as part of an ongoing open, close, or (for read/write/ioctl)
425 * cancel call. If so, wake up that thread; if not, send a reply to the
426 * requesting process. This function MUST NOT block its calling thread.
428 static void
429 cdev_generic_reply(message * m_ptr)
431 struct fproc *rfp;
432 struct worker_thread *wp;
433 endpoint_t proc_e;
434 int r, slot;
436 proc_e = m_ptr->m_lchardriver_vfs_reply.id;
438 if (m_ptr->m_lchardriver_vfs_reply.status == SUSPEND) {
439 printf("VFS: ignoring SUSPEND status from %d\n",
440 m_ptr->m_source);
441 return;
444 if (isokendpt(proc_e, &slot) != OK) {
445 printf("VFS: proc %d from %d not found\n",
446 proc_e, m_ptr->m_source);
447 return;
449 rfp = &fproc[slot];
450 wp = rfp->fp_worker;
451 if (wp != NULL && wp->w_task == who_e && wp->w_drv_sendrec != NULL) {
452 assert(!fp_is_blocked(rfp));
453 *wp->w_drv_sendrec = *m_ptr;
454 wp->w_drv_sendrec = NULL;
455 worker_signal(wp); /* continue open/close/cancel */
456 } else if (rfp->fp_blocked_on != FP_BLOCKED_ON_CDEV ||
457 rfp->fp_cdev.endpt != m_ptr->m_source) {
459 * This would typically be caused by a protocol error, i.e., a
460 * driver not properly following the character driver protocol.
462 printf("VFS: proc %d not blocked on %d\n",
463 proc_e, m_ptr->m_source);
464 } else {
466 * Some services use the same infrastructure for nonblocking
467 * and cancelled requests, resulting in one of EINTR or EAGAIN
468 * when the other is really the appropriate code. Thus,
469 * cdev_cancel converts EAGAIN into EINTR, and we convert EINTR
470 * into EAGAIN here. TODO: this may be obsolete by now..?
472 r = m_ptr->m_lchardriver_vfs_reply.status;
473 revive(proc_e, (r == EINTR) ? EAGAIN : r);
478 * A character driver has results for us.
480 void
481 cdev_reply(void)
484 if (get_dmap_by_endpt(who_e) == NULL) {
485 printf("VFS: ignoring char dev reply from unknown driver %d\n",
486 who_e);
487 return;
490 switch (call_nr) {
491 case CDEV_REPLY:
492 cdev_generic_reply(&m_in);
493 break;
494 case CDEV_SEL1_REPLY:
495 select_cdev_reply1(m_in.m_source,
496 m_in.m_lchardriver_vfs_sel1.minor,
497 m_in.m_lchardriver_vfs_sel1.status);
498 break;
499 case CDEV_SEL2_REPLY:
500 select_cdev_reply2(m_in.m_source,
501 m_in.m_lchardriver_vfs_sel2.minor,
502 m_in.m_lchardriver_vfs_sel2.status);
503 break;
504 default:
505 printf("VFS: char driver %u sent unknown reply %x\n",
506 who_e, call_nr);