Hi Hackers,

I wanted to share with you some work I've been doing for a couple of
months with the spin verifier and OS C code. [1]

Although not directly and immediately relevant to current kernel
development, this may be relevant to folks thinking about architecture
and future directions in managing design complexity.

As I was working on plumbing the ARC code [1] to an actual block driver,
it dawned on me that there may be additional advantages to writing code
based on formal models, other than just consistency verification of
existing production code. So while I'm getting ahead of myself, let me
re-start in a reasonable middle of this narrative, and describe how I
got here.

Intro:
Software must be the only "Engineering" discipline, where the equivalent
of drawing up an architectural masterplan before you build a skyscraper
in software, is to start by getting some shovels and start digging the
basement. While there are a few efforts driven by industry ("inspired"
by regulation, possibly) [2] [3], I really couldn't find any open source
attempts at ongoing, continuously integrated work that is in lockstep
with production sourcecode, with the exception of academic work in
kernel code verification, eg: [4],  or much smaller codebases such as
seL4 [5]. (Please feel free to point me at everything I've surely
missed).

I think we're thinking about the problem from the wrong end. Inspired by
"Use the Source, Luke", I realised that perhaps we should approach the
problem of systems software development using the old unix/engineering
principles of modularity and clean interfaces.

"D3" - Design Driven Development:
Let's say we want to develop a new block driver interface that "front"s
an existing block device, say for eg: as a first step to implementing a
cache of some sort. In addition, let's assume that we've been given the
requirement that this driver needs to be available on several OSs. As a
software architect, how do you put something concrete down on paper,
which serves as the "Source of Truth", before hammering on the actual
drivers ?

I propose, that a formal model, specified using a modelling language can
serve this purpose. However, a formal model alone, seems to be
insufficient. Most formal models seem to exist in a sort of echo chamber
of their own, where the model sits aside from actual production code,
or the other extreme, where implementation code itself serves as the
source of truth, instead of a more abstract formal spec.

While looking for a verifier tool that addresses this problem of the
"gap" between a model and the implementation code written to express
that model, I came across the model extractor from the spin
verifier. [6]. Modex really blew my mind. For the first time, I could
write a complete loop starting from:

a) A Formal model to represent concurrent processes and the joint
   evolution of their state.
b) Invariants expressed in linear temporal logic, to reason about bounds
   within which this model should operate.
c) An implementation in a programming language based on eyeballed model
   code from "a)" above.
d) A model extractor that could parse the above implementation code, and
   pull it back into a formal model representation such as in "a)"
   above.
e) And finally the ability to apply invariants in "b)" , originally
   meant for the abstract model in "a)" over the extracted model from
   "d)".

If there were inconsistencies in step "e)", while none in "b)", this
could mean that either the extractor hadn't pulled out the right model,
or the implementation had strayed from that specified in "a)"
above. This would then require further eyeballing, patching, and
repeating steps "c)" -> "e)" until the extracted model "d)" and the hand
crafted model "a)" satisfied the invariants in "b)".

The key assumption here (which I have no way of proving is true) is that
the satisfaction of invariants in "b)" above, could stand in place for
equivalence of model and code. Thus we have a development process that
I'm calling "Design Driven Development" (or "D3" in short form) which
allows for iterative tightening up of implemented code to a formal
specification.

Discussion:
If one were a software architect in a larger project, one could just
focus on the correctness of one's model, while software engineers could
focus on implementing the model faithfully, using the D3 loop above to
ensure fidelity.

I believe this is really powerful - as I'm new to this area, I would
love to hear from folks that have used this or analogous techniques in
industry or open-source (ideally both), to get a sense of where my
current thinking is, with respect to the industry.

Please find attached, a long-ish patch that is heavily WIP. It attempts
to model a "generic" block driver in spin's promela language. The model
is in cbd.pm[hl] , the invariants are in cbd.inv. The implementation of
the model for linux is in linux/cbd.c ( you can use the Makefile to
compile it as an actual linux module). The implementation of the model
for NetBSD is in NetBSD/cbd.c (ditto - instructions in respective
Makefile). Note that I haven't actually tested the driver - it will most
likely crash, as I haven't really looked at the engineering side of
things yet - but thought I should publish this right now, to get early
feedback, and also give a sense of why actual CI testing is still needed
to ensure that the final implementation plumbing is right.

The "extraction" is specified in a "harness" defined in files with
extensions "*.prx", suplimented by LUTs to help translate C code back to
the extracted model. This is where the age of spin/modex becomes evident
- clearly the extraction language needs to be a lot more expressive (I
see it analagous to the expressivity in a regex machine, vs. a
parser). This is an area I'd love to see more work on, to the point
where we have a tool (if not already available) that can enable
something like D3 as the gold standard for software quality and
reliability.

For architects, it takes off the burden of having to rely
on their memory and random tests and bespoke models to keep state. For
engineers, it provides a clear "source of truth" and a process based
mechanism to verify their implementation based on this source. And for
project managers, there's much better visibility over the entire QA,
analogous to but much more powerful than a simple TDD style development
process.

Thank you for your attention - I hope to hear back from you  - do please
Cc: me if you're replying to list. Especially if you actively work on
formally verified infrastructure code where QA is a regulatory need
(avionics ? MISRA-C related constraints ? healthcare ?)

Patch inlined below. To try it out, you need to have 'spin' installed
using your favourite package manager, and modex [6] compiled , with both
binaries in $PATH.

Then you can try things out:

1) Deploy sources to local directory: $ patch -tp1 < cbd.diff

2) Check out the model. $ make spin-gen; less spinmodel.pml

3) Try to verify the model (see various commented out #defines in
   spinmodel.pml if you want to spread your wings.):
   $ make spin-run

4) Try to extract the model from linux/cbd.c:
   $ make clean modex-linux; less spinmodel.pml
   You can try to verify this extracted spinmodel.pml via 3) above.

5) Try to extract the model from NetBSD/cbd.c:
   $ make clean modex-NetBSD; less spinmodel.pml
   You can try to verify this extracted spinmodel.pml via 3) above.

Looking forward!

Many Thanks,

MattC (/~cherry)

[1] https://mail-index.netbsd.org/tech-kern/2023/09/28/msg029203.html
[2] https://en.wikipedia.org/wiki/Model-based_systems_engineering
[3] https://en.wikipedia.org/wiki/Model-driven_engineering
[4] https://unsat.cs.washington.edu/papers/nelson-hyperkernel.pdf
[5] https://sel4.systems/
[6] https://github.com/nimble-code/Modex


diff -urN cbd.null/cbd.drv cbd/cbd.drv
--- cbd.null/cbd.drv    1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.drv 2023-10-19 17:56:08.023048865 +0000
@@ -0,0 +1,299 @@
+/* Spin model driver for NetBSD cbd(4) cbd.c */
+
+/*
+ * Copyright 2023, Mathew, Cherry G. <c...@bow.st>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:  
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. 
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+   
+#include "cbd.pmh"
+
+/* We drive the routines concurrently */
+#define NPROCS 2
+#define index_pid (_pid - 1)
+
+/* Per-device instance state. Currently we only have one instance */
+bit sc_lock;  /* Driver instance lock */
+unsigned cbdevsz : DEVSZBITS; /* Same as size of the backing block dev */
+
+unsigned cbdev : DEVIDBITS;     /* Our Device "handle" */
+unsigned backingdev : DEVIDBITS;  /* Backing device "handle" */
+unsigned cachingdev : DEVIDBITS;  /* Caching device "handle" */
+
+/* Aggregated traffic stats (per-device) */
+unsigned cbdbytesread : DEVSZBITS;
+unsigned cbdbyteswritten : DEVSZBITS;;
+
+/*
+ * Helper functions - better as inline because:
+ * - spin parser doesn't care about location vs. preprocessor does.
+ * - trace output looks better
+ */
+hidden int error[NPROCS];   /* Error indicator for last op */
+
+inline SETERROR(_errno)
+{
+       error[index_pid] = _errno;
+}
+
+
+inline
+mutex_enter(_mutex)
+{
+       atomic {
+               (_mutex == 0) -> _mutex = 1;
+       }
+}
+
+inline
+mutex_exit(_mutex)
+{
+       atomic {
+               (_mutex == 1) -> _mutex = 0;
+       }
+}
+
+/* Set state to "Configured", so that ioctl() can work */
+inline
+cbd_init()
+{
+       if
+       :: cbd_unconfigured() ->
+          cbdev = CBD_DEVID_CBD1;
+       :: else -> SETERROR(EBUSY);
+          skip;
+       fi
+}
+
+/* Undo init() */
+inline
+cbd_fini()
+{
+       do
+       :: cbd_backed(cbdev) ->
+               mutex_enter(sc_lock);
+               cbd_detach_backend(CBD_DEVID_BACKING);
+               mutex_exit(sc_lock);
+       :: cbd_configured(cbdev) ->
+               mutex_enter(sc_lock);
+               if
+               :: cbd_backed(cbdev) -> /* Lost race. Retry */
+                  mutex_exit(sc_lock);
+               :: else ->
+                  cbdev = CBD_DEVID_NONE;
+                  mutex_exit(sc_lock);
+                  break;
+               fi
+       :: else -> skip; /* Retry until things quiesce */
+       od
+}
+
+inline
+io_backing(backingdev, bp)
+{
+       /* Aggregate i/o data */
+       if
+       :: is_set(bp.direction, B_READ) ->
+          /* Increment bytes read */
+          cbdbytesread = cbdbytesread + bp.spooled;
+       :: else -> /* Assume B_WRITE */
+          /* Increment bytes written */
+          cbdbyteswritten = cbdbyteswritten + bp.spooled;
+       fi
+
+}
+
+inline
+ioctl_backing(_dev, _cmd, _data, _flag)
+{
+       /* Wouldn't have got here without specific intention.
+        * Therefore we just make sure we're in the right state.
+        */
+       SETERROR((cbd_backed(_dev) -> 0 : ENXIO));
+}
+
+inline
+reset_cbdstate()
+{
+       sc_lock = 0;
+       cbdbytesread = 0;
+       cbdbyteswritten = 0;
+       cbdev = CBD_DEVID_NONE;
+       cbdevsz = 0;    
+       backingdev = CBD_DEVID_NONE;
+       cachingdev = CBD_DEVID_NONE;    
+}
+
+bit strategy = 0;
+bit ioctl = 0;
+bit configured = 0;
+bit rdwr = 0;
+
+inline
+drive_cbd(_dev, _flags, _buffer,
+         _cmd, _data)
+{
+       /* Start with state that covers max code */
+       _dev = CBD_DEVID_CBD1;
+       set_flag(_flags, FREAD | FWRITE);       
+
+       _buffer.error = 0;
+       _buffer.address = 0;
+       _buffer.offset = 0;
+       _buffer.length = CBD_BACKING_DISKSIZE; /* Try moving the whole buffer */
+       _buffer.spooled = 0;
+       _buffer.dev = CBD_DEVID_NONE;
+       set_flag(_buffer.direction, B_READ);
+
+       _cmd = CBDIO_ATTACH_BACKEND;
+       _data = CBD_DEVID_BACKING;
+
+       do
+       ::
+       if
+       :: (!configured) ->
+          cbd_init();
+          configured = 1;
+       :: (ioctl) ->
+          cbdioctl(_dev, _cmd, _data, _flags);
+          if
+//#define EXPLORE_IOCTL
+#ifdef EXPLORE_IOCTL
+          :: (_dev > CBD_DEVID_NONE) -> _dev--;
+          :: (_dev < CBD_DEVID_CACHING) -> _dev++;
+#endif                    
+          :: (_cmd > CBDIO_INVAL) -> _cmd--;
+          :: (_cmd < CBDIO_GET_STATUS) -> _cmd++;
+          :: (_data > CBD_DEVID_NONE) -> _data--;
+          :: (_data < CBD_DEVID_CACHING) -> _data++;
+          :: is_set(_flags, FREAD) -> clear_flag(_flags, FREAD); 
set_flag(_flags, FWRITE);
+          :: is_set(_flags, FWRITE) -> clear_flag(_flags, FWRITE); 
set_flag(_flags, FREAD);
+          :: else -> set_flag(_flags, FREAD);
+          fi
+          ioctl = 0;
+
+       :: (!ioctl) -> ioctl = 1;
+       :: (strategy) ->
+          mutex_enter(sc_lock);        
+          cbdstrategy(_buffer);
+          mutex_exit(sc_lock);
+//#define EXPLORE_STRATEGY
+#ifdef EXPLORE_STRATEGY        
+          if
+          :: is_set(_buffer.direction, B_WRITE) ->
+                 clear_flag(_buffer.direction, B_WRITE);
+                 set_flag(_buffer.direction, B_READ);
+          :: is_set(_buffer.direction, B_READ) ->
+                 clear_flag(_buffer.direction, B_READ);
+                 set_flag(_buffer.direction, B_WRITE);
+          :: else ->
+                 set_flag(_buffer.direction, B_READ);
+          fi
+          strategy = 0;
+# endif           
+       :: (!strategy) -> strategy = 1;
+//#define EXPLORE_BUFFERSTATE
+#ifdef  EXPLORE_BUFFERSTATE
+       :: (_buffer.offset < (CBD_BACKING_DISKSIZE + 1)) ->
+                _buffer.offset++;
+       :: (_buffer.length < (CBD_BACKING_DISKSIZE + 2)) ->
+                _buffer.length++;
+       :: (_buffer.address < VAMAX) -> _buffer.address++;
+       :: (_buffer.spooled < (CBD_BACKING_DISKSIZE + 2)) ->
+                _buffer.spooled++;
+       :: (_buffer.offset > 0) -> _buffer.length--;
+       :: (_buffer.length > 0) -> _buffer.length--;
+       :: (_buffer.spooled > 0) -> _buffer.spooled--;
+       :: (_buffer.address > 0) -> _buffer.address--;
+#endif
+//#define EXPLORE_READWRITESTATE
+#ifdef  EXPLORE_READWRITESTATE
+       :: (rdwr) ->
+          cbdread(_dev, _buffer);
+          cbdwrite(_dev, _buffer);
+          rdwr = 0;
+       :: (!rdwr) -> rdwr = 1;
+       
+       :: (configured) ->
+          cbd_fini();
+          configured = 0;
+#endif    
+       :: else ->
+                 break;
+       fi
+
+       od
+}
+
+/* Per proc data to drive the state machine */
+typedef user_vars_t {
+       unsigned dev : DEVIDBITS;
+       unsigned flags : FLAGBITS;
+       buf_t buffer;
+       unsigned cmd : IOCMDBITS;
+
+       /* IRL, this would be eg: the backing device node path.
+        * For now we just pass a fake DEVIDXXX "handle" that
+        * represents the backing device.
+        */
+
+       unsigned data : DEVIDBITS;      
+};
+
+/* Drive functions through various state variations */
+proctype
+p_drive_cbd()
+{
+       /* Setup initial per-proc caller values. These will get
+        * iterated in the inline callees below below
+        */
+       user_vars_t user_vars[NPROCS];
+       
+       drive_cbd(user_vars[index_pid].dev, user_vars[index_pid].flags,
+                 user_vars[index_pid].buffer, user_vars[index_pid].cmd,
+                 user_vars[index_pid].data);
+
+}
+
+/* Drive the procs */
+init {
+
+       /* Initialise Device params */
+       reset_cbdstate();
+
+       pid proc;
+
+       /* Spin up NPROCS concurrent processes */
+       atomic {
+                proc = 0;
+               do
+                :: proc < NPROCS ->
+                          run p_drive_cbd();
+                          proc++
+               :: else ->
+                       break
+               od
+       }
+
+}
diff -urN cbd.null/cbd.inv cbd/cbd.inv
--- cbd.null/cbd.inv    1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.inv 2023-10-18 17:08:10.842776509 +0000
@@ -0,0 +1,49 @@
+/* Spin process invariants for NetBSD cbd(4) cbd.c */
+
+/*
+ * Copyright 2023, MattC <c...@bow.st>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:  
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. 
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+ltl
+{
+/* Checks while outside critical section - ie; kernel state is stable. */
+       always ((sc_lock == 0) implies (
+               /* Device state basic consistency */
+               (cbd_configured(cbdev) implies
+                 !cbd_unconfigured()) &&
+               /* Active ioctl passthrough implies configured device */        
+               (cbd_backed(cbdev) implies
+                 cbd_configured(cbdev)) &&
+               /* There will be I/O eventually */
+               (eventually always (cbdbytesread + cbdbyteswritten) > 0)
+               ) /* (sc_lock == 0) */
+       ) /* always */ //&&
+//     always ((sc_lock == 1) implies (
+              /* Lock must always be released */
+//            (eventually (sc_lock == 0))
+//            ) /* (sc_lock == 1) */
+//     ) /* always */ // &&
+}
diff -urN cbd.null/cbdioctl.linux.lut cbd/cbdioctl.linux.lut
--- cbd.null/cbdioctl.linux.lut 1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbdioctl.linux.lut      2023-10-23 08:14:11.902411553 +0000
@@ -0,0 +1,15 @@
+// cbd_ioctl(...) -> cbdioctl(...)
+
+cbd_backed(cbp)                cbd_backed(_dev)
+
+Substitute     c_expr { 222 == cmd }   (_cmd == CBDIO_ATTACH_BACKEND)
+Substitute     c_expr { 333 == cmd }   (_cmd == CBDIO_DETACH_BACKEND)
+
+Substitute     c_expr { (cbp==0) }     false
+Substitute     c_expr { (!cbd_configured(cbp)) }       !cbd_configured(_dev)
+Substitute     c_expr { (!cbd_backed(cbp)) }   !cbd_backed(_dev)
+Substitute     c_code { err=(-ENXIO); }        SETERROR(ENXIO)
+Substitute     c_code { err=(-EBUSY); }        SETERROR(EBUSY)
+Substitute     c_code { err=(-ENODEV); }       SETERROR(ENODEV)
+Substitute     c_code { err=(-EPERM); }        SETERROR(EPERM)
+Substitute     c_code { err=(-ENOTTY); }       skip
diff -urN cbd.null/cbdioctl.NetBSD.lut cbd/cbdioctl.NetBSD.lut
--- cbd.null/cbdioctl.NetBSD.lut        1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbdioctl.NetBSD.lut     2023-10-23 08:23:23.430620615 +0000
@@ -0,0 +1,16 @@
+// cbd_ioctl(...) -> cbdioctl(...)
+
+cbd_backed(sc)         cbd_backed(_dev)
+
+Substitute     c_expr { (sc==0) }      false
+Substitute     c_expr { 222 == cmd }   (_cmd == CBDIO_ATTACH_BACKEND)
+Substitute     c_expr { 333 == cmd }   (_cmd == CBDIO_DETACH_BACKEND)
+Substitute     c_code { error=EBUSY; } SETERROR(EBUSY)
+Substitute     c_code { error=ENODEV; }        SETERROR(ENODEV)
+Substitute     c_code { error=EPERM; } SETERROR(EPERM)
+
+Substitute     c_code { error=cbd_attach_backend(sc,(&(cbdioctlargs))); }      
cbd_attach_backend(_dev, _data)
+Substitute     c_code { error=cbd_detach_backend(sc); }        
cbd_detach_backend(_data)
+Substitute     c_expr { (!cbd_backed(sc)) }    !cbd_backed(_dev)
+
+
diff -urN cbd.null/cbdattachbackend.linux.lut cbd/cbdattachbackend.linux.lut
--- cbd.null/cbdattachbackend.linux.lut 1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbdattachbackend.linux.lut      2023-10-23 08:12:51.340517105 +0000
@@ -0,0 +1 @@
+Substitute     c_expr { IS_ERR(backingdev) }   false
diff -urN cbd.null/cbdattachbackend.NetBSD.lut cbd/cbdattachbackend.NetBSD.lut
--- cbd.null/cbdattachbackend.NetBSD.lut        1970-01-01 00:00:00.000000000 
+0000
+++ cbd/cbdattachbackend.NetBSD.lut     2023-10-23 06:56:21.559519110 +0000
@@ -0,0 +1,2 @@
+// Hard hammer - XXX: Find a better way to handle error ?
+Substitute     c_expr { (error!=0) }   false
diff -urN cbd.null/cbd.linux.prx cbd/cbd.linux.prx
--- cbd.null/cbd.linux.prx      1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.linux.prx   2023-10-23 08:16:16.155489259 +0000
@@ -0,0 +1,217 @@
+// Spin model extractor harness for linux/cbd.c written by cherry
+//
+%F linux/cbd.c
+%X -n modexbug // Somehow this is needed to activate per-func LUT
+%X -L cbdstrategy.linux -n cbd_submit_bio
+%X -L cbdioctl.linux -n cbd_ioctl
+%X -L cbdattachbackend.linux -n cbd_attach_backend
+%X -n cbd_detach_backend
+%H
+// Disable effects of all included files and try to implement a subset of the 
APIs they provide.
+#define _LINUX_BLKDEV_H
+
+// linux/blk_types.h
+#define SECTOR_SHIFT 9
+typedef void struct block_device;
+typedef void blk_mode_t;
+
+#define __LINUX_MUTEX_H
+struct mutex {
+       void *dummy;
+};
+
+// sys/null.h
+#define        NULL    0
+
+#define true 1
+#define false 0
+
+typedef int bool;
+
+/* Linuxisms */
+#define __user
+#define __init
+#define __exit
+
+#define module_init(x)
+#define module_exit(x)
+#define MODULE_LICENSE(L)
+
+%%
+//%C // c_code {}
+//%%
+//%D // c_cdecl {}
+//%%
+%L // XXX: Looks like per function tables are broken for now.
+// Common transformations
+
+NonState               hidden  cbd_major       global
+NonState               hidden  exclusive       global
+NonState               hidden  cbd_fops        global
+NonState               hidden  exclusive       global
+
+mutex_lock(...         mutex_enter(sc_lock)
+mutex_unlock(...       mutex_exit(sc_lock)
+
+// cbd_submit_bio(...) -> cbdstrategy(...)
+// See cbdstrategy.linux.lut broad substs which need scope limiting.
+NonState               hidden  bio     cbd_submit_bio
+NonState               hidden  cbp     cbd_submit_bio
+NonState               hidden  bdev    cbd_submit_bio
+NonState               hidden  bio_offset      cbd_submit_bio
+NonState               hidden  bdevsz  cbd_submit_bio
+
+// Gen-ed Code substitution. This is due to a lack of a sensible parser 
harness language with the right semantics.
+Substitute     c_expr { ((bio_offset+bio->bi_iter.bi_size)>bdevsz) }   
((bp.offset + bp.length) > cbdevsz)
+Substitute     c_expr { (bio_op(bio)&REQ_OP_READ) }    is_set(bp.direction, 
B_READ)
+Substitute     c_code { bio_offset=((bio_offset>bdevsz) ? bdevsz : 
bio_offset); }      bp.offset = ((bp.offset > cbdevsz) -> cbdevsz : bp.offset)
+Substitute     c_code [bio] { bio->bi_iter.bi_size=(bdevsz-bio_offset); }      
bp.length = (cbdevsz - bp.offset)
+Substitute     c_code [bio] { bio->bi_iter.bi_sector=(bio_offset>>9); }        
skip
+Substitute     c_code [bio] { bio->bi_status=BLK_STS_NOTSUPP; }        
bp.error = EINVAL;
+Substitute     c_code [bio] { bio->bi_status=BLK_STS_OFFLINE; }        
bp.error = ENXIO
+
+// cbd_ioctl(...) -> cbdioctl(...)
+// See cbdioctl.linux.lut broad substs which need scope limiting.
+NonState               hidden  userdata        cbd_ioctl
+NonState               hidden  arg     cbd_ioctl
+NonState               hidden  cmd     cbd_ioctl
+NonState               hidden  mode    cbd_ioctl
+NonState               hidden  bdev    cbd_ioctl
+NonState               hidden  cbp     cbd_ioctl
+NonState               hidden  err     cbd_ioctl
+
+
+Substitute     c_expr { (!(mode&BLK_OPEN_WRITE)) }     !is_set(_flag, FWRITE)
+Substitute     c_code [bdev->bd_disk && bdev] { 
cbp=bdev->bd_disk->private_data; }     skip
+Substitute     c_code { err=cbd_attach_backend(cbp,userdata); }        
cbd_attach_backend(_dev,_data)
+Substitute     c_code { err=cbd_detach_backend(cbp); } 
cbd_detach_backend(_data)
+Substitute     c_code [cbp && cbp->backingdev->bd_disk->fops && 
cbp->backingdev->bd_disk && cbp->backingdev && cbp] { 
err=cbp->backingdev->bd_disk->fops->ioctl(cbp->backingdev,mode,cmd,arg); }      
 ioctl_backing(backingdev, _cmd, _data, _flag)
+
+// cbd_attach_backend(...) -> cbd_attach_backend()
+// See cbdattachbackend.linux.lut broad substs which need scope limiting.
+NonState               hidden  lockedcbp       cbd_attach_backend
+NonState               hidden  pathname        cbd_attach_backend
+NonState               hidden  userdata        cbd_attach_backend
+NonState               hidden  backingdev      cbd_attach_backend
+
+Substitute     c_expr { (lockedcbp->cbdev==0) }        (_dev != 
CBD_DEVID_CBD1)-> SETERROR(ENODEV)
+Substitute     c_code { pathname=kzalloc(PATH_MAX,GFP_KERNEL); }       skip;
+Substitute     c_expr { (pathname==0) }        false
+Substitute     c_expr { (strncpy_from_user(pathname,(void 
*)userdata,PATH_MAX)==(-EFAULT)) }   (_data != CBD_DEVID_BACKING); 
SETERROR(EINVAL)
+Substitute     c_code { kfree(pathname); }     skip
+Substitute     c_code { 
backingdev=blkdev_get_by_path(strim(pathname),((BLK_OPEN_READ|BLK_OPEN_WRITE)|BLK_OPEN_EXCL),exclusive,0);
 }   skip
+Substitute     c_code [lockedcbp] { lockedcbp->backingdev=backingdev; }        
backingdev = CBD_DEVID_BACKING
+Substitute     c_code [lockedcbp->cbdev && lockedcbp] { 
set_capacity(lockedcbp->cbdev->bd_disk,bdev_nr_sectors(backingdev)); } cbdevsz 
= CBD_BACKING_DISKSIZE
+
+// cbd_detach_backend(...) -> cbd_detach_backend()
+NonState               hidden  lockedcbp       cbd_detach_backend
+
+Substitute     c_expr { ((lockedcbp->cbdev==0)||(lockedcbp->backingdev==0)) }  
(_dev != CBD_DEVID_CBD1 || backingdev != CBD_DEVID_BACKING) -> SETERROR(ENODEV)
+Substitute     c_code [lockedcbp] { 
blkdev_put(lockedcbp->backingdev,exclusive); }     skip
+Substitute     c_code [lockedcbp] { lockedcbp->backingdev=0; } backingdev = 
CBD_DEVID_NONE
+Substitute     c_code [lockedcbp->cbdev && lockedcbp] { 
set_capacity(lockedcbp->cbdev->bd_disk,0); }   cbdevsz = 0
+%%
+
+%P
+hidden int tmpdev; /* Saved during passthrough */
+
+inline
+cbdstrategy(bp)
+{
+       #include "_modex_cbd_submit_bio.pml"
+}
+
+inline
+cbdioctl(_dev, _cmd, _data, _flag)
+{
+       SETERROR(0); /* default */
+       #include "_modex_cbd_ioctl.pml"
+}
+
+inline
+cbd_attach_backend(_dev, _data)
+{
+       SETERROR(0);
+#include "_modex_cbd_attach_backend.pml"
+}
+
+inline
+cbd_detach_backend(_dev)
+{
+       SETERROR(0);
+#include "_modex_cbd_detach_backend.pml"
+}
+
+// linux/cbd.c doesn't have read/write fops, so we
+// fake it to allow cbd.drv to be parsed
+// However See: cbd.drv: -D EXPLORE_READWRITESTATE
+/* For now, we just check re-entrancy consistency.
+ * XXX: uio validation
+ */
+inline
+cbdread(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);
+
+       /* simulate uio -> buf translation */
+       buf_t rd_buf;
+       rd_buf.direction = B_READ;
+       rd_buf.error = 0;
+       rd_buf.length = _uio.length;
+       rd_buf.address = _uio.address;
+       rd_buf.spooled = 0;
+       cbdstrategy(rd_buf);
+
+cbdread_out:
+}
+
+inline
+cbdwrite(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);            
+
+       /* simulate uio -> buf translation */
+       buf_t rw_buf;
+       rw_buf.direction = B_WRITE;
+       rw_buf.error = 0;
+       rw_buf.length = _uio.length;
+       rw_buf.address = _uio.address;
+       rw_buf.spooled = 0;
+
+       cbdstrategy(rw_buf);
+cbdwrite_out:
+}
+
+%%
\ No newline at end of file
diff -urN cbd.null/cbd.NetBSD.prx cbd/cbd.NetBSD.prx
--- cbd.null/cbd.NetBSD.prx     1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.NetBSD.prx  2023-10-23 08:20:37.150517408 +0000
@@ -0,0 +1,232 @@
+// Spin model extractor  harness for NetBSD/cbd.c written by cherry
+//
+%F NetBSD/cbd.c
+%X -n modexbug // Somehow this is needed to activate per-func LUT
+%X -L cbdstrategy.NetBSD -n cbd_strategy
+%X -L cbdioctl.NetBSD -n cbd_ioctl
+%X -L cbdattachbackend.NetBSD -n cbd_attach_backend
+%X -n cbd_detach_backend
+%H
+// Disable effects of all included files and try to implement a subset of the 
APIs they provide.
+
+#define _SYS_TYPES_H_
+#define _SYS_STAT_H_
+#define _SYS_BUF_H
+#define _SYS_BUFQ_H
+#define _SYS_CONF_H
+#define _SYS_DISK_H
+#define _SYS_DISKLABEL_H
+#define _SYS_ERRNO_H
+#define _UVM_PROT_
+#define _SYS_ACL_H
+#define _SYS_VNODE_IF_H_
+#define _SYS_VNODE_H_
+
+typedef void device_t;
+//typedef void *kmutex_t; //Kludgy hack, see below
+#define kmutex_t (void *)
+typedef void dev_t;
+typedef void u_long;
+typedef void buf_t;
+typedef void uint64_t;
+
+/* Override switch arrays */
+void cbd_bdevsw;
+void cbd_cdevsw;
+void cbddkdriver;
+
+#define CFATTACH_DECL3_NEW(_ign1, _ign2, _ign3, _ign4, _ign5, _ign6, _ign7, 
_ign8, _ign9)
+
+// sys/null.h
+#define        NULL    0
+
+#define true 1
+#define false 0
+
+typedef int bool;
+
+#define disk_busy(_dk)
+#define disk_unbusy(_dk, _count, _flags)
+
+#define biodone(_bp)
+
+%%
+//%C // c_code {}
+//%%
+//%D // c_cdecl {}
+//%%
+%L // XXX: Looks like per function tables are broken for now.
+// Common transformations
+
+NonState               hidden  cbd_major       global
+NonState               hidden  cbd_cdevsw      global
+NonState               hidden  sc_lock         global
+
+mutex_enter(...                mutex_enter(sc_lock)
+mutex_exit(...         mutex_exit(sc_lock)
+
+Substitute     c_code { unit=DISKUNIT(dev); }  skip
+Substitute     c_code { sc=device_lookup_private((&(cbd_cd)),unit); }  skip
+
+// cbd_strategy(...) -> cbdstrategy(...)
+NonState               hidden  sc              cbd_strategy
+NonState               hidden  buf             cbd_strategy
+NonState               hidden  dev             cbd_strategy
+NonState               hidden  unit            cbd_strategy
+
+// Gen-ed Code substitution. This is due to a lack of a sensible parser 
harness language with the right semantics.
+Substitute     c_expr { ((dbtob(buf->b_blkno)+buf->b_bcount)>sc->sc_tvn_bsize) 
}       ((bp.offset + bp.length) > cbdevsz)
+Substitute     c_expr { BUF_ISREAD(buf) }      is_set(bp.direction, B_READ)
+Substitute     c_code [sc && sc && buf && buf] { 
buf->b_blkno=((dbtob(buf->b_blkno)>sc->sc_tvn_bsize) ? F: 
btodb(sc->sc_tvn_bsize) : bufb_blkno); }    bp.offset = ((bp.offset > cbdevsz) 
-> cbdevsz : bp.offset)
+Substitute     c_code [buf && sc && buf] { 
buf->b_bcount=(sc->sc_tvn_bsize-dbtob(buf->b_blkno)); }     bp.length = 
(cbdevsz - bp.offset)
+Substitute     c_code [buf] { buf->b_error=EINVAL; }   bp.error = EINVAL
+Substitute     c_code [buf] { buf->b_error=ENXIO; }    bp.error = ENXIO
+
+// cbd_ioctl(...) -> cbdioctl(...)
+NonState               hidden  sc              cbd_ioctl
+NonState               hidden  dev             cbd_ioctl
+NonState               hidden  cmd             cbd_ioctl
+NonState               hidden  data            cbd_ioctl
+NonState               hidden  flag            cbd_ioctl
+NonState               hidden  l               cbd_ioctl
+NonState               hidden  unit            cbd_ioctl
+NonState               hidden  error           cbd_ioctl
+NonState               hidden  cbdioctlargs            cbd_ioctl
+
+Substitute     c_code { cbdioctlargs.diskpath=data; }  skip
+Substitute     c_code { cbdioctlargs.l=l; }    skip
+Substitute     c_expr { (!cbd_configured(sc)) }        !cbd_configured(_dev) 
-> SETERROR(ENXIO)
+Substitute     c_expr { (!(flag&FWRITE)) }     !is_set(_flag, FWRITE)
+Substitute     c_code [l && sc] { 
error=VOP_IOCTL(sc->sc_tvn,cmd,data,flag,l->l_cred); }       
ioctl_backing(backingdev, _cmd, _data, _flag)
+
+// cbd_attach_backend(...) -> cbd_attach_backend()
+NonState               hidden  lockedsc        cbd_attach_backend
+NonState               hidden  cbdioctl        cbd_attach_backend
+NonState               hidden  data    cbd_attach_backend
+NonState               hidden  pbuf    cbd_attach_backend
+NonState               hidden  error   cbd_attach_backend
+NonState               hidden  vp      cbd_attach_backend
+NonState               hidden  numsec  cbd_attach_backend
+NonState               hidden  secsz   cbd_attach_backend
+
+Substitute     c_expr { (lockedsc->sc_dev==0) }        (_dev != 
CBD_DEVID_CBD1)-> SETERROR(ENODEV)
+Substitute     c_code [cbdioctl] { 
error=pathbuf_copyin(cbdioctl->diskpath,(&(pbuf))); }       skip
+Substitute     c_code [cbdioctl] { 
error=vn_bdev_openpath(pbuf,(&(vp)),cbdioctl->l); } if :: (_data != 
CBD_DEVID_BACKING) -> SETERROR(ENODEV) :: else; fi
+Substitute     c_code { pathbuf_destroy(pbuf); }       skip
+Substitute     c_code { error=getdisksize(vp,(&(numsec)),(&(secsz))); }        
skip
+Substitute     c_code [lockedsc] { lockedsc->sc_tvn=vp; }      backingdev = 
CBD_DEVID_BACKING
+Substitute     c_code [lockedsc] { lockedsc->sc_tvn_bsize=(numsec*secsz); }    
cbdevsz = CBD_BACKING_DISKSIZE
+Substitute     c_code [cbdioctl->l && cbdioctl && lockedsc] { 
lockedsc->sc_tvn_l_cred=cbdioctl->l->l_cred; }   skip
+
+
+// cbd_detach_backend(...) -> cbd_detach_backend()
+NonState               hidden  lockedsc        cbd_detach_backend
+
+Substitute     c_expr { 
((((lockedsc->sc_dev==0)||(lockedsc->sc_tvn==0))||(lockedsc->sc_tvn_bsize==0))||(lockedsc->sc_tvn_l_cred==0))
 }        (_dev != CBD_DEVID_CBD1 || backingdev != CBD_DEVID_BACKING) -> 
SETERROR(ENODEV);
+Substitute     c_code [lockedsc && lockedsc] { 
vn_close(lockedsc->sc_tvn,(FREAD|FWRITE),lockedsc->sc_tvn_l_cred); }    skip
+Substitute     c_code [lockedsc] { lockedsc->sc_tvn_bsize=0; } cbdevsz = 0
+Substitute     c_code [lockedsc] { lockedsc->sc_tvn_l_cred=0; }        skip
+Substitute     c_code [lockedsc] { lockedsc->sc_tvn=0; }       backingdev = 
CBD_DEVID_NONE
+%%
+
+%P
+hidden int tmpdev; /* Saved during passthrough */
+
+inline
+cbdstrategy(bp)
+{
+       #include "_modex_cbd_strategy.pml"
+}
+
+inline
+cbdioctl(_dev, _cmd, _data, _flag)
+{
+       SETERROR(0); /* default */
+       #include "_modex_cbd_ioctl.pml"
+}
+
+inline
+cbd_attach_backend(_dev, _data)
+{
+       SETERROR(0);
+#include "_modex_cbd_attach_backend.pml"
+}
+
+inline
+cbd_detach_backend(_dev)
+{
+       SETERROR(0);
+#include "_modex_cbd_detach_backend.pml"
+}
+
+// XXX: please extract below functions.
+/* For now, we just check re-entrancy consistency.
+ * XXX: uio validation
+ */
+inline
+cbdread(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);
+
+       /* simulate uio -> buf translation */
+       buf_t rd_buf;
+       rd_buf.direction = B_READ;
+       rd_buf.error = 0;
+       rd_buf.length = _uio.length;
+       rd_buf.address = _uio.address;
+       rd_buf.spooled = 0;
+       cbdstrategy(rd_buf);
+
+cbdread_out:
+}
+
+inline
+cbdwrite(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);            
+
+       /* simulate uio -> buf translation */
+       buf_t rw_buf;
+       rw_buf.direction = B_WRITE;
+       rw_buf.error = 0;
+       rw_buf.length = _uio.length;
+       rw_buf.address = _uio.address;
+       rw_buf.spooled = 0;
+
+       cbdstrategy(rw_buf);
+cbdwrite_out:
+}
+
+%%
\ No newline at end of file
diff -urN cbd.null/cbd.pmh cbd/cbd.pmh
--- cbd.null/cbd.pmh    1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.pmh 2023-10-22 19:48:38.564639937 +0000
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023, MattC <c...@bow.st>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:  
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. 
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef _CBD_PMH_
+#define _CBD_PMH_
+
+#define ERRBITS 6 /* Add as list below grows */
+
+/* Modelled on NetBSD sys/errno.h */
+#define        EPERM           1               /* Operation not permitted */
+#define        ENOENT          2               /* No such file or directory */
+#define        EIO             5               /* Input/output error */
+#define        ENXIO           6               /* Device not configured */
+#define        EBADF           9               /* Bad file descriptor */
+#define        EBUSY           16              /* Device busy */
+#define        ENODEV          19              /* Operation not supported by 
device */
+#define        EINVAL          22              /* Invalid argument */
+
+#define DEVSZBITS 10 /* Max block device log2(sz) */
+#define DEVIDBITS 4 /* Add more as list below grows */
+
+/* handle IDs for "device"s */
+#define CBD_DEVID_NONE 0
+#define CBD_DEVID_CBD1 1
+#define CBD_DEVID_BACKING 2
+#define CBD_DEVID_CACHING 3
+
+/* "State" values, to report current states, on query */
+#define CBD_UNCONFIGURED 0
+#define CBD_CONFIGURED 1
+#define CBD_BACKED 2
+#define CBD_CACHED 3
+
+#define FLAGBITS 4
+/* Buffer direction flag (mutually exclusive) */
+#define        B_READ          1       /* Read flag. */
+#define        B_WRITE         2       /* Write flag. */
+
+/* dev read()/write(). Write flag imples Read flag */
+#define FREAD 1
+#define FWRITE 3
+
+inline set_flag(_flags, _FLAG) { _flags = (_flags | _FLAG) }
+inline clear_flag(_flags, _FLAG) { _flags = _flags & ~_FLAG }
+#define is_set(_flags, _FLAG) ((_flags & _FLAG) && 1)
+
+#define IOCMDBITS 4
+
+/* ioctl related command codes */
+#define CBDIO_INVAL 0
+#define CBDIO_ATTACH_BACKEND 1
+#define CBDIO_DETACH_BACKEND 2
+#define CBDIO_GET_STATUS 3
+
+#define VABITS DEVSZBITS
+#define VAMAX (1 << (VABITS - 1)) /* A boundary for a "valid" address */
+#define is_valid_address(_addr) (_addr <= VAMAX)
+
+#define CBD_BACKING_DISKSIZE (VAMAX)
+
+/* Device state queries */
+#define cbd_unconfigured() (cbdev == CBD_DEVID_NONE)
+
+/* In a multi-device scenario (out of scope of this
+ * model), this would be a search for the device matching
+ * the arg _dev.
+ */
+
+#define cbd_configured(_dev) ((cbdev == CBD_DEVID_CBD1) && (_dev == cbdev))
+
+#define cbd_backed(_dev) (cbd_configured(_dev) && (backingdev == 
CBD_DEVID_BACKING))
+
+#define cbd_cached(_dev) (cbd_backed() && cachingdev == CBD_DEVID_CACHING)
+
+#define cbd_status(_dev) \
+       ((_dev == CBD_DEVID_NONE) -> CBD_UNCONFIGURED : \
+            ((cbdev == CBD_DEVID_CBD1) -> \
+             ((backingdev == CBD_DEVID_BACKING) -> \
+              ((cachingdev == CBD_DEVID_CACHING) -> \
+                         CBD_CACHED : CBD_BACKED) : \
+                         CBD_CONFIGURED) : \
+                         CBD_UNCONFIGURED))
+
+
+typedef buf_t {
+       unsigned error : ERRBITS;               /* error flagged during 
processing */
+       unsigned address : VABITS;              /* address representation */
+       unsigned offset  : DEVSZBITS;           /* Offset into device */
+       unsigned length : DEVSZBITS;            /* total length of buffer */
+       unsigned spooled : DEVSZBITS;           /* length processed so far */   
+       unsigned direction : FLAGBITS;          /* whether callee is to read 
from or write to */
+       unsigned dev : DEVIDBITS;               /* Device involved */
+};
+
+
+#endif  /* _CBD_PMH_ */
\ No newline at end of file
diff -urN cbd.null/cbd.pml cbd/cbd.pml
--- cbd.null/cbd.pml    1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.pml 2023-10-22 21:00:12.814145286 +0000
@@ -0,0 +1,261 @@
+/* Spin process models for NetBSD cbd(4) cbd.c */
+
+/*
+ * Copyright 2023, MattC <c...@bow.st>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:  
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. 
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "cbd.pmh"
+
+hidden int tmpdev; /* Saved during passthrough */
+inline
+cbdstrategy(/* buf_t */ bp)
+{
+
+
+       /* sanity check buffer */
+
+       if
+       :: ((bp.offset + bp.length) > cbdevsz) ->
+          if
+          :: is_set(bp.direction, B_READ) ->
+             /* Clip the requested length.
+              * Total length effectively read will be in
+              * bp.spooled
+              */
+             bp.offset = ((bp.offset > cbdevsz) -> cbdevsz :
+                                     bp.offset);
+             bp.length = (cbdevsz - bp.offset);
+                                     
+             skip;
+
+          :: is_set(bp.direction, B_WRITE) ->
+             bp.error = EINVAL;
+          fi
+          goto strategy_out;
+       :: else ->
+          skip;
+       fi
+       
+       if /* Always check if device is still active before acting */
+       :: cbd_backed(bp.dev) ->
+          tmpdev = bp.dev;
+          mutex_enter(sc_lock);                   
+          bp.dev = backingdev; /* Set for callee */
+          io_backing(backingdev, bp);
+          bp.dev = tmpdev; /* Reset for caller */
+          mutex_exit(sc_lock);    
+       :: else -> /* Fallback - don't block */
+                  bp.error = ENXIO;
+                  goto strategy_out;
+       fi
+strategy_out:
+
+}
+
+/* Attach backing disk device specified in data */
+
+inline
+cbd_attach_backend(_dev, _data)
+{
+       SETERROR(0);
+
+       /* Simulate data validation */  
+       if
+       :: (_dev != CBD_DEVID_CBD1 || /* Device validity */
+           _data != CBD_DEVID_BACKING) ->
+             SETERROR(ENODEV);
+             goto attach_out;
+       :: else ->
+             /* IRL, this would come from the backing dev */
+             cbdevsz = CBD_BACKING_DISKSIZE;
+             backingdev = _data;
+       fi
+
+attach_out:
+}
+
+/* Detach backing disk device specified in data */
+inline
+cbd_detach_backend(_dev)
+{
+       SETERROR(0);
+
+       /* Simulate dev state validation */     
+
+       if
+       :: (_dev != CBD_DEVID_CBD1 ||
+           backingdev != CBD_DEVID_BACKING) ->
+             SETERROR(ENODEV);
+             goto detach_out;
+       :: else ->
+             backingdev = CBD_DEVID_NONE;
+             cbdevsz = 0;
+       fi
+
+detach_out:
+}
+
+inline
+cbdioctl(_dev, _cmd, _data, _flag)
+{
+       SETERROR(0); /* default */
+
+       mutex_enter(sc_lock);   
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(ENXIO);
+          goto ioctl_out; /* Invalid device ID */
+       :: else ->
+          skip;
+       fi
+
+       if
+       :: (_cmd == CBDIO_ATTACH_BACKEND) ->
+          /* Simulate attaching a backing device */
+          if
+          :: cbd_backed(_dev) -> /* Already backed. */
+             SETERROR(EBUSY); /* return EBUSY */
+             goto ioctl_out; 
+          :: else ->
+             skip;
+          fi
+          if
+          :: !is_set(_flag, FWRITE) -> /* Semantic inconsistency */
+             SETERROR(EPERM);
+             goto ioctl_out; 
+          :: else ->
+             skip;
+          fi
+          cbd_attach_backend(_dev, _data);
+          goto ioctl_out;
+
+        :: (_cmd == CBDIO_DETACH_BACKEND) ->
+          /* Simulate detaching a backing device */
+          if
+          :: !cbd_backed(_dev) -> /* Nothing to detach. */
+             SETERROR(ENODEV); 
+             goto ioctl_out; 
+          :: else ->
+             skip;
+          fi
+          if
+          :: !is_set(_flag, FWRITE) -> /* Semantic inconsistency */
+             SETERROR(EPERM);
+             goto ioctl_out; 
+          :: else ->
+             skip;
+          fi
+          cbd_detach_backend(_dev);
+          goto ioctl_out;
+
+       :: (_cmd == CBDIO_GET_STATUS) ->
+          /* Simulate Query */
+          /* dev "handle" as proxy for "status" */
+          _data = cbd_status(_dev);
+          goto ioctl_out; /* NOP */
+       :: (_cmd == CBDIO_INVAL) -> /* Adversarial/careless user */ 
+          SETERROR(EINVAL);
+          goto ioctl_out;
+       :: else -> 
+          skip; /* Pass through to backing device ioctl() */
+       fi
+
+       /* Anything else is passed to the backing disk */
+       ioctl_backing(backingdev, _cmd, _data, _flag);
+
+ioctl_out:
+       mutex_exit(sc_lock);
+}
+
+/* For now, we just check re-entrancy consistency.
+ * XXX: uio validation
+ */
+inline
+cbdread(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(ENXIO);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);
+
+       /* simulate uio -> buf translation */
+       buf_t rd_buf;
+       rd_buf.direction = B_READ;
+       rd_buf.error = 0;
+       rd_buf.length = _uio.length;
+       rd_buf.address = _uio.address;
+       rd_buf.spooled = 0;
+       cbdstrategy(rd_buf);
+
+cbdread_out:
+}
+
+inline
+cbdwrite(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(ENXIO);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);            
+
+       /* simulate uio -> buf translation */
+       buf_t rw_buf;
+       rw_buf.direction = B_WRITE;
+       rw_buf.error = 0;
+       rw_buf.length = _uio.length;
+       rw_buf.address = _uio.address;
+       rw_buf.spooled = 0;
+
+       cbdstrategy(rw_buf);
+cbdwrite_out:
+}
+
diff -urN cbd.null/cbd.prx cbd/cbd.prx
--- cbd.null/cbd.prx    1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbd.prx 2023-10-19 20:12:35.838587981 +0000
@@ -0,0 +1,218 @@
+// Spin model extractor harness written by cherry
+//
+%F linux/cbd.c
+%X -n modexbug // Somehow this is needed to activate per-func LUT
+
+// See cbdstrategy for translation table only for *function()*s
+%X -L cbdstrategy -n cbd_submit_bio
+// See cbdioctl.lut for translation table only for *function()*s
+%X -L cbdioctl -n cbd_ioctl
+
+%X -n cbd_attach_backend
+%X -n cbd_detach_backend
+%H
+// Disable effects of all included files and try to implement a subset of the 
APIs they provide.
+#define _LINUX_BLKDEV_H
+
+// linux/blk_types.h
+#define SECTOR_SHIFT 9
+typedef void struct block_device;
+typedef void blk_mode_t;
+
+#define __LINUX_MUTEX_H
+struct mutex {
+       void *dummy;
+};
+
+// sys/null.h
+#define        NULL    0
+
+#define true 1
+#define false 0
+
+typedef int bool;
+
+/* Linuxisms */
+#define __user
+#define __init
+#define __exit
+
+#define module_init(x)
+#define module_exit(x)
+#define MODULE_LICENSE(L)
+
+%%
+//%C // c_code {}
+//%%
+//%D // c_cdecl {}
+//%%
+%L // XXX: Looks like per function tables are broken for now.
+// Common transformations
+
+NonState               hidden  cbd_major       global
+NonState               hidden  exclusive       global
+NonState               hidden  cbd_fops        global
+NonState               hidden  exclusive       global
+
+mutex_lock(...         mutex_enter(sc_lock)
+mutex_unlock(...       mutex_exit(sc_lock)
+
+// cbd_submit_bio(...) -> cbdstrategy(...)
+NonState               hidden  bio     cbd_submit_bio
+NonState               hidden  cbp     cbd_submit_bio
+NonState               hidden  bdev    cbd_submit_bio
+NonState               hidden  bio_offset      cbd_submit_bio
+NonState               hidden  bdevsz  cbd_submit_bio
+
+// Gen-ed Code substitution. This is due to a lack of a sensible parser 
harness language with the right semantics.
+Substitute     c_expr { ((bio_offset+bio->bi_iter.bi_size)>bdevsz) }   
((bp.offset + bp.length) > cbdevsz)
+Substitute     c_expr { (bio_op(bio)&REQ_OP_READ) }    is_set(bp.direction, 
B_READ)
+Substitute     c_code { bio_offset=((bio_offset>bdevsz) ? bdevsz : 
bio_offset); }      bp.offset = ((bp.offset > cbdevsz) -> cbdevsz : bp.offset)
+Substitute     c_code [bio] { bio->bi_iter.bi_size=(bdevsz-bio_offset); }      
bp.length = (cbdevsz - bp.offset)
+Substitute     c_code [bio] { bio->bi_iter.bi_sector=(bio_offset>>9); }        
skip
+Substitute     c_code [bio] { bio->bi_status=BLK_STS_IOERR; }  bp.error = EIO;
+Substitute     c_code [bio] { bio->bi_status=BLK_STS_OFFLINE; }        
bp.error = ENXIO
+
+// cbd_ioctl(...) -> cbdioctl(...)
+NonState               hidden  userdata        cbd_ioctl
+NonState               hidden  arg     cbd_ioctl
+NonState               hidden  cmd     cbd_ioctl
+NonState               hidden  mode    cbd_ioctl
+NonState               hidden  bdev    cbd_ioctl
+NonState               hidden  cbp     cbd_ioctl
+NonState               hidden  err     cbd_ioctl
+
+
+Substitute     c_expr { (!cbd_configured(cbp)) }       cbd_configured(_dev)
+Substitute     c_code { err=(-ENXIO); }        SETERROR(ENXIO)
+Substitute     c_expr { 222 == cmd }   (_cmd == CBDIO_ATTACH_BACKEND)
+Substitute     c_expr { 333 == cmd }   (_cmd == CBDIO_DETACH_BACKEND)
+Substitute     c_code [bdev->bd_disk && bdev] { 
cbp=bdev->bd_disk->private_data; }     skip
+Substitute     c_code { err=cbd_attach_backend(cbp,userdata); }        
cbd_attach_backend(_data)
+Substitute     c_code { err=cbd_detach_backend(cbp); } 
cbd_detach_backend(_data)
+Substitute     c_code [cbp && cbp->backingdev->bd_disk->fops && 
cbp->backingdev->bd_disk && cbp->backingdev && cbp] { 
err=cbp->backingdev->bd_disk->fops->ioctl(cbp->backingdev,mode,cmd,arg); }      
 ioctl_backing(backingdev, _cmd, _data, _flag)
+Substitute     c_code { err=(-ENOTTY); }       skip
+
+// cbd_attach_backend(...) -> cbd_attach_backend()
+NonState               hidden  lockedcbp       cbd_attach_backend
+NonState               hidden  pathname        cbd_attach_backend
+NonState               hidden  userdata        cbd_attach_backend
+NonState               hidden  backingdev      cbd_attach_backend
+
+Substitute     c_code { pathname=kzalloc(PATH_MAX,GFP_KERNEL); }       skip;
+Substitute     c_expr { (pathname==0) }        false
+Substitute     c_expr { (strncpy_from_user(pathname,(void 
*)userdata,PATH_MAX)==(-EFAULT)) }   (_data != CBD_DEVID_BACKING); 
SETERROR(EINVAL)
+Substitute     c_code { kfree(pathname); }     skip
+Substitute     c_code { 
backingdev=blkdev_get_by_path(strim(pathname),((BLK_OPEN_READ|BLK_OPEN_WRITE)|BLK_OPEN_EXCL),exclusive,0);
 }   skip
+Substitute     c_expr { IS_ERR(backingdev) }   false
+Substitute     c_code [lockedcbp] { lockedcbp->backingdev=backingdev; }        
backingdev = CBD_DEVID_BACKING
+Substitute     c_code [lockedcbp->cbdev && lockedcbp] { 
set_capacity(lockedcbp->cbdev->bd_disk,bdev_nr_sectors(backingdev)); } cbdevsz 
= CBD_BACKING_DISKSIZE
+
+NonState               hidden  lockedcbp       cbd_detach_backend
+Substitute     c_code [lockedcbp] { 
blkdev_put(lockedcbp->backingdev,exclusive); }     skip
+Substitute     c_code [lockedcbp] { lockedcbp->backingdev=0; } backingdev = 
CBD_DEVID_NONE
+Substitute     c_code [lockedcbp->cbdev && lockedcbp] { 
set_capacity(lockedcbp->cbdev->bd_disk,0); }   cbdevsz = 0
+%%
+
+%P
+hidden int tmpdev; /* Saved during passthrough */
+
+inline
+cbdstrategy(bp)
+{
+#include "_modex_cbd_submit_bio.pml"
+}
+
+inline
+cbdioctl(_dev, _cmd, _data, _flag)
+{
+#include "_modex_cbd_ioctl.pml"
+}
+
+inline
+cbd_attach_backend(_data)
+{
+       SETERROR(0);
+#include "_modex_cbd_attach_backend.pml"
+}
+
+inline
+cbd_detach_backend(_data)
+{
+       SETERROR(0);
+#include "_modex_cbd_detach_backend.pml"
+}
+
+// linux/cbd.c doesn't have read/write fops, so we
+// fake it to allow cbd.drv to be parsed
+// However See: cbd.drv: -D EXPLORE_READWRITESTATE
+/* For now, we just check re-entrancy consistency.
+ * XXX: uio validation
+ */
+inline
+cbdread(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdread_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);
+
+       /* simulate uio -> buf translation */
+       buf_t rd_buf;
+       rd_buf.direction = B_READ;
+       rd_buf.error = 0;
+       rd_buf.length = _uio.length;
+       rd_buf.address = _uio.address;
+       rd_buf.spooled = 0;
+       cbdstrategy(rd_buf);
+
+cbdread_out:
+}
+
+inline
+cbdwrite(_dev, _uio)
+{
+       SETERROR(0);
+
+       /* Check for backing disk */
+       mutex_enter(sc_lock);
+       if
+       :: !cbd_configured(_dev) ->
+          SETERROR(EINVAL);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: !cbd_backed(_dev) ->
+          SETERROR(ENOENT);
+          mutex_exit(sc_lock);    
+          goto cbdwrite_out; 
+       :: else ->
+          skip;
+       fi
+       mutex_exit(sc_lock);            
+
+       /* simulate uio -> buf translation */
+       buf_t rw_buf;
+       rw_buf.direction = B_WRITE;
+       rw_buf.error = 0;
+       rw_buf.length = _uio.length;
+       rw_buf.address = _uio.address;
+       rw_buf.spooled = 0;
+
+       cbdstrategy(rw_buf);
+cbdwrite_out:
+}
+
+%%
\ No newline at end of file
diff -urN cbd.null/cbdstrategy.linux.lut cbd/cbdstrategy.linux.lut
--- cbd.null/cbdstrategy.linux.lut      1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbdstrategy.linux.lut   2023-10-19 13:12:04.576568138 +0000
@@ -0,0 +1,7 @@
+// cbd_submit_bio(...) -> cbdstrategy(...)
+
+cbd_backed(cbp)                cbd_backed(bp.dev)
+bio_set_dev(...                tmpdev = bp.dev; bp.dev = backingdev
+submit_bio_noacct(bio) io_backing(backingdev, bp); bp.dev = tmpdev
+
+
diff -urN cbd.null/cbdstrategy.NetBSD.lut cbd/cbdstrategy.NetBSD.lut
--- cbd.null/cbdstrategy.NetBSD.lut     1970-01-01 00:00:00.000000000 +0000
+++ cbd/cbdstrategy.NetBSD.lut  2023-10-23 08:23:18.598269036 +0000
@@ -0,0 +1,10 @@
+// cbd_strategy(...) -> cbdstrategy(...)
+
+cbd_backed(sc)         cbd_backed(bp.dev)
+VOP_STRATEGY(sc->sc_tvn,buf)   tmpdev = bp.dev; bp.dev = backingdev; 
io_backing(backingdev, bp); bp.dev = tmpdev
+
+Substitute     c_expr { (sc==0) }      false
+
+
+
+
diff -urN cbd.null/linux/cbd.c cbd/linux/cbd.c
--- cbd.null/linux/cbd.c        1970-01-01 00:00:00.000000000 +0000
+++ cbd/linux/cbd.c     2023-10-22 21:10:18.387421555 +0000
@@ -0,0 +1,315 @@
+#include <linux/mutex.h>
+#include <linux/blkdev.h>
+
+#define CBD_ATTACH_BACKEND 222 /* XXX: fit into ioctl namespace */
+#define CBD_DETACH_BACKEND 333 /* XXX: ditto */
+
+
+/* Driver-wide values that don't change over the life of the module */
+
+/* At the moment, we assume that there's 1:1 correspondence between  a
+ * driver instance (ie; module_init/exit pair) and a device
+ * instance. Thus we use cbd_major below as the primary "key" to
+ * lookup device state, via blk_get_no_open(). Note - this is not
+ * related to "minor" devices, which can still use cbd_major as
+ * above.
+ * XXX: Fix this.
+ */
+static int cbd_major = 0;
+static char *exclusive = "cbd_ioctl";
+
+struct cbd_private {
+       struct block_device *cbdev;     /* Back ref for state tracking
+                                        * - see cbd_configured()
+                                        * below
+                                        */ 
+       struct block_device *backingdev;
+       struct mutex cbdlock;
+};
+
+/* Driver state enquiry */
+static bool
+cbd_configured(struct cbd_private *cbp)
+{
+       return (cbp->cbdev != NULL);
+}
+
+/* Implies attached */
+static bool
+cbd_backed(struct cbd_private *cbp)
+{
+       // XXX: check sc->sc_size == backing disk size 
+       return (cbd_configured(cbp) && cbp->backingdev != NULL);
+}
+
+static int
+cbd_attach_backend(struct cbd_private *lockedcbp, void *userdata)
+{
+       char *pathname;
+       struct block_device *backingdev;
+
+       /* XXX: proper dev validation */
+       if (lockedcbp->cbdev == NULL) {
+               return -ENODEV;
+       }
+
+       pathname = kzalloc(PATH_MAX, GFP_KERNEL);
+
+       if (pathname == NULL) {
+               return -ENOMEM;
+       }
+       
+       if (strncpy_from_user(pathname, (void __user *)userdata,
+                             PATH_MAX) == -EFAULT) {
+               kfree(pathname);
+               return -EFAULT;
+       }
+
+       backingdev = blkdev_get_by_path(strim(pathname),
+                                       
BLK_OPEN_READ|BLK_OPEN_WRITE|BLK_OPEN_EXCL,
+                                       exclusive, NULL);
+
+       kfree(pathname);
+       
+       if (IS_ERR(backingdev)) {
+               return PTR_ERR(backingdev);
+       }
+
+       set_capacity(lockedcbp->cbdev->bd_disk, bdev_nr_sectors(backingdev));
+       lockedcbp->backingdev = backingdev;
+
+
+       return 0;
+}
+
+static int
+cbd_detach_backend(struct cbd_private *lockedcbp)
+{
+       /* XXX: proper dev state validity */
+       if (lockedcbp->cbdev == NULL ||
+           lockedcbp->backingdev == NULL) {
+               return -ENODEV;
+       }
+
+       blkdev_put(lockedcbp->backingdev, exclusive);
+
+       lockedcbp->backingdev = NULL;
+       set_capacity(lockedcbp->cbdev->bd_disk, 0);
+
+       return 0;
+}
+
+static int cbd_ioctl(struct block_device *bdev, blk_mode_t mode,
+                       unsigned cmd, unsigned long arg)
+{
+       int err = 0;
+       void __user *userdata = (void __user *)arg;
+       struct cbd_private *cbp;
+       cbp = bdev->bd_disk->private_data;
+
+       if (cbp == NULL) {
+               err = -ENXIO;
+               goto ioctl_out;
+       }
+
+       mutex_lock(&cbp->cbdlock);
+
+       if (!cbd_configured(cbp)) {
+               err = -ENXIO;
+               goto ioctl_out;
+       }
+                
+       switch(cmd) {
+       case CBD_ATTACH_BACKEND:
+               if (cbd_backed(cbp)) {
+                       err = -EBUSY;
+                       break;
+               }
+               if (!(mode & BLK_OPEN_WRITE)) {
+                       err = -EPERM;
+                       break;
+               }
+               err = cbd_attach_backend(cbp, userdata);
+               break;
+       case CBD_DETACH_BACKEND: /* XXX: */
+               if (!cbd_backed(cbp)) {
+                       err = -ENODEV;
+                       break;
+               }
+               if (!(mode & BLK_OPEN_WRITE)) {
+                       err = -EPERM;
+                       break;
+               }
+               err = cbd_detach_backend(cbp);
+               break;
+       default: /*Pass through */
+               if (cbd_backed(cbp)) {
+                       err = cbp->backingdev->bd_disk->fops->
+                               ioctl(cbp->backingdev,  mode, cmd, arg);
+               } else {
+                               err = -ENOTTY;
+               }
+               break;
+       }
+ioctl_out:
+       mutex_unlock(&cbp->cbdlock);
+       return err;
+}
+
+static void cbd_submit_bio(struct bio *bio)
+{
+       struct block_device *bdev = bio->bi_bdev;
+       struct cbd_private *cbp = bdev->bd_disk->private_data;
+
+       size_t bio_offset = bio->bi_iter.bi_sector << SECTOR_SHIFT;
+       /* Model assumes this is cached in driver instance after
+        * backing device is attached.
+        */
+       size_t bdevsz = bdev_nr_bytes(bdev);
+
+       /* sanity check bio limits */
+       if ((bio_offset + bio->bi_iter.bi_size) > bdevsz) {
+               if (bio_op(bio) & REQ_OP_READ) {
+                       /* Clip bio */
+                       bio_offset = ((bio_offset > bdevsz) ? bdevsz : 
bio_offset);
+                       /* Update the bio */                    
+                       bio->bi_iter.bi_size = bdevsz - bio_offset;
+
+                       bio->bi_iter.bi_sector = (bio_offset >> SECTOR_SHIFT);
+               }
+       } else  {
+               bio->bi_status = BLK_STS_NOTSUPP;
+               return;
+       } 
+
+       if (cbd_backed(cbp)) {
+               mutex_lock(&cbp->cbdlock);
+               /* Pass through to backing dev */
+               bio_set_dev(bio, cbp->backingdev);
+               mutex_unlock(&cbp->cbdlock);
+               submit_bio_noacct(bio);
+       } else {
+               bio->bi_status = BLK_STS_OFFLINE;
+       }
+
+       return;
+
+}
+
+static const struct block_device_operations cbd_fops = {
+       .owner =                THIS_MODULE,
+       .ioctl =                cbd_ioctl,
+       .submit_bio =           cbd_submit_bio,
+};
+
+
+static int __init cbd_init(void)
+{
+       int err = 0;
+       struct block_device *bdev;
+       struct gendisk *gendisk;
+       struct cbd_private *cbp;
+
+       cbp = kzalloc(sizeof(struct cbd_private),
+                     GFP_KERNEL);
+
+       if (cbp == NULL) {
+               return -ENOMEM;
+       }
+
+       gendisk = blk_alloc_disk(NUMA_NO_NODE);
+
+       if (gendisk == NULL) {
+               kfree(cbp);             
+               goto out;
+       }
+
+       sprintf(gendisk->disk_name, "cbd");
+       gendisk->fops = &cbd_fops;
+
+       /* Since we pass through for now. XXX: review */
+       blk_queue_flag_set(QUEUE_FLAG_SYNCHRONOUS, gendisk->queue);
+       blk_queue_flag_set(QUEUE_FLAG_NOWAIT, gendisk->queue);
+
+       gendisk->private_data = cbp;
+
+       /* Note: set_capacity() is called later when the backend is attached.
+        *       XXX: Will this work ?
+        */
+       err = add_disk(gendisk);
+       if (err) {
+               put_disk(gendisk);
+               kfree(cbp);
+               goto out;
+       }
+
+       cbd_major = register_blkdev(0, "cachedrive");
+       if (cbd_major < 0) {
+               err = cbd_major;
+               put_disk(gendisk);
+               kfree(cbp);
+               goto out;
+       } else {
+               bdev = blkdev_get_by_dev(MKDEV(cbd_major, 0),
+                                        BLK_OPEN_READ|BLK_OPEN_WRITE, 
exclusive, NULL);
+               if (IS_ERR(bdev)) {
+                       err = PTR_ERR(bdev);
+                       put_disk(gendisk);
+                       kfree(cbp);
+                       goto out;
+               }
+               
+               if (bdev == NULL) {
+                       put_disk(gendisk);
+                       kfree(cbp);
+                       goto out;
+               }
+                       
+               bdev->bd_disk = gendisk;
+               mutex_init(&cbp->cbdlock);
+               mutex_lock(&cbp->cbdlock);
+               cbp->cbdev = bdev;
+               mutex_unlock(&cbp->cbdlock);
+               err = 0; /* Mark success */
+       }
+
+out:
+       return err;
+}
+
+static void __exit cbd_exit(void)
+{
+       struct block_device *bdev;
+       struct gendisk *gendisk;
+       struct cbd_private *cbp;
+       
+       bdev = blkdev_get_by_dev(MKDEV(cbd_major, 0),
+                                        BLK_OPEN_READ|BLK_OPEN_WRITE, 
exclusive, NULL);
+
+       if (bdev == NULL) {
+               // XXX: panic() or something else ? 
+               panic("can't find allocated block device major number %d\n", 
cbd_major);
+       }
+
+       gendisk = bdev->bd_disk;
+       cbp = gendisk->private_data;
+
+       mutex_lock(&cbp->cbdlock);
+       if (cbp->backingdev != NULL) {
+               mutex_unlock(&cbp->cbdlock);
+               cbd_detach_backend(cbp);
+       }
+       mutex_unlock(&cbp->cbdlock);
+
+       put_disk(gendisk);
+       kfree(cbp);
+       blkdev_put(bdev, exclusive);
+
+       unregister_blkdev(cbd_major, "cbd");
+}
+
+module_init(cbd_init);
+module_exit(cbd_exit);
+
+MODULE_LICENSE("GPL");
+
diff -urN cbd.null/linux/Makefile cbd/linux/Makefile
--- cbd.null/linux/Makefile     1970-01-01 00:00:00.000000000 +0000
+++ cbd/linux/Makefile  2023-10-16 09:20:14.033674649 +0000
@@ -0,0 +1,10 @@
+obj-m += cbd.o
+LINUXSRC ?= $(HOME)/linux #For dev, for now.
+# The "standard seems to be"
+#LINUXSRC?=/lib/modules/$(shell uname -r)/build
+
+all:
+       make -C $(LINUXSRC)  M=$(PWD) modules
+
+clean:
+       make -C $(LINUXSRC) M=$(PWD) clean
diff -urN cbd.null/Makefile cbd/Makefile
--- cbd.null/Makefile   1970-01-01 00:00:00.000000000 +0000
+++ cbd/Makefile        2023-10-21 17:25:46.201831270 +0000
@@ -0,0 +1,96 @@
+# This set of spinroot related files were written by MattC
+# <c...@bow.st> in the Gregorian Calendar year AD.2023, in the month
+# of February that year.
+#
+# We have two specification files and a properties file (".inv")
+#
+# The properties file contains a "constraint" sections
+# such as ltl or never claims (either or, not both).
+#
+# The specification is divided into two files:
+# the file with suffix '.drv' is a "driver" which
+# instantiates processes that will ultimately "drive" the
+# models under test.
+# The file with the suffix '.pml' contains the process
+# model code, which, is intended to be the formal specification
+# for the code we are interested in writing in C.
+#
+# A file with a '.pmh' extension is a "header" file which
+# both the driver and the model can use.
+#
+# We process these files in slightly different ways during
+# the dev cycle, but broadly speaking, the idea is to generate
+# a file called 'spinmodel.pml' which contains the final
+# model file that is fed to spin.
+#
+# During a model extraction verification run, "modex" is run to
+# extract the 'specification' from C code written to implement
+# the model defined above. We use a 'harness' file (see file with
+# suffix '.prx' below.
+#
+# Once the harness has been run, spinmodel.pml should be
+# synthesised and processed as usual.
+# 
+# The broad idea is that software development starts by writing the
+# spec first, validating the model, and then implementing the model in
+# C, after which we come back to extract the model from the C file
+# and cross check our implementation using spin.
+#
+# If things go well, the constraints specified in the '.inv' file
+# should hold exactly for both the handwritten model, and the
+# extracted one.
+
+spin-gen: cbd.pml cbd.drv cbd.inv
+       cp cbd.pml model #mimic modex - see below.
+       cat model >> spinmodel.pml;cat cbd.drv >> spinmodel.pml;cat cbd.inv >> 
spinmodel.pml;
+       spin -am spinmodel.pml
+       cc -DVECTORSZ=65536 -o cbd.pan pan.c
+
+all: spin-gen prog
+
+# Verification related targets.
+spin-run: spinmodel.pml
+       ./cbd.pan -a #Generate cbd.pml.trail    on error
+       @test -f spinmodel.pml.trail && spin -t spinmodel.pml -p -g -l && 
./cbd.pan -r spinmodel.pml.trail -g || true
+
+# Modex Extracts from C code to 'model' - see cbd.linux.prx
+modex-linux: cbd.linux.prx cbd.drv cbd.inv linux/cbd.c
+       modex -v -w cbd.linux.prx
+       echo "//Included via command line - see Makefile:modex-*:\n\n\n#include 
\"cbd.pmh\"\n\n"  >> spinmodel.pml #include "cbd.pmh" - avoid modex cpp 
expansion
+       cat model >> spinmodel.pml;cat cbd.drv >> spinmodel.pml;cat cbd.inv >> 
spinmodel.pml;
+       spin -am spinmodel.pml #Sanity check
+       cc -DVECTORSZ=65536 -o cbd.pan pan.c
+
+# Modex Extracts from C code to 'model' - see cbd.NetBSD.prx
+modex-NetBSD: cbd.NetBSD.prx cbd.drv cbd.inv NetBSD/cbd.c
+       modex -v -w cbd.NetBSD.prx
+       echo "//Included via command line - see Makefile:modex-*:\n\n\n#include 
\"cbd.pmh\"\n\n"  >> spinmodel.pml #include "cbd.pmh" - avoid modex cpp 
expansion
+       cat model >> spinmodel.pml;cat cbd.drv >> spinmodel.pml;cat cbd.inv >> 
spinmodel.pml;
+       spin -am spinmodel.pml #Sanity check
+       cc -DVECTORSZ=65536 -o cbd.pan pan.c
+
+# Housekeeping
+modex-gen-clean:
+       rm -f spinmodel.pml # Our consolidated model file
+       rm -f _spin_nvr.tmp # Never claim file
+       rm -f model # modex generated intermediate "model" file
+       rm -f pan.* # Spin generated source files
+       rm -f _modex* # modex generated script files
+       rm -f linux/cbd.[IM]
+       rm -f NetBSD/cbd.[IM]
+
+prog-clean:
+       rm -f cbd
+
+spin-run-clean:
+       rm -f spinmodel.pml.trail
+
+spin-gen-clean:
+       rm -f cbd.pan # model executables.
+       rm -f spinmodel.pml # Consolidated model source files
+       rm -f _spin_nvr.tmp # Never claim file
+       rm -f model # Intermediate "model" file
+       rm -f pan.* # Spin generated source files
+
+clean: modex-gen-clean spin-gen-clean spin-run-clean prog-clean
+       rm -f *~
diff -urN cbd.null/NetBSD/cbd.c cbd/NetBSD/cbd.c
--- cbd.null/NetBSD/cbd.c       1970-01-01 00:00:00.000000000 +0000
+++ cbd/NetBSD/cbd.c    2023-10-23 06:37:45.843939111 +0000
@@ -0,0 +1,588 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/buf.h>
+#include <sys/bufq.h>
+#include <sys/conf.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/errno.h>
+
+#include <uvm/uvm_prot.h>
+#include <sys/acl.h>
+#include <sys/vnode_if.h>
+#include <sys/vnode.h>
+
+#include <sys/module.h>
+
+#include <sys/fcntl.h>
+
+#define CBD_ATTACH_BACKEND 222 /* XXX: fit into ioctl namespace */
+#define CBD_DETACH_BACKEND 333 /* XXX: ditto */
+
+static void cbd_attach(device_t, device_t, void *);
+static int cbd_detach(device_t, int);
+
+static dev_type_open(cbd_open);
+static dev_type_close(cbd_close);
+static dev_type_read(cbd_read);
+static dev_type_write(cbd_write);
+static dev_type_ioctl(cbd_ioctl);
+static dev_type_strategy(cbd_strategy);
+static dev_type_size(cbd_size);
+
+struct cbd_ioctl {
+       const char      *diskpath; /* Path to backing block disk dev node */
+       struct lwp      *l;        /* Userland caller lwp */
+};
+
+const struct dkdriver cbddkdriver = {
+        .d_open = cbd_open,
+        .d_close = cbd_close,
+        .d_strategy = cbd_strategy,
+        .d_minphys = minphys,
+};
+
+struct cbd_softc {
+       device_t sc_dev;                /* Back ref to autoconf dev ptr. */
+       struct  disk    sc_dk;          /* generic disk information */
+       kmutex_t sc_lock;               /* Protects access to instance of this 
struct. */
+       struct bufq_state *sc_buflist;  /* transaction request/reply queue */
+       struct vnode            *sc_tvn;        /* backing device
+                                                * vnode */
+       int     sc_tvn_bsize;           /* Backing device size, which
+                                        * is also the size of our
+                                        * device
+                                        */
+       struct kauth_cred *sc_tvn_l_cred;       /* Creds used with sc_tvn */
+};
+
+const struct bdevsw cbd_bdevsw = {
+       .d_open = cbd_open,
+       .d_close = cbd_close,
+       .d_strategy = cbd_strategy,
+       .d_ioctl = cbd_ioctl,
+       .d_dump = nodump,
+       .d_psize = cbd_size,
+       .d_discard = nodiscard,
+       .d_flag = D_DISK | D_MPSAFE
+};
+
+const struct cdevsw cbd_cdevsw = {
+       .d_open = cbd_open,
+       .d_close = cbd_close,
+       .d_read = cbd_read,
+       .d_write = cbd_write,
+       .d_ioctl = cbd_ioctl,
+       .d_stop = nostop,
+       .d_tty = notty,
+       .d_poll = nopoll,
+       .d_mmap = nommap,
+       .d_kqfilter = nokqfilter,
+       .d_discard = nodiscard,
+       .d_flag = D_DISK | D_MPSAFE
+};
+
+CFATTACH_DECL3_NEW(cbd, sizeof(struct cbd_softc),
+       0, cbd_attach, cbd_detach, NULL, NULL, NULL, DVF_DETACH_SHUTDOWN);
+
+#ifdef _MODULE
+MODULE(MODULE_CLASS_DRIVER, cbd, NULL);
+CFDRIVER_DECL(cbd, DV_DISK, NULL);
+
+
+
+static int
+cbd_modcmd(modcmd_t cmd, void *arg)
+{
+       devmajor_t bmajor = -1, cmajor = -1;
+
+       int error = 0;
+       switch (cmd) {
+       case MODULE_CMD_INIT:
+
+               error = devsw_attach("cbd", &cbd_bdevsw, &bmajor,
+                   &cbd_cdevsw, &cmajor);
+
+               if (error) {
+                       break;
+               }
+
+               error = config_cfdriver_attach(&cbd_cd);
+
+               if (error) {
+                       devsw_detach(&cbd_bdevsw, &cbd_cdevsw);
+                       break;
+               }
+               
+               error = config_cfattach_attach(cbd_cd.cd_name, &cbd_ca);
+               if (error) {
+                       config_cfdriver_detach(&cbd_cd);
+                       devsw_detach(&cbd_bdevsw, &cbd_cdevsw);
+                       break;
+               }
+
+               break;
+
+       case MODULE_CMD_FINI:
+               error = config_cfattach_detach(cbd_cd.cd_name, &cbd_ca);
+               if (error) {
+                       break;
+               }
+               error = config_cfdriver_detach(&cbd_cd);
+               if (error) {
+                       break;
+               }
+
+               devsw_detach(&cbd_bdevsw, &cbd_cdevsw);
+               break;
+       case MODULE_CMD_STAT:
+               error = ENOTTY;
+               break;
+
+       default:
+               error = ENOTTY;
+               break;
+       }
+
+       return error;
+               
+}
+
+
+#endif
+
+static void cbd_attach(device_t, device_t, void *);
+static int cbd_detach(device_t, int);
+static bool
+is_read(struct buf *buf)
+{
+       return ((buf->b_flags & B_READ) == B_READ);
+}
+
+#if notyet
+static bool
+is_write(struct buf *buf)
+{
+       return ((buf->b_flags & B_WRITE) == B_WRITE);
+}
+#endif
+
+/*
+ * The idea here is that the state sequentially progresses
+ * forwards as configuration completes.
+ *
+ * So for eg: a device that has a caching device setup,  implies it is
+ * already cbd_backed(), but not vice-versa, because an unfronted
+ * backed device is merely an uncached "passthrough" situation.
+ */
+
+static bool
+cbd_configured(struct cbd_softc *sc)
+{
+       /*
+        * Note: device_t is a pointer, no specific "NULL" typedef
+        * available
+        */ 
+       return (sc->sc_dev != NULL);
+}
+
+/* Implies attached */
+static bool
+cbd_backed(struct cbd_softc *sc)
+{
+       // XXX: check sc->sc_size == backing disk size 
+       return (cbd_configured(sc) && sc->sc_tvn != NULL);
+}
+
+static void
+cbd_attach(device_t parent, device_t self, void *aux)
+{
+       struct cbd_softc *sc = device_private(self);
+
+       mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+
+       mutex_enter(&sc->sc_lock);
+
+       if (cbd_configured(sc)) { /* Sorry, busy */
+               mutex_exit(&sc->sc_lock);
+       }
+
+       sc->sc_dev = self;
+
+        /* Initialize and attach the disk structure. */
+        disk_init(&sc->sc_dk, device_xname(self), &cbddkdriver);
+        disk_attach(&sc->sc_dk);
+
+       bufq_alloc(&sc->sc_buflist, "fcfs", 0);
+
+       mutex_exit(&sc->sc_lock);
+
+       if (!pmf_device_register(self, NULL, NULL))
+               aprint_error_dev(self, "couldn't establish power handler\n");
+}
+
+static int
+cbd_detach(device_t self, int flags)
+{
+       struct cbd_softc *sc = device_private(self);
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_configured(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+
+       /* We assume the !(flags & DETACH_FORCE) case here. */
+       if (((flags & DETACH_FORCE) == 0) &&
+           (bufq_peek(sc->sc_buflist) != NULL)) {
+               mutex_exit(&sc->sc_lock);
+               return EBUSY; /* There are pending transactions */
+       } else { /* DETACH_FORCE */
+               bufq_drain(sc->sc_buflist);
+               //XXX: Tell backing device to drain/flush etc ?
+       }
+
+       // XXX: detach backed/fronted devices ?
+       // quiesce everything first, then sc->sc_tvn = NULL; ?
+       // All threads need to cease related  activity.
+
+       mutex_exit(&sc->sc_lock);
+
+       pmf_device_deregister(self);
+       bufq_free(sc->sc_buflist);
+        disk_detach(&sc->sc_dk);
+
+       mutex_destroy(&sc->sc_lock);
+
+       return 0;
+}
+
+static int
+cbd_open(dev_t dev, int flag, int ifmt, struct lwp *l)
+{
+       /* Sanity check params */
+       switch(ifmt) {
+               case S_IFCHR:
+               case S_IFBLK:
+                       break;
+       default:
+               return EINVAL;
+       }
+
+       int unit;
+       struct cbd_softc *sc;
+       
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       /*
+        * Trying to open unconfigured dev.
+        * cbdconfig(8) should be run first.
+        */
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_backed(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+
+       mutex_exit(&sc->sc_lock);
+
+       return 0;
+}
+
+
+static int
+cbd_close(dev_t dev, int flag, int ifmt, struct lwp *l)
+{
+       /* Sanity check params */
+       switch(ifmt) {
+               case S_IFCHR:
+               case S_IFBLK:
+                       break;
+       default:
+               return EINVAL;
+       }
+
+       int unit;
+       struct cbd_softc *sc;
+       
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       /*
+        * Trying to close unconfigured dev.
+        * cbdconfig(8) should be run first.
+        */
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_backed(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+
+       mutex_exit(&sc->sc_lock);
+       return 0;
+}
+
+
+static void
+cbd_strategy(struct buf *buf)
+{
+
+       dev_t dev = buf->b_dev;
+
+       int unit;
+       struct cbd_softc *sc;
+
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       if (sc == NULL) { 
+               return;
+       }
+
+       /* Sanity check buffer */
+       if ((dbtob(buf->b_blkno) + buf->b_bcount) > sc->sc_tvn_bsize) {
+               if (BUF_ISREAD(buf)) {
+                       /* Clip offset */
+                       buf->b_blkno = (dbtob(buf->b_blkno) > sc->sc_tvn_bsize) 
? btodb(sc->sc_tvn_bsize) : buf->b_blkno;
+                       /* Update the request length */
+                       buf->b_bcount = (sc->sc_tvn_bsize - 
dbtob(buf->b_blkno));
+               } else {
+                       buf->b_error = EINVAL;
+                       biodone(buf);
+                       return;
+               }
+       }
+
+
+       if (cbd_backed(sc)) {
+               mutex_enter(&sc->sc_lock);
+               disk_busy(&sc->sc_dk);
+               VOP_STRATEGY(sc->sc_tvn, buf);
+               disk_unbusy(&sc->sc_dk, buf->b_bcount, is_read(buf));
+               mutex_exit(&sc->sc_lock);
+       } else {
+               buf->b_error = ENXIO;
+               biodone(buf);
+               return;
+       }
+       
+       /* We assume that the backing driver has frobbed buf
+        * appropriately, for return;
+        */
+}
+
+static int
+cbd_attach_backend(struct cbd_softc *lockedsc, void *data)
+{
+       struct cbd_ioctl *cbdioctl = data;
+
+       int error = 0;
+       struct pathbuf *pbuf;
+       struct   vnode *vp;
+
+       uint64_t numsec;
+       unsigned int secsz;
+
+       /* XXX: proper dev validation */
+       if (lockedsc->sc_dev == NULL) {
+               return -ENODEV;
+       }
+
+       error = pathbuf_copyin(cbdioctl->diskpath, &pbuf);
+       if (error != 0) {
+               return error;
+       }
+
+       error = vn_bdev_openpath(pbuf, &vp, cbdioctl->l);
+       pathbuf_destroy(pbuf);
+       if (error != 0) {
+               return error;
+       }
+
+       error = getdisksize(vp, &numsec, &secsz);
+       if (error != 0) {
+               vn_close(vp, /* See vn_bdev_openpath() */ FREAD | FWRITE, 
cbdioctl->l->l_cred);
+               return error;
+       }
+
+       lockedsc->sc_tvn = vp;
+       lockedsc->sc_tvn_l_cred = cbdioctl->l->l_cred;
+       lockedsc->sc_tvn_bsize = numsec * secsz;
+       
+       return error;
+}
+
+static int
+cbd_detach_backend(struct cbd_softc *lockedsc)
+{
+       /* XXX: proper dev state validity */
+       if (lockedsc->sc_dev == NULL ||
+           lockedsc->sc_tvn == NULL ||
+           lockedsc->sc_tvn_bsize == 0 ||
+           lockedsc->sc_tvn_l_cred == NULL) {
+               return -ENODEV;
+       }
+
+       vn_close(lockedsc->sc_tvn, /* See vn_bdev_openpath() */ FREAD | FWRITE,
+                lockedsc->sc_tvn_l_cred);
+
+       lockedsc->sc_tvn_bsize = 0;
+       lockedsc->sc_tvn_l_cred = NULL; 
+       lockedsc->sc_tvn = NULL; /* reset */
+
+       return 0;
+}
+
+static int
+cbd_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
+{
+       int unit, error = 0;
+       struct cbd_softc *sc;
+       struct cbd_ioctl cbdioctlargs;
+
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_configured(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+       
+       switch(cmd) {
+       case CBD_ATTACH_BACKEND:
+               if (cbd_backed(sc)) {
+                       error = EBUSY;
+                       break;
+               }
+               if (!(flag & FWRITE)) {
+                       error = EPERM;
+               }
+               cbdioctlargs.diskpath = data;
+               cbdioctlargs.l = l;
+               error = cbd_attach_backend(sc, &cbdioctlargs);
+               break;
+       case CBD_DETACH_BACKEND:
+               if (!cbd_backed(sc)) {
+                       error = ENODEV;
+                       break;
+               }
+               if (!(flag & FWRITE)) {
+                       error = EPERM;
+                       break;
+               }
+               error = cbd_detach_backend(sc);
+               break;
+
+               /* default: Pass through */
+       }
+               
+       error = VOP_IOCTL(sc->sc_tvn, cmd, data, flag, l->l_cred);
+
+       mutex_exit(&sc->sc_lock);
+
+       return error;
+}
+
+static int
+cbd_read(dev_t dev, struct uio *uio, int flags)
+{
+       int unit;
+       struct cbd_softc *sc;
+       
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_backed(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+
+       mutex_exit(&sc->sc_lock);
+
+       return physio(cbd_strategy, NULL, dev, B_READ, minphys, uio);
+}
+
+static int
+cbd_write(dev_t dev, struct uio *uio, int flags)
+{
+       int unit;
+       struct cbd_softc *sc;
+       
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       /*
+        * Trying to close unconfigured dev.
+        * cbdconfig(8) should be run first.
+        */
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_backed(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+       
+       mutex_exit(&sc->sc_lock);
+       
+       return physio(cbd_strategy, NULL, dev, B_WRITE, minphys, uio);
+}
+
+
+static int
+cbd_size(dev_t dev)
+{
+       int unit;
+       struct cbd_softc *sc;
+       
+       unit = DISKUNIT(dev);
+       sc = device_lookup_private(&cbd_cd, unit);
+
+       /*
+        * Trying to close unconfigured dev.
+        * cbdconfig(8) should be run first.
+        */
+       if (sc == NULL) { 
+               return ENXIO;
+       }
+
+       mutex_enter(&sc->sc_lock);
+
+       if (!cbd_backed(sc)) {
+               mutex_exit(&sc->sc_lock);
+               return ENXIO;
+       }
+
+       // XXPass through.
+
+       mutex_exit(&sc->sc_lock);
+
+       return -1; // XXX: 
+}
diff -urN cbd.null/NetBSD/cbd.ioconf cbd/NetBSD/cbd.ioconf
--- cbd.null/NetBSD/cbd.ioconf  1970-01-01 00:00:00.000000000 +0000
+++ cbd/NetBSD/cbd.ioconf       2023-10-20 15:40:16.888208026 +0000
@@ -0,0 +1,8 @@
+# $NetBSD$
+
+ioconf cbd 
+
+include "conf/files"
+
+pseudo-device cbd
+
diff -urN cbd.null/NetBSD/Makefile cbd/NetBSD/Makefile
--- cbd.null/NetBSD/Makefile    1970-01-01 00:00:00.000000000 +0000
+++ cbd/NetBSD/Makefile 2023-10-20 15:41:52.334710787 +0000
@@ -0,0 +1,10 @@
+#      $NetBSD$
+# Please build using crossbuild nbmake-$ARCH
+
+.include "${NETBSDSRCDIR}/sys/modules/Makefile.inc"
+
+KMOD=  cbd
+IOCONF=        cbd.ioconf
+SRCS=  cbd.c
+
+.include <bsd.kmodule.mk>

Reply via email to