1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 /* This file is dual-licensed; see usr/src/contrib/bhyve/LICENSE */
12 
13 /*
14  * Copyright 2022 Oxide Computer Company
15  */
16 
17 #include <sys/types.h>
18 #include <sys/conf.h>
19 #include <sys/ddi.h>
20 #include <sys/mkdev.h>
21 #include <sys/sunddi.h>
22 #include <sys/id_space.h>
23 #include <sys/stat.h>
24 
25 #include <sys/vmm_drv.h>
26 #include <sys/vmm_drv_test.h>
27 
28 #define	VDT_CTL_NAME	"vmm_drv_test"
29 #define	VDT_CTL_MINOR	0
30 
31 static dev_info_t	*vdt_dip;
32 static void		*vdt_state;
33 static id_space_t	*vdt_minors;
34 
35 typedef struct vdt_soft_state {
36 	kmutex_t	vss_lock;
37 	vmm_hold_t	*vss_hold;
38 } vdt_soft_state_t;
39 
40 
41 static int
vdt_open(dev_t * devp,int flag,int otype,cred_t * cr)42 vdt_open(dev_t *devp, int flag, int otype, cred_t *cr)
43 {
44 	id_t minor;
45 
46 	if (otype != OTYP_CHR) {
47 		return (EINVAL);
48 	}
49 	if (getminor(*devp) != VDT_CTL_MINOR) {
50 		return (ENXIO);
51 	}
52 
53 	minor = id_alloc_nosleep(vdt_minors);
54 	if (minor == -1) {
55 		return (EBUSY);
56 	}
57 	if (ddi_soft_state_zalloc(vdt_state, minor) != DDI_SUCCESS) {
58 		id_free(vdt_minors, minor);
59 		return (ENOMEM);
60 	}
61 
62 	vdt_soft_state_t *ss;
63 	ss = ddi_get_soft_state(vdt_state, minor);
64 	mutex_init(&ss->vss_lock, NULL, MUTEX_DEFAULT, NULL);
65 	*devp = makedevice(getmajor(*devp), minor);
66 
67 	return (0);
68 }
69 
70 static int
vdt_close(dev_t dev,int flag,int otype,cred_t * cr)71 vdt_close(dev_t dev, int flag, int otype, cred_t *cr)
72 {
73 	if (otype != OTYP_CHR) {
74 		return (EINVAL);
75 	}
76 
77 	id_t minor = getminor(dev);
78 	vdt_soft_state_t *ss = ddi_get_soft_state(vdt_state, minor);
79 	if (ss == NULL) {
80 		return (ENXIO);
81 	}
82 
83 	if (ss->vss_hold != NULL) {
84 		vmm_drv_rele(ss->vss_hold);
85 		ss->vss_hold = NULL;
86 	}
87 	mutex_destroy(&ss->vss_lock);
88 	ddi_soft_state_free(vdt_state, minor);
89 	id_free(vdt_minors, minor);
90 
91 	return (0);
92 }
93 
94 static int
vdt_ioc_hold(vdt_soft_state_t * ss,cred_t * cr,int vmm_fd)95 vdt_ioc_hold(vdt_soft_state_t *ss, cred_t *cr, int vmm_fd)
96 {
97 	mutex_enter(&ss->vss_lock);
98 	if (ss->vss_hold != NULL) {
99 		mutex_exit(&ss->vss_lock);
100 		return (EEXIST);
101 	}
102 
103 	file_t *fp = getf(vmm_fd);
104 	if (fp == NULL) {
105 		mutex_exit(&ss->vss_lock);
106 		return (EBADF);
107 	}
108 
109 	int err = vmm_drv_hold(fp, cr, &ss->vss_hold);
110 	releasef(vmm_fd);
111 	mutex_exit(&ss->vss_lock);
112 	return (err);
113 }
114 
115 static int
vdt_ioc_rele(vdt_soft_state_t * ss)116 vdt_ioc_rele(vdt_soft_state_t *ss)
117 {
118 	mutex_enter(&ss->vss_lock);
119 	if (ss->vss_hold == NULL) {
120 		mutex_exit(&ss->vss_lock);
121 		return (ENODEV);
122 	}
123 
124 	vmm_drv_rele(ss->vss_hold);
125 	ss->vss_hold = NULL;
126 	mutex_exit(&ss->vss_lock);
127 	return (0);
128 }
129 
130 static int
vdt_ioctl(dev_t dev,int cmd,intptr_t data,int md,cred_t * cr,int * rv)131 vdt_ioctl(dev_t dev, int cmd, intptr_t data, int md, cred_t *cr, int *rv)
132 {
133 	vdt_soft_state_t *ss = ddi_get_soft_state(vdt_state, getminor(dev));
134 	if (ss == NULL) {
135 		return (ENXIO);
136 	}
137 
138 	int err = 0;
139 	*rv = 0;
140 	switch (cmd) {
141 	case VDT_IOC_HOLD:
142 		err = vdt_ioc_hold(ss, cr, (int)data);
143 		break;
144 	case VDT_IOC_RELE:
145 		err = vdt_ioc_rele(ss);
146 		break;
147 	default:
148 		err = ENOTTY;
149 		break;
150 	}
151 
152 	return (err);
153 }
154 
155 static int
vdt_info(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** result)156 vdt_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
157 {
158 	switch (cmd) {
159 	case DDI_INFO_DEVT2DEVINFO:
160 		*result = (void *)vdt_dip;
161 		return (DDI_SUCCESS);
162 	case DDI_INFO_DEVT2INSTANCE:
163 		*result = (void *)0;
164 		return (DDI_SUCCESS);
165 	default:
166 		return (DDI_FAILURE);
167 	}
168 }
169 
170 static int
vdt_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)171 vdt_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
172 {
173 	if (cmd != DDI_ATTACH) {
174 		return (DDI_FAILURE);
175 	}
176 
177 	if (vdt_dip != NULL) {
178 		return (DDI_FAILURE);
179 	}
180 
181 	/* Create "control" node from which other instances are spawned */
182 	if (ddi_create_minor_node(dip, VDT_CTL_NAME, S_IFCHR, VDT_CTL_MINOR,
183 	    DDI_PSEUDO, 0) != 0) {
184 		return (DDI_FAILURE);
185 	}
186 
187 	ddi_report_dev(dip);
188 	vdt_dip = dip;
189 	return (DDI_SUCCESS);
190 }
191 
192 static int
vdt_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)193 vdt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
194 {
195 	if (cmd != DDI_DETACH) {
196 		return (DDI_FAILURE);
197 	}
198 
199 	ddi_remove_minor_node(vdt_dip, NULL);
200 	vdt_dip = NULL;
201 
202 	return (DDI_SUCCESS);
203 }
204 
205 static struct cb_ops vdt_cb_ops = {
206 	.cb_open	= vdt_open,
207 	.cb_close	= vdt_close,
208 	.cb_strategy	= nodev,
209 	.cb_print	= nodev,
210 	.cb_dump	= nodev,
211 	.cb_read	= nodev,
212 	.cb_write	= nodev,
213 	.cb_ioctl	= vdt_ioctl,
214 	.cb_devmap	= nodev,
215 	.cb_mmap	= nodev,
216 	.cb_segmap	= nodev,
217 	.cb_chpoll	= nochpoll,
218 	.cb_prop_op	= ddi_prop_op,
219 
220 	.cb_str		= NULL,
221 
222 	.cb_flag	= D_NEW | D_MP | D_DEVMAP,
223 	.cb_rev		= CB_REV,
224 	.cb_aread	= nodev,
225 	.cb_awrite	= nodev,
226 };
227 
228 static struct dev_ops vdt_ops = {
229 	.devo_rev	= DEVO_REV,
230 	.devo_refcnt	= 0,
231 
232 	.devo_getinfo	= vdt_info,
233 	.devo_identify	= nulldev,
234 	.devo_probe	= nulldev,
235 	.devo_attach	= vdt_attach,
236 	.devo_detach	= vdt_detach,
237 	.devo_reset	= nodev,
238 	.devo_cb_ops	= &vdt_cb_ops,
239 
240 	.devo_bus_ops	= NULL,
241 	.devo_power	= ddi_power,
242 	.devo_quiesce	= ddi_quiesce_not_needed,
243 };
244 
245 static struct modldrv modldrv = {
246 	&mod_driverops,
247 	"bhyve vmm drv test",
248 	&vdt_ops
249 };
250 
251 static struct modlinkage modlinkage = {
252 	MODREV_1,
253 	&modldrv,
254 	NULL
255 };
256 
257 int
_init(void)258 _init(void)
259 {
260 	int err;
261 
262 	vdt_minors = id_space_create("vmm_drv_test_minors",
263 	    VDT_CTL_MINOR + 1, MAXMIN32);
264 
265 	err = ddi_soft_state_init(&vdt_state, sizeof (vdt_soft_state_t), 0);
266 	if (err != 0) {
267 		return (err);
268 	}
269 
270 	err = mod_install(&modlinkage);
271 	if (err != 0) {
272 		ddi_soft_state_fini(&vdt_state);
273 	}
274 
275 	return (0);
276 }
277 
278 int
_fini(void)279 _fini(void)
280 {
281 	int err = mod_remove(&modlinkage);
282 	if (err != 0) {
283 		return (err);
284 	}
285 
286 	ddi_soft_state_fini(&vdt_state);
287 
288 	id_space_destroy(vdt_minors);
289 
290 	return (0);
291 }
292 
293 int
_info(struct modinfo * modinfop)294 _info(struct modinfo *modinfop)
295 {
296 	return (mod_info(&modlinkage, modinfop));
297 }
298