1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13
14#include <sys/cdefs.h>
15#include <sys/param.h>
16#include <sys/cpu.h>
17#include <sys/kernel.h>
18#include <sys/module.h>
19#include <rump-sys/vfs.h>
20#include <sys/ioctl.h>
21#include <sys/termios.h>
22
23#include <sys/stat.h>
24#include <rump-sys/kern.h>
25#include "sel4_stdio.h"
26
27/*
28	This file implements stdio device files that are accessed through the
29	files /dev/stdin, /dev/stdout, /dev/stderr.  Its purpose is to provide input and output over an underlying
30	circular buffer using seL4 notification objects for signalling.
31	 Currently read and write are supported.  Read will block if
32	no input data is available.  There is currently no way to check if data is available
33	to be read without blocking.
34*/
35
36MODULE(MODULE_CLASS_DRIVER, sel4_stdio, NULL);
37
38static struct rumpuser_cv *cvp_serial;
39static struct rumpuser_mtx *mtxp_serial;
40
41
42/* Interrupt handler, it signals a thread waiting for data available in sel4_stdio_read */
43static void get_char_handler(void) {
44    rumpuser_cv_signal(cvp_serial);
45}
46
47static int
48sel4_stdio_open(dev_t dev, int flag, int mode, struct lwp *l)
49{
50	static bool open_once[RR_NUMIO] = {0};
51
52	if (minor(dev) < 0 || minor(dev) >= RR_NUMIO) {
53		panic("sel4_stdio: invalid minor device number");
54	}
55	if (open_once[minor(dev)]) {
56		panic("sel4_stdio only supports each file being opened once and never closed");
57	}
58	open_once[minor(dev)] = true;
59
60	if (rumpcomp_sel4_stdio_init(minor(dev))) {
61		aprint_error("Failed to init sel4_stdio");
62		return -1;
63	}
64
65	/* If stdin */
66	if (minor(dev) == RR_STDIN) {
67		/* Create condition variable and mutex for interrupt handling syncronisation */
68		rumpuser_cv_init(&cvp_serial);
69		rumpuser_mutex_init(&mtxp_serial, RUMPUSER_MTX_SPIN);
70		rumpuser_mutex_enter(mtxp_serial);
71		/* Register input interrupt handler */
72		rumpcomp_register_handler(get_char_handler);
73	}
74	return 0;
75}
76
77/* TODO Deal with carrige return stuff */
78static int
79sel4_stdio_read(dev_t dev, struct uio *uio, int flag)
80{
81	char *buf;
82	size_t len, n;
83	int error = 0;
84	if (minor(dev) != RR_STDIN) {
85		aprint_error("Only stdin supports read");
86		return -1;
87	}
88
89	/* Allocate buffer for read */
90	buf = kmem_alloc(PAGE_SIZE, KM_SLEEP);
91	if (buf == NULL) {
92		aprint_error("kmem_alloc returned null");
93		return -1;
94	}
95	n = 0;
96	len = min(PAGE_SIZE, uio->uio_resid);
97	/* Try and dequeue characters from the buffer
98	   If no characters are available, block on a condition variable
99	   When characters become available, wake up and read out characters.
100	   Blocking only occurs if no characters have been read */
101	while (n < len ) {
102		n += rumpcomp_sel4_stdio_gets(minor(dev), buf, len);
103		if (n == 0) {
104			rumpuser_cv_wait(cvp_serial, mtxp_serial);
105		} else {
106			break;
107		}
108	}
109	/* Move read characters into user buffers */
110	error = uiomove(buf, n, uio);
111
112	kmem_free(buf, PAGE_SIZE);
113
114	return error;
115}
116
117static int
118sel4_stdio_write(dev_t dev, struct uio *uio, int flag)
119{
120
121	char *buf;
122	size_t len;
123	int error = 0;
124	if (minor(dev) == RR_STDIN) {
125		aprint_error("stdin doesn't support write");
126		return -1;
127	}
128	/* Allocate write buffer */
129	buf = kmem_alloc(PAGE_SIZE, KM_SLEEP);
130	if (buf == NULL) {
131		aprint_error("kmem_alloc returned null");
132		return -1;
133	}
134
135	while (uio->uio_resid > 0) {
136		len = min(PAGE_SIZE, uio->uio_resid);
137		error = uiomove(buf, len, uio);
138		if (error)
139			break;
140		rumpcomp_sel4_stdio_puts(minor(dev), buf, len);
141	}
142	kmem_free(buf, PAGE_SIZE);
143
144	return error;
145
146}
147
148static int
149sel4_stdio_ioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
150{
151	/* We do not support tty */
152	if (cmd == TIOCGETA)
153		return 0;
154
155	return ENOTTY;
156
157}
158
159static int
160sel4_stdio_poll(dev_t dev, int events, struct lwp *l)
161{
162	/* No poll currently supported */
163	aprint_normal("poll called.\n");
164
165	return -1;
166}
167
168static int
169sel4_stdio_kqfilter(dev_t dev, struct knote *kn)
170{
171	/* no kqfilter currently supported */
172	aprint_normal("ioctl kqfilter.\n");
173
174	return -1;
175}
176
177
178const struct cdevsw sel4_stdio = {
179	.d_open = sel4_stdio_open,
180	.d_close = nullclose,
181	.d_read = sel4_stdio_read,
182	.d_write = sel4_stdio_write,
183	.d_ioctl = sel4_stdio_ioctl,
184	.d_stop = nostop,
185	.d_tty = notty,
186	.d_poll = sel4_stdio_poll,
187	.d_mmap = nommap,
188	.d_kqfilter = sel4_stdio_kqfilter,
189	.d_discard = nodiscard,
190	.d_flag = D_TTY
191};
192
193
194
195static int
196sel4_stdio_modcmd(modcmd_t cmd, void *arg)
197{
198	devmajor_t bmaj, cmaj;
199
200	bmaj = cmaj = -1;
201	/* Register cdevsw with cmaj driver number. */
202	FLAWLESSCALL(devsw_attach("sel4_stdio", NULL, &bmaj, &sel4_stdio, &cmaj));
203	/* Create character device node using cmaj number we were assigned */
204	FLAWLESSCALL(rump_vfs_makeonedevnode(S_IFCHR, "/dev/stdin", cmaj, RR_STDIN));
205	FLAWLESSCALL(rump_vfs_makeonedevnode(S_IFCHR, "/dev/stdout", cmaj, RR_STDOUT));
206	FLAWLESSCALL(rump_vfs_makeonedevnode(S_IFCHR, "/dev/stderr", cmaj, RR_STDERR));
207	/* Print to init output */
208	aprint_normal("sel4_stdio: Created stdio device files.\n");
209	return 0;
210}