diff --git a/test/xaverius-registry.c b/test/xaverius-registry.c index 8103ab1b1b..27e9d33477 100644 --- a/test/xaverius-registry.c +++ b/test/xaverius-registry.c @@ -12,6 +12,11 @@ #include #include +#include "dix/dix_priv.h" +#include "dix/resource_priv.h" +#include "include/dixstruct.h" +#include "include/resource.h" +#include "os/client_priv.h" #include "tests-common.h" #include "xaverius_priv.h" @@ -90,7 +95,6 @@ xaverius_module_role_descriptor_validation(void) static void xaverius_identity_comparison(void) { - /* PCI equality */ { XaveriusDeviceIdentity a = { 0 }; XaveriusDeviceIdentity b = { 0 }; @@ -101,7 +105,6 @@ xaverius_identity_comparison(void) assert(!xaverius_identity_equal(&a, &b)); } - /* DRM equality */ { XaveriusDeviceIdentity a = { 0 }; XaveriusDeviceIdentity b = { 0 }; @@ -173,7 +176,6 @@ xaverius_ordering_key(void) assert(strncmp(buf, "pci:0000:01:00.0::", 18) == 0); assert(strstr(buf, "test-role") != NULL); - /* DRM identity produces different canonical key (ordering uses canonical identity) */ { XaveriusDeviceIdentity drm = { 0 }; drm.drm_major = 226; @@ -185,13 +187,11 @@ xaverius_ordering_key(void) assert(strcmp(buf, dbuf) != 0); } - /* Too-small buffer must fail (no silent truncation) */ { char tiny[8]; assert(xaverius_create_ordering_key(tiny, sizeof(tiny), &pci, "long-owner-key") == XAVERIUS_REGISTRY_INVALID_ARGUMENT); } - /* Bytewise ordering comparator */ { char a[128], b[128]; assert(xaverius_create_ordering_key(a, sizeof(a), &pci, "aaa") == XAVERIUS_REGISTRY_SUCCESS); @@ -208,7 +208,20 @@ xaverius_module_role_ownership(void) XaveriusModuleRoleDescriptor desc; XaveriusModuleRole *role = NULL; - /* Successful create does not call hooks */ + /* + * Ownership plan (module-owner original ref + binding refs): + * - XaveriusModuleRoleCreate() creates and registers a role; no hook called. + * The module owner holds the original role reference. + * - XaveriusModuleRoleRetain/Release() are lifetime hook primitives. + * - xaverius_binding_alloc() retains the role for a binding; xaverius_binding_free() + * releases that binding reference (exactly once per binding lifetime). + * - XaveriusModuleRoleFinalize() invokes the release hook once to drop the + * module owner's original reference and frees server-owned role storage. + * Caller must invoke finalize exactly once after all binding refs are gone. + * - XaveriusRegistryFini() finalizes any remaining roles via Finalize() to + * perform ordered shutdown cleanup. + */ + make_valid_desc(&desc, "owner-a"); reset_hook_counts(); assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS); @@ -216,17 +229,16 @@ xaverius_module_role_ownership(void) assert(test_retain_calls == 0); assert(test_release_calls == 0); - /* Explicit retain/release balance */ XaveriusModuleRoleRetain(role); assert(test_retain_calls == 1); XaveriusModuleRoleRelease(role); assert(test_release_calls == 1); - /* Finalize performs final release path */ + /* Finalize releases original owner reference and frees role storage. */ XaveriusModuleRoleFinalize(role); assert(test_release_calls == 2); - /* Invalid descriptor: no hooks called */ + /* Invalid descriptors must not invoke hooks. */ make_valid_desc(&desc, "owner-b"); desc.abi_version = XAVERIUS_MODULE_ROLE_DESCRIPTOR_ABI_VERSION + 1; reset_hook_counts(); @@ -236,7 +248,6 @@ xaverius_module_role_ownership(void) assert(test_retain_calls == 0); assert(test_release_calls == 0); - /* Missing hook: rejected, no hooks called */ make_valid_desc(&desc, "owner-c"); desc.retain_module_role = NULL; reset_hook_counts(); @@ -257,7 +268,6 @@ test_query_snapshot_ok(struct _XaveriusDeviceBackendBinding *binding, snapshot_calls++; if (snapshot_result_mode != XAVERIUS_BACKEND_SUCCESS) return snapshot_result_mode; - /* Registry deep-copies and frees the callback-provided snapshot buffer. */ out->size = 16; out->data = calloc(1, out->size); assert(out->data != NULL); @@ -312,7 +322,6 @@ xaverius_registry_registration_paths(void) assert(XaveriusRegistryInit()); - /* Pre-retain/pre-binding validation failures must not call role hooks */ make_valid_desc(&desc, "reg-owner"); reset_hook_counts(); assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS); @@ -320,27 +329,25 @@ xaverius_registry_registration_paths(void) id.bus = 1; memset(id.driver_uuid, 0x44, sizeof(id.driver_uuid)); - /* Missing snapshot callback -> INVALID_ARGUMENT, no retain/release */ + /* Pre-binding validation failures must not call role hooks. */ setup_min_ops(&ops, true); ops.query_physical_snapshot = NULL; setup_min_reg(®, &id, &ops, role); assert(XaveriusRegistryRegisterDdxDevice(NULL, ®, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_INVALID_ARGUMENT); assert(test_retain_calls == 0 && test_release_calls == 0); - /* Not-registered role generation -> NOT_REGISTERED, no hook calls */ setup_min_ops(&ops, true); setup_min_reg(®, &id, &ops, role); reg.owner_role_generation = role->role_generation + 9999; assert(XaveriusRegistryRegisterDdxDevice(NULL, ®, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_NOT_REGISTERED); assert(test_retain_calls == 0 && test_release_calls == 0); - /* Invalid registration ABI -> INVALID_ARGUMENT, no hook calls */ setup_min_reg(®, &id, &ops, role); reg.abi_version = XAVERIUS_DEVICE_BACKEND_REGISTRATION_ABI_VERSION + 1; assert(XaveriusRegistryRegisterDdxDevice(NULL, ®, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_INVALID_ARGUMENT); assert(test_retain_calls == 0 && test_release_calls == 0); - /* Snapshot callback failure after binding alloc: binding retain balanced by cleanup */ + /* Snapshot callback failure after binding alloc: retain/release balanced. */ reg.abi_version = XAVERIUS_DEVICE_BACKEND_REGISTRATION_ABI_VERSION; snapshot_calls = 0; snapshot_result_mode = XAVERIUS_BACKEND_UNAVAILABLE; @@ -351,7 +358,7 @@ xaverius_registry_registration_paths(void) assert(test_retain_calls == 1); assert(test_release_calls == 1); - /* Invalid snapshot content: balanced cleanup */ + /* Invalid snapshot content: retain/release balanced. */ snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS; ops.query_physical_snapshot = test_query_snapshot_bad; reg.ops = &ops; @@ -362,7 +369,7 @@ xaverius_registry_registration_paths(void) assert(test_retain_calls == 1); assert(test_release_calls == 1); - /* Successful registration */ + /* Successful registration: binding retains role once. */ setup_min_ops(&ops, true); setup_min_reg(®, &id, &ops, role); snapshot_calls = 0; @@ -374,9 +381,9 @@ xaverius_registry_registration_paths(void) assert(test_retain_calls == 1); assert(test_release_calls == 0); assert(dev->binding != NULL); - assert(dev->xid == 0); /* publication is separate */ + assert(dev->xid == 0); - /* Exact replay: additional caller ref, no new binding/retain */ + /* Exact replay: additional caller ref, no new binding/retain. */ { XaveriusPhysicalDevice *dev2 = NULL; uint64_t dg2 = 0, bg2 = 0; @@ -392,7 +399,7 @@ xaverius_registry_registration_paths(void) assert(dev->caller_refcount == ref_before); } - /* Conflict: same identity, different owning role */ + /* Conflict: same identity, different owning role -> no hook calls. */ { XaveriusModuleRoleDescriptor desc2; XaveriusModuleRole *role2 = NULL; @@ -401,31 +408,164 @@ xaverius_registry_registration_paths(void) XaveriusPhysicalDevice *devc = NULL; uint64_t dgc = 0, bgc = 0; make_valid_desc(&desc2, "reg-owner-2"); + reset_hook_counts(); assert(XaveriusModuleRoleCreate(&desc2, &role2) == XAVERIUS_REGISTRY_SUCCESS); + assert(test_retain_calls == 0 && test_release_calls == 0); setup_min_ops(&ops2, true); setup_min_reg(®2, &id, &ops2, role2); reset_hook_counts(); assert(XaveriusRegistryRegisterDdxDevice(NULL, ®2, &devc, &dgc, &bgc) == XAVERIUS_REGISTRY_IDENTITY_CONFLICT); assert(test_retain_calls == 0); assert(test_release_calls == 0); + + /* Binding removal releases exactly one retained binding role ref. */ + reset_hook_counts(); + assert(XaveriusRegistryRemoveDeviceBackendBinding(NULL, dev, dev_gen, role->role_generation, bind_gen) == XAVERIUS_REGISTRY_SUCCESS); + assert(dev->binding == NULL); + assert(test_release_calls == 1); + + XaveriusPhysicalDeviceRelease(dev); + + /* Shutdown finalizes remaining roles (owner original release once each). */ + reset_hook_counts(); + XaveriusRegistryFini(); + assert(test_release_calls == 2); /* role + role2 */ + return; } - /* Binding removal releases exactly one retained binding role ref */ - reset_hook_counts(); - assert(XaveriusRegistryRemoveDeviceBackendBinding(NULL, dev, dev_gen, role->role_generation, bind_gen) == XAVERIUS_REGISTRY_SUCCESS); - assert(dev->binding == NULL); - assert(test_release_calls == 1); - - /* Caller ref release is separate and does not free registry record */ - XaveriusPhysicalDeviceRelease(dev); - - /* Registry shutdown cleans remaining records/resources. - * (Exercise cleanup on a registry state without a registered DIX - * PhysicalDevice resource type in this isolated unit-test process.) */ - XaveriusPhysicalDeviceType = 0; + /* Fallback (not reached) */ XaveriusRegistryFini(); } +static void +xaverius_registry_tombstone_lifecycle(void) +{ + XaveriusModuleRoleDescriptor desc; + XaveriusModuleRole *role = NULL; + XaveriusDeviceIdentity id = { 0 }; + struct XaveriusDeviceBackendOps ops; + XaveriusDeviceBackendRegistration reg; + XaveriusPhysicalDevice *dev = NULL; + uint64_t dev_gen = 0, bind_gen = 0; + + assert(XaveriusRegistryInit()); + make_valid_desc(&desc, "tomb-owner"); + reset_hook_counts(); + assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS); + + id.bus = 7; + id.function = 1; + memset(id.driver_uuid, 0x55, sizeof(id.driver_uuid)); + setup_min_ops(&ops, true); + snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS; + setup_min_reg(®, &id, &ops, role); + + reset_hook_counts(); + assert(XaveriusRegistryRegisterDdxDevice(NULL, ®, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_SUCCESS); + assert(dev != NULL); + assert(test_retain_calls == 1 && test_release_calls == 0); + assert(xaverius_physical_device_count == 1); + assert(xaverius_tombstone_count == 0); + + { + XaveriusPhysicalDevice *obs_dev = NULL; + uint64_t obs_gen = 0; + const char source[] = "src-tomb"; + uint64_t obs_id = 1001; + assert(XaveriusRegistryObserveDevice(NULL, &id, sizeof(id), + source, sizeof(source), + 1, obs_id, + &obs_dev, &obs_gen) == XAVERIUS_REGISTRY_SUCCESS); + assert(obs_dev == dev); + + /* Confirm removal moves device exactly once to tombstone list. */ + assert(XaveriusRegistryConfirmDeviceRemoved(NULL, dev, dev_gen, &id, sizeof(id)) == XAVERIUS_REGISTRY_SUCCESS); + assert(dev->status == XAVERIUS_REGISTRY_REMOVED); + assert(xaverius_physical_device_count == 0); + assert(xaverius_tombstone_count == 1); + assert(dev->xid == 0); /* unpublished device keeps no XID */ + + /* Second removal attempt must not move again. */ + assert(XaveriusRegistryConfirmDeviceRemoved(NULL, dev, dev_gen, &id, sizeof(id)) == XAVERIUS_REGISTRY_STALE_EVENT); + assert(xaverius_physical_device_count == 0); + assert(xaverius_tombstone_count == 1); + } + + /* Ordered shutdown frees tombstones and finalizes remaining role. */ + reset_hook_counts(); + XaveriusRegistryFini(); + assert(test_release_calls == 1); /* binding-retained role ref finalized on shutdown cleanup path */ +} + +static void +xaverius_registry_publish_dix(void) +{ + static ClientRec test_client; + static int dix_inited; + XaveriusModuleRoleDescriptor desc; + XaveriusModuleRole *role = NULL; + XaveriusDeviceIdentity id = { 0 }; + struct XaveriusDeviceBackendOps ops; + XaveriusDeviceBackendRegistration reg; + XaveriusPhysicalDevice *dev = NULL; + uint64_t dev_gen = 0, bind_gen = 0; + void *res = NULL; + int rc; + + if (!dix_inited) { + dixResetPrivates(); + memset(&test_client, 0, sizeof(test_client)); + test_client.index = 1; + test_client.clientAsMask = ((XID)1 << CLIENTOFFSET); + serverClient = &test_client; + InitClient(serverClient, 0, (void *)NULL); + assert(InitClientResources(serverClient)); + dix_inited = 1; + } + + XaveriusPhysicalDeviceType = 0; + assert(XaveriusRegistryInit()); + make_valid_desc(&desc, "pub-owner"); + assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS); + + id.bus = 3; + id.device = 2; + memset(id.driver_uuid, 0x66, sizeof(id.driver_uuid)); + setup_min_ops(&ops, true); + snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS; + setup_min_reg(®, &id, &ops, role); + + assert(XaveriusRegistryRegisterDdxDevice(NULL, ®, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_SUCCESS); + assert(dev != NULL); + assert(dev->xid == 0); + + /* Successful publication with real DIX resource registration. */ + assert(XaveriusPhysicalDevicePublish(dev) == true); + assert(dev->xid != 0); + res = NULL; + rc = dixLookupResourceByType(&res, dev->xid, XaveriusPhysicalDeviceType, + serverClient, DixReadAccess); + assert(rc == Success); + assert(res == dev); + + /* Duplicate publication must fail and keep same XID/resource mapping. */ + { + XID old = dev->xid; + assert(XaveriusPhysicalDevicePublish(dev) == false); + assert(dev->xid == old); + res = NULL; + rc = dixLookupResourceByType(&res, dev->xid, XaveriusPhysicalDeviceType, + serverClient, DixReadAccess); + assert(rc == Success); + assert(res == dev); + } + + /* Ordered shutdown must release DIX wrapper and registry/tombstone state. */ + reset_hook_counts(); + XaveriusRegistryFini(); + assert(test_release_calls >= 1); +} + const testfunc_t* xaverius_registry_test(void) { @@ -435,6 +575,8 @@ xaverius_registry_test(void) xaverius_ordering_key, xaverius_module_role_ownership, xaverius_registry_registration_paths, + xaverius_registry_tombstone_lifecycle, + xaverius_registry_publish_dix, NULL, };