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
26 #include <sys/ttycom.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.
36 cdev_map(dev_t dev
, struct fproc
* rfp
)
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. */
53 if (major
< 0 || major
>= NR_DEVICES
) return NO_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.
63 cdev_get(dev_t dev
, devminor_t
* minor_dev
)
69 * Remap /dev/tty as needed. Perform a bounds check on the major
72 if ((dev
= cdev_map(dev
, fp
)) == NO_DEV
)
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
);
87 /* Also return the (possibly redirected) minor number. */
88 *minor_dev
= minor(dev
);
93 * A new minor device number has been returned. Request PFS to create a
94 * temporary device file to hold it.
97 cdev_clone(int fd
, dev_t dev
, devminor_t new_minor
)
100 struct node_details res
;
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
);
112 (void)cdev_close(dev
);
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
);
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
;
131 vp
->v_inode_nr
= res
.inode_nr
;
132 vp
->v_mode
= res
.fmode
;
136 fp
->fp_filp
[fd
]->filp_vno
= vp
;
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.
149 cdev_opcl(int op
, dev_t dev
, int fd
, int flags
)
151 devminor_t minor_dev
, new_minor
;
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
)
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
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) {
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
)
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
) {
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
;
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)
237 /* Did this call make the TTY the controlling TTY? */
240 dp
->dmap_seen_tty
= TRUE
;
246 /* Return the result from the driver. */
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
;
289 assert(op
== CDEV_READ
|| op
== CDEV_WRITE
|| op
== CDEV_IOCTL
);
291 /* Determine task map. */
292 if ((dp
= cdev_get(dev
, &minor_dev
)) == NULL
)
296 * Handle TIOCSCTTY ioctl: set controlling TTY. FIXME: this should not
297 * hardcode major device numbers, and not assume that the IOCTL request
300 if (op
== CDEV_IOCTL
&& bytes
== TIOCSCTTY
&&
301 (major(dev
) == TTY_MAJOR
|| major(dev
) == PTY_MAJOR
)) {
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");
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
;
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
);
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
)
357 /* Determine task dmap, without CTTY mapping. */
358 assert(dev
!= NO_DEV
);
360 assert(major
>= 0 && major
< NR_DEVICES
);
361 assert(major
!= CTTY_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
);
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
;
388 /* Determine task dmap. */
389 if ((dp
= cdev_get(dev
, &minor_dev
)) == NULL
)
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
;
409 assert(self
->w_drv_sendrec
== NULL
);
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.
429 cdev_generic_reply(message
* m_ptr
)
432 struct worker_thread
*wp
;
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",
444 if (isokendpt(proc_e
, &slot
) != OK
) {
445 printf("VFS: proc %d from %d not found\n",
446 proc_e
, m_ptr
->m_source
);
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
);
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.
484 if (get_dmap_by_endpt(who_e
) == NULL
) {
485 printf("VFS: ignoring char dev reply from unknown driver %d\n",
492 cdev_generic_reply(&m_in
);
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
);
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
);
505 printf("VFS: char driver %u sent unknown reply %x\n",