⬢ DragonFlyBSD Kernel Audit
DF-2591 / panasim.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
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/*
 * panasim.c -- verbatim-interposition harness for
 *              sys/dev/acpica/acpi_panasonic/acpi_panasonic.c
 *              (DF-2386, DF-2387, DF-2590, DF-2591)
 *
 * Strategy (same shape as the acpi_hp rig, DF-2383/DF-2384):
 *   - The REAL acpi_panasonic.c is compiled VERBATIM into the kernel
 *     via pana_shim.c (build glue that #defines every ACPI external
 *     the driver calls to a panasim_* implementation BEFORE
 *     #including the sha256-gated verbatim source).  The driver source
 *     itself is byte-identical to the repo file
 *     (sha256 23e49741027f53dcedd05423f7c5eacd1be0dada1c46277dde0be6694b8d0912).
 *   - This file provides the fabricated ACPI world:
 *       panasim_AcpiEvaluateObject   runtime-selectable SINF/HINF/SSET
 *                                    replies (succeed / fail with the
 *                                    buffer untouched exactly like real
 *                                    ACPICA nsxfeval.c:536-600 failure
 *                                    paths / short package with canary
 *                                    tail / tight allocation)
 *       panasim_AcpiInstallNotifyHandler  captures the verbatim
 *                                    acpi_panasonic_notify + ctx so a
 *                                    notify 0x80 can be injected the
 *                                    way ACPICA's deferred dispatch
 *                                    thread would
 *   - The fake acpi_panasonic device is added to the REAL acpi0 via
 *     the real newbus BUS_ADD_CHILD, so the REAL verbatim probe /
 *     attach / sysctl / detach / shutdown code runs unmodified, its
 *     sysctls appear under hw.acpi.panasonic.* (CTLFLAG_ANYBODY,
 *     acpi_panasonic.c:171) and are readable by ANY local user.
 *
 * Findings exercised through this rig:
 *   DF-2386 : sinf AcpiEvaluateObject failure -> res->Type NULL deref
 *             (acpi_panasonic.c:277-279), unprivileged sysctl read
 *   DF-2387 : sinf Elements[index] read without index < Count check
 *             (acpi_panasonic.c:280) -> OOB value to userspace
 *   DF-2590 : hkey_event AcpiEvaluateObject failure -> res->Type NULL
 *             deref (acpi_panasonic.c:403-405), ACPI notify 0x80
 *   DF-2591 : acpi_panasonic_shutdown -> hkey_sound_mute without
 *             ACPI_SERIAL_BEGIN (acpi_panasonic.c:231, assert :374)
 *
 * The interposed SINF/HINF "firmware" lives at exactly the boundary
 * the findings describe: what the real AcpiEvaluateObject would have
 * returned to the verbatim driver.
 */

#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/lock.h>
#include <sys/types.h>
#include <sys/kthread.h>
#include <sys/power.h>

#include "acpi.h"
#include <dev/acpica/acpivar.h>

#include "panasim.h"

#define PANASIM "PANASIM: "

MALLOC_DEFINE(M_PANASIM, "panasim", "PANASIM fabricated ACPI objects");

/* SINF reply modes */
#define	SINF_FAIL	0	/* AE_NO_MEMORY, buffer untouched (DF-2386) */
#define	SINF_CANARY	1	/* Package Count=2, 9-element backing store
				 * with per-index canaries past Count
				 * (DF-2387 attribution) */
#define	SINF_TIGHT	2	/* Package Count=2, 2-element backing store:
				 * reads past the kmalloc = real heap OOB
				 * (DF-2387 leak) */
#define	SINF_TIGHT_SPRAY 3	/* tight + groomed 256-zone neighbors
				 * (marker chunks + freed chunks) */
#define	SINF_SANE	4	/* well-formed 9-element package (control) */

/* HINF reply modes */
#define	HINF_FAIL	0	/* AE_NO_MEMORY, buffer untouched (DF-2590) */
#define	HINF_INT	1	/* integer reply = hinf_val (happy path) */

static int	panasim_sinf_mode = SINF_SANE;
static int	panasim_hinf_mode = HINF_INT;
static int	panasim_hinf_val = 0x81;	/* key 1 pressed */
static int	panasim_power_profile = POWER_PROFILE_PERFORMANCE;
static int	panasim_created;		/* fake device attached */
static int	panasim_notify_seen;		/* acpi_UserNotify reached */
static int	panasim_sinf_calls;
static int	panasim_sset_calls;
static int	panasim_sset_last_index = -1;
static int	panasim_sset_last_val = -1;
static int	panasim_last_sinf_status = -1;

static device_t	panasim_acpi0;
static device_t	panasim_child;

/* captured by the interposed AcpiInstallNotifyHandler */
static ACPI_NOTIFY_HANDLER	panasim_notify_handler;
static void			*panasim_notify_ctx;
static ACPI_HANDLE		panasim_notify_handle;

/* the handle we hand the driver: a real, always-valid ACPICA object
 * (the namespace root) -- stray real-ACPICA use of it (e.g. newbus
 * devd pnpinfo) degrades gracefully to "not found"; the interposed
 * evaluate never looks at it. */
static ACPI_HANDLE	panasim_handle_cookie = ACPI_ROOT_OBJECT;

/* persistent log ring for the status sysctl */
static char		panasim_log[8192];
static size_t		panasim_log_len;
static struct lock	panasim_log_lock;

static void
panasim_vlog(const char *fmt, ...)
{
	__va_list ap;

	kprintf(PANASIM);
	__va_start(ap, fmt);
	kvprintf(fmt, ap);
	__va_end(ap);
	kprintf("\n");

	lockmgr(&panasim_log_lock, LK_EXCLUSIVE);
	if (panasim_log_len < sizeof(panasim_log) - 1) {
		int n;
		__va_start(ap, fmt);
		n = kvsnprintf(panasim_log + panasim_log_len,
		    sizeof(panasim_log) - panasim_log_len - 2, fmt, ap);
		__va_end(ap);
		if (n > 0) {
			if ((size_t)n >= sizeof(panasim_log) - panasim_log_len - 2)
				n = sizeof(panasim_log) - panasim_log_len - 2;
			panasim_log_len += n;
			panasim_log[panasim_log_len++] = '\n';
			panasim_log[panasim_log_len] = 0;
		}
	}
	lockmgr(&panasim_log_lock, LK_RELEASE);
}

/* ------------------------------------------------------------------ */
/* interposed ACPI externals (called by the VERBATIM driver TU)        */
/* ------------------------------------------------------------------ */

/*
 * Fabricate a Package ACPI_OBJECT.  Backing allocation is
 * sizeof(ACPI_OBJECT) [the package object itself, with
 * Package.Elements pointing just past it] + nelts * sizeof(ACPI_OBJECT).
 */
static ACPI_OBJECT *
panasim_make_package(int count, int backed)
{
	ACPI_OBJECT *o, *e;
	size_t sz = sizeof(ACPI_OBJECT) + backed * sizeof(ACPI_OBJECT);
	int i;

	o = kmalloc(sz, M_PANASIM, M_INTWAIT | M_ZERO);
	o->Type = ACPI_TYPE_PACKAGE;
	o->Package.Count = count;
	e = (ACPI_OBJECT *)((char *)o + sizeof(ACPI_OBJECT));
	o->Package.Elements = e;
	for (i = 0; i < backed; i++) {
		e[i].Type = ACPI_TYPE_INTEGER;
		if (i < count) {
			/* sane-looking values for the declared elements */
			e[i].Integer.Value = (i == 2) ? 255 :
			    (i == 3) ? 0 : (i == 4) ? 128 : (i == 8) ? 0 : 0xAA;
		} else {
			/* canary: each index past Count gets its own
			 * pattern C<i>C<i>C<i>C<i>... so the value that
			 * reaches userspace identifies the exact OOB
			 * index that was read */
			e[i].Integer.Value = 0xC0C0C0C0C0C0C0C0ULL +
			    ((UINT64)(i & 0xf) * 0x0101010101010101ULL);
		}
	}
	return (o);
}

/*
 * Interposed AcpiEvaluateObject.  Mirrors the REAL ACPICA contract
 * (sys/contrib/dev/acpica/source/components/namespace/nsxfeval.c,
 * AcpiEvaluateObject :525-631): on ANY failure the ReturnBuffer is
 * left exactly as the caller initialized it -- Pointer stays NULL,
 * nothing is written (the `!Info->ReturnObject -> Length=0` path at
 * :541-545 also never touches Pointer).  That is the failure contract
 * acpi_panasonic_sinf()/acpi_panasonic_hkey_event() violate.
 */
ACPI_STATUS
panasim_AcpiEvaluateObject(ACPI_HANDLE h, ACPI_STRING pathname,
    ACPI_OBJECT_LIST *ExternalParameters, ACPI_BUFFER *ReturnBuffer)
{
	char path[8];
	int i;

	for (i = 0; i < 7 && pathname && pathname[i]; i++)
		path[i] = pathname[i];
	path[i] = 0;

	if (pathname == NULL) {
		/* sset-style activation eval (#if 0 block) is not live */
		panasim_last_sinf_status = -2;
		return (AE_OK);
	}
		if (strcmp(path, "SINF") == 0) {
			panasim_sinf_calls++;
			switch (panasim_sinf_mode) {
			case SINF_FAIL:
				panasim_last_sinf_status = SINF_FAIL;
				panasim_vlog("eval SINF -> AE_NO_MEMORY (buffer "
				    "untouched, Pointer stays NULL)");
				return (AE_NO_MEMORY);
			case SINF_CANARY:
				ReturnBuffer->Pointer =
				    panasim_make_package(2, 9);
				ReturnBuffer->Length = sizeof(ACPI_OBJECT) +
				    9 * sizeof(ACPI_OBJECT);
				panasim_last_sinf_status = SINF_CANARY;
				panasim_vlog("eval SINF -> Package Count=2 "
				    "(9-elem backing, canary tail)");
				return (AE_OK);
			case SINF_TIGHT:
				ReturnBuffer->Pointer =
				    panasim_make_package(2, 2);
				ReturnBuffer->Length = sizeof(ACPI_OBJECT) +
				    2 * sizeof(ACPI_OBJECT);
				panasim_last_sinf_status = SINF_TIGHT;
				panasim_vlog("eval SINF -> Package Count=2 "
				    "(tight 2-elem allocation, nothing past it)");
				return (AE_OK);
			case SINF_TIGHT_SPRAY: {
				/*
				 * tight allocation with groomed neighbors in
				 * the SAME malloc zone: the tight package is
				 * 3 * sizeof(ACPI_OBJECT) = 72 bytes
				 * (ACPI_OBJECT external is 24 bytes); spray
				 * same-size chunks AFTER it so they occupy
				 * the slab space directly behind the package
				 * chunk -- reads past the package land in the
				 * marked 0xEE neighbor bodies.  Attributes
				 * the cross-chunk OOB read.
				 */
				void *s[6];
				size_t psz = 3 * sizeof(ACPI_OBJECT);
				int i;

				ReturnBuffer->Pointer =
				    panasim_make_package(2, 2);
				ReturnBuffer->Length = psz;
				for (i = 0; i < 6; i++) {
					s[i] = kmalloc(psz, M_PANASIM,
					    M_INTWAIT);
					memset(s[i], 0xEE, psz);
				}
				kfree(s[2], M_PANASIM);
				kfree(s[4], M_PANASIM);
				panasim_last_sinf_status = SINF_TIGHT_SPRAY;
				panasim_vlog("eval SINF -> Package Count=2 "
				    "(tight %zu-byte alloc + 6 same-zone "
				    "neighbors marked 0xEE, s[2]/s[4] freed)",
				    psz);
				/* remaining chunks are leaked intentionally
				 * (rig teardown is a reboot) */
				return (AE_OK);
			}
			default:
				ReturnBuffer->Pointer =
				    panasim_make_package(9, 9);
				ReturnBuffer->Length = sizeof(ACPI_OBJECT) +
				    9 * sizeof(ACPI_OBJECT);
				panasim_last_sinf_status = SINF_SANE;
				return (AE_OK);
			}
		}
	if (strcmp(path, "HINF") == 0) {
		if (panasim_hinf_mode == HINF_FAIL) {
			panasim_vlog("eval HINF -> AE_NO_MEMORY (buffer "
			    "untouched, Pointer stays NULL)");
			return (AE_NO_MEMORY);
		} else {
			ACPI_OBJECT *o = kmalloc(sizeof(ACPI_OBJECT),
			    M_PANASIM, M_INTWAIT | M_ZERO);
			o->Type = ACPI_TYPE_INTEGER;
			o->Integer.Value = (UINT64)panasim_hinf_val;
			ReturnBuffer->Pointer = o;
			ReturnBuffer->Length = sizeof(ACPI_OBJECT);
			return (AE_OK);
		}
	}
	if (strcmp(path, "SSET") == 0) {
		panasim_sset_calls++;
		if (ExternalParameters != NULL &&
		    ExternalParameters->Count == 2 &&
		    ExternalParameters->Pointer != NULL) {
			panasim_sset_last_index = (int)ExternalParameters->
			    Pointer[0].Integer.Value;
			panasim_sset_last_val = (int)ExternalParameters->
			    Pointer[1].Integer.Value;
		}
		return (AE_OK);
	}
	return (AE_OK);
}

/* the driver frees exactly what panasim_AcpiEvaluateObject fabricated */
void
panasim_AcpiOsFree(void *Memory)
{
	kfree(Memory, M_PANASIM);
}

/* mirrors real AcpiInstallNotifyHandler (just the registration) */
ACPI_STATUS
panasim_AcpiInstallNotifyHandler(ACPI_HANDLE Handle, UINT32 Type,
    ACPI_NOTIFY_HANDLER Handler, void *Context)
{
	panasim_notify_handler = Handler;
	panasim_notify_ctx = Context;
	panasim_notify_handle = Handle;
	panasim_vlog("install_notify_handler: handler=%p ctx=%p handle=%p",
	    Handler, Context, Handle);
	return (AE_OK);
}

ACPI_STATUS
panasim_AcpiRemoveNotifyHandler(ACPI_HANDLE Handle, UINT32 Type,
    ACPI_NOTIFY_HANDLER Handler)
{
	if (panasim_notify_handler == Handler) {
		panasim_notify_handler = NULL;
		panasim_notify_ctx = NULL;
	}
	return (AE_OK);
}

const char *
panasim_AcpiFormatException(ACPI_STATUS Status)
{
	return ("PANASIM-FABRICATED");
}

int
panasim_acpi_disabled(char *subsys)
{
	return (0);	/* pretend "panasonic" ACPI support is enabled */
}

/* interposed ACPI_ID_PROBE (kobj method call in the verbatim probe):
 * claim the MAT0019 HID match ONLY for the fake child this rig creates
 * (a real ACPI_ID_PROBE compares _HID against "MAT0019" and matches
 * nothing on non-Panasonic firmware; blanket-matching here would let
 * the driver hijack unrelated unit-0 ACPI children at boot). */
char *
panasim_id_probe(device_t bus, device_t dev, char **ids)
{
	if (panasim_child != NULL && dev == panasim_child)
		return (ids[0]);
	return (NULL);
}

void
panasim_acpi_UserNotify(const char *subsystem, ACPI_HANDLE h,
    uint8_t notify)
{
	panasim_notify_seen++;
	panasim_vlog("UserNotify(subsystem=%s val=0x%02x)", subsystem,
	    notify);
}

UINT32
panasim_acpi_event_sleep_button_sleep(void *context)
{
	panasim_vlog("sleep_button_sleep(context=%p) [interposed no-op]",
	    context);
	return (0);
}

int
panasim_power_profile_get_state(void)
{
	return (panasim_power_profile);
}

/* ------------------------------------------------------------------ */
/* fake device plumbing: real newbus child of the REAL acpi0           */
/* ------------------------------------------------------------------ */

static void
panasim_create(void)
{
	devclass_t dc;

	if (panasim_created)
		return;

	dc = devclass_find("acpi");
	if (dc == NULL) {
		panasim_vlog("create: no acpi devclass");
		return;
	}
	panasim_acpi0 = devclass_get_device(dc, 0);
	if (panasim_acpi0 == NULL) {
		panasim_vlog("create: no acpi0");
		return;
	}

	/* real acpi bus add_child: gives the child real ACPI ivars
	 * (pnpinfo at add time sees handle==NULL, like the acpi_hp rig) */
	panasim_child = BUS_ADD_CHILD(panasim_acpi0, panasim_acpi0, 0,
	    "acpi_panasonic", -1);
	if (panasim_child == NULL) {
		panasim_vlog("create: BUS_ADD_CHILD failed");
		return;
	}
	device_quiet(panasim_child);
	acpi_set_handle(panasim_child, panasim_handle_cookie);

	/* runs the REAL verbatim acpi_panasonic_probe + attach */
	if (device_probe_and_attach(panasim_child) != 0) {
		panasim_vlog("create: probe_and_attach FAILED");
		panasim_child = NULL;
		return;
	}
	panasim_created = 1;
	panasim_vlog("created: %s on %s (softc=%p) -- REAL verbatim "
	    "probe/attach ran; hw.acpi.panasonic.* sysctls live",
	    device_get_nameunit(panasim_child),
	    device_get_nameunit(panasim_acpi0),
	    device_get_softc(panasim_child));
}

static void
panasim_destroy(void)
{
	if (!panasim_created)
		return;
	panasim_vlog("destroy: device_delete_child -> real verbatim detach");
	device_delete_child(panasim_acpi0, panasim_child);
	panasim_child = NULL;
	panasim_created = 0;
}

/*
 * Notify injection: mirrors ACPICA deferred notification dispatch
 * (acpi_ev_notify_dispatch runs the handler in its own thread context,
 * no interpreter/ACPI lock held).
 */
static void
panasim_notify_thread(void *arg)
{
	ACPI_NOTIFY_HANDLER h = panasim_notify_handler;
	void *ctx = panasim_notify_ctx;
	ACPI_HANDLE hd = panasim_notify_handle;

	if (h == NULL) {
		panasim_vlog("inject: no notify handler registered");
		return;
	}
	panasim_vlog("inject: dispatching notify 0x%02x to captured "
	    "handler %p(ctx=%p) [ACPI_DEVICE_NOTIFY]", (UINT32)(uintptr_t)arg,
	    h, ctx);
	h(hd, (UINT32)(uintptr_t)arg, ctx);
	panasim_vlog("inject: handler returned");
}

/* ------------------------------------------------------------------ */
/* command plane: structural operations run in this worker kthread     */
/* (a sysctl handler must not create/destroy sysctls or newbus         */
/* devices reentrantly)                                                */
/* ------------------------------------------------------------------ */

static int		panasim_cmd;
static volatile int	panasim_cmdgen;
static int		panasim_cmd_done;

static void
panasim_worker(void *arg)
{
	int lastgen = 0;

	for (;;) {
		tsleep(&panasim_cmdgen, 0, "panawkr", 0);
		if (panasim_cmdgen == lastgen)
			continue;
		lastgen = panasim_cmdgen;
		panasim_cmd_done = 0;
		panasim_vlog("worker: exec cmd=%d", panasim_cmd);
		switch (panasim_cmd) {
		case 1:
			panasim_create();
			break;
		case 2:
			panasim_destroy();
			break;
		case 3:		/* inject ACPI notify 0x80 */
			kthread_create(panasim_notify_thread,
			    (void *)(uintptr_t)0x80, NULL, "pananotify");
			break;
		case 9:
			/*
			 * DF-2591: call the REAL device_shutdown path
			 * (subr_bus.c:2154-2162 -> DEVICE_SHUTDOWN ->
			 * verbatim acpi_panasonic_shutdown) WITHOUT the
			 * serial -- exactly what MOD_SHUTDOWN does for
			 * every device at reboot (subr_bus.c:3549-3551
			 * -> device_shutdown(root_bus) ->
			 * bus_generic_shutdown).
			 */
			if (panasim_created) {
				panasim_vlog("worker: device_shutdown(%s) "
				    "-- verbatim "
				    "acpi_panasonic_shutdown begins",
				    device_get_nameunit(panasim_child));
				device_shutdown(panasim_child);
				panasim_vlog("worker: device_shutdown "
				    "returned (no assert fired)");
			}
			break;
		default:
			break;
		}
		panasim_vlog("worker: cmd=%d DONE", panasim_cmd);
		panasim_cmd_done = lastgen;
		wakeup(&panasim_cmd_done);
	}
}

static int
panasim_sysctl_cmd(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (v < 1 || (v > 3 && v != 9))
		return (EINVAL);
	panasim_cmd = v;
	panasim_cmdgen++;
	wakeup(&panasim_cmdgen);
	return (0);
}

static int
panasim_sysctl_log(SYSCTL_HANDLER_ARGS)
{
	int len = strlen(panasim_log);

	return (SYSCTL_OUT(req, panasim_log, len));
}

SYSCTL_NODE(_hw, OID_AUTO, panasim, CTLFLAG_RD, 0,
    "acpi_panasonic verbatim-interposition rig");

SYSCTL_PROC(_hw_panasim, OID_AUTO, cmd, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, panasim_sysctl_cmd, "I",
    "worker cmd: 1=create fake dev 2=destroy 3=inject notify 0x80 "
    "9=device_shutdown(dev)");
SYSCTL_PROC(_hw_panasim, OID_AUTO, log, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, panasim_sysctl_log, "A", "rig log");
SYSCTL_INT(_hw_panasim, OID_AUTO, sinf_mode, CTLFLAG_RW,
    &panasim_sinf_mode, 0,
    "SINF reply: 0=FAIL 1=Count2+canary 2=Count2 tight 3=tight+spray 4=sane9");
SYSCTL_INT(_hw_panasim, OID_AUTO, hinf_mode, CTLFLAG_RW,
    &panasim_hinf_mode, 0, "HINF reply: 0=FAIL 1=integer");
SYSCTL_INT(_hw_panasim, OID_AUTO, hinf_val, CTLFLAG_RW,
    &panasim_hinf_val, 0, "HINF integer value (0x81 = key1 press)");
SYSCTL_INT(_hw_panasim, OID_AUTO, power_profile, CTLFLAG_RW,
    &panasim_power_profile, 0, "power_profile_get_state() reply");
SYSCTL_INT(_hw_panasim, OID_AUTO, created, CTLFLAG_RD,
    &panasim_created, 0, "fake acpi_panasonic device attached");
SYSCTL_INT(_hw_panasim, OID_AUTO, cmd_done, CTLFLAG_RD,
    &panasim_cmd_done, 0,
    "generation of the last finished worker cmd");
SYSCTL_INT(_hw_panasim, OID_AUTO, sinf_calls, CTLFLAG_RD,
    &panasim_sinf_calls, 0, "SINF evaluations served");
SYSCTL_INT(_hw_panasim, OID_AUTO, sset_calls, CTLFLAG_RD,
    &panasim_sset_calls, 0, "SSET evaluations served");
SYSCTL_INT(_hw_panasim, OID_AUTO, sset_last_index, CTLFLAG_RD,
    &panasim_sset_last_index, 0, "last SSET index arg");
SYSCTL_INT(_hw_panasim, OID_AUTO, sset_last_val, CTLFLAG_RD,
    &panasim_sset_last_val, 0, "last SSET value arg");
SYSCTL_INT(_hw_panasim, OID_AUTO, last_sinf_status, CTLFLAG_RD,
    &panasim_last_sinf_status, 0, "mode of the last SINF reply");
SYSCTL_INT(_hw_panasim, OID_AUTO, notify_seen, CTLFLAG_RD,
    &panasim_notify_seen, 0, "acpi_UserNotify calls reached");

static void
panasim_boot(void *arg)
{
	lockinit(&panasim_log_lock, "panalog", 0, 0);
	kthread_create(panasim_worker, NULL, NULL, "panawork");
	kprintf(PANASIM "harness up: sizeof(ACPI_OBJECT)=%zu "
	    "9-element package alloc=%zu\n", sizeof(ACPI_OBJECT),
	    sizeof(ACPI_OBJECT) + 9 * sizeof(ACPI_OBJECT));
	panasim_vlog("harness up (interposed externs active; verbatim "
	    "driver compiled in via pana_shim.c)");
}

SYSINIT(panasim, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, panasim_boot, NULL);