1 /* This file contains the device independent character driver interface.
3 * Character drivers support the following requests. Message format m10 is
4 * used. Field names are prefixed with CDEV_. Separate field names are used for
5 * the "access", "ops", "user", and "request" fields.
7 * m_type MINOR GRANT COUNT FLAGS ID POS_LO POS_HI
8 * +-------------+-------+--------+-------+-------+------+---------+--------+
9 * | CDEV_OPEN | minor | access | user | | id | | |
10 * |-------------+-------+--------+-------+-------+------+---------+--------|
11 * | CDEV_CLOSE | minor | | | | id | | |
12 * |-------------+-------+--------+-------+-------+------+---------+--------|
13 * | CDEV_READ | minor | grant | bytes | flags | id | position |
14 * |-------------+-------+--------+-------+-------+------+---------+--------|
15 * | CDEV_WRITE | minor | grant | bytes | flags | id | position |
16 * |-------------+-------+--------+-------+-------+------+---------+--------|
17 * | CDEV_IOCTL | minor | grant | user | flags | id | request | |
18 * |-------------+-------+--------+-------+-------+------+---------+--------|
19 * | CDEV_CANCEL | minor | | | | id | | |
20 * |-------------+-------+--------+-------+-------+------+---------+--------|
21 * | CDEV_SELECT | minor | ops | | | | | |
22 * --------------------------------------------------------------------------
24 * The following reply messages are used.
26 * m_type MINOR STATUS ID
27 * +-----------------+-------+--------+-----+-----+------+---------+--------+
28 * | CDEV_REPLY | | status | | | id | | |
29 * |-----------------+-------+--------+-----+-----+------+---------+--------|
30 * | CDEV_SEL1_REPLY | minor | status | | | | | |
31 * |-----------------+-------+--------+-----+-----+------+---------+--------|
32 * | CDEV_SEL2_REPLY | minor | status | | | | | |
33 * --------------------------------------------------------------------------
36 * Sep 01, 2013 complete rewrite of the API (D.C. van Moolenboek)
37 * Aug 20, 2013 retire synchronous protocol (D.C. van Moolenbroek)
38 * Oct 16, 2011 split character and block protocol (D.C. van Moolenbroek)
39 * Aug 27, 2011 move common functions into driver.c (A. Welzel)
40 * Jul 25, 2005 added SYS_SIG type for signals (Jorrit N. Herder)
41 * Sep 15, 2004 added SYN_ALARM type for timeouts (Jorrit N. Herder)
42 * Jul 23, 2004 removed kernel dependencies (Jorrit N. Herder)
43 * Apr 02, 1992 constructed from AT wini and floppy driver (Kees J. Bot)
48 #include <minix/drivers.h>
49 #include <minix/chardriver.h>
54 /* Management data for opened devices. */
55 static devminor_t open_devs
[MAX_NR_OPEN_DEVICES
];
56 static int next_open_devs_slot
= 0;
58 /*===========================================================================*
60 *===========================================================================*/
61 static void clear_open_devs(void)
63 /* Reset the set of previously opened minor devices. */
64 next_open_devs_slot
= 0;
67 /*===========================================================================*
69 *===========================================================================*/
70 static int is_open_dev(devminor_t minor
)
72 /* Check whether the given minor device has previously been opened. */
75 for (i
= 0; i
< next_open_devs_slot
; i
++)
76 if (open_devs
[i
] == minor
)
82 /*===========================================================================*
84 *===========================================================================*/
85 static void set_open_dev(devminor_t minor
)
87 /* Mark the given minor device as having been opened. */
89 if (next_open_devs_slot
>= MAX_NR_OPEN_DEVICES
)
90 panic("out of slots for open devices");
92 open_devs
[next_open_devs_slot
] = minor
;
93 next_open_devs_slot
++;
96 /*===========================================================================*
97 * chardriver_announce *
98 *===========================================================================*/
99 void chardriver_announce(void)
101 /* Announce we are up after a fresh start or restart. */
103 char key
[DS_MAX_KEYLEN
];
104 char label
[DS_MAX_KEYLEN
];
105 const char *driver_prefix
= "drv.chr.";
107 /* Callers are allowed to use ipc_sendrec to communicate with drivers.
108 * For this reason, there may blocked callers when a driver restarts.
109 * Ask the kernel to unblock them (if any).
111 if ((r
= sys_statectl(SYS_STATE_CLEAR_IPC_REFS
, 0, 0)) != OK
)
112 panic("chardriver_announce: sys_statectl failed: %d", r
);
114 /* Publish a driver up event. */
115 if ((r
= ds_retrieve_label_name(label
, sef_self())) != OK
)
116 panic("chardriver_announce: unable to get own label: %d", r
);
118 snprintf(key
, DS_MAX_KEYLEN
, "%s%s", driver_prefix
, label
);
119 if ((r
= ds_publish_u32(key
, DS_DRIVER_UP
, DSF_OVERWRITE
)) != OK
)
120 panic("chardriver_announce: unable to publish driver up event: %d", r
);
122 /* Expect an open for any device before serving regular driver requests. */
126 /*===========================================================================*
127 * chardriver_reply_task *
128 *===========================================================================*/
129 void chardriver_reply_task(endpoint_t endpt
, cdev_id_t id
, int r
)
131 /* Reply to a (read, write, ioctl) task request that was suspended earlier.
132 * Not-so-well-written drivers may use this function to send a reply to a
133 * request that is being processed right now, and then return EDONTREPLY later.
137 if (r
== EDONTREPLY
|| r
== SUSPEND
)
138 panic("chardriver: bad task reply: %d", r
);
140 memset(&m_reply
, 0, sizeof(m_reply
));
142 m_reply
.m_type
= CDEV_REPLY
;
143 m_reply
.m_lchardriver_vfs_reply
.status
= r
;
144 m_reply
.m_lchardriver_vfs_reply
.id
= id
;
146 if ((r
= asynsend3(endpt
, &m_reply
, AMF_NOREPLY
)) != OK
)
147 printf("chardriver_reply_task: send to %d failed: %d\n", endpt
, r
);
150 /*===========================================================================*
151 * chardriver_reply_select *
152 *===========================================================================*/
153 void chardriver_reply_select(endpoint_t endpt
, devminor_t minor
, int r
)
155 /* Reply to a select request with a status update. This must not be used to
156 * reply to a select request that is being processed right now.
160 /* Replying with an error is allowed (if unusual). */
161 if (r
== EDONTREPLY
|| r
== SUSPEND
)
162 panic("chardriver: bad select reply: %d", r
);
164 memset(&m_reply
, 0, sizeof(m_reply
));
166 m_reply
.m_type
= CDEV_SEL2_REPLY
;
167 m_reply
.m_lchardriver_vfs_sel2
.minor
= minor
;
168 m_reply
.m_lchardriver_vfs_sel2
.status
= r
;
170 if ((r
= asynsend3(endpt
, &m_reply
, AMF_NOREPLY
)) != OK
)
171 printf("chardriver_reply_select: send to %d failed: %d\n", endpt
, r
);
174 /*===========================================================================*
176 *===========================================================================*/
177 static void send_reply(endpoint_t endpt
, message
*m_ptr
, int ipc_status
)
179 /* Send a reply message to a request. */
182 /* If we would block sending the message, send it asynchronously. */
183 if (IPC_STATUS_CALL(ipc_status
) == SENDREC
)
184 r
= ipc_sendnb(endpt
, m_ptr
);
186 r
= asynsend3(endpt
, m_ptr
, AMF_NOREPLY
);
189 printf("chardriver: unable to send reply to %d: %d\n", endpt
, r
);
192 /*===========================================================================*
194 *===========================================================================*/
195 static void chardriver_reply(message
*mess
, int ipc_status
, int r
)
197 /* Prepare and send a reply message. */
200 /* If the EDONTREPLY pseudo-reply is given, we do not reply. This is however
201 * allowed only for blocking task calls. Perform a sanity check.
203 if (r
== EDONTREPLY
) {
204 switch (mess
->m_type
) {
208 /* FIXME: we should be able to check FLAGS against
209 * CDEV_NONBLOCK here, but in practice, several drivers do not
210 * send a reply through this path (eg TTY) or simply do not
211 * implement nonblocking calls properly (eg audio).
214 if (mess
->m_vfs_lchardriver_readwrite
.flags
& CDEV_NONBLOCK
)
215 panic("chardriver: cannot suspend nonblocking I/O");
219 return; /* alright */
221 panic("chardriver: cannot suspend request %d", mess
->m_type
);
226 panic("chardriver: SUSPEND should not be used anymore");
228 /* Do not reply with ERESTART. The only possible caller, VFS, will find out
229 * through other means when we have restarted, and is not (fully) ready to
230 * deal with ERESTART errors.
235 memset(&reply_mess
, 0, sizeof(reply_mess
));
237 switch (mess
->m_type
) {
240 reply_mess
.m_type
= CDEV_REPLY
;
241 reply_mess
.m_lchardriver_vfs_reply
.status
= r
;
242 reply_mess
.m_lchardriver_vfs_reply
.id
=
243 mess
->m_vfs_lchardriver_openclose
.id
;
249 reply_mess
.m_type
= CDEV_REPLY
;
250 reply_mess
.m_lchardriver_vfs_reply
.status
= r
;
251 reply_mess
.m_lchardriver_vfs_reply
.id
=
252 mess
->m_vfs_lchardriver_readwrite
.id
;
255 case CDEV_CANCEL
: /* For cancel, this is a reply to the original request! */
256 reply_mess
.m_type
= CDEV_REPLY
;
257 reply_mess
.m_lchardriver_vfs_reply
.status
= r
;
258 reply_mess
.m_lchardriver_vfs_reply
.id
=
259 mess
->m_vfs_lchardriver_cancel
.id
;
263 reply_mess
.m_type
= CDEV_SEL1_REPLY
;
264 reply_mess
.m_lchardriver_vfs_sel1
.status
= r
;
265 reply_mess
.m_lchardriver_vfs_sel1
.minor
=
266 mess
->m_vfs_lchardriver_select
.minor
;
270 panic("chardriver: unknown request %d", mess
->m_type
);
273 send_reply(mess
->m_source
, &reply_mess
, ipc_status
);
276 /*===========================================================================*
278 *===========================================================================*/
279 static int do_open(const struct chardriver
*cdp
, message
*m_ptr
)
281 /* Open a minor device. */
282 endpoint_t user_endpt
;
286 /* Default action if no open hook is in place. */
287 if (cdp
->cdr_open
== NULL
)
290 /* Call the open hook. */
291 minor
= m_ptr
->m_vfs_lchardriver_openclose
.minor
;
292 bits
= m_ptr
->m_vfs_lchardriver_openclose
.access
;
293 user_endpt
= m_ptr
->m_vfs_lchardriver_openclose
.user
;
295 r
= cdp
->cdr_open(minor
, bits
, user_endpt
);
297 /* If the device has been cloned, mark the new minor as open too. */
298 if (r
>= 0 && (r
& CDEV_CLONED
)) {
299 minor
= r
& ~(CDEV_CLONED
| CDEV_CTTY
);
300 if (!is_open_dev(minor
))
307 /*===========================================================================*
309 *===========================================================================*/
310 static int do_close(const struct chardriver
*cdp
, message
*m_ptr
)
312 /* Close a minor device. */
315 /* Default action if no close hook is in place. */
316 if (cdp
->cdr_close
== NULL
)
319 /* Call the close hook. */
320 minor
= m_ptr
->m_vfs_lchardriver_openclose
.minor
;
322 return cdp
->cdr_close(minor
);
325 /*===========================================================================*
327 *===========================================================================*/
328 static int do_transfer(const struct chardriver
*cdp
, message
*m_ptr
,
331 /* Carry out a read or write task request. */
341 minor
= m_ptr
->m_vfs_lchardriver_readwrite
.minor
;
342 position
= m_ptr
->m_vfs_lchardriver_readwrite
.pos
;
343 endpt
= m_ptr
->m_source
;
344 grant
= m_ptr
->m_vfs_lchardriver_readwrite
.grant
;
345 size
= m_ptr
->m_vfs_lchardriver_readwrite
.count
;
346 flags
= m_ptr
->m_vfs_lchardriver_readwrite
.flags
;
347 id
= m_ptr
->m_vfs_lchardriver_readwrite
.id
;
349 /* Call the read/write hook, if the appropriate one is in place. */
350 if (!do_write
&& cdp
->cdr_read
!= NULL
)
351 r
= cdp
->cdr_read(minor
, position
, endpt
, grant
, size
, flags
, id
);
352 else if (do_write
&& cdp
->cdr_write
!= NULL
)
353 r
= cdp
->cdr_write(minor
, position
, endpt
, grant
, size
, flags
, id
);
355 r
= EIO
; /* Default action if no read/write hook is in place. */
360 /*===========================================================================*
362 *===========================================================================*/
363 static int do_ioctl(const struct chardriver
*cdp
, message
*m_ptr
)
365 /* Carry out an I/O control task request. */
367 unsigned long request
;
369 endpoint_t endpt
, user_endpt
;
373 /* Default action if no ioctl hook is in place. */
374 if (cdp
->cdr_ioctl
== NULL
)
377 /* Call the ioctl hook. */
378 minor
= m_ptr
->m_vfs_lchardriver_readwrite
.minor
;
379 request
= m_ptr
->m_vfs_lchardriver_readwrite
.request
;
380 endpt
= m_ptr
->m_source
;
381 grant
= m_ptr
->m_vfs_lchardriver_readwrite
.grant
;
382 flags
= m_ptr
->m_vfs_lchardriver_readwrite
.flags
;
383 user_endpt
= m_ptr
->m_vfs_lchardriver_readwrite
.user
;
384 id
= m_ptr
->m_vfs_lchardriver_readwrite
.id
;
386 return cdp
->cdr_ioctl(minor
, request
, endpt
, grant
, flags
, user_endpt
, id
);
389 /*===========================================================================*
391 *===========================================================================*/
392 static int do_cancel(const struct chardriver
*cdp
, message
*m_ptr
)
394 /* Cancel a suspended (read, write, ioctl) task request. The original request
395 * may already have finished, in which case no reply should be sent.
401 /* Default action if no cancel hook is in place: let the request finish. */
402 if (cdp
->cdr_cancel
== NULL
)
405 /* Call the cancel hook. */
406 minor
= m_ptr
->m_vfs_lchardriver_cancel
.minor
;
407 endpt
= m_ptr
->m_source
;
408 id
= m_ptr
->m_vfs_lchardriver_cancel
.id
;
410 return cdp
->cdr_cancel(minor
, endpt
, id
);
413 /*===========================================================================*
415 *===========================================================================*/
416 static int do_select(const struct chardriver
*cdp
, message
*m_ptr
)
418 /* Perform a select query on a minor device. */
423 /* Default action if no select hook is in place. */
424 if (cdp
->cdr_select
== NULL
)
427 /* Call the select hook. */
428 minor
= m_ptr
->m_vfs_lchardriver_select
.minor
;
429 ops
= m_ptr
->m_vfs_lchardriver_select
.ops
;
430 endpt
= m_ptr
->m_source
;
432 return cdp
->cdr_select(minor
, ops
, endpt
);
435 /*===========================================================================*
437 *===========================================================================*/
438 static void do_block_open(message
*m_ptr
, int ipc_status
)
440 /* Reply to a block driver open request stating there is no such device. */
443 memset(&m_reply
, 0, sizeof(m_reply
));
445 m_reply
.m_type
= BDEV_REPLY
;
446 m_reply
.m_lblockdriver_lbdev_reply
.status
= ENXIO
;
447 m_reply
.m_lblockdriver_lbdev_reply
.id
= m_ptr
->m_lbdev_lblockdriver_msg
.id
;
449 send_reply(m_ptr
->m_source
, &m_reply
, ipc_status
);
452 /*===========================================================================*
453 * chardriver_process *
454 *===========================================================================*/
455 void chardriver_process(const struct chardriver
*cdp
, message
*m_ptr
,
458 /* Call the appropiate driver function, based on the type of request. Send a
459 * reply to the caller if necessary.
463 /* Check for notifications first. We never reply to notifications. */
464 if (is_ipc_notify(ipc_status
)) {
465 switch (_ENDPOINT_P(m_ptr
->m_source
)) {
468 cdp
->cdr_intr(m_ptr
->m_notify
.interrupts
);
473 cdp
->cdr_alarm(m_ptr
->m_notify
.timestamp
);
478 cdp
->cdr_other(m_ptr
, ipc_status
);
481 return; /* do not send a reply */
484 /* Reply to block driver open requests with an error code. Otherwise, if
485 * someone creates a block device node for a character driver, opening that
486 * device node will cause the corresponding VFS thread to block forever.
488 if (m_ptr
->m_type
== BDEV_OPEN
) {
489 do_block_open(m_ptr
, ipc_status
);
494 if (IS_CDEV_RQ(m_ptr
->m_type
)) {
497 /* Try to retrieve minor device number */
498 r
= chardriver_get_minor(m_ptr
, &minor
);
503 /* We might get spurious requests if the driver has been restarted.
504 * Deny any requests on devices that have not previously been opened.
506 if (!is_open_dev(minor
)) {
507 /* Ignore spurious requests for unopened devices. */
508 if (m_ptr
->m_type
!= CDEV_OPEN
)
509 return; /* do not send a reply */
511 /* Mark the device as opened otherwise. */
516 /* Call the appropriate function(s) for this request. */
517 switch (m_ptr
->m_type
) {
518 case CDEV_OPEN
: r
= do_open(cdp
, m_ptr
); break;
519 case CDEV_CLOSE
: r
= do_close(cdp
, m_ptr
); break;
520 case CDEV_READ
: r
= do_transfer(cdp
, m_ptr
, FALSE
); break;
521 case CDEV_WRITE
: r
= do_transfer(cdp
, m_ptr
, TRUE
); break;
522 case CDEV_IOCTL
: r
= do_ioctl(cdp
, m_ptr
); break;
523 case CDEV_CANCEL
: r
= do_cancel(cdp
, m_ptr
); break;
524 case CDEV_SELECT
: r
= do_select(cdp
, m_ptr
); break;
527 cdp
->cdr_other(m_ptr
, ipc_status
);
528 return; /* do not send a reply */
531 chardriver_reply(m_ptr
, ipc_status
, r
);
534 /*===========================================================================*
535 * chardriver_terminate *
536 *===========================================================================*/
537 void chardriver_terminate(void)
539 /* Break out of the main loop after finishing the current request. */
546 /*===========================================================================*
548 *===========================================================================*/
549 void chardriver_task(const struct chardriver
*cdp
)
551 /* Main program of any character device driver task. */
557 /* Here is the main loop of the character driver task. It waits for a
558 * message, carries it out, and sends a reply.
561 if ((r
= sef_receive_status(ANY
, &mess
, &ipc_status
)) != OK
) {
562 if (r
== EINTR
&& !running
)
565 panic("chardriver: sef_receive_status failed: %d", r
);
568 chardriver_process(cdp
, &mess
, ipc_status
);
572 /*===========================================================================*
573 * chardriver_get_minor *
574 *===========================================================================*/
575 int chardriver_get_minor(const message
*m
, devminor_t
*minor
)
578 assert(NULL
!= minor
);
584 *minor
= m
->m_vfs_lchardriver_openclose
.minor
;
587 *minor
= m
->m_vfs_lchardriver_cancel
.minor
;
590 *minor
= m
->m_vfs_lchardriver_select
.minor
;
595 *minor
= m
->m_vfs_lchardriver_readwrite
.minor
;