Add Expat as a dependency, and use it to parse the XML returned from FreeBSD's kern.sched.topology_spec sysctl.
-- v4: ntdll: implement create_logical_proc_info on FreeBSD.
From: Damjan Jovanovic damjan.jov@gmail.com
Add Expat as a dependency, and use it to parse the XML returned from FreeBSD's kern.sched.topology_spec sysctl. --- configure.ac | 18 +++ dlls/ntdll/Makefile.in | 4 +- dlls/ntdll/unix/system.c | 242 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+), 2 deletions(-)
diff --git a/configure.ac b/configure.ac index ea53ce58e21..c7a7c30026c 100644 --- a/configure.ac +++ b/configure.ac @@ -30,6 +30,7 @@ AC_ARG_WITH(capi, AS_HELP_STRING([--without-capi],[do not use CAPI (ISDN su AC_ARG_WITH(coreaudio, AS_HELP_STRING([--without-coreaudio],[do not use the CoreAudio sound support])) AC_ARG_WITH(cups, AS_HELP_STRING([--without-cups],[do not use CUPS])) AC_ARG_WITH(dbus, AS_HELP_STRING([--without-dbus],[do not use DBus (dynamic device support)])) +AC_ARG_WITH(expat, AS_HELP_STRING([--without-expat],[do not use Expat])) AC_ARG_WITH(ffmpeg, AS_HELP_STRING([--without-ffmpeg],[do not use the FFmpeg library])) AC_ARG_WITH(fontconfig,AS_HELP_STRING([--without-fontconfig],[do not use fontconfig])) AC_ARG_WITH(freetype, AS_HELP_STRING([--without-freetype],[do not use the FreeType library])) @@ -1465,6 +1466,23 @@ case $host_os in [libdbus ${notice_platform}development files not found, no dynamic device support.]) ;; esac
+dnl **** Check for expat **** +if test "x$with_expat" != "xno" +then + WINE_PACKAGE_FLAGS(EXPAT,[expat],,,, + [AC_CHECK_HEADER([expat.h], + [AC_CHECK_LIB(expat,XML_ParserCreate, + [AC_DEFINE(HAVE_LIBEXPAT, 1, [Define to 1 if you have the 'expat' library (-lexpat).])], + [EXPAT_LIBS=""], + [$EXPAT_LIBS])], + [EXPAT_LIBS=""])]) +fi +case $host_os in + freebsd*) WINE_NOTICE_WITH(expat,[test "$ac_cv_lib_expat_XML_ParserCreate" != "yes"], + [expat ${notice_platform}development files not found, detailed CPU info on FreeBSD won't be supported.]) ;; + *) ;; +esac + dnl **** Check for libgnutls **** if test "x$with_gnutls" != "xno" then diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 37bd6c86e31..09062937963 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -3,8 +3,8 @@ MODULE = ntdll.dll UNIXLIB = ntdll.so IMPORTLIB = ntdll IMPORTS = $(MUSL_PE_LIBS) winecrt0 -UNIX_CFLAGS = $(UNWIND_CFLAGS) -UNIX_LIBS = $(IOKIT_LIBS) $(COREFOUNDATION_LIBS) $(CORESERVICES_LIBS) $(RT_LIBS) $(PTHREAD_LIBS) $(UNWIND_LIBS) $(I386_LIBS) $(PROCSTAT_LIBS) +UNIX_CFLAGS = $(UNWIND_CFLAGS) $(EXPAT_CFLAGS) +UNIX_LIBS = $(IOKIT_LIBS) $(COREFOUNDATION_LIBS) $(CORESERVICES_LIBS) $(RT_LIBS) $(PTHREAD_LIBS) $(UNWIND_LIBS) $(I386_LIBS) $(PROCSTAT_LIBS) $(EXPAT_LIBS)
EXTRADLLFLAGS = -nodefaultlibs i386_EXTRADLLFLAGS = -Wl,--image-base,0x7bc00000 diff --git a/dlls/ntdll/unix/system.c b/dlls/ntdll/unix/system.c index efc5914577b..db7441e22a6 100644 --- a/dlls/ntdll/unix/system.c +++ b/dlls/ntdll/unix/system.c @@ -69,6 +69,10 @@ # include <mach/vm_map.h> #endif
+#if defined(HAVE_LIBEXPAT) +# include <expat.h> +#endif + #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" @@ -1332,6 +1336,244 @@ static NTSTATUS create_logical_proc_info(void) return STATUS_SUCCESS; }
+#elif defined(__FreeBSD__) || defined(__FreeBSD__kernel__) + +/* + * str is a comma-separated array of longs, in hexadecimal, in the kernel's + * bitness, eg: "f,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" for 4 CPUs. With + * CPU_MAXSIZE==1024, and with (sizeof(long)*8)==64, there will be + * ceil(1024/64)==16 array entries. CPU IDs fill the longs from first to last, + * lowest to highest bit. + * + * Wine currently doesn't support processor groups, so we only care about + * the first ULONG_PTR size bits, others wouldn't fit in the mask. + */ +static BOOL parse_cpu_mask(const char *str, ULONG_PTR *mask) +{ + *mask = 0; + for (; *str && *str != ','; str++) + { + *mask <<= 4; + if ('0' <= *str && *str <= '9') + *mask |= *str - '0'; + else if ('a' <= *str && *str <= 'f') + *mask |= 0xa + (*str - 'a'); + else + return FALSE; + } + return TRUE; +} + +struct group_state { + unsigned int cache_level; + ULONG_PTR cpu_mask; + BOOL is_numa_node; + BOOL has_children; +}; + +#define MAX_GROUP_LEVELS 10 + +struct topology_parse_state { + NTSTATUS nt_status; + DWORD group_level; + struct group_state groups[1 + MAX_GROUP_LEVELS]; + + unsigned int package_count; + unsigned int core_count; + unsigned int numa_node_count; + ULONG_PTR all_cpus_mask; +}; + +static void topology_start_elem(void *user_data, const char *name, const char **attrs) +{ + struct group_state *group; + struct topology_parse_state *state = user_data; + int i; + + if (state->nt_status != STATUS_SUCCESS) + return; + group = &state->groups[state->group_level]; + + if (!strcmp(name, "groups")) + ; + else if (!strcmp(name, "group")) + { + group->has_children = TRUE; + ++state->group_level; + if (state->group_level > MAX_GROUP_LEVELS) + { + FIXME("excessive <group> element nesting\n"); + state->nt_status = STATUS_XML_PARSE_ERROR; + return; + } + group = &state->groups[state->group_level]; + group->cache_level = 0; + group->cpu_mask = 0; + group->is_numa_node = FALSE; + group->has_children = FALSE; + for (i = 0; attrs[i]; i += 2) + { + if (!strcmp(attrs[i], "cache-level")) + group->cache_level = atoi(attrs[i+1]); + } + } + else if (!strcmp(name, "cpu")) + { + for (i = 0; attrs[i]; i += 2) + { + if (!strcmp(attrs[i], "mask")) + parse_cpu_mask(attrs[i+1], &group->cpu_mask); + } + } + else if (!strcmp(name, "flags")) + ; + else if (!strcmp(name, "flag")) + { + for (i = 0; attrs[i]; i += 2) + { + if (!strcmp(attrs[i], "name") && !strcmp(attrs[i+1], "NODE")) + group->is_numa_node = TRUE; + } + } + else if (!strcmp(name, "children")) + ; +} + +static void topology_end_elem(void *user_data, const char *name) +{ + struct topology_parse_state *state = user_data; + + if (state->nt_status != STATUS_SUCCESS) + return; + + /* Treat top-level groups as packages, leaf groups as cores */ + if (!strcmp(name, "group")) + { + struct group_state *group = &state->groups[state->group_level]; + state->all_cpus_mask |= group->cpu_mask; + if (!group->has_children) + { + if (!logical_proc_info_add_by_id(RelationProcessorPackage, state->package_count, group->cpu_mask)) + { + state->nt_status = STATUS_NO_MEMORY; + return; + } + TRACE("added processors 0x%016llx to package %u\n", (long long)group->cpu_mask, state->package_count); + if (!logical_proc_info_add_by_id(RelationProcessorCore, state->core_count, group->cpu_mask)) + { + state->nt_status = STATUS_NO_MEMORY; + return; + } + TRACE("added processors 0x%016llx to core %u\n", (long long)group->cpu_mask, state->core_count); + ++state->core_count; + } + if (group->is_numa_node) + { + if (!logical_proc_info_add_numa_node(group->cpu_mask, state->numa_node_count)) + { + state->nt_status = STATUS_NO_MEMORY; + return; + } + TRACE("added processors 0x%016llx to NUMA node %u\n", (long long)group->cpu_mask, state->numa_node_count); + ++state->numa_node_count; + } + /* FreeBSD provides almost no info about caching */ + if (1 <= group->cache_level && group->cache_level <= 3) + { + CACHE_DESCRIPTOR cache; + cache.Level = group->cache_level; + cache.Associativity = 12; /* reasonable default */ + cache.LineSize = 64; /* reasonable default */ + cache.Size = 64 * 1024; /* reasonable default */ + cache.Type = CacheUnified; /* reasonable default */ + if (!logical_proc_info_add_cache(group->cpu_mask, &cache)) + { + state->nt_status = STATUS_NO_MEMORY; + return; + } + TRACE("added processors 0x%016llx to cache level %u\n", (long long)group->cpu_mask, group->cache_level); + } + if (state->group_level > 0) + { + --state->group_level; + if (state->group_level == 0) + ++state->package_count; + } + else + { + FIXME("excessive </group> elements\n"); + state->nt_status = STATUS_XML_PARSE_ERROR; + return; + } + } +} + +static NTSTATUS create_logical_proc_info(void) +{ +#if defined(HAVE_LIBEXPAT) + size_t size; + int ret; + NTSTATUS nt_status = STATUS_SUCCESS; + char *topology_spec = NULL; + XML_Parser parser = NULL; + struct topology_parse_state state = {}; + + /* See the smp(4) man page, and https://forums.freebsd.org/threads/number-of-cpus-and-cores.41299/ */ + ret = sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0); + if (ret != 0) + { + if (errno == ENOENT || errno == ENOTDIR || errno == EISDIR) + { + FIXME("the kern.sched.topology_spec sysctl does not exist, you need to use sched_ule\n"); + nt_status = STATUS_NOT_IMPLEMENTED; + } + else + nt_status = STATUS_NO_MEMORY; + goto end; + } + topology_spec = malloc(size); + if (topology_spec == NULL) + { + nt_status = STATUS_NO_MEMORY; + goto end; + } + ret = sysctlbyname("kern.sched.topology_spec", topology_spec, &size, NULL, 0); + if (ret != 0) + { + nt_status = STATUS_NO_MEMORY; + goto end; + } + + parser = XML_ParserCreate("US-ASCII"); + if (parser == NULL) + { + nt_status = STATUS_NO_MEMORY; + goto end; + } + state.nt_status = STATUS_SUCCESS; + XML_SetUserData(parser, &state); + XML_SetElementHandler(parser, topology_start_elem, topology_end_elem); + if (XML_Parse(parser, topology_spec, size-1, XML_TRUE) == XML_STATUS_OK) + nt_status = state.nt_status; + else + { + FIXME("failed to parse sysctl kern.sched.topology_spec, expat errorcode %d\n", XML_GetErrorCode(parser)); + nt_status = STATUS_XML_PARSE_ERROR; + } + logical_proc_info_add_group(count_bits(state.all_cpus_mask), state.all_cpus_mask); + +end: + free(topology_spec); + if (parser != NULL) + XML_ParserFree(parser); + return nt_status; + +#else + FIXME("expat unavailable, cannot parse sysctl kern.sched.topology_spec\n"); + return STATUS_NOT_IMPLEMENTED; +#endif +} + #else
static NTSTATUS create_logical_proc_info(void)
On Tue Feb 18 06:18:22 2025 +0000, Damjan Jovanovic wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/7339/diffs?diff_id=158313&start_sha=0042873a33b929adfba8bb66e959ec6309601a02#3a639ef04e9216a79fce1f3e26b70889ca51cc16_6_6)
Sorry, fixed now.
On Tue Feb 18 05:17:22 2025 +0000, Damjan Jovanovic wrote:
I've changed it now to use expat. It's relatively complete and works on both i386 and amd64 applications. The XML structure could maybe be validated better, but since it comes from kernel, I think it's safe to assume it's always valid. @nsivov: how could the manifest parsing code (which I assume is in dlls/ntdll/actctx.c?) be reused when the "unix" layer can't call the "windows" layer? That's why I originally ported it to the "unix" layer. And the sysctl is stable and documented.
You don't need to call one from another, you could share sources for example. To me this does look worth a dependency.
On Tue Feb 18 11:07:34 2025 +0000, Nikolay Sivov wrote:
You don't need to call one from another, you could share sources for example. To me this does look worth a dependency.
@nsivov: What looks worth a dependency? The actctx.c XML parser, or the expat external library?
On Tue Feb 18 20:18:30 2025 +0000, Damjan Jovanovic wrote:
@nsivov: What looks worth a dependency? The actctx.c XML parser, or the expat external library?
Libexpat, or anything external and non-essential for ntdll.so. But that's only my opinion.