On Tue, Aug 05, 2025 at 04:15:28PM +0200, Roger Pau Monné wrote: > On Wed, Jul 30, 2025 at 05:41:00PM +0000, dm...@proton.me wrote: > > From: Denis Mukhin <dmuk...@ford.com> > > > > Introduce some basic infrastructure for doing domain ID allocation unit > > tests, > > and add a few tests that ensure correctness of the domain ID allocator. > > > > Signed-off-by: Denis Mukhin <dmuk...@ford.com> > > --- > > Changes since v12: > > - fixed Makefile > > - dropped unused symbols/includes from the test harness header > > - s/printk/printf/g in the test code > > --- > > tools/tests/Makefile | 2 +- > > tools/tests/domid/.gitignore | 2 + > > tools/tests/domid/Makefile | 48 ++++++++++ > > tools/tests/domid/include/xen/domain.h | 126 +++++++++++++++++++++++++ > > tools/tests/domid/test-domid.c | 78 +++++++++++++++ > > 5 files changed, 255 insertions(+), 1 deletion(-) > > create mode 100644 tools/tests/domid/.gitignore > > create mode 100644 tools/tests/domid/Makefile > > create mode 100644 tools/tests/domid/include/xen/domain.h > > create mode 100644 tools/tests/domid/test-domid.c > > > > diff --git a/tools/tests/Makefile b/tools/tests/Makefile > > index 36928676a666..ff1666425436 100644 > > --- a/tools/tests/Makefile > > +++ b/tools/tests/Makefile > > @@ -1,7 +1,7 @@ > > XEN_ROOT = $(CURDIR)/../.. > > include $(XEN_ROOT)/tools/Rules.mk > > > > -SUBDIRS-y := > > +SUBDIRS-y := domid > > SUBDIRS-y += resource > > SUBDIRS-$(CONFIG_X86) += cpu-policy > > SUBDIRS-$(CONFIG_X86) += tsx > > diff --git a/tools/tests/domid/.gitignore b/tools/tests/domid/.gitignore > > new file mode 100644 > > index 000000000000..70e306b3c074 > > --- /dev/null > > +++ b/tools/tests/domid/.gitignore > > @@ -0,0 +1,2 @@ > > +*.o > > +test-domid > > diff --git a/tools/tests/domid/Makefile b/tools/tests/domid/Makefile > > new file mode 100644 > > index 000000000000..08fbad096aec > > --- /dev/null > > +++ b/tools/tests/domid/Makefile > > @@ -0,0 +1,48 @@ > > +# SPDX-License-Identifier: GPL-2.0-only > > +# > > +# Unit tests for domain ID allocator. > > +# > > +# Copyright 2025 Ford Motor Company > > + > > +XEN_ROOT=$(CURDIR)/../../.. > > +include $(XEN_ROOT)/tools/Rules.mk > > + > > +TESTS := test-domid > > + > > +vpath domid.c $(XEN_ROOT)/xen/common/ > > + > > +.PHONY: all > > +all: $(TESTS) > > + > > +.PHONY: run > > +run: $(TESTS) > > + $(foreach t,$(TESTS),./$(t);) > > + > > +.PHONY: clean > > +clean: > > + $(RM) -- *.o $(TESTS) $(DEPS_RM) > > + > > +.PHONY: distclean > > +distclean: clean > > + $(RM) -- *~ > > + > > +.PHONY: install > > +install: all > > + $(INSTALL_DIR) $(DESTDIR)$(LIBEXEC)/tests > > + $(INSTALL_PROG) test-domid $(DESTDIR)$(LIBEXEC)/tests > > + > > +.PHONY: uninstall > > +uninstall: > > + $(RM) -- $(DESTDIR)$(LIBEXEC)/tests/test-domid > > + > > +CFLAGS += -D__XEN_TOOLS__ > > +CFLAGS += $(APPEND_CFLAGS) > > +CFLAGS += $(CFLAGS_xeninclude) > > +CFLAGS += -I./include/ > > + > > +LDFLAGS += $(APPEND_LDFLAGS) > > + > > +test-domid: domid.o test-domid.o > > + $(CC) $^ -o $@ $(LDFLAGS) > > + > > +-include $(DEPS_INCLUDE) > > diff --git a/tools/tests/domid/include/xen/domain.h > > b/tools/tests/domid/include/xen/domain.h > > new file mode 100644 > > index 000000000000..e5db0235445e > > --- /dev/null > > +++ b/tools/tests/domid/include/xen/domain.h > > @@ -0,0 +1,126 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Unit test harness for domain ID allocator. > > + * > > + * Copyright 2025 Ford Motor Company > > + */ > > + > > +#ifndef _TEST_HARNESS_ > > +#define _TEST_HARNESS_ > > + > > +#include <assert.h> > > +#include <stdbool.h> > > +#include <stdint.h> > > +#include <stdio.h> > > + > > +#include <xen-tools/common-macros.h> > > + > > +#define BUG_ON(x) assert(!(x)) > > +#define ASSERT(x) assert(x) > > + > > +#define DOMID_FIRST_RESERVED (10) > > +#define DOMID_INVALID (11) > > + > > +#define DEFINE_SPINLOCK(x) unsigned long *(x) > > I think this shouldn't be a pointer? As you otherwise trigger a NULL > pointer dereference in the increases and decreases done below?
Sorry, this bitops integration is very raw. Thanks for all your suggestions, I reworked it. > > > +#define spin_lock(x) ((*(x))++) > > +#define spin_unlock(x) ((*(x))--) > > FWIW, I would use a plain bool: > > #define DEFINE_SPINLOCK(l) bool l > #define spin_lock(l) (*(l) = true) > #define spin_unlock(l) (*(l) = false) > > As you don't expect concurrency tests, you could even assert the lock > is in the expected state before taking/releasing it. > > > + > > +#define printk printf > > + > > +#define BITS_PER_LONG sizeof(unsigned long) > > That's BYTES_PER_LONG, BITS_PER_LONG would be (sizeof(unsigned long) * 8). > > > +#define BITS_PER_WORD (8U * BITS_PER_LONG) > > +#define BITS_TO_LONGS(bits) \ > > + (((bits) + BITS_PER_LONG - 1) / BITS_PER_LONG) > > +#define DECLARE_BITMAP(name, bits) \ > > + unsigned long name[BITS_TO_LONGS(bits)] > > + > > +static inline int __test_and_set_bit(unsigned int nr, unsigned long *addr) > > +{ > > + unsigned long mask = 1UL << (nr % BITS_PER_WORD); > > + unsigned long *p = addr + (nr / BITS_PER_WORD); > > + int old = (*p & mask) != 0; > > + > > + *p |= mask; > > + > > + return old; > > +} > > + > > +static inline int __test_and_clear_bit(unsigned int nr, unsigned long > > *addr) > > +{ > > + unsigned long mask = 1UL << (nr % BITS_PER_WORD); > > + unsigned long *p = addr + (nr / BITS_PER_WORD); > > + int old = (*p & mask) != 0; > > + > > + *p &= ~mask; > > + > > + return old; > > +} > > Could you somehow use the generic__test_and_set_bit() and > generic__test_and_clear_bit() implementations in bitops.h? I tried that originally, and it pulls a lot of dependencies from xen/bitops.h; that will be a mini project to compile xen/bitops.h for the host, which I think I can skip doing for the purpose of this test. I followed another approach as discussed offline in matrix: re-purpose tools/libs/ctrl/xc_bitops.h which seems to be working nice! > > > + > > +static inline void __set_bit(unsigned int nr, volatile unsigned long *addr) > > +{ > > + unsigned long mask = 1UL << (nr % BITS_PER_WORD); > > + unsigned long *p = (unsigned long *)addr + (nr / BITS_PER_WORD); > > Why do you need the cast to drop the volatile here? > > > + > > + *p |= mask; > > +} > > I think you could possibly simplify to a single line: > > ((unsigned int *)addr)[nr >> 5] |= (1u << (nr & 31)); > > That's the implementation of constant_set_bit() in x86. > > > + > > +static inline void __clear_bit(unsigned int nr, volatile unsigned long > > *addr) > > +{ > > + unsigned long mask = 1UL << (nr % BITS_PER_WORD); > > + unsigned long *p = (unsigned long *)addr + (nr / BITS_PER_WORD); > > + > > + *p &= ~mask; > > +} > > I don't think you need __clear_bit()? It's not used by domid.c AFAICT. Overlooked, thanks. > > > + > > +static inline unsigned long find_next_zero_bit(const unsigned long *addr, > > + unsigned long size, > > + unsigned long offset) > > +{ > > + unsigned long idx = offset / BITS_PER_WORD; > > + unsigned long bit = offset % BITS_PER_WORD; > > + > > + if (offset >= size) > > + return size; > > + > > + while (offset < size) > > + { > > + unsigned long val = addr[idx] | (~0UL >> (BITS_PER_WORD - bit)); > > + > > + if (~val) > > + { > > + unsigned long pos = __builtin_ffsl(~val); > > + > > + if (pos > 0) > > + { > > + unsigned long rc = idx * BITS_PER_WORD + (pos - 1); > > + > > + if (rc < size) > > + return rc; > > + } > > + } > > + > > + offset = (idx + 1) * BITS_PER_WORD; > > + idx++; > > + bit = 0; > > + } > > + > > + return size; > > +} > > Hm, you need a full find_next_zero_bit() implementation here because > addr can be arbitrarily long. Could you somehow include > xen/lib/find-next-bit.c and set the right defines so only the > implementation of find_next_bit() is included? That's a good idea! xen/lib/find-next-bit.c seems to be integrating pretty simple. Thanks for the hint! > > > + > > +typedef bool spinlock_t; > > You want to put this ahead, so that DEFINE_SPINLOCK can be: > > #define DEFINE_SPINLOCK(l) spinlock_t l > > > +typedef uint16_t domid_t; > > + > > +/* See include/xen/domain.h */ > > +extern domid_t domid_alloc(domid_t domid); > > +extern void domid_free(domid_t domid); > > + > > +#endif /* _TEST_HARNESS_ */ > > + > > +/* > > + * Local variables: > > + * mode: C > > + * c-file-style: "BSD" > > + * c-basic-offset: 4 > > + * indent-tabs-mode: nil > > + * End: > > + */ > > diff --git a/tools/tests/domid/test-domid.c b/tools/tests/domid/test-domid.c > > new file mode 100644 > > index 000000000000..d52eaf5f1f55 > > --- /dev/null > > +++ b/tools/tests/domid/test-domid.c > > @@ -0,0 +1,78 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Unit tests for domain ID allocator. > > + * > > + * Copyright 2025 Ford Motor Company > > + */ > > + > > +/* Local test include replicating hypervisor includes. */ > > +#include <xen/domain.h> > > I think this is a difficult to maintain position. Right now domid.c > only includes xen/domain.h, so you can easily replace this in > user-space. However if/when domid.c starts including more headers, > replicating this in user-space will be cumbersome IMO. > > I would just guard the includes in domid.c with #ifdef __XEN__ for the > preprocessor to remove them when domid.c is compiled as part of the > unit-tests harness. > > I usually include a harness.h that contains the glue to make the > imported code build (much like what you have placed in the test > harness xen/domain.h header. I like that there's no need to modify the tested code. I reworked this slightly differently: local include/xen/domain.h is a symlink to local harness.h, and all future dependendent files will be symlinks to harness.h as well. > > > + > > +int main(int argc, char **argv) > > +{ > > + domid_t expected, allocated; > > + > > + printf("DOMID_FIRST_RESERVED=%u DOMID_INVALID=%u\n", > > + DOMID_FIRST_RESERVED, DOMID_INVALID); > > + > > + /* Test ID#0 cannot be allocated twice. */ > > + allocated = domid_alloc(0); > > + printf("TEST 1: expected %u allocated %u\n", 0, allocated); > > + ASSERT(allocated == 0); > > + allocated = domid_alloc(0); > > + printf("TEST 1: expected %u allocated %u\n", DOMID_INVALID, allocated); > > + ASSERT(allocated == DOMID_INVALID); > > + > > + /* Ensure ID is not allocated. */ > > + domid_free(0); > > + > > + /* > > + * Test that that two consecutive calls of domid_alloc(DOMID_INVALID) > > + * will never return the same ID. > > + * NB: ID#0 is reserved and shall not be allocated by > > + * domid_alloc(DOMID_INVALID). > > + */ > > + for ( expected = 1; expected < DOMID_FIRST_RESERVED; expected++ ) > > + { > > + allocated = domid_alloc(DOMID_INVALID); > > + printf("TEST 2: expected %u allocated %u\n", expected, allocated); > > + ASSERT(allocated == expected); > > + } > > + for ( expected = 1; expected < DOMID_FIRST_RESERVED; expected++ ) > > + { > > + allocated = domid_alloc(DOMID_INVALID); > > + printf("TEST 3: expected %u allocated %u\n", DOMID_INVALID, > > allocated); > > + ASSERT(allocated == DOMID_INVALID); > > + } > > + > > + /* Re-allocate first ID from [1..DOMID_FIRST_RESERVED - 1]. */ > > + expected = 1; > > + domid_free(1); > > + allocated = domid_alloc(DOMID_INVALID); > > + printf("TEST 4: expected %u allocated %u\n", expected, allocated); > > + ASSERT(allocated == expected); > > + > > + /* Re-allocate last ID from [1..DOMID_FIRST_RESERVED - 1]. */ > > + expected = DOMID_FIRST_RESERVED - 1; > > + domid_free(DOMID_FIRST_RESERVED - 1); > > + allocated = domid_alloc(DOMID_INVALID); > > + printf("TEST 5: expected %u allocated %u\n", expected, allocated); > > + ASSERT(allocated == expected); > > + > > + /* Allocate an invalid ID. */ > > + expected = DOMID_INVALID; > > + allocated = domid_alloc(DOMID_FIRST_RESERVED); > > + printf("TEST 6: expected %u allocated %u\n", expected, allocated); > > + ASSERT(allocated == expected); > > I would make this a bit less chatty maybe? Ack. > > I think you only need to print on errors, and you probably don't want > to ASSERT() on failure, and rather try to finish all the tests in > order to report multiple failures in a single run. I thought about it originally, but my "tests" depend on each other, so I'll keep failing on the first error as is, if there's no strong objection. > > Thanks, Roger.