xref: /minix3/minix/lib/libchardriver/chardriver.c (revision 36dcc4a4a93f782ada76dce3d52fbeab0e063cf1)
1 /* This file contains the device independent character driver interface.
2  *
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.
6  *
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  * --------------------------------------------------------------------------
23  *
24  * The following reply messages are used.
25  *
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  * --------------------------------------------------------------------------
34  *
35  * Changes:
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)
44  */
45 
46 #include <assert.h>
47 
48 #include <minix/drivers.h>
49 #include <minix/chardriver.h>
50 #include <minix/ds.h>
51 
52 static int running;
53 
54 /* Management data for opened devices. */
55 static devminor_t open_devs[MAX_NR_OPEN_DEVICES];
56 static int next_open_devs_slot = 0;
57 
58 /*===========================================================================*
59  *				clear_open_devs				     *
60  *===========================================================================*/
61 static void clear_open_devs(void)
62 {
63 /* Reset the set of previously opened minor devices. */
64   next_open_devs_slot = 0;
65 }
66 
67 /*===========================================================================*
68  *				is_open_dev				     *
69  *===========================================================================*/
70 static int is_open_dev(devminor_t minor)
71 {
72 /* Check whether the given minor device has previously been opened. */
73   int i;
74 
75   for (i = 0; i < next_open_devs_slot; i++)
76 	if (open_devs[i] == minor)
77 		return TRUE;
78 
79   return FALSE;
80 }
81 
82 /*===========================================================================*
83  *				set_open_dev				     *
84  *===========================================================================*/
85 static void set_open_dev(devminor_t minor)
86 {
87 /* Mark the given minor device as having been opened. */
88 
89   if (next_open_devs_slot >= MAX_NR_OPEN_DEVICES)
90 	panic("out of slots for open devices");
91 
92   open_devs[next_open_devs_slot] = minor;
93   next_open_devs_slot++;
94 }
95 
96 /*===========================================================================*
97  *				chardriver_announce			     *
98  *===========================================================================*/
99 void chardriver_announce(void)
100 {
101 /* Announce we are up after a fresh start or restart. */
102   int r;
103   char key[DS_MAX_KEYLEN];
104   char label[DS_MAX_KEYLEN];
105   const char *driver_prefix = "drv.chr.";
106 
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).
110    */
111   if ((r = sys_statectl(SYS_STATE_CLEAR_IPC_REFS, 0, 0)) != OK)
112 	panic("chardriver_announce: sys_statectl failed: %d", r);
113 
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);
117 
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);
121 
122   /* Expect an open for any device before serving regular driver requests. */
123   clear_open_devs();
124 }
125 
126 /*===========================================================================*
127  *				chardriver_reply_task			     *
128  *===========================================================================*/
129 void chardriver_reply_task(endpoint_t endpt, cdev_id_t id, int r)
130 {
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.
134  */
135   message m_reply;
136 
137   if (r == EDONTREPLY || r == SUSPEND)
138 	panic("chardriver: bad task reply: %d", r);
139 
140   memset(&m_reply, 0, sizeof(m_reply));
141 
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;
145 
146   if ((r = asynsend3(endpt, &m_reply, AMF_NOREPLY)) != OK)
147 	printf("chardriver_reply_task: send to %d failed: %d\n", endpt, r);
148 }
149 
150 /*===========================================================================*
151  *				chardriver_reply_select			     *
152  *===========================================================================*/
153 void chardriver_reply_select(endpoint_t endpt, devminor_t minor, int r)
154 {
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.
157  */
158   message m_reply;
159 
160   /* Replying with an error is allowed (if unusual). */
161   if (r == EDONTREPLY || r == SUSPEND)
162 	panic("chardriver: bad select reply: %d", r);
163 
164   memset(&m_reply, 0, sizeof(m_reply));
165 
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;
169 
170   if ((r = asynsend3(endpt, &m_reply, AMF_NOREPLY)) != OK)
171 	printf("chardriver_reply_select: send to %d failed: %d\n", endpt, r);
172 }
173 
174 /*===========================================================================*
175  *				send_reply				     *
176  *===========================================================================*/
177 static void send_reply(endpoint_t endpt, message *m_ptr, int ipc_status)
178 {
179 /* Send a reply message to a request. */
180   int r;
181 
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);
185   else
186 	r = asynsend3(endpt, m_ptr, AMF_NOREPLY);
187 
188   if (r != OK)
189 	printf("chardriver: unable to send reply to %d: %d\n", endpt, r);
190 }
191 
192 /*===========================================================================*
193  *				chardriver_reply			     *
194  *===========================================================================*/
195 static void chardriver_reply(message *mess, int ipc_status, int r)
196 {
197 /* Prepare and send a reply message. */
198   message reply_mess;
199 
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.
202    */
203   if (r == EDONTREPLY) {
204 	switch (mess->m_type) {
205 	case CDEV_READ:
206 	case CDEV_WRITE:
207 	case CDEV_IOCTL:
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, LWIP).
212 		 */
213 #if 0
214 		if (mess->m_vfs_lchardriver_readwrite.flags & CDEV_NONBLOCK)
215 			panic("chardriver: cannot suspend nonblocking I/O");
216 #endif
217 		/*fall-through*/
218 	case CDEV_CANCEL:
219 		return;	/* alright */
220 	default:
221 		panic("chardriver: cannot suspend request %d", mess->m_type);
222 	}
223   }
224 
225   if (r == SUSPEND)
226 	panic("chardriver: SUSPEND should not be used anymore");
227 
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.
231    */
232   if (r == ERESTART)
233 	return;
234 
235   memset(&reply_mess, 0, sizeof(reply_mess));
236 
237   switch (mess->m_type) {
238   case CDEV_OPEN:
239   case CDEV_CLOSE:
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;
244 	break;
245 
246   case CDEV_READ:
247   case CDEV_WRITE:
248   case CDEV_IOCTL:
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;
253 	break;
254 
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;
260 	break;
261 
262   case CDEV_SELECT:
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;
267 	break;
268 
269   default:
270 	panic("chardriver: unknown request %d", mess->m_type);
271   }
272 
273   send_reply(mess->m_source, &reply_mess, ipc_status);
274 }
275 
276 /*===========================================================================*
277  *				do_open					     *
278  *===========================================================================*/
279 static int do_open(const struct chardriver *cdp, message *m_ptr)
280 {
281 /* Open a minor device. */
282   endpoint_t user_endpt;
283   devminor_t minor;
284   int r, bits;
285 
286   /* Default action if no open hook is in place. */
287   if (cdp->cdr_open == NULL)
288 	return OK;
289 
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;
294 
295   r = cdp->cdr_open(minor, bits, user_endpt);
296 
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))
301 		set_open_dev(minor);
302   }
303 
304   return r;
305 }
306 
307 /*===========================================================================*
308  *				do_close				     *
309  *===========================================================================*/
310 static int do_close(const struct chardriver *cdp, message *m_ptr)
311 {
312 /* Close a minor device. */
313   devminor_t minor;
314 
315   /* Default action if no close hook is in place. */
316   if (cdp->cdr_close == NULL)
317 	return OK;
318 
319   /* Call the close hook. */
320   minor = m_ptr->m_vfs_lchardriver_openclose.minor;
321 
322   return cdp->cdr_close(minor);
323 }
324 
325 /*===========================================================================*
326  *				do_trasnfer				     *
327  *===========================================================================*/
328 static int do_transfer(const struct chardriver *cdp, message *m_ptr,
329 	int do_write)
330 {
331 /* Carry out a read or write task request. */
332   devminor_t minor;
333   off_t position;
334   endpoint_t endpt;
335   cp_grant_id_t grant;
336   size_t size;
337   int flags;
338   cdev_id_t id;
339   ssize_t r;
340 
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;
348 
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);
354   else
355 	r = EIO; /* Default action if no read/write hook is in place. */
356 
357   return r;
358 }
359 
360 /*===========================================================================*
361  *				do_ioctl				     *
362  *===========================================================================*/
363 static int do_ioctl(const struct chardriver *cdp, message *m_ptr)
364 {
365 /* Carry out an I/O control task request. */
366   devminor_t minor;
367   unsigned long request;
368   cp_grant_id_t grant;
369   endpoint_t endpt, user_endpt;
370   int flags;
371   cdev_id_t id;
372 
373   /* Default action if no ioctl hook is in place. */
374   if (cdp->cdr_ioctl == NULL)
375 	return ENOTTY;
376 
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;
385 
386   return cdp->cdr_ioctl(minor, request, endpt, grant, flags, user_endpt, id);
387 }
388 
389 /*===========================================================================*
390  *				do_cancel				     *
391  *===========================================================================*/
392 static int do_cancel(const struct chardriver *cdp, message *m_ptr)
393 {
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.
396  */
397   devminor_t minor;
398   endpoint_t endpt;
399   cdev_id_t id;
400 
401   /* Default action if no cancel hook is in place: let the request finish. */
402   if (cdp->cdr_cancel == NULL)
403 	return EDONTREPLY;
404 
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;
409 
410   return cdp->cdr_cancel(minor, endpt, id);
411 }
412 
413 /*===========================================================================*
414  *				do_select				     *
415  *===========================================================================*/
416 static int do_select(const struct chardriver *cdp, message *m_ptr)
417 {
418 /* Perform a select query on a minor device. */
419   devminor_t minor;
420   unsigned int ops;
421   endpoint_t endpt;
422 
423   /* Default action if no select hook is in place. */
424   if (cdp->cdr_select == NULL)
425 	return EBADF;
426 
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;
431 
432   return cdp->cdr_select(minor, ops, endpt);
433 }
434 
435 /*===========================================================================*
436  *				do_block_open				     *
437  *===========================================================================*/
438 static void do_block_open(message *m_ptr, int ipc_status)
439 {
440 /* Reply to a block driver open request stating there is no such device. */
441   message m_reply;
442 
443   memset(&m_reply, 0, sizeof(m_reply));
444 
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;
448 
449   send_reply(m_ptr->m_source, &m_reply, ipc_status);
450 }
451 
452 /*===========================================================================*
453  *				chardriver_process			     *
454  *===========================================================================*/
455 void chardriver_process(const struct chardriver *cdp, message *m_ptr,
456 	int ipc_status)
457 {
458 /* Call the appropiate driver function, based on the type of request. Send a
459  * reply to the caller if necessary.
460  */
461   int r;
462 
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)) {
466 	case HARDWARE:
467 		if (cdp->cdr_intr)
468 			cdp->cdr_intr(m_ptr->m_notify.interrupts);
469 		break;
470 
471 	case CLOCK:
472 		if (cdp->cdr_alarm)
473 			cdp->cdr_alarm(m_ptr->m_notify.timestamp);
474 		break;
475 
476 	default:
477 		if (cdp->cdr_other)
478 			cdp->cdr_other(m_ptr, ipc_status);
479 	}
480 
481 	return; /* do not send a reply */
482   }
483 
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.
487    */
488   if (m_ptr->m_type == BDEV_OPEN) {
489 	do_block_open(m_ptr, ipc_status);
490 
491 	return;
492   }
493 
494   if (IS_CDEV_RQ(m_ptr->m_type)) {
495 	int minor;
496 
497 	/* Try to retrieve minor device number */
498 	r = chardriver_get_minor(m_ptr, &minor);
499 
500 	if (OK != r)
501 		return;
502 
503 	/* We might get spurious requests if the driver has been restarted.
504 	 * Deny any requests on devices that have not previously been opened.
505 	 */
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 */
510 
511 		/* Mark the device as opened otherwise. */
512 		set_open_dev(minor);
513 	}
514   }
515 
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;
525   default:
526 	if (cdp->cdr_other)
527 		cdp->cdr_other(m_ptr, ipc_status);
528 	return; /* do not send a reply */
529   }
530 
531   chardriver_reply(m_ptr, ipc_status, r);
532 }
533 
534 /*===========================================================================*
535  *				chardriver_terminate			     *
536  *===========================================================================*/
537 void chardriver_terminate(void)
538 {
539 /* Break out of the main loop after finishing the current request. */
540 
541   running = FALSE;
542 
543   sef_cancel();
544 }
545 
546 /*===========================================================================*
547  *				chardriver_task				     *
548  *===========================================================================*/
549 void chardriver_task(const struct chardriver *cdp)
550 {
551 /* Main program of any character device driver task. */
552   int r, ipc_status;
553   message mess;
554 
555   running = TRUE;
556 
557   /* Here is the main loop of the character driver task.  It waits for a
558    * message, carries it out, and sends a reply.
559    */
560   while (running) {
561 	if ((r = sef_receive_status(ANY, &mess, &ipc_status)) != OK) {
562 		if (r == EINTR && !running)
563 			break;
564 
565 		panic("chardriver: sef_receive_status failed: %d", r);
566 	}
567 
568 	chardriver_process(cdp, &mess, ipc_status);
569   }
570 }
571 
572 /*===========================================================================*
573  *				chardriver_get_minor			     *
574  *===========================================================================*/
575 int chardriver_get_minor(const message *m, devminor_t *minor)
576 {
577   assert(NULL != m);
578   assert(NULL != minor);
579 
580   switch(m->m_type)
581   {
582 	case CDEV_OPEN:
583 	case CDEV_CLOSE:
584 	    *minor = m->m_vfs_lchardriver_openclose.minor;
585 	    return OK;
586 	case CDEV_CANCEL:
587 	    *minor = m->m_vfs_lchardriver_cancel.minor;
588 	    return OK;
589 	case CDEV_SELECT:
590 	    *minor = m->m_vfs_lchardriver_select.minor;
591 	    return OK;
592 	case CDEV_READ:
593 	case CDEV_WRITE:
594 	case CDEV_IOCTL:
595 	    *minor = m->m_vfs_lchardriver_readwrite.minor;
596 	    return OK;
597 	default:
598 	    return EINVAL;
599   }
600 }
601