diff --git a/dll/ntdll/def/ntdll.spec b/dll/ntdll/def/ntdll.spec index 7cd2525e0b9..8a0db915a69 100644 --- a/dll/ntdll/def/ntdll.spec +++ b/dll/ntdll/def/ntdll.spec @@ -678,8 +678,8 @@ @ stdcall RtlClearBits(ptr long long) @ stdcall RtlCloneMemoryStream(ptr ptr) @ stdcall -stub -version=0x600+ RtlCloneUserProcess(long long long long long) -@ stdcall -stub -version=0x600+ RtlCmDecodeMemIoResource(ptr ptr) -@ stdcall -stub -version=0x600+ RtlCmEncodeMemIoResource(ptr long long long) +@ stdcall -ret64 -version=0x600+ RtlCmDecodeMemIoResource(ptr ptr) +@ stdcall -version=0x600+ RtlCmEncodeMemIoResource(ptr long int64 int64) @ stdcall -stub -version=0x600+ RtlCommitDebugInfo(ptr long) @ stdcall RtlCommitMemoryStream(ptr long) @ stdcall RtlCompactHeap(long long) @@ -840,7 +840,7 @@ @ stdcall RtlFindClearBits(ptr long long) @ stdcall RtlFindClearBitsAndSet(ptr long long) @ stdcall RtlFindClearRuns(ptr ptr long long) -@ stdcall -stub -version=0x600+ RtlFindClosestEncodableLength(long ptr) +@ stdcall -version=0x600+ RtlFindClosestEncodableLength(int64 ptr) @ stdcall RtlFindLastBackwardRunClear(ptr long ptr) @ stdcall RtlFindLeastSignificantBit(double) @ stdcall RtlFindLongestRunClear(ptr long) @@ -973,8 +973,8 @@ @ stdcall -arch=x86_64 RtlInterlockedPushListSList(ptr ptr ptr long) @ stdcall -version=0x602+ -arch=i386 RtlInterlockedPushListSListEx(ptr ptr ptr long) @ stdcall -version=0x602+ -arch=!i386 RtlInterlockedPushListSListEx(ptr ptr ptr long) RtlInterlockedPushListSList -@ stdcall -stub -version=0x600+ RtlIoDecodeMemIoResource(ptr ptr ptr ptr) -@ stdcall -stub -version=0x600+ RtlIoEncodeMemIoResource(ptr long long long long long) +@ stdcall -ret64 -version=0x600+ RtlIoDecodeMemIoResource(ptr ptr ptr ptr) +@ stdcall -version=0x600+ RtlIoEncodeMemIoResource(ptr long int64 int64 int64 int64) @ stdcall RtlIpv4AddressToStringA(ptr ptr) @ stdcall RtlIpv4AddressToStringExA(ptr long ptr ptr) @ stdcall RtlIpv4AddressToStringExW(ptr long ptr ptr) diff --git a/modules/rostests/apitests/ntdll/CMakeLists.txt b/modules/rostests/apitests/ntdll/CMakeLists.txt index 3c75135007e..6caf1a86ebf 100644 --- a/modules/rostests/apitests/ntdll/CMakeLists.txt +++ b/modules/rostests/apitests/ntdll/CMakeLists.txt @@ -128,6 +128,7 @@ list(APPEND SOURCE RtlIsNameInExpression.c RtlIsNameLegalDOS8Dot3.c RtlLocale.c + RtlMemIoResource.c RtlMemoryStream.c RtlMultipleAllocateHeap.c RtlNtPathNameToDosPathName.c diff --git a/modules/rostests/apitests/ntdll/RtlMemIoResource.c b/modules/rostests/apitests/ntdll/RtlMemIoResource.c new file mode 100644 index 00000000000..b8617744e6e --- /dev/null +++ b/modules/rostests/apitests/ntdll/RtlMemIoResource.c @@ -0,0 +1,502 @@ +/* + * PROJECT: ReactOS API Tests + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Tests for the Rtl memory/port resource descriptor encoding API: + * RtlCmEncodeMemIoResource, RtlCmDecodeMemIoResource, + * RtlIoEncodeMemIoResource, RtlIoDecodeMemIoResource and + * RtlFindClosestEncodableLength. + * COPYRIGHT: Copyright 2026 Justin Miller + */ + +#include "precomp.h" +#include + +static ULONGLONG (NTAPI *pRtlCmDecodeMemIoResource)( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Start); + +static NTSTATUS (NTAPI *pRtlCmEncodeMemIoResource)( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _In_ UCHAR Type, + _In_ ULONGLONG Length, + _In_ ULONGLONG Start); + +static NTSTATUS (NTAPI *pRtlFindClosestEncodableLength)( + _In_ ULONGLONG SourceLength, + _Out_ PULONGLONG TargetLength); + +static ULONGLONG (NTAPI *pRtlIoDecodeMemIoResource)( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Alignment, + _Out_opt_ PULONGLONG MinimumAddress, + _Out_opt_ PULONGLONG MaximumAddress); + +static NTSTATUS (NTAPI *pRtlIoEncodeMemIoResource)( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _In_ UCHAR Type, + _In_ ULONGLONG Length, + _In_ ULONGLONG Alignment, + _In_ ULONGLONG MinimumAddress, + _In_ ULONGLONG MaximumAddress); + +static +void +Test_CmEncode(void) +{ + CM_PARTIAL_RESOURCE_DESCRIPTOR Desc, Saved; + NTSTATUS Status; + + /* Unsupported types must fail without touching the descriptor */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Saved = Desc; + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeNull, 0x1000, 0); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n"); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeInterrupt, 0x1000, 0); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeDma, 0x1000, 0); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n"); + + /* Basic port descriptor */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 0x3F8); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypePort); + ok_eq_ulong(Desc.u.Port.Length, 0x100UL); + ok_eq_hex64(Desc.u.Port.Start.QuadPart, 0x3F8); + /* The port path does not clean stale large flags */ + ok_eq_hex(Desc.Flags, 0x5555); + + /* A port length cannot exceed 32 bits, and the failure leaves no writes */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Saved = Desc; + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100000000ULL, 0); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n"); + + /* Small memory length: plain descriptor, stale large flags are cleared */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48 | CM_RESOURCE_MEMORY_READ_ONLY; + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000, 0xFFFFFFFFFF000000ULL); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemory); + ok_eq_ulong(Desc.u.Memory.Length, 0x1000UL); + ok_eq_hex64(Desc.u.Memory.Start.QuadPart, 0xFFFFFFFFFF000000ULL); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_READ_ONLY); + + /* Asking for MemoryLarge with a small length demotes to plain Memory */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemoryLarge, MAXULONG, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemory); + ok_eq_ulong(Desc.u.Memory.Length, MAXULONG); + + /* Asking for plain Memory with a big length promotes to MemoryLarge (40-bit) */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0x8000000000ULL); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40); + ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL); + ok_eq_hex64(Desc.u.Memory.Start.QuadPart, 0x8000000000ULL); + + /* 40-bit tier upper bound */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemoryLarge, CM_RESOURCE_MEMORY_LARGE_40_MAXLEN, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40); + ok_eq_ulong(Desc.u.Memory.Length, MAXULONG); + + /* Low 8 bits set in the 40-bit tier: not encodable */ + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000080ULL, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + + /* 48-bit tier */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000000ULL, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_48); + ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL); + + /* + * The tier is picked by magnitude alone: a 256-byte-aligned length in the + * 48-bit tier still fails because it is not 64K-aligned + */ + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000100ULL, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + + /* 64-bit tier and its upper bound */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000000000000ULL, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64); + ok_eq_ulong(Desc.u.Memory.Length, 0x10000UL); + + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, CM_RESOURCE_MEMORY_LARGE_64_MAXLEN, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64); + ok_eq_ulong(Desc.u.Memory.Length, MAXULONG); + + /* Low 32 bits set in the 64-bit tier, and beyond-the-max lengths */ + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000000100ULL, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, CM_RESOURCE_MEMORY_LARGE_64_MAXLEN + 1, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, ULLONG_MAX, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); +} + +static +void +Test_CmDecode(void) +{ + static const struct + { + ULONGLONG Length; + USHORT ExpectedFlags; + } RoundTrips[] = + { + { 0x1000, 0 }, + { MAXULONG, 0 }, + { 0x100000000ULL, CM_RESOURCE_MEMORY_LARGE_40 }, + { 0xFFFFFFFF00ULL, CM_RESOURCE_MEMORY_LARGE_40 }, + { 0x10000000000ULL, CM_RESOURCE_MEMORY_LARGE_48 }, + { 0xFFFFFFFF0000ULL, CM_RESOURCE_MEMORY_LARGE_48 }, + { 0x1000000000000ULL, CM_RESOURCE_MEMORY_LARGE_64 }, + { 0xFFFFFFFF00000000ULL, CM_RESOURCE_MEMORY_LARGE_64 }, + }; + CM_PARTIAL_RESOURCE_DESCRIPTOR Desc; + ULONGLONG Length, Start; + NTSTATUS Status; + ULONG i; + + /* Plain port descriptor decodes verbatim */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Type = CmResourceTypePort; + Desc.u.Port.Start.QuadPart = 0x1122334455667788ULL; + Desc.u.Port.Length = 0x1234; + Start = 0; + Length = pRtlCmDecodeMemIoResource(&Desc, &Start); + ok_eq_hex64(Length, 0x1234); + ok_eq_hex64(Start, 0x1122334455667788ULL); + + /* Start is optional */ + Length = pRtlCmDecodeMemIoResource(&Desc, NULL); + ok_eq_hex64(Length, 0x1234); + + /* Large flags are ignored for plain port/memory types */ + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64; + Length = pRtlCmDecodeMemIoResource(&Desc, NULL); + ok_eq_hex64(Length, 0x1234); + + /* Hand-built large forms decode to the shifted length */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Type = CmResourceTypeMemoryLarge; + Desc.u.Memory.Length = 0x01000000; + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40; + ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x100000000ULL); + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48; + ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x10000000000ULL); + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64; + ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x100000000000000ULL); + + /* A large type without any large flag decodes to zero, Start still returned */ + Desc.Flags = 0; + Desc.u.Memory.Start.QuadPart = 0xABCD; + Start = 0; + ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, &Start), 0); + ok_eq_hex64(Start, 0xABCD); + + /* Encode/decode round-trips across all tiers */ + for (i = 0; i < ARRAYSIZE(RoundTrips); i++) + { + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, + CmResourceTypeMemory, + RoundTrips[i].Length, + 0x40000000ULL + i); + ok(Status == STATUS_SUCCESS, "[%lu] encode failed: 0x%lx\n", i, Status); + ok(Desc.Flags == RoundTrips[i].ExpectedFlags, + "[%lu] Flags = 0x%x, expected 0x%x\n", + i, Desc.Flags, RoundTrips[i].ExpectedFlags); + Start = 0; + Length = pRtlCmDecodeMemIoResource(&Desc, &Start); + ok(Length == RoundTrips[i].Length, + "[%lu] Length = 0x%I64x, expected 0x%I64x\n", + i, Length, RoundTrips[i].Length); + ok(Start == 0x40000000ULL + i, + "[%lu] Start = 0x%I64x\n", i, Start); + } +} + +static +void +Test_IoEncode(void) +{ + IO_RESOURCE_DESCRIPTOR Desc, Saved; + NTSTATUS Status; + + /* Unsupported types fail without touching the descriptor */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Saved = Desc; + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeInterrupt, 0x1000, 4, 0, 0xFFFF); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n"); + + /* Basic port requirement */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 4, 0x1000, 0xFFFF); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypePort); + ok_eq_ulong(Desc.u.Port.Length, 0x100UL); + ok_eq_ulong(Desc.u.Port.Alignment, 4UL); + ok_eq_hex64(Desc.u.Port.MinimumAddress.QuadPart, 0x1000); + ok_eq_hex64(Desc.u.Port.MaximumAddress.QuadPart, 0xFFFF); + + /* Port length/alignment above 32 bits are rejected, no writes */ + RtlFillMemory(&Desc, sizeof(Desc), 0x55); + Saved = Desc; + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100000000ULL, 4, 0, 0xFFFF); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 0x100000000ULL, 0, 0xFFFF); + ok_eq_hex(Status, STATUS_INVALID_PARAMETER); + ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n"); + + /* Small memory requirement stays plain and clears stale large flags */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40 | CM_RESOURCE_MEMORY_PREFETCHABLE; + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000, 0x1000, 0, 0xFFFFFFFFULL); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemory); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_PREFETCHABLE); + ok_eq_ulong(Desc.u.Memory.Length, 0x1000UL); + ok_eq_ulong(Desc.u.Memory.Alignment, 0x1000UL); + ok_eq_hex64(Desc.u.Memory.MinimumAddress.QuadPart, 0); + ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFULL); + + /* Large length: a small alignment is scaled up to the tier granularity */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x100000000ULL, 1, + 0, 0xFFFFFFFFFFULL); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40); + ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL); + ok_eq_ulong(Desc.u.Memory.Alignment, 1UL); /* i.e. 0x100 bytes */ + ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFFFULL); + + /* Zero alignment encodes as zero */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0, 0, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_ulong(Desc.u.Memory.Alignment, 0UL); + + /* A non-power-of-two alignment is doubled until it fits the granularity */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0x81, 0, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_ulong(Desc.u.Memory.Alignment, 0x81UL); /* 0x81 << 8 == 0x8100 bytes */ + + /* + * A small length with a >4GB alignment still selects the large form + * (the plain path needs both to fit in 32 bits) + */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x1000, 0x100000000ULL, 0, 0); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40); + ok_eq_ulong(Desc.u.Memory.Length, 0x10UL); + ok_eq_ulong(Desc.u.Memory.Alignment, 0x01000000UL); + + /* Length not representable at the tier granularity */ + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000080ULL, 1, 0, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000100ULL, 1, 0, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + + /* Alignment above the tier picked by the length */ + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x100000000ULL, 0x10000000000ULL, 0, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + + /* Alignment whose scaling overflows 64 bits */ + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x1000000000000ULL, 0x8000000000000001ULL, 0, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + + /* 64-bit tier */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x1000000000000ULL, 0x100000000ULL, + 0x100000000ULL, 0xFFFFFFFFFFFFFFFFULL); + ok_eq_hex(Status, STATUS_SUCCESS); + ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64); + ok_eq_ulong(Desc.u.Memory.Length, 0x10000UL); + ok_eq_ulong(Desc.u.Memory.Alignment, 1UL); + ok_eq_hex64(Desc.u.Memory.MinimumAddress.QuadPart, 0x100000000ULL); + ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFFFFFFFFFULL); + + /* Length beyond every tier */ + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + ULLONG_MAX, 1, 0, 0); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); +} + +static +void +Test_IoDecode(void) +{ + IO_RESOURCE_DESCRIPTOR Desc; + ULONGLONG Length, Alignment, Minimum, Maximum; + NTSTATUS Status; + + /* Plain memory requirement */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Type = CmResourceTypeMemory; + Desc.u.Memory.Length = 0x2000; + Desc.u.Memory.Alignment = 0x1000; + Desc.u.Memory.MinimumAddress.QuadPart = 0x100000; + Desc.u.Memory.MaximumAddress.QuadPart = 0xFFFFFFFFULL; + Alignment = Minimum = Maximum = 0; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum); + ok_eq_hex64(Length, 0x2000); + ok_eq_hex64(Alignment, 0x1000); + ok_eq_hex64(Minimum, 0x100000); + ok_eq_hex64(Maximum, 0xFFFFFFFFULL); + + Length = pRtlIoDecodeMemIoResource(&Desc, NULL, NULL, NULL); + ok_eq_hex64(Length, 0x2000); + + /* Length and alignment both scale */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Desc.Type = CmResourceTypeMemoryLarge; + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40; + Desc.u.Memory.Length = 0x01000000; + Desc.u.Memory.Alignment = 0x01000000; + Alignment = 0; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL); + ok_eq_hex64(Length, 0x100000000ULL); + ok_eq_hex64(Alignment, 0x100000000ULL); + + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL); + ok_eq_hex64(Length, 0x10000000000ULL); + ok_eq_hex64(Alignment, 0x10000000000ULL); + + Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL); + ok_eq_hex64(Length, 0x100000000000000ULL); + ok_eq_hex64(Alignment, 0x100000000000000ULL); + + /* A large type without large flags decodes to zero, bounds still returned */ + Desc.Flags = 0; + Desc.u.Memory.MinimumAddress.QuadPart = 0x1234; + Desc.u.Memory.MaximumAddress.QuadPart = 0x5678; + Alignment = Minimum = Maximum = ULLONG_MAX; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum); + ok_eq_hex64(Length, 0); + ok_eq_hex64(Alignment, 0); + ok_eq_hex64(Minimum, 0x1234); + ok_eq_hex64(Maximum, 0x5678); + + /* Encode/decode round-trip: the rounded-up alignment is what comes back */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, + 0x10000000000ULL, 0x1000, + 0x10000, 0xFFFFFFFFFFFFULL); + ok_eq_hex(Status, STATUS_SUCCESS); + Alignment = Minimum = Maximum = 0; + Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum); + ok_eq_hex64(Length, 0x10000000000ULL); + ok_eq_hex64(Alignment, 0x10000); /* 0x1000 rounded up to the 64K granularity */ + ok_eq_hex64(Minimum, 0x10000); + ok_eq_hex64(Maximum, 0xFFFFFFFFFFFFULL); +} + +static +void +Test_FindClosest(void) +{ + static const struct + { + ULONGLONG Source; + ULONGLONG Expected; + } Cases[] = + { + /* 32-bit values are always exact */ + { 0, 0 }, + { 0x1234, 0x1234 }, + { MAXULONG, MAXULONG }, + /* 40-bit tier: rounded up to 256 bytes */ + { 0x100000000ULL, 0x100000000ULL }, + { 0x100000001ULL, 0x100000100ULL }, + { 0xFFFFFFFF00ULL, 0xFFFFFFFF00ULL }, + /* just past the 40-bit tier: 64K granularity now */ + { 0xFFFFFFFF01ULL, 0x10000000000ULL }, + { 0x10000000001ULL, 0x10000010000ULL }, + { 0xFFFFFFFF0000ULL, 0xFFFFFFFF0000ULL }, + /* just past the 48-bit tier: 4G granularity */ + { 0xFFFFFFFF0001ULL, 0x1000000000000ULL }, + { 0x1000000000000ULL, 0x1000000000000ULL }, + { 0x1000000000001ULL, 0x1000100000000ULL }, + { 0xFFFFFFFF00000000ULL, 0xFFFFFFFF00000000ULL }, + }; + CM_PARTIAL_RESOURCE_DESCRIPTOR Desc; + ULONGLONG Target; + NTSTATUS Status; + ULONG i; + + for (i = 0; i < ARRAYSIZE(Cases); i++) + { + Target = ULLONG_MAX; + Status = pRtlFindClosestEncodableLength(Cases[i].Source, &Target); + ok(Status == STATUS_SUCCESS, "[%lu] failed: 0x%lx\n", i, Status); + ok(Target == Cases[i].Expected, + "[%lu] Target = 0x%I64x, expected 0x%I64x\n", + i, Target, Cases[i].Expected); + + /* The returned length must itself be encodable */ + RtlZeroMemory(&Desc, sizeof(Desc)); + Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, Target, 0); + ok(Status == STATUS_SUCCESS, "[%lu] closest length not encodable: 0x%lx\n", i, Status); + } + + /* Beyond the 64-bit tier there is nothing to round to */ + Target = ULLONG_MAX; + Status = pRtlFindClosestEncodableLength(CM_RESOURCE_MEMORY_LARGE_64_MAXLEN + 1, &Target); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); + ok_eq_hex64(Target, 0); + Status = pRtlFindClosestEncodableLength(ULLONG_MAX, &Target); + ok_eq_hex(Status, STATUS_UNSUCCESSFUL); +} + +START_TEST(RtlMemIoResource) +{ + HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + + pRtlCmDecodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlCmDecodeMemIoResource"); + pRtlCmEncodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlCmEncodeMemIoResource"); + pRtlFindClosestEncodableLength = (PVOID)GetProcAddress(hNtdll, "RtlFindClosestEncodableLength"); + pRtlIoDecodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlIoDecodeMemIoResource"); + pRtlIoEncodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlIoEncodeMemIoResource"); + + if (!pRtlCmDecodeMemIoResource || !pRtlCmEncodeMemIoResource || + !pRtlFindClosestEncodableLength || + !pRtlIoDecodeMemIoResource || !pRtlIoEncodeMemIoResource) + { + skip("Rtl*MemIoResource API not available (NT 6.0+ only)\n"); + return; + } + + Test_CmEncode(); + Test_CmDecode(); + Test_IoEncode(); + Test_IoDecode(); + Test_FindClosest(); +} diff --git a/modules/rostests/apitests/ntdll/testlist.c b/modules/rostests/apitests/ntdll/testlist.c index dc3be4afbfa..60778740858 100644 --- a/modules/rostests/apitests/ntdll/testlist.c +++ b/modules/rostests/apitests/ntdll/testlist.c @@ -114,6 +114,7 @@ extern void func_RtlIntSafe(void); extern void func_RtlIsNameInExpression(void); extern void func_RtlIsNameLegalDOS8Dot3(void); extern void func_RtlLocale(void); +extern void func_RtlMemIoResource(void); extern void func_RtlMemoryStream(void); extern void func_RtlMultipleAllocateHeap(void); extern void func_RtlNtPathNameToDosPathName(void); @@ -252,6 +253,7 @@ const struct test winetest_testlist[] = { "RtlIsNameInExpression", func_RtlIsNameInExpression}, { "RtlIsNameLegalDOS8Dot3", func_RtlIsNameLegalDOS8Dot3 }, { "RtlLocale", func_RtlLocale }, + { "RtlMemIoResource", func_RtlMemIoResource }, { "RtlMemoryStream", func_RtlMemoryStream }, { "RtlMultipleAllocateHeap", func_RtlMultipleAllocateHeap }, { "RtlNtPathNameToDosPathName", func_RtlNtPathNameToDosPathName }, diff --git a/sdk/lib/rtl/CMakeLists.txt b/sdk/lib/rtl/CMakeLists.txt index 24e904e1fe1..0398f4519e5 100644 --- a/sdk/lib/rtl/CMakeLists.txt +++ b/sdk/lib/rtl/CMakeLists.txt @@ -61,6 +61,7 @@ list(APPEND SOURCE largeint.c locale.c luid.c + memres.c memstream.c message.c network.c diff --git a/sdk/lib/rtl/memres.c b/sdk/lib/rtl/memres.c new file mode 100644 index 00000000000..5d1a3e82400 --- /dev/null +++ b/sdk/lib/rtl/memres.c @@ -0,0 +1,329 @@ +/* + * PROJECT: ReactOS system libraries + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Encode/decode helpers for memory and port resource descriptors. + * COPYRIGHT: Copyright 2026 Justin Miller + */ + +/* INCLUDES *****************************************************************/ + +#include + +#define NDEBUG +#include + +/* FUNCTIONS ****************************************************************/ + +/* + * A plain Port/Memory descriptor stores its Length (and, for a requirement, its + * Alignment) in a 32-bit field, so it can only describe regions up to 4 GB. A + * CmResourceTypeMemoryLarge descriptor reuses that same 32-bit field to hold a + * bigger value by dropping the low bits that are known to be zero; the Flags say + * how many were dropped: + * + * CM_RESOURCE_MEMORY_LARGE_40 (0x200) field holds bits [39:8] (<< 8) + * CM_RESOURCE_MEMORY_LARGE_48 (0x400) field holds bits [47:16] (<< 16) + * CM_RESOURCE_MEMORY_LARGE_64 (0x800) field holds bits [63:32] (<< 32) + */ +static +ULONGLONG +RtlpDecodeMemIoValue( + _In_ UCHAR Type, + _In_ USHORT Flags, + _In_ ULONG Encoded) +{ + if (Type == CmResourceTypePort || Type == CmResourceTypeMemory) + return Encoded; + + if (Flags & CM_RESOURCE_MEMORY_LARGE_40) + return (ULONGLONG)Encoded << 8; + + if (Flags & CM_RESOURCE_MEMORY_LARGE_48) + return (ULONGLONG)Encoded << 16; + + if (Flags & CM_RESOURCE_MEMORY_LARGE_64) + return (ULONGLONG)Encoded << 32; + + return 0; +} + +/* + * @implemented + * + * Decode an assigned (CM_PARTIAL_RESOURCE_DESCRIPTOR) memory or port descriptor: + * return its full 64-bit length and, optionally, its start address. + */ +ULONGLONG +NTAPI +RtlCmDecodeMemIoResource( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Start) +{ + if (Start != NULL) + *Start = Descriptor->u.Generic.Start.QuadPart; + + return RtlpDecodeMemIoValue(Descriptor->Type, + Descriptor->Flags, + Descriptor->u.Generic.Length); +} + +/* + * @implemented + * + * Decode a requirement (IO_RESOURCE_DESCRIPTOR) memory or port descriptor: + * return its full 64-bit length, maybe its (also large-encoded) + * alignment and the 64-bit minimum/maximum address bounds. + */ +ULONGLONG +NTAPI +RtlIoDecodeMemIoResource( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Alignment, + _Out_opt_ PULONGLONG MinimumAddress, + _Out_opt_ PULONGLONG MaximumAddress) +{ + if (Alignment != NULL) + { + *Alignment = RtlpDecodeMemIoValue(Descriptor->Type, + Descriptor->Flags, + Descriptor->u.Generic.Alignment); + } + + if (MinimumAddress != NULL) + *MinimumAddress = Descriptor->u.Generic.MinimumAddress.QuadPart; + + if (MaximumAddress != NULL) + *MaximumAddress = Descriptor->u.Generic.MaximumAddress.QuadPart; + + return RtlpDecodeMemIoValue(Descriptor->Type, + Descriptor->Flags, + Descriptor->u.Generic.Length); +} + +/* + * @implemented + * + * Fill in a CM_PARTIAL_RESOURCE_DESCRIPTOR for a port or memory region. When a + * memory Length does not fit in 32 bits the descriptor is promoted to a + * CmResourceTypeMemoryLarge form. + * + * Returns STATUS_INVALID_PARAMETER for an unsupported Type (or a port length + * above 4 GB) and STATUS_UNSUCCESSFUL if the length cannot be represented by any + * large form without dropping bits. + */ +NTSTATUS +NTAPI +RtlCmEncodeMemIoResource( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _In_ UCHAR Type, + _In_ ULONGLONG Length, + _In_ ULONGLONG Start) +{ + if (Type != CmResourceTypePort && + Type != CmResourceTypeMemory && + Type != CmResourceTypeMemoryLarge) + { + return STATUS_INVALID_PARAMETER; + } + + /* A port descriptor has only a 32-bit length and no large form. */ + if (Type == CmResourceTypePort) + { + if (Length > MAXULONG) + return STATUS_INVALID_PARAMETER; + + Descriptor->Type = CmResourceTypePort; + Descriptor->u.Generic.Start.QuadPart = Start; + Descriptor->u.Generic.Length = (ULONG)Length; + return STATUS_SUCCESS; + } + + /* Drop any stale large-form flags and record the start address. */ + Descriptor->Flags &= ~CM_RESOURCE_MEMORY_LARGE; + Descriptor->u.Generic.Start.QuadPart = Start; + + /* A length that fits 32 bits stays a plain memory descriptor. */ + if (Length <= MAXULONG) + { + Descriptor->Type = CmResourceTypeMemory; + Descriptor->u.Generic.Length = (ULONG)Length; + return STATUS_SUCCESS; + } + + /* Otherwise pick the tightest large form whose dropped low bits are zero. */ + if (Length <= ((ULONGLONG)MAXULONG << 8) && (Length & 0xFF) == 0) + { + Descriptor->u.Generic.Length = (ULONG)(Length >> 8); + Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_40; + } + else if (Length <= ((ULONGLONG)MAXULONG << 16) && (Length & 0xFFFF) == 0) + { + Descriptor->u.Generic.Length = (ULONG)(Length >> 16); + Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_48; + } + else if (Length <= ((ULONGLONG)MAXULONG << 32) && (Length & 0xFFFFFFFF) == 0) + { + Descriptor->u.Generic.Length = (ULONG)(Length >> 32); + Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_64; + } + else + { + return STATUS_UNSUCCESSFUL; + } + + Descriptor->Type = CmResourceTypeMemoryLarge; + return STATUS_SUCCESS; +} + +/* + * A large descriptor expresses alignment at the same granularity as its length. + * Scale the alignment up until it is a multiple of that granularity. + */ +static +BOOLEAN +RtlpEncodeLargeAlignment( + _In_ ULONGLONG Alignment, + _In_ ULONG Shift, + _Out_ PULONG Encoded) +{ + ULONGLONG LowMask = ((ULONGLONG)1 << Shift) - 1; + + while ((Alignment & LowMask) != 0) + { + ULONGLONG Doubled = Alignment << 1; + + if (Doubled < Alignment) /* overflowed past 64 bits */ + return FALSE; + + Alignment = Doubled; + } + + *Encoded = (ULONG)(Alignment >> Shift); + return TRUE; +} + +/* + * @implemented + * + * Fill in an IO_RESOURCE_DESCRIPTOR (a resource *requirement*) for a port or + * memory range. Also encodes the alignment and the 64-bit address bounds. + * A memory length above 4 GB is promoted to a CmResourceTypeMemoryLarge form, + * which matters as drivers depend on this behavior. + */ +NTSTATUS +NTAPI +RtlIoEncodeMemIoResource( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _In_ UCHAR Type, + _In_ ULONGLONG Length, + _In_ ULONGLONG Alignment, + _In_ ULONGLONG MinimumAddress, + _In_ ULONGLONG MaximumAddress) +{ + ULONG Shift; + USHORT LargeFlag; + + if (Type != CmResourceTypePort && + Type != CmResourceTypeMemory && + Type != CmResourceTypeMemoryLarge) + { + return STATUS_INVALID_PARAMETER; + } + + /* A port descriptor is limited to 32-bit length and alignment. */ + if (Type == CmResourceTypePort) + { + if (Length > MAXULONG || Alignment > MAXULONG) + return STATUS_INVALID_PARAMETER; + + Descriptor->Type = CmResourceTypePort; + Descriptor->u.Generic.MinimumAddress.QuadPart = MinimumAddress; + Descriptor->u.Generic.MaximumAddress.QuadPart = MaximumAddress; + Descriptor->u.Generic.Length = (ULONG)Length; + Descriptor->u.Generic.Alignment = (ULONG)Alignment; + return STATUS_SUCCESS; + } + + /* Memory: reset any stale large-form flags and record the address bounds. */ + Descriptor->Flags &= ~CM_RESOURCE_MEMORY_LARGE; + Descriptor->u.Generic.MinimumAddress.QuadPart = MinimumAddress; + Descriptor->u.Generic.MaximumAddress.QuadPart = MaximumAddress; + + /* Both values fit 32 bits: a plain memory descriptor. */ + if (Length <= MAXULONG && Alignment <= MAXULONG) + { + Descriptor->Type = CmResourceTypeMemory; + Descriptor->u.Generic.Length = (ULONG)Length; + Descriptor->u.Generic.Alignment = (ULONG)Alignment; + return STATUS_SUCCESS; + } + + /* Select the large form from the length; the alignment must fit that tier. */ + if (Length <= ((ULONGLONG)MAXULONG << 8)) + { + if (Alignment > ((ULONGLONG)MAXULONG << 8)) + return STATUS_UNSUCCESSFUL; + + Shift = 8; + LargeFlag = CM_RESOURCE_MEMORY_LARGE_40; + } + else if (Length <= ((ULONGLONG)MAXULONG << 16)) + { + if (Alignment > ((ULONGLONG)MAXULONG << 16)) + return STATUS_UNSUCCESSFUL; + + Shift = 16; + LargeFlag = CM_RESOURCE_MEMORY_LARGE_48; + } + else if (Length <= ((ULONGLONG)MAXULONG << 32)) + { + Shift = 32; + LargeFlag = CM_RESOURCE_MEMORY_LARGE_64; + } + else + { + return STATUS_UNSUCCESSFUL; + } + + /* The length must be exactly representable at this granularity. */ + if ((Length & (((ULONGLONG)1 << Shift) - 1)) != 0) + return STATUS_UNSUCCESSFUL; + + if (!RtlpEncodeLargeAlignment(Alignment, Shift, &Descriptor->u.Generic.Alignment)) + return STATUS_UNSUCCESSFUL; + + Descriptor->Type = CmResourceTypeMemoryLarge; + Descriptor->u.Generic.Length = (ULONG)(Length >> Shift); + Descriptor->Flags |= LargeFlag; + return STATUS_SUCCESS; +} + +/* + * @implemented + * + * Round a requested length up to the nearest value the descriptor + * 1:1 when it fits 32 bits, otherwise the next multiple of the + * granularity. + */ +NTSTATUS +NTAPI +RtlFindClosestEncodableLength( + _In_ ULONGLONG SourceLength, + _Out_ PULONGLONG TargetLength) +{ + if (SourceLength <= MAXULONG) + *TargetLength = SourceLength; + else if (SourceLength <= ((ULONGLONG)MAXULONG << 8)) + *TargetLength = (SourceLength + 0xFF) & ~(ULONGLONG)0xFF; + else if (SourceLength <= ((ULONGLONG)MAXULONG << 16)) + *TargetLength = (SourceLength + 0xFFFF) & ~(ULONGLONG)0xFFFF; + else if (SourceLength <= ((ULONGLONG)MAXULONG << 32)) + *TargetLength = (SourceLength + 0xFFFFFFFF) & ~(ULONGLONG)0xFFFFFFFF; + else + { + *TargetLength = 0; + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +}