⬢ DragonFlyBSD Kernel Audit
DF-2343 / umcs_harness.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
/*
 * umcs_harness.c -- verification harness for DF-2343 / DF-2344.
 * Audit target: sys/bus/u4b/serial/umcs.c (MCS7840 USB multiport
 * serial adapter driver), VERBATIM, TU-compiled into the kernel.
 *
 * VERBATIM-INTERPOSITION DESIGN (established pattern, cf. DF-2314
 * pnp.c rig / DF-2300 fwohci rig):
 *   - This TU #includes the UNMODIFIED sys/bus/u4b/serial/umcs.c, so
 *     the audited code runs byte-identical to stock (sha256-gated by
 *     prep.sh against the pristine audit-tree hash).  Because its
 *     statics (umcs7840_attach, umcs7840_calc_baudrate,
 *     umcs7840_callback, ...) become callable, the rig can drive the
 *     REAL driver code with a fabricated USB device.
 *   - Interposed externals: ONLY the ones that cannot execute against
 *     a hardware-less fabricated device -- the synchronous control
 *     transfer (usbd_do_request_proc) is simulated (configurable
 *     per-register values / failure), the bulk/interrupt transfer
 *     plumbing (usbd_transfer_setup/start/stop/poll/unsetup,
 *     usbd_xfer_set_stall) is stubbed (no real endpoints exist), and
 *     the newbus device_t accessors (device_get_softc/get_ivars/
 *     printf, device_set_usb_desc, ucom_set_pnpinfo_usb) are shims.
 *     EVERYTHING ELSE passes through to the stock in-kernel
 *     implementation: the ucom framework (usb_serial.c: ucom_attach
 *     creates the real /dev/ttyU* /dev/cuaU* nodes via the real
 *     ttymalloc/make_dev and runs the real console-attach block at
 *     usb_serial.c:511-535), the tty layer, lockmgr, usb core
 *     helpers.
 *
 * DF-2343 (umcs.c:1053-1069): rate==0 falls through the bucket search
 *   with i==8, evaluates umcs7840_baudrate_divisors[9] (1-element OOB
 *   read of the 9-entry array) and divides by rate==0 -> #DE kernel
 *   panic.  Reached in production via
 *     (a) first open of /dev/ttyU* / cuaU*: usb_serial.c:839-842
 *         ucom_param(tp, &t) with t = sc_it_in, which is never
 *         initialized (zeroed) -> c_ospeed == 0 straight into
 *         ucom_pre_param (usb_serial.c:1680);
 *     (b) USB serial console: usb_serial.c:523-533 with
 *         hw.usb.ucom.cons_baud=0 loader tunable;
 *     while the TIOCSETA* ioctl route is blocked by
 *     sys/kern/tty.c:1033-1041 + usb_serial.c:1670-1674 (verified
 *     live, see tioc0.c).
 *
 * DF-2344 (umcs.c:301/324-325): umcs7840_attach() discards the return
 *   of the GPIO register read; on control-transfer failure `data'
 *   (never initialized) decides the 2-vs-4-port configuration.  The
 *   rig grooms the kernel stack below attach()'s frame with a chosen
 *   byte and shows sc_numports follows the poison, not the device.
 *
 * Control device: /dev/umcsh0 (root-only, 0600).
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/types.h>
#include <sys/lock.h>

/* u4b headers first (in umcs.c's own order: condvar.h provides the
 * struct cv usb_process.h embeds), so the shim prototypes below have
 * complete types */
#include <sys/condvar.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/serial/usb_serial.h>

/*
 * Interposition of the u4b/usb externals that cannot run against a
 * fabricated (hardware-less) USB device.  All headers are already
 * included above, so the object-like renames below only rewrite the
 * call sites inside the verbatim umcs.c body.
 */
#define	usbd_do_request_proc	umcsh_do_request_proc
#define	usbd_transfer_setup	umcsh_transfer_setup
#define	usbd_transfer_unsetup	umcsh_transfer_unsetup
#define	usbd_transfer_start	umcsh_transfer_start
#define	usbd_transfer_stop	umcsh_transfer_stop
#define	usbd_transfer_poll	umcsh_transfer_poll
#define	usbd_xfer_set_stall	umcsh_xfer_set_stall
#define	device_set_usb_desc	umcsh_set_usb_desc
#define	ucom_set_pnpinfo_usb	umcsh_set_pnpinfo
#define	device_printf		umcsh_dev_printf
#define	device_get_ivars	umcsh_dev_get_ivars
#define	device_get_softc	umcsh_dev_get_softc

/* forward prototypes for the shims (defined below the verbatim unit);
 * the renames above rewrite umcs.c's call sites to these */
static usb_error_t	umcsh_do_request_proc(struct usb_device *,
			    struct usb_process *, struct usb_device_request *,
			    void *, uint16_t, uint16_t *, usb_timeout_t);
static usb_error_t	umcsh_transfer_setup(struct usb_device *,
			    const uint8_t *, struct usb_xfer **,
			    const struct usb_config *, uint16_t, void *,
			    struct lock *);
static void		umcsh_transfer_unsetup(struct usb_xfer **, uint16_t);
static void		umcsh_transfer_start(struct usb_xfer *);
static void		umcsh_transfer_stop(struct usb_xfer *);
static void		umcsh_transfer_poll(struct usb_xfer **, uint16_t);
static void		umcsh_xfer_set_stall(struct usb_xfer *);
static void		umcsh_set_usb_desc(device_t);
static void		umcsh_set_pnpinfo(struct ucom_super_softc *, device_t);
static int		umcsh_dev_printf(device_t, const char *, ...);
static void		*umcsh_dev_get_ivars(device_t);
static void		*umcsh_dev_get_softc(device_t);

/*
 * Codegen selector for the verbatim unit.
 *
 * 0 (default): stock kernel codegen (gcc80 -O2, exactly what the
 *   shipping kernel does).  IMPORTANT: at -O2 gcc80 exploits the
 *   div-by-zero UB in umcs7840_calc_baudrate(rate==0) and compiles
 *   the loop-exhausted path to "*divisor=0, *clk=0x80, ret 0" with
 *   NEITHER the divisors[9] load NOR the division executed -- see
 *   adjacency.txt disassembly, path at +0x33.
 *
 * 1 (-O0 semantics): translate the C source literally, so the
 *   source-level defect executes: load umcs7840_baudrate_divisors[9]
 *   (1-element OOB read), then divide by rate==0 -> #DE panic.
 *   This demonstrates the C-level behavior under different codegen
 *   (-O0, other compilers / versions); it is NOT the shipping
 *   behavior.
 */
#define	UMCSH_VERBATIM_CODEGEN_O0	0

#if UMCSH_VERBATIM_CODEGEN_O0
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif

#include "umcs.c"			/* VERBATIM, sha256-gated by prep.sh */

#if UMCSH_VERBATIM_CODEGEN_O0
#pragma GCC pop_options
#endif

#undef	usbd_do_request_proc
#undef	usbd_transfer_setup
#undef	usbd_transfer_unsetup
#undef	usbd_transfer_start
#undef	usbd_transfer_stop
#undef	usbd_transfer_poll
#undef	usbd_xfer_set_stall
#undef	device_set_usb_desc
#undef	ucom_set_pnpinfo_usb
#undef	device_printf
#undef	device_get_ivars
#undef	device_get_softc

#include "umcsh.h"

/* ------------------------------------------------------------------ */
/* fabricated device state                                             */
/* ------------------------------------------------------------------ */

static struct umcs7840_softc umcsh_sc;	/* the REAL driver softc */
static int umcsh_attached;		/* attach() returned 0 at least once */

static struct usb_attach_arg umcsh_uaa = {
	.info = {
		.idVendor = USB_VENDOR_MOSCHIP,
		.idProduct = USB_PRODUCT_MOSCHIP_MCS7840,
		.bIfaceIndex = MCS7840_IFACE_INDEX,
		.bConfigIndex = MCS7840_CONFIG_INDEX,
	},
	.device = NULL,			/* transfers are simulated */
	.usb_mode = USB_MODE_HOST,
};

/* opaque token standing in for the newbus device_t */
static device_t umcsh_fake_dev = (device_t)0xC0FFEE42UL;

/* ------------------------------------------------------------------ */
/* simulated control transfers (the only functional seam)              */
/* ------------------------------------------------------------------ */

static uint32_t sim_fail_mask;		/* UMCSH_AT_* bits */
static uint8_t sim_gpio_val = 0x01;
static uint8_t sim_mode_val = 0x40;	/* EEPROMWR default-ish */
static uint8_t sim_reg_default = 0x00;
static uint32_t sim_rd_cnt, sim_wr_cnt;

static void
sim_reset(void)
{
	sim_fail_mask = 0;
	sim_gpio_val = 0x01;
	sim_mode_val = 0x40;
	sim_reg_default = 0x00;
	sim_rd_cnt = sim_wr_cnt = 0;
}

/*
 * The seam the real driver uses for every register access:
 * umcs7840_get_reg_sync / umcs7840_set_reg_sync / *_UART_reg_sync
 * (umcs.c:914-1001).  Faithful failure semantics: on error NOTHING is
 * written to *data -- exactly like the real usbd_do_request_proc(),
 * which is what leaves `data' uninitialized in umcs7840_attach().
 */
static usb_error_t
umcsh_do_request_proc(struct usb_device *udev, struct usb_process *pp,
    struct usb_device_request *req, void *data, uint16_t flags,
    uint16_t *actlen, uint32_t timeout)
{
	uint16_t index = UGETW(req->wIndex);
	uint16_t value = UGETW(req->wValue);
	uint8_t portno = (uint8_t)(value >> 8);

	if (req->bmRequestType == UT_READ_VENDOR_DEVICE &&
	    req->bRequest == MCS7840_RDREQ) {
		sim_rd_cnt++;
		if ((sim_fail_mask & UMCSH_AT_GPIOFAIL) &&
		    index == MCS7840_DEV_REG_GPIO && value == 0) {
			kprintf("UMCSH SIM: RD  reg 0x%02x -> STALLED "
			    "(no byte written to caller buffer!)\n", index);
			return (USB_ERR_STALLED);
		}
		if ((sim_fail_mask & UMCSH_AT_MODEFAIL) &&
		    index == MCS7840_DEV_REG_MODE && value == 0) {
			kprintf("UMCSH SIM: RD  reg 0x%02x -> STALLED\n", index);
			return (USB_ERR_STALLED);
		}
		*(uint8_t *)data =
		    (index == MCS7840_DEV_REG_GPIO) ? sim_gpio_val :
		    (index == MCS7840_DEV_REG_MODE) ? sim_mode_val :
		    sim_reg_default;
		if (actlen)
			*actlen = 1;
		kprintf("UMCSH SIM: RD  reg 0x%02x port %u -> 0x%02x\n",
		    index, portno, *(uint8_t *)data);
		return (USB_ERR_NORMAL_COMPLETION);
	}
	if (req->bmRequestType == UT_WRITE_VENDOR_DEVICE &&
	    req->bRequest == MCS7840_WRREQ) {
		sim_wr_cnt++;
		kprintf("UMCSH SIM: WR  reg 0x%02x port %u <- 0x%02x\n",
		    index, portno, (uint8_t)(value & 0xff));
		return (USB_ERR_NORMAL_COMPLETION);
	}
	kprintf("UMCSH SIM: unexpected request type 0x%02x/%02x\n",
	    req->bmRequestType, req->bRequest);
	return (USB_ERR_STALLED);
}

/* no real endpoints exist on the fabricated device */
static usb_error_t
umcsh_transfer_setup(struct usb_device *udev, const uint8_t *ifaces,
    struct usb_xfer **pxfer, const struct usb_config *setup_start,
    uint16_t n_setup, void *priv_sc, struct lock *priv_lock)
{
	uint16_t n;

	for (n = 0; n != n_setup; n++)
		pxfer[n] = NULL;
	kprintf("UMCSH SIM: transfer_setup ep 0x%02x n=%u -> OK (NULL)\n",
	    setup_start[0].endpoint, n_setup);
	return (0);
}

static void umcsh_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n) { }
static void umcsh_transfer_start(struct usb_xfer *xfer) { }
static void umcsh_transfer_stop(struct usb_xfer *xfer) { }
static void umcsh_transfer_poll(struct usb_xfer **pxfer, uint16_t n) { }
static void umcsh_xfer_set_stall(struct usb_xfer *xfer) { }
static void umcsh_set_usb_desc(device_t dev) { }
static void umcsh_set_pnpinfo(struct ucom_super_softc *ssc, device_t dev) { }

static int
umcsh_dev_printf(device_t dev, const char *fmt, ...)
{
	__va_list ap;

	kprintf("umcs7840: ");		/* same prefix as on real hardware */
	__va_start(ap, fmt);
	kvprintf(fmt, ap);
	__va_end(ap);
	return (0);
}

static void *
umcsh_dev_get_ivars(device_t dev)
{
	return (&umcsh_uaa);
}

static void *
umcsh_dev_get_softc(device_t dev)
{
	return (&umcsh_sc);
}

/* ------------------------------------------------------------------ */
/* DF-2344: kernel-stack groom                                         */
/*                                                                     */
/* `data' in umcs7840_attach() has its address taken (umcs.c:324), so  */
/* it lives in attach()'s stack frame.  groom_stack() burns a pad of   */
/* the chosen byte immediately before attach() is entered; the frame   */
/* (and the uninitialized slot) lands inside the poisoned pad.         */
/* ------------------------------------------------------------------ */
#define	GROOM_SPAN	8192
static int umcsh_groom_sink;

static __noinline void
groom_stack(uint8_t fill)
{
	volatile char pad[GROOM_SPAN];
	int i;

	for (i = 0; i < GROOM_SPAN; i++)
		pad[i] = (char)fill;
	if (pad[GROOM_SPAN - 1] == (char)0xAB)	/* keep pad materialized */
		umcsh_groom_sink = 1;
}

/* ------------------------------------------------------------------ */
/* scenarios                                                           */
/* ------------------------------------------------------------------ */

static int
umcsh_scenario(struct umcsh_req *r)
{
	uint32_t sc = r->scenario;
	int rc = 0;

	switch (sc) {
	case UMCSH_SC_LAYOUT: {
		const uint32_t *arr = umcs7840_baudrate_divisors;
		const uint32_t *oob = arr + 9;	/* what index 9 fetches */
		int i;

		kprintf("UMCSH layout: umcs7840_baudrate_divisors=%p "
		    "len=%u entries=%u bytes; UMCS7840_MAX_PORTS=%u; "
		    "GPIO_4PORTS bit=0x%02x\n",
		    arr, (unsigned)umcs7840_baudrate_divisors_len,
		    (unsigned)(sizeof(umcs7840_baudrate_divisors)),
		    (unsigned)UMCS7840_MAX_PORTS,
		    (unsigned)MCS7840_DEV_GPIO_4PORTS);
		kprintf("UMCSH layout: entries:");
		for (i = 0; i < (int)umcs7840_baudrate_divisors_len; i++)
			kprintf(" %u", arr[i]);
		kprintf("\n");
		/*
		 * Instrumentation (harness-only) read of divisors[9]:
		 * the exact dword the faulting instruction at umcs.c:1066
		 * loads before dividing by rate==0.
		 */
		r->r_oob_dword = *oob;
		kprintf("UMCSH layout: divisors[9] (OOB, instrumentation) "
		    "@%p = 0x%08x (%u)\n", oob, *oob, *oob);
		kprintf("UMCSH layout: spill context [9]=%08x "
		    "[10]=%08x [11]=%08x [12]=%08x\n",
		    arr[9], arr[10], arr[11], arr[12]);
		break;
	}
	case UMCSH_SC_CALC: {
		uint16_t divisor = 0xDEAD;
		uint8_t clk = 0xEE;
		uint32_t rate = r->arg;

		if (rate == 0 && !(r->arg2 & 1)) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "refusing rate=0 without arg2&1 (it panics on "
			    "the stock kernel)");
			return (EPERM);
		}
		kprintf("UMCSH calc: rate=%u divisor(before)=%u\n",
		    rate, divisor);
		rc = umcs7840_calc_baudrate(rate, &divisor, &clk);
		r->r_divisor = divisor;
		r->r_clk = clk;
		kprintf("UMCSH calc: rate=%u -> rc=%d divisor=%u clk=0x%02x"
		    "\n", rate, rc, divisor, clk);
		break;
	}
	case UMCSH_SC_PREPARAM: {
		struct termios t;

		bzero(&t, sizeof(t));
		t.c_ospeed = r->arg;
		t.c_ispeed = r->arg;
		kprintf("UMCSH preparam: dispatching real "
		    "umcs7840_pre_param(ucom, t.c_ospeed=%u) -- the exact "
		    "call usb_serial.c:1680 makes\n", t.c_ospeed);
		kprintf("UMCSH preparam: ABOUT TO ENTER (stock kernel: "
		    "expect Fatal trap 0 divide error)\n");
		rc = umcs7840_callback.ucom_pre_param(&umcsh_sc.sc_ucom[0],
		    &t);
		kprintf("UMCSH preparam: returned %d (%s panic on stock)\n",
		    rc, rc ? "EINVAL, no" : "accepted, no");
		break;
	}
	case UMCSH_SC_ATTACH: {
		uint8_t poison = (uint8_t)r->arg;

		if (umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "already attached, run DETACH first");
			return (EBUSY);
		}
		sim_reset();
		sim_gpio_val = poison;		/* used when not failing */
		sim_fail_mask = r->arg2;
		bzero(&umcsh_sc, sizeof(umcsh_sc));	/* fresh softc, like newbus */

		kprintf("UMCSH attach: gpio_fail=%u mode_fail=%u "
		    "groom=%u poison=0x%02x gpio_ok_value=0x%02x\n",
		    !!(r->arg2 & UMCSH_AT_GPIOFAIL),
		    !!(r->arg2 & UMCSH_AT_MODEFAIL),
		    !!(r->arg2 & UMCSH_AT_GROOM), poison, sim_gpio_val);
		kprintf("UMCSH attach: ABOUT TO enter real umcs7840_attach"
		    "()\n");
		/* groom LAST: any call between groom_stack() and attach()
		 * would overwrite the poisoned pad with its own frame */
		if (r->arg2 & UMCSH_AT_GROOM)
			groom_stack(poison);
		else
			groom_stack(0x5A);	/* distinctive default residue */
		rc = umcs7840_attach(umcsh_fake_dev);
		umcsh_attached = (rc == 0);
		r->r_numports = umcsh_sc.sc_numports;
		r->r_rd_cnt = sim_rd_cnt;
		r->r_wr_cnt = sim_wr_cnt;
		if (rc == 0)
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "attached, tty=%s",
			    umcsh_sc.sc_super_ucom.sc_ttyname);
		kprintf("UMCSH attach: rc=%d numports=%u rd=%u wr=%u\n",
		    rc, r->r_numports, sim_rd_cnt, sim_wr_cnt);
		break;
	}
	case UMCSH_SC_DETACH:
		if (!umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg), "not attached");
			return (ENXIO);
		}
		rc = umcs7840_detach(umcsh_fake_dev);
		umcsh_attached = 0;
		kprintf("UMCSH detach: rc=%d (numports was %u)\n",
		    rc, umcsh_sc.sc_numports);
		break;
	case UMCSH_SC_SEED_ITIN: {
		struct termios tmpl;
		uint32_t speed = r->arg;

		if (!umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "not attached (seed needs live sc_ucom)");
			return (ENXIO);
		}
		bzero(&tmpl, sizeof(tmpl));
		tmpl.c_cflag = TTYDEF_CFLAG;
		tmpl.c_ospeed = speed;
		tmpl.c_ispeed = speed;
		if (r->arg2 == UMCSH_SEED_IN || r->arg2 == UMCSH_SEED_BOTH)
			umcsh_sc.sc_ucom[0].sc_it_in = tmpl;
		if (r->arg2 == UMCSH_SEED_OUT || r->arg2 == UMCSH_SEED_BOTH)
			umcsh_sc.sc_ucom[0].sc_it_out = tmpl;
		kprintf("UMCSH seed: it_in.c_ospeed=%u it_out.c_ospeed=%u "
		    "(simulating a driver that initializes its termios)\n",
		    umcsh_sc.sc_ucom[0].sc_it_in.c_ospeed,
		    umcsh_sc.sc_ucom[0].sc_it_out.c_ospeed);
		break;
	}
	case UMCSH_SC_STATUS:
		r->r_attached = umcsh_attached;
		r->r_numports = umcsh_sc.sc_numports;
		r->r_rd_cnt = sim_rd_cnt;
		r->r_wr_cnt = sim_wr_cnt;
		ksnprintf(r->r_msg, sizeof(r->r_msg), "attached=%d numports=%u",
		    umcsh_attached, umcsh_sc.sc_numports);
		kprintf("UMCSH status: %s\n", r->r_msg);
		break;
	default:
		rc = EINVAL;
		break;
	}
	return (rc);
}

/* ------------------------------------------------------------------ */
/* control device                                                      */
/* ------------------------------------------------------------------ */
static int	umcsh_ioctl(struct dev_ioctl_args *ap);
static int	umcsh_open(struct dev_open_args *ap);

static int
umcsh_open(struct dev_open_args *ap)
{
	return (0);
}

static int
umcsh_ioctl(struct dev_ioctl_args *ap)
{
	struct umcsh_req r;

	if (ap->a_cmd != UMCSH_IOC)
		return (ENOTTY);
	bcopy(ap->a_data, &r, sizeof(r));
	r.r_rc = (uint32_t)umcsh_scenario(&r);
	bcopy(&r, ap->a_data, sizeof(r));
	/*
	 * Always return 0 so the syscall layer copyouts r (and never sees
	 * a raw negative rc: -1 is ERESTART on DragonFly, which would
	 * restart the ioctl forever).  Scenario results travel in r_rc.
	 */
	return (0);
}

static struct dev_ops umcsh_ops = {
	{ "umcsh", 0, D_MEM },
	.d_open =	umcsh_open,
	.d_ioctl =	umcsh_ioctl,
};

static void
umcsh_init(void *dummy)
{
	make_dev(&umcsh_ops, 0, UID_ROOT, GID_WHEEL, 0600, "umcsh0");
	kprintf("UMCSHARNESS-READY: umcs.c verbatim (TU-compiled) "
	    "/dev/umcsh0 ready\n");
}
SYSINIT(umcs_harness, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, umcsh_init, NULL);