Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5088d6b
zfs: selectable in-kernel ZFS (conf_zfs=bsd|openzfs) via upstream Ope…
gburd Jul 20, 2026
5987377
zfs(openzfs): fix early-boot heap corruption from BSD kstat ABI mismatch
gburd Jul 20, 2026
fb212c4
zfs(openzfs): fix zfs destroy/zpool export EBUSY via mnttab-backed un…
gburd Jul 20, 2026
562a3fe
zfs(openzfs): fix zpool export/import lfmutex owner assert (Bug 1)
gburd Jul 20, 2026
e88db0d
zfs(openzfs): drain all live znodes in zfs_osv_unmount (Bug 1, child …
gburd Jul 20, 2026
79caeea
zfs(openzfs): use whole raw disk on OSv when no .1 partition (Bug 2)
gburd Jul 20, 2026
739e98c
zfs(openzfs): guard NULL column header in zpool list (OSv Phase D Tie…
gburd Jul 20, 2026
efb3a34
zfs(openzfs): enforce the readonly property on OSv (Phase D Tier 0)
gburd Jul 20, 2026
12d97ed
docs(openzfs): record Phase D Tier 0 + Tier 1 PASS matrix
gburd Jul 20, 2026
8943591
zfs(openzfs): advertise TRIM support on OSv vdev_disk (Phase D Tier 3)
gburd Jul 20, 2026
163571a
docs(openzfs): record Phase D Tier 3 matrix (compression/checksum/enc…
gburd Jul 20, 2026
a5be41e
perf: Phase E ZFS microbenchmark (OpenZFS vs BSD-ZFS on OSv)
gburd Jul 21, 2026
59d583d
zfs(openzfs): move the OpenZFS submodule from external/ to modules/op…
gburd Jul 22, 2026
9e22c51
zfs: split in-kernel ZFS into bsd_zfs / open_zfs modules that provide…
gburd Jul 23, 2026
72e8af3
zfs(openzfs): fix mkfs CONF_ZFS_OPENZFS define so open_zfs mounts the…
gburd Jul 24, 2026
ec2e79e
tests: build/run the OpenZFS-userspace tests and libs only for conf_z…
gburd Jul 25, 2026
6bdfebc
lua: revert system-lua-fallback convenience (unrelated to ZFS PR)
gburd Jul 25, 2026
918745e
zfs: drain the pool before exporting it in the zfs_builder (no "pool …
gburd Jul 25, 2026
79f0389
zfs(openzfs): patch 0028 - refresh OSv vnode v_size after write
gburd Jul 25, 2026
ea3e38c
zfs(openzfs): patch 0029 - implement zfs_write_simple + zfs_space for…
gburd Jul 26, 2026
4fea666
zfs(openzfs): bump OpenZFS submodule 2.4.2 -> 2.4.3
gburd Jul 27, 2026
05ffb94
zfs(openzfs): fix taskq_wait to wait for full taskq quiescence
gburd Jul 27, 2026
a73a033
zfs(openzfs): patch 0030 - force single-threaded inline ARC eviction …
gburd Jul 27, 2026
385a680
zfs(openzfs): de-duplicate patch 0030 (drop hunks already in patch 00…
gburd Jul 29, 2026
15c05e6
zfs(openzfs): patch 0031 - guard vnode_pager_setsize against NULL vno…
gburd Jul 29, 2026
3cd48a1
zfs(openzfs): patch 0031 - enable aarch64 SPL isa_defs and sha512 SIM…
gburd Jul 28, 2026
b700524
zfs(openzfs): make ICP asm object lists arch-conditional for aarch64
gburd Jul 28, 2026
d974300
libc: gate aarch64 atomic.h kernel-only includes so ZFS userspace builds
gburd Jul 29, 2026
6632d41
aarch64: raise ARM generic-timer frequency ceiling to 2GHz
gburd Jul 29, 2026
53dec06
docs/comments: keep prose hardware- and vendor-neutral
gburd Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ modules/dl_tests/usr.manifest
compile_commands.json
downloaded_packages
.firecracker
!modules/open_zfs/patches/*.patch
5 changes: 5 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@
[submodule "kbuild"]
path = kbuild
url = https://github.com/osvunikernel/kbuild-standalone.git
[submodule "open_zfs"]
path = modules/open_zfs/openzfs
url = https://github.com/openzfs/zfs.git
branch = zfs-2.4.3
ignore = dirty
582 changes: 433 additions & 149 deletions Makefile

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions arch/aarch64/arm-clock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ class arm_clock : public clock {

arm_clock::arm_clock() {
asm volatile ("mrs %0, cntfrq_el0; isb; " : "=r"(freq_hz) :: "memory");
/* spec documents a typical range of 1-50 MHZ,
* the ampere-1a however, runs on 1Ghz, so allow up to that frequency */
if (freq_hz < 1 * MHZ || freq_hz > 1000 * MHZ) {
/* spec documents a typical range of 1-50 MHZ, but real cores run higher:
* some server ARM cores run at 1GHz and up (one Neoverse-V1 part passes a
* 1.05GHz generic-timer frequency through to the guest), so the previous
* 1GHz ceiling aborted the boot. Allow up to 2GHz to cover current server
* ARM cores with headroom while still catching a garbage read. */
if (freq_hz < 1 * MHZ || freq_hz > 2000 * MHZ) {
debug_early_u64("arm_clock(): read invalid frequency ", freq_hz);
abort();
}
Expand Down
5 changes: 5 additions & 0 deletions bsd/porting/cpu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ extern "C" OSV_LIBSOLARIS_API int get_cpuid(void)
return sched::cpu::current()->id;
}

extern "C" OSV_LIBSOLARIS_API unsigned int sched_current_cpu(void)
{
return (unsigned int)sched::cpu::current()->id;
}

uint64_t get_cyclecount(void)
{
return processor::ticks();
Expand Down
4 changes: 2 additions & 2 deletions bsd/porting/kthread.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ kthread_add(void (*func)(void *), void *arg, struct proc *p,
vsnprintf(name_buf, sizeof(name_buf), fmt, va);
va_end(va);
sched::thread* t = sched::thread::make([=] { func(arg); },
sched::thread::attr().detached().name(name_buf).stack(16 << 10));
sched::thread::attr().detached().name(name_buf).stack(256 << 10));
t->start();

*newtdp = reinterpret_cast<struct thread *>(t);
Expand All @@ -50,7 +50,7 @@ int kproc_create(void (*func)(void *), void *arg, struct proc **p,
vsnprintf(name_buf, sizeof(name_buf), str, va);
va_end(va);
sched::thread* t = sched::thread::make([=] { func(arg); },
sched::thread::attr().detached().name(name_buf).stack(16 << 10));
sched::thread::attr().detached().name(name_buf).stack(256 << 10));
t->start();

if (p) {
Expand Down
22 changes: 22 additions & 0 deletions bsd/porting/netport1.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,31 @@ size_t get_physmem(void)
OSV_LIBSOLARIS_API
int cv_timedwait(kcondvar_t *cv, mutex_t *mutex, clock_t tmo)
{
// Legacy BSD/Illumos ZFS convention: tmo is a RELATIVE timeout in ticks
// (e.g. cv_timedwait(cv, lock, hz) == wait one second; see arc.c, txg.c).
if (tmo <= 0) {
return -1;
}
auto ret = cv->wait(mutex, std::chrono::nanoseconds(ticks2ns(tmo)));
return ret == ETIMEDOUT ? -1 : 0;
}

// OpenZFS convention: the timeout is an ABSOLUTE deadline in ddi_get_lbolt()
// ticks (its cv_timedwait parameter is named abstime; callers pass
// ddi_get_lbolt() + N). Exported as a distinct symbol so the kernel/loader
// stays ZFS-implementation-agnostic: the OpenZFS libsolaris.so binds
// cv_timedwait to this variant (via the OSv SPL condvar header), while the BSD
// libsolaris.so binds the relative cv_timedwait above. Same loader.elf serves
// both.
OSV_LIBSOLARIS_API
int openzfs_cv_timedwait(kcondvar_t *cv, mutex_t *mutex, clock_t abstime)
{
auto now_ns = osv::clock::wall::now().time_since_epoch().count();
clock_t now_ticks = (clock_t)(now_ns / (TSECOND / hz));
clock_t delta = abstime - now_ticks;
if (delta <= 0) {
return -1;
}
auto ret = cv->wait(mutex, std::chrono::nanoseconds(ticks2ns(delta)));
return ret == ETIMEDOUT ? -1 : 0;
}
22 changes: 18 additions & 4 deletions bsd/porting/shrinker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#include <osv/mempool.hh>
#include <osv/debug.hh>
#include <osv/export.h>
#include <osv/sched.hh>
#include <sys/eventhandler.h>
#include <algorithm>
#include <chrono>

struct eventhandler_entry_generic {
struct eventhandler_entry ee;
Expand Down Expand Up @@ -57,10 +59,11 @@ size_t arc_shrinker::request_memory(size_t s, bool hard)
size_t ret = 0;
if (hard) {
ret = (*arc_lowmem_fun)(nullptr, 0);
// ARC's aggressive mode will call arc_adjust, which will reduce the size of the
// cache, but won't necessarily free as much memory as we need. If it doesn't,
// keep going in soft mode. This is better than calling arc_lowmem() again, since
// that could reduce the cache size even further.
// arc_lowmem_fun wakes arc_evict_zthr and signals it to start evicting.
// Give the ARC eviction thread time to actually free physical pages
// before the reclaimer calls wake_waiters(). Without this yield the
// reclaimer immediately finds no free page ranges and calls oom().
sched::thread::sleep(std::chrono::milliseconds(100));
if (ret >= s) {
return ret;
}
Expand All @@ -76,6 +79,8 @@ size_t arc_shrinker::request_memory(size_t s, bool hard)
break;
}
ret += r;
// Allow eviction thread to run between iterations.
sched::thread::sleep(std::chrono::milliseconds(50));
} while (ret < s);
return ret;
}
Expand All @@ -87,6 +92,11 @@ void bsd_shrinker_init(void)

debugf("BSD shrinker: event handler list found: %p\n", list);

if (!list) {
debug("BSD shrinker: vm_lowmem event handler list not found, skipping\n");
return;
}

TAILQ_FOREACH(ep, &list->el_entries, ee_link) {
debugf("\tBSD shrinker found: %p\n",
((struct eventhandler_entry_generic *)ep)->func);
Expand All @@ -111,4 +121,8 @@ extern "C" OSV_LIBSOLARIS_API void register_shrinker_arc_funs(
size_t (*_arc_sized_adjust_fun)(int64_t)) {
arc_lowmem_fun = _arc_lowmem_fun;
arc_sized_adjust_fun = _arc_sized_adjust_fun;
// bsd_shrinker_init() runs at loader startup, before libsolaris.so is
// loaded, so it cannot find the arc_shrinker entry. Register it here
// now that the function pointers are valid.
new arc_shrinker();
}
17 changes: 11 additions & 6 deletions bsd/sys/cddl/compat/opensolaris/kern/opensolaris_misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,22 @@
char hw_serial[11] = "0";

struct opensolaris_utsname utsname = {
.machine = "amd64"
.machine = "amd64",
.sysname = "OSv",
.nodename = "osv", /* SYSINIT is a no-op in OSv; provide static default */
.release = "0",
};

static void
opensolaris_utsname_init(void *arg)
{

utsname.sysname = ostype;
utsname.nodename = prison0.pr_hostname;
utsname.release = osrelease;
snprintf(utsname.version, sizeof(utsname.version), "%d", osreldate);
/*
* On FreeBSD, SYSINIT calls this to set utsname from kernel globals
* and prison0.pr_hostname. In OSv, SYSINIT is a no-op
* (bsd/porting/netport.h), so the static defaults above are used.
* nodename must be non-NULL for fnvlist_add_string() in
* spa_config_generate().
*/
}
SYSINIT(opensolaris_utsname_init, SI_SUB_TUNABLES, SI_ORDER_ANY,
opensolaris_utsname_init, NULL);
160 changes: 159 additions & 1 deletion bsd/sys/cddl/compat/opensolaris/kern/opensolaris_taskq.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,23 @@
#include <sys/taskqueue.h>
#include <sys/taskq.h>
#include <osv/export.h>
#include <string.h>

static uma_zone_t taskq_zone;

OSV_LIB_SOLARIS_API
taskq_t *system_taskq = NULL;

OSV_LIB_SOLARIS_API
taskq_t *system_delay_taskq = NULL;

OSV_LIB_SOLARIS_API void
system_taskq_init(void *arg)
{
taskq_zone = uma_zcreate("taskq_zone", sizeof(struct ostask),
NULL, NULL, NULL, NULL, 0, 0);
system_taskq = taskq_create("system_taskq", 8, 0, 0, 0, 0);
system_delay_taskq = taskq_create("system_delay_taskq", 4, 0, 0, 0, 0);
}
SYSINIT(system_taskq_init, SI_SUB_CONFIGURE, SI_ORDER_ANY, system_taskq_init, NULL);

Expand All @@ -56,6 +61,7 @@ system_taskq_fini(void *arg)
{

taskq_destroy(system_taskq);
taskq_destroy(system_delay_taskq);
uma_zdestroy(taskq_zone);
}
SYSUNINIT(system_taskq_fini, SI_SUB_CONFIGURE, SI_ORDER_ANY, system_taskq_fini, NULL);
Expand Down Expand Up @@ -100,6 +106,26 @@ taskq_member(taskq_t *tq, kthread_t *thread)
return (taskqueue_member(tq->tq_queue, thread));
}

int
taskq_cancel_id(taskq_t *tq, taskqid_t tid, boolean_t wait)
{
struct ostask *task = (struct ostask *)(void *)tid;
uint32_t pend = 0;
int rc;

if (task == NULL)
return (ENOENT);

rc = taskqueue_cancel(tq->tq_queue, &task->ost_task, &pend);
if (rc == EBUSY && wait)
taskqueue_drain(tq->tq_queue, &task->ost_task);

if (pend)
uma_zfree(taskq_zone, task);

return (pend ? 0 : ENOENT);
}

static void
taskq_run(void *arg, int pending __bsd_unused2)
{
Expand Down Expand Up @@ -155,7 +181,7 @@ taskq_dispatch_safe(taskq_t *tq, task_func_t func, void *arg, u_int flags,
{
int prio;

/*
/*
* If TQ_FRONT is given, we want higher priority for this task, so it
* can go at the front of the queue.
*/
Expand All @@ -169,3 +195,135 @@ taskq_dispatch_safe(taskq_t *tq, task_func_t func, void *arg, u_int flags,

return ((taskqid_t)(void *)task);
}

/* ------------------------------------------------------------------ */
/* OpenZFS 2.x extended taskq API */
/* ------------------------------------------------------------------ */

/*
* taskq_wait - wait for all currently-queued tasks to complete.
* We enqueue a do-nothing barrier task and drain on it; since tasks
* execute in FIFO order all prior tasks will have finished first.
*/
OSV_LIB_SOLARIS_API void
taskq_wait(taskq_t *tq)
{
/*
* ZFS requires taskq_wait() to block until the taskq is fully idle:
* no task queued AND no task active (illumos semantics; see the PORTING
* note in dmu_objset.c). A system_taskq has 8 worker threads, so the
* old "enqueue one barrier, drain that barrier" trick was WRONG: a free
* worker runs the barrier while the other 7 are still mid dnode_sync /
* dbuf_sync, so taskq_wait returned early and the syncing thread raced
* the still-running workers -> dbuf/dirty-record corruption
* (VERIFY3U(db_level==level) / db_buf==NULL panics in the sync taskq).
* Wait for the whole queue to quiesce instead.
*/
taskqueue_drain_all(tq->tq_queue);
}

/*
* taskq_wait_id - wait for the task identified by id to complete.
*/
OSV_LIB_SOLARIS_API void
taskq_wait_id(taskq_t *tq, taskqid_t id)
{
struct ostask *task = (struct ostask *)(void *)id;

if (task == NULL)
return;
taskqueue_drain(tq->tq_queue, &task->ost_task);
}

/*
* taskq_wait_outstanding - wait until at most `id` tasks are outstanding.
* For OSv, we simply drain the whole queue (conservative but correct).
*/
OSV_LIB_SOLARIS_API void
taskq_wait_outstanding(taskq_t *tq, taskqid_t id __bsd_unused2)
{
taskq_wait(tq);
}

/*
* taskq_of_curthread - return the taskq the current thread belongs to.
* OSv does not track this per-thread; return NULL.
*/
OSV_LIB_SOLARIS_API taskq_t *
taskq_of_curthread(void)
{
return (NULL);
}

/*
* taskq_create_synced - create a taskq with barrier semantics.
* On OSv this is equivalent to taskq_create. The optional threads
* output parameter receives a freshly-allocated array of nthreads
* NULL-valued kthread_t* handles (OSv has no real kthread handles).
* Callers (e.g. spa_sync_tq_create) must kmem_free() the array.
*/
OSV_LIB_SOLARIS_API taskq_t *
taskq_create_synced(const char *name, int nthreads, pri_t pri,
int minalloc, int maxalloc, uint_t flags, kthread_t ***threads)
{
taskq_t *tq = taskq_create(name, nthreads, pri, minalloc, maxalloc,
flags);
if (threads != NULL) {
/*
* Provide a valid (zeroed) array so the caller can safely
* iterate and kmem_free() it without heap corruption.
*/
*threads = kmem_zalloc(sizeof (kthread_t *) * MAX(nthreads, 1),
KM_SLEEP);
}
return (tq);
}

/*
* taskq_suspend / taskq_suspended / taskq_resume - not implemented on OSv.
*/
OSV_LIB_SOLARIS_API void
taskq_suspend(taskq_t *tq __bsd_unused2)
{
}

OSV_LIB_SOLARIS_API int
taskq_suspended(taskq_t *tq __bsd_unused2)
{
return (0);
}

OSV_LIB_SOLARIS_API void
taskq_resume(taskq_t *tq __bsd_unused2)
{
}

/*
* taskq_dispatch_delay - dispatch after a delay of `ticks` clock ticks.
* On OSv we do NOT dispatch immediately because callers such as
* spa_deadman() rely on the delay to avoid a premature watchdog fire.
* Without a real timer facility, we simply drop the delayed task (no-op).
* This is safe: spa_deadman is a watchdog — if it never fires, pool
* operations complete normally without a false abort.
*/
OSV_LIB_SOLARIS_API taskqid_t
taskq_dispatch_delay(taskq_t *tq __bsd_unused2, task_func_t func __bsd_unused2,
void *arg __bsd_unused2, uint_t flags __bsd_unused2,
clock_t ticks __bsd_unused2)
{
return (0); /* 0 = not scheduled; taskq_cancel_id handles NULL safely */
}

/*
* nulltask - do-nothing task function used as a placeholder.
*/
OSV_LIB_SOLARIS_API void
nulltask(void *arg __bsd_unused2)
{
}

/*
* taskq_init_ent, taskq_empty_ent, taskq_dispatch_ent are defined in
* openzfs_osv_compat.c because that file is compiled with the full
* OpenZFS include paths needed for taskq_ent_t.
*/
Loading