From 1500a35309969adc2f38a4e5958b7922a5bb2bf8 Mon Sep 17 00:00:00 2001 From: Justin Miller Date: Sat, 5 Sep 2026 01:31:04 +0000 Subject: [PATCH] [NTOS:IO][ARBITER][SDK] Finish the root arbiters and fix some compatibility divergences (#9482) The root memory arbiter is the most important out of all of these. The main intention here is that there's some special ranges PCI can't ever look at it even if the firmware tries to claim it can. This is missing a few gaps still! for example in another PR I'm going to add in the ACPI hal to reserve the ECAM space and once that is published itll also be added to the root mem arbiter. * [ARBITER] Resolve ARBITER_RANGE_SHARED_DRIVER, fixes compat with Vista+ * [NTOS:IO] Finish the initial impl of the Root Arbiters * [NTOS:IO][SDK] Fix RtlFindRange truncation, use the Vista+ Sig --- ntoskrnl/io/pnpmgr/arb/arbbus.c | 2 +- ntoskrnl/io/pnpmgr/arb/arbdma.c | 33 +- ntoskrnl/io/pnpmgr/arb/arbmem.c | 390 +++++++++++++++++++-- ntoskrnl/io/pnpmgr/arb/arbport.c | 545 ++++++++++++++++++++++++++++-- ntoskrnl/ntoskrnl.spec | 2 +- sdk/include/ndk/rtlfuncs.h | 4 +- sdk/lib/drivers/arbiter/arbiter.h | 7 +- sdk/lib/drivers/arbiter/range.c | 4 +- sdk/lib/rtl/rangelist.c | 12 +- 9 files changed, 920 insertions(+), 79 deletions(-) diff --git a/ntoskrnl/io/pnpmgr/arb/arbbus.c b/ntoskrnl/io/pnpmgr/arb/arbbus.c index 3a0c55975d8..158b9b72479 100644 --- a/ntoskrnl/io/pnpmgr/arb/arbbus.c +++ b/ntoskrnl/io/pnpmgr/arb/arbbus.c @@ -116,7 +116,7 @@ IopArbBusNumberInitialize(VOID) NULL); if (!NT_SUCCESS(Status)) { - DPRINT1("IopArbBusNumberInitialize: Failed with %X", Status); + DPRINT1("IopArbBusNumberInitialize: Failed with %X\n", Status); } return Status; diff --git a/ntoskrnl/io/pnpmgr/arb/arbdma.c b/ntoskrnl/io/pnpmgr/arb/arbdma.c index 1b4a439fb5b..01a353a33d5 100644 --- a/ntoskrnl/io/pnpmgr/arb/arbdma.c +++ b/ntoskrnl/io/pnpmgr/arb/arbdma.c @@ -93,6 +93,34 @@ IopArbDmaScoreRequirement( return (INT32)(IoDescriptor->u.Dma.MaximumChannel - IoDescriptor->u.Dma.MinimumChannel); } +/** + * @brief + * The Root DMA arbiter's OverrideConflict: refuses every conflict. + * + * @param[in] Arbiter + * The Root DMA arbiter instance. + * + * @param[in,out] ArbState + * The allocation state of the requirement that could not be placed. + * + * @return + * Returns FALSE, always. + **/ +static +BOOLEAN +NTAPI +IopArbDmaOverrideConflict( + _In_ PARBITER_INSTANCE Arbiter, + _Inout_ PARBITER_ALLOCATION_STATE ArbState) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Arbiter); + UNREFERENCED_PARAMETER(ArbState); + + return FALSE; +} + /** * @brief Initialize the RootDmaArbiter. * @@ -108,11 +136,12 @@ IopArbDmaInitialize(VOID) NTSTATUS Status = STATUS_UNSUCCESSFUL; PAGED_CODE(); - IopRootDmaArbiter.Name = L"RootDma"; + IopRootDmaArbiter.Name = L"RootDMA"; IopRootDmaArbiter.UnpackRequirement = IopArbDmaUnpackRequirements; IopRootDmaArbiter.PackResource = IopArbDmaPackResource; IopRootDmaArbiter.UnpackResource = IopArbDmaUnpackResource; IopRootDmaArbiter.ScoreRequirement = IopArbDmaScoreRequirement; + IopRootDmaArbiter.OverrideConflict = IopArbDmaOverrideConflict; Status = ArbiterLibInitializeInstance(&IopRootDmaArbiter, NULL, @@ -122,7 +151,7 @@ IopArbDmaInitialize(VOID) NULL); if (!NT_SUCCESS(Status)) { - DPRINT1("IopArbDmaInitialize: Failed with %X", Status); + DPRINT1("IopArbDmaInitialize: Failed with %X\n", Status); } return Status; diff --git a/ntoskrnl/io/pnpmgr/arb/arbmem.c b/ntoskrnl/io/pnpmgr/arb/arbmem.c index 5906a908623..80da2bc2de3 100644 --- a/ntoskrnl/io/pnpmgr/arb/arbmem.c +++ b/ntoskrnl/io/pnpmgr/arb/arbmem.c @@ -15,8 +15,122 @@ extern ARBITER_INSTANCE IopRootMemArbiter; +ULONGLONG +NTAPI +RtlIoDecodeMemIoResource( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Alignment, + _Out_opt_ PULONGLONG MinimumAddress, + _Out_opt_ PULONGLONG MaximumAddress); + +ULONGLONG +NTAPI +RtlCmDecodeMemIoResource( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Start); + +/* The last address a card that only wires up 24 address lines can reach. */ +#define MEMORY_24_BIT_MAX_ADDRESS 0xFFFFFF + /* FUNCTIONS *****************************************************************/ +/** + * @brief + * Translates one bus-relative address to a system-physical one on + * the ISA bus, and reports the space it landed in as a CM resource + * type. + * + * @param[in] SourceType + * The CM resource type of the address being translated. + * + * @param[in] SourceAddress + * The bus-relative address. + * + * @param[out] TranslatedAddress + * Receives the system-physical address. + * + * @param[out] TranslatedType + * Receives the post-translation resource type. A HAL may map one + * space onto another, so the type is taken from the space the + * translation actually returned rather than assumed, e.g IOPorts, MMIO, etc. + * + * @return + * Returns STATUS_SUCCESS, STATUS_INVALID_PARAMETER for a resource + * type that carries no address or for an address space the HAL is + * not expected to report, or STATUS_UNSUCCESSFUL when the HAL + * declines the translation. + */ +static +NTSTATUS +IopArbMemTranslateAddress( + _In_ UCHAR SourceType, + _In_ PHYSICAL_ADDRESS SourceAddress, + _Out_ PPHYSICAL_ADDRESS TranslatedAddress, + _Out_ PUCHAR TranslatedType) +{ + ULONG AddressSpace; + + PAGED_CODE(); + + if (SourceType == CmResourceTypeMemory || SourceType == CmResourceTypeMemoryLarge) + AddressSpace = 0; /* System memory */ + else if (SourceType == CmResourceTypePort) + AddressSpace = 1; /* I/O port space */ + else + return STATUS_INVALID_PARAMETER; + + if (!HalTranslateBusAddress(Isa, 0, SourceAddress, &AddressSpace, TranslatedAddress)) + return STATUS_UNSUCCESSFUL; + + /* The HAL reports back the space it landed in; only these two are expected. */ + if (AddressSpace == 1) + { + *TranslatedType = CmResourceTypePort; + } + else if (AddressSpace == 0) + { + *TranslatedType = (SourceType == CmResourceTypeMemoryLarge) + ? CmResourceTypeMemoryLarge + : CmResourceTypeMemory; + } + else + { + return STATUS_INVALID_PARAMETER; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * Extracts the placement window from one memory requirement. + * The UnpackRequirement callback of the Root Memory arbiter. + * + * @param[in] IoDescriptor + * The requirement to decode. + * + * @param[out] OutMinimumAddress + * Receives the lowest physical address the requirement accepts. + * + * @param[out] OutMaximumAddress + * Receives the highest physical address the requirement accepts. + * A CM_RESOURCE_MEMORY_24 card only wires up 24 address lines and + * cannot be reached above 16 MB whatever its descriptor claims, so + * its window is clamped here rather than left for the placement + * search to get wrong. + * + * @param[out] OutLength + * Receives the size of the window wanted. + * + * @param[out] OutAlignment + * Receives the requirement's alignment; a zero alignment means the + * device does not care and is normalised to byte alignment, since + * the placement arithmetic divides by it. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI IopArbMemUnpackRequirements( @@ -27,17 +141,46 @@ IopArbMemUnpackRequirements( _Out_ PUINT64 OutAlignment) { PAGED_CODE(); - DPRINT("IopArbMemUnpackRequirements: IoDescriptor: %p, OutMinimumAddress: %p, OutMaximumAddress: %p, OutLength: %p, OutAlignment: %p\n", - IoDescriptor, - OutMinimumAddress, - OutMaximumAddress, - OutLength, - OutAlignment); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + *OutLength = RtlIoDecodeMemIoResource(IoDescriptor, + OutAlignment, + OutMinimumAddress, + OutMaximumAddress); + + if (*OutAlignment == 0) + *OutAlignment = 1; + + if (IoDescriptor->Type == CmResourceTypeMemory && + (IoDescriptor->Flags & CM_RESOURCE_MEMORY_24) && + *OutMaximumAddress > MEMORY_24_BIT_MAX_ADDRESS) + { + *OutMaximumAddress = MEMORY_24_BIT_MAX_ADDRESS; + } + + return STATUS_SUCCESS; } +/** + * @brief + * Materialises the arbiter's chosen placement as an assigned CM + * descriptor. The PackResource callback of the Root Memory arbiter. + * + * @param[in] IoDescriptor + * The requirement the placement satisfies. Type, Flags and + * ShareDisposition come across unchanged - and have to, since for + * a large-memory descriptor the Flags are what say how the Length + * beside them is encoded. + * + * @param[in] Start + * The physical address the arbiter settled on. + * + * @param[out] CmDescriptor + * Receives the assigned descriptor. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI IopArbMemPackResource( @@ -46,15 +189,37 @@ IopArbMemPackResource( _Out_ PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescriptor) { PAGED_CODE(); - DPRINT("IopArbMemPackResource: IoDescriptor: %p, Start: %p, CmDescriptor: %p\n", - IoDescriptor, - Start, - CmDescriptor); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + CmDescriptor->Type = IoDescriptor->Type; + CmDescriptor->Flags = IoDescriptor->Flags; + CmDescriptor->ShareDisposition = IoDescriptor->ShareDisposition; + + CmDescriptor->u.Generic.Start.QuadPart = Start; + CmDescriptor->u.Generic.Length = IoDescriptor->u.Generic.Length; + + return STATUS_SUCCESS; } +/** + * @brief + * Reads the placement back out of an already-assigned descriptor + * (a firmware boot configuration, typically) so the arbiter can + * mark that span occupied. The inverse of IopArbMemPackResource + * and the UnpackResource callback of the Root Memory arbiter. + * + * @param[in] CmDescriptor + * The assigned descriptor to decode. + * + * @param[out] Start + * Receives the assigned physical address. + * + * @param[out] OutLength + * Receives the assigned length. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI IopArbMemUnpackResource( @@ -63,50 +228,219 @@ IopArbMemUnpackResource( _Out_ PUINT64 OutLength) { PAGED_CODE(); - DPRINT("IopArbMemUnpackResource: CmDescriptor: %p, Start: %p, OutLength: %p\n", - CmDescriptor, - Start, - OutLength); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + *OutLength = RtlCmDecodeMemIoResource(CmDescriptor, Start); + + return STATUS_SUCCESS; } +/** + * @brief + * Scores how constrained a requirement is: the number of distinct + * aligned addresses it could be placed at inside its own window, + * ignoring what is already allocated. The ScoreRequirement + * callback of the Root Memory arbiter. + * + * @param[in] IoDescriptor + * The requirement to score. + * + * @return + * Returns the placement count, saturated to MAXLONG, or -1 for a + * window that cannot hold the requirement at all - which the + * engine treats as a bad configuration and fails the arbitration + * on. + * + * @remarks + * The engine places the most constrained device (the lowest score) + * first, PCI devices have a habit of absorbing a remaining range. + */ +static INT32 NTAPI IopArbMemScoreRequirement( _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor) { - PAGED_CODE(); - DPRINT("IopArbMemScoreRequirement: IoDescriptor: %p\n", - IoDescriptor); + UINT64 Length, Alignment; + UINT64 Minimum, Maximum, AlignedMinimum; + UINT64 Span, Placements; - UNIMPLEMENTED; - return 0; + PAGED_CODE(); + + Length = RtlIoDecodeMemIoResource(IoDescriptor, &Alignment, &Minimum, &Maximum); + + if (Alignment == 0) + Alignment = 1; + + /* Round the window's base up to the first address the device can sit at. */ + AlignedMinimum = (Minimum + Alignment - 1) & ~(Alignment - 1); + if (AlignedMinimum < Minimum || AlignedMinimum > Maximum) + return -1; + + /* Count in addresses above the base, so a full-width window cannot overflow. */ + Span = Maximum - AlignedMinimum; + + if (Length != 0) + { + if (Length - 1 > Span) + return -1; + + Span -= Length - 1; + } + + Placements = Span / Alignment + 1; + return (INT32)min(Placements, MAXLONG); } +/** + * @brief + * Translates one registry allocation-ordering window from the + * bus-relative addresses it is written in into the system-physical + * space the arbiter allocates in. The TranslateOrdering function + * of the Root Memory arbiter. + * + * @param[out] OutIoDescriptor + * Receives the translated copy. An entry whose endpoints cannot + * both be translated is marked CmResourceTypeNull for the ordering + * reader to drop; otherwise it takes the translated type. + * + * @param[in] IoDescriptor + * The ordering window. A descriptor that carries no address passes + * through untouched. + * + * @return + * Returns STATUS_SUCCESS. + */ +static +NTSTATUS +NTAPI +IopArbMemTranslateOrdering( + _Out_ PIO_RESOURCE_DESCRIPTOR OutIoDescriptor, + _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor) +{ + UCHAR SourceType; + UCHAR MinimumType; + UCHAR MaximumType; + + PAGED_CODE(); + + *OutIoDescriptor = *IoDescriptor; + + SourceType = IoDescriptor->Type; + if (SourceType != CmResourceTypePort && + SourceType != CmResourceTypeMemory && + SourceType != CmResourceTypeMemoryLarge) + { + return STATUS_SUCCESS; + } + + MinimumType = SourceType; + MaximumType = SourceType; + + if (!NT_SUCCESS(IopArbMemTranslateAddress(SourceType, + IoDescriptor->u.Generic.MinimumAddress, + &OutIoDescriptor->u.Generic.MinimumAddress, + &MinimumType)) || + !NT_SUCCESS(IopArbMemTranslateAddress(SourceType, + IoDescriptor->u.Generic.MaximumAddress, + &OutIoDescriptor->u.Generic.MaximumAddress, + &MaximumType))) + { + OutIoDescriptor->Type = CmResourceTypeNull; + } + else + { + OutIoDescriptor->Type = MaximumType; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * The Root Memory arbiter's FindSuitableRange: the engine search, + * incremented for a device asking to keep the MMIO window the firmware + * already programmed it into. + * + * @param[in] Arbiter + * The Root Memory arbiter instance. + * + * @param[in,out] ArbState + * The allocation state of the requirement being placed. + * + * @return + * Returns TRUE if a placement was found, FALSE otherwise. + * + * @remarks + * A boot configuration is held in the range list as an + * ARBITER_RANGE_BOOT_ALLOCATED range and so reads as occupied. A + * request carrying ARBITER_FLAG_BOOT_CONFIG is precisely the one + * asking for that space back, so those ranges are made available + * to it. The engine already extends the same courtesy to legacy + * request sources. + */ +static +BOOLEAN +NTAPI +IopArbMemFindSuitableRange( + _In_ PARBITER_INSTANCE Arbiter, + _Inout_ PARBITER_ALLOCATION_STATE ArbState) +{ + PAGED_CODE(); + + if (ArbState->Entry != NULL && (ArbState->Entry->Flags & ARBITER_FLAG_BOOT_CONFIG)) + ArbState->RangeAvailableAttributes |= ARBITER_RANGE_BOOT_ALLOCATED; + + return ArbiterLibFindSuitableRange(Arbiter, ArbState); +} + +/** + * @brief Initialize the RootMemoryArbiter + * + * The root memory arbiter owns CPU physical address space and hands each + * device the MMIO window it decodes. It is the fallback for every device whose + * memory no closer arbiter claims. + * + * The physical page 0 is reserved with no attributes at all, so that no + * availability mask can hand it out: the real-mode interrupt vector table and + * the BIOS data area live there, and a device decoding over them corrupts the + * machine rather than merely failing. + * + * @return NTSTATUS + * @retval STATUS_SUCCESS + * @retval STATUS_UNSUCCESSFUL + * @retval STATUS_INSUFFICIENT_RESOURCES + */ NTSTATUS NTAPI IopArbMemInitialize(VOID) { - NTSTATUS Status = STATUS_UNSUCCESSFUL; + NTSTATUS Status; PAGED_CODE(); + IopRootMemArbiter.Name = L"RootMemory"; IopRootMemArbiter.UnpackRequirement = IopArbMemUnpackRequirements; IopRootMemArbiter.PackResource = IopArbMemPackResource; IopRootMemArbiter.UnpackResource = IopArbMemUnpackResource; IopRootMemArbiter.ScoreRequirement = IopArbMemScoreRequirement; + IopRootMemArbiter.FindSuitableRange = IopArbMemFindSuitableRange; Status = ArbiterLibInitializeInstance(&IopRootMemArbiter, NULL, CmResourceTypeMemory, IopRootMemArbiter.Name, L"Root", - NULL); + IopArbMemTranslateOrdering); if (!NT_SUCCESS(Status)) { - DPRINT1("IopArbMemInitialize: Failed with %X", Status); + DPRINT1("IopArbMemInitialize: Failed with %X\n", Status); + return Status; + } + + Status = RtlAddRange(IopRootMemArbiter.Allocation, 0ULL, 0xFFFULL, 0, 0, NULL, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("IopArbMemInitialize: Reserving page 0 failed with %X\n", Status); } return Status; diff --git a/ntoskrnl/io/pnpmgr/arb/arbport.c b/ntoskrnl/io/pnpmgr/arb/arbport.c index 0529c35a10d..f3c00d40f39 100644 --- a/ntoskrnl/io/pnpmgr/arb/arbport.c +++ b/ntoskrnl/io/pnpmgr/arb/arbport.c @@ -15,11 +15,174 @@ extern ARBITER_INSTANCE IopRootPortArbiter; +ULONGLONG +NTAPI +RtlIoDecodeMemIoResource( + _In_ PIO_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Alignment, + _Out_opt_ PULONGLONG MinimumAddress, + _Out_opt_ PULONGLONG MaximumAddress); + +ULONGLONG +NTAPI +RtlCmDecodeMemIoResource( + _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor, + _Out_opt_ PULONGLONG Start); + +/* + * A card that decodes only the low 10 or 12 address lines answers not just on + * its own range but on every "alias" a multiple of 0x400 / 0x1000 above it, up + * to the top of the 64 KB port space. + */ +#define PORT_ALIAS_STRIDE_10_BIT 0x400 +#define PORT_ALIAS_STRIDE_12_BIT 0x1000 +#define PORT_MAX_ADDRESS 0xFFFF + /* FUNCTIONS *****************************************************************/ +/** + * @brief + * Translates one bus-relative address to a system-physical one on + * the ISA bus, and reports the space it landed in as a CM resource + * type. + * + * @param[in] SourceType + * The CM resource type of the address being translated. + * + * @param[in] SourceAddress + * The bus-relative address. + * + * @param[out] TranslatedAddress + * Receives the system-physical address. + * + * @param[out] TranslatedType + * Receives the post-translation resource type. A HAL may map one + * space onto another (memory-mapped I/O ports being the usual + * case) so the type is taken from the space the translation + * actually returned rather than assumed, e.g IOPorts, MMIO, etc. + * + * @return + * Returns STATUS_SUCCESS, STATUS_INVALID_PARAMETER for a resource + * type that carries no address or for an address space the HAL is + * not expected to report, or STATUS_UNSUCCESSFUL when the HAL + * declines the translation. + */ +static +NTSTATUS +IopArbPortTranslateAddress( + _In_ UCHAR SourceType, + _In_ PHYSICAL_ADDRESS SourceAddress, + _Out_ PPHYSICAL_ADDRESS TranslatedAddress, + _Out_ PUCHAR TranslatedType) +{ + ULONG AddressSpace; + + PAGED_CODE(); + + if (SourceType == CmResourceTypePort) + AddressSpace = 1; /* I/O port space */ + else if (SourceType == CmResourceTypeMemory || SourceType == CmResourceTypeMemoryLarge) + AddressSpace = 0; /* System memory */ + else + return STATUS_INVALID_PARAMETER; + + if (!HalTranslateBusAddress(Isa, 0, SourceAddress, &AddressSpace, TranslatedAddress)) + return STATUS_UNSUCCESSFUL; + + /* The HAL reports back the space it landed in; only these two are expected. */ + if (AddressSpace == 1) + { + *TranslatedType = CmResourceTypePort; + } + else if (AddressSpace == 0) + { + *TranslatedType = (SourceType == CmResourceTypeMemoryLarge) + ? CmResourceTypeMemoryLarge + : CmResourceTypeMemory; + } + else + { + return STATUS_INVALID_PARAMETER; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * Walks the decode-alias chain of a port range: given the previous + * alias, or the granted range's start on the first call, + * produces the next one. + * + * @param[in] DescriptorFlags + * The requirement's flags. Only 10-bit and 12-bit decode + * requirements alias; a full 16-bit decoder owns exactly what + * it asked for. + * + * @param[in] LastAlias + * The previous alias start, or the granted range's start. + * + * @param[out] NextAlias + * Receives the next alias start. + * + * @return + * Returns TRUE with the next alias, or FALSE once the card decodes + * fully or the next alias would leave I/O space. + */ +static +BOOLEAN +IopArbPortGetNextAlias( + _In_ USHORT DescriptorFlags, + _In_ UINT64 LastAlias, + _Out_ PUINT64 NextAlias) +{ + UINT64 Next; + + PAGED_CODE(); + + if (DescriptorFlags & CM_RESOURCE_PORT_10_BIT_DECODE) + Next = LastAlias + PORT_ALIAS_STRIDE_10_BIT; + else if (DescriptorFlags & CM_RESOURCE_PORT_12_BIT_DECODE) + Next = LastAlias + PORT_ALIAS_STRIDE_12_BIT; + else + return FALSE; + + if (Next > PORT_MAX_ADDRESS) + return FALSE; + + *NextAlias = Next; + return TRUE; +} + +/** + * @brief + * Extracts the placement window from one I/O port requirement. + * The UnpackRequirement callback of the Root Port arbiter. + * + * @param[in] IoDescriptor + * The requirement to decode. + * + * @param[out] OutMinimumAddress + * Receives the lowest port address the requirement accepts. + * + * @param[out] OutMaximumAddress + * Receives the highest port address the requirement accepts. + * + * @param[out] OutLength + * Receives the number of consecutive ports wanted. + * + * @param[out] OutAlignment + * Receives the requirement's alignment; a zero alignment means the + * device does not care and is normalized to byte alignment, since + * the placement arithmetic divides by it. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI -IopPortMemUnpackRequirements( +IopArbPortUnpackRequirements( _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor, _Out_ PUINT64 OutMinimumAddress, _Out_ PUINT64 OutMaximumAddress, @@ -27,86 +190,396 @@ IopPortMemUnpackRequirements( _Out_ PUINT64 OutAlignment) { PAGED_CODE(); - DPRINT("IopPortMemUnpackRequirements: IoDescriptor: %p, OutMinimumAddress: %p, OutMaximumAddress: %p, OutLength: %p, OutAlignment: %p\n", - IoDescriptor, - OutMinimumAddress, - OutMaximumAddress, - OutLength, - OutAlignment); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + *OutLength = RtlIoDecodeMemIoResource(IoDescriptor, + OutAlignment, + OutMinimumAddress, + OutMaximumAddress); + + if (*OutAlignment == 0) + *OutAlignment = 1; + + return STATUS_SUCCESS; } +/** + * @brief + * Materialises the arbiter's chosen placement as an assigned CM + * descriptor. The PackResource callback of the Root Port arbiter. + * + * @param[in] IoDescriptor + * The requirement the placement satisfies. Type, Flags and + * ShareDisposition come across unchanged, and so does the Length, + * still in the encoding its Flags describe. + * + * @param[in] Start + * The port address the arbiter settled on. + * + * @param[out] CmDescriptor + * Receives the assigned descriptor. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI -IopPortMemPackResource( +IopArbPortPackResource( _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor, _In_ UINT64 Start, _Out_ PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescriptor) { PAGED_CODE(); - DPRINT("IopPortMemPackResource: IoDescriptor: %p, Start: %p, CmDescriptor: %p\n", - IoDescriptor, - Start, - CmDescriptor); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + CmDescriptor->Type = IoDescriptor->Type; + CmDescriptor->Flags = IoDescriptor->Flags; + CmDescriptor->ShareDisposition = IoDescriptor->ShareDisposition; + + CmDescriptor->u.Generic.Start.QuadPart = Start; + CmDescriptor->u.Generic.Length = IoDescriptor->u.Generic.Length; + + return STATUS_SUCCESS; } +/** + * @brief + * Reads the placement back out of an already-assigned descriptor + * (a firmware boot configuration, typically) so the arbiter can + * mark that span occupied. The inverse of IopArbPortPackResource + * and the UnpackResource callback of the Root Port arbiter. + * + * @param[in] CmDescriptor + * The assigned descriptor to decode. + * + * @param[out] Start + * Receives the assigned port address. + * + * @param[out] OutLength + * Receives the assigned length. + * + * @return + * Returns STATUS_SUCCESS. + */ +static NTSTATUS NTAPI -IopPortMemUnpackResource( +IopArbPortUnpackResource( _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescriptor, _Out_ PUINT64 Start, _Out_ PUINT64 OutLength) { PAGED_CODE(); - DPRINT("IopPortMemUnpackResource: CmDescriptor: %p, Start: %p, OutLength: %p\n", - CmDescriptor, - Start, - OutLength); - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + *OutLength = RtlCmDecodeMemIoResource(CmDescriptor, Start); + + return STATUS_SUCCESS; } +/** + * @brief + * Scores how constrained a requirement is: the number of distinct + * aligned addresses it could be placed at inside its own window, + * ignoring what is already allocated. The ScoreRequirement + * callback of the Root Port arbiter. + * + * @param[in] IoDescriptor + * The requirement to score. + * + * @return + * Returns the placement count, saturated to MAXLONG, or -1 for a + * window that cannot hold the requirement at all - which the + * engine treats as a bad configuration and fails the arbitration + * on. + * + * @remarks + * The engine places the most constrained device (the lowest score) + * first, so the devices with real freedom of movement are left to + * absorb whatever space is still available. + */ +static INT32 NTAPI -IopPortMemScoreRequirement( +IopArbPortScoreRequirement( _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor) { - PAGED_CODE(); - DPRINT("IopPortMemScoreRequirement: IoDescriptor: %p\n", - IoDescriptor); + UINT64 Length, Alignment; + UINT64 Minimum, Maximum, AlignedMinimum; + UINT64 Span, Placements; - UNIMPLEMENTED; - return 0; + PAGED_CODE(); + + Length = RtlIoDecodeMemIoResource(IoDescriptor, &Alignment, &Minimum, &Maximum); + + if (Alignment == 0) + Alignment = 1; + + /* Round the window's base up to the first address the device can sit at. */ + AlignedMinimum = (Minimum + Alignment - 1) & ~(Alignment - 1); + if (AlignedMinimum < Minimum || AlignedMinimum > Maximum) + return -1; + + /* Count in addresses above the base, so a full-width window cannot overflow. */ + Span = Maximum - AlignedMinimum; + + if (Length != 0) + { + if (Length - 1 > Span) + return -1; + + Span -= Length - 1; + } + + Placements = Span / Alignment + 1; + return (INT32)min(Placements, MAXLONG); } +/** + * @brief + * Translates one registry allocation-ordering window from the + * bus-relative addresses it is written in into the system-physical + * space the arbiter allocates in. The TranslateOrdering function + * of the Root Port arbiter. + * + * @param[out] OutIoDescriptor + * Receives the translated copy. An entry whose endpoints cannot + * both be translated is marked CmResourceTypeNull for the ordering + * reader to drop; otherwise it takes the translated type. + * + * @param[in] IoDescriptor + * The ordering window. A descriptor that carries no address passes + * through untouched. + * + * @return + * Returns STATUS_SUCCESS. + */ +static +NTSTATUS +NTAPI +IopArbPortTranslateOrdering( + _Out_ PIO_RESOURCE_DESCRIPTOR OutIoDescriptor, + _In_ PIO_RESOURCE_DESCRIPTOR IoDescriptor) +{ + UCHAR SourceType; + UCHAR MinimumType; + UCHAR MaximumType; + + PAGED_CODE(); + + *OutIoDescriptor = *IoDescriptor; + + SourceType = IoDescriptor->Type; + if (SourceType != CmResourceTypePort && + SourceType != CmResourceTypeMemory && + SourceType != CmResourceTypeMemoryLarge) + { + return STATUS_SUCCESS; + } + + MinimumType = SourceType; + MaximumType = SourceType; + + if (!NT_SUCCESS(IopArbPortTranslateAddress(SourceType, + IoDescriptor->u.Generic.MinimumAddress, + &OutIoDescriptor->u.Generic.MinimumAddress, + &MinimumType)) || + !NT_SUCCESS(IopArbPortTranslateAddress(SourceType, + IoDescriptor->u.Generic.MaximumAddress, + &OutIoDescriptor->u.Generic.MaximumAddress, + &MaximumType))) + { + OutIoDescriptor->Type = CmResourceTypeNull; + } + else + { + OutIoDescriptor->Type = MaximumType; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * The Root Port arbiter's FindSuitableRange: the engine search, + * incremented for a device asking to keep the ports the firmware + * already programmed it into. + * + * @param[in] Arbiter + * The Root Port arbiter instance. + * + * @param[in,out] ArbState + * The allocation state of the requirement being placed. + * + * @return + * Returns TRUE if a placement was found, FALSE otherwise. + * + * @remarks + * A boot configuration is held in the range list as an + * ARBITER_RANGE_BOOT_ALLOCATED range and so reads as occupied. A + * request carrying ARBITER_FLAG_BOOT_CONFIG is precisely the one + * asking for that space back, so those ranges are made available + * to it. The engine already extends the same courtesy to legacy + * request sources. + */ +static +BOOLEAN +NTAPI +IopArbPortFindSuitableRange( + _In_ PARBITER_INSTANCE Arbiter, + _Inout_ PARBITER_ALLOCATION_STATE ArbState) +{ + PAGED_CODE(); + + if (ArbState->Entry != NULL && (ArbState->Entry->Flags & ARBITER_FLAG_BOOT_CONFIG)) + ArbState->RangeAvailableAttributes |= ARBITER_RANGE_BOOT_ALLOCATED; + + return ArbiterLibFindSuitableRange(Arbiter, ArbState); +} + +/** + * @brief + * The Root Port arbiter's AddAllocation: records the granted range + * in the tentative allocation, and behind it every port range a + * partially-decoding ISA card would shadow. + * + * @param[in] Arbiter + * The Root Port arbiter instance. + * + * @param[in,out] ArbState + * The allocation state carrying the granted Start and End. Alias + * ranges span the same width, belong to the same device, and are + * tagged ARBITER_RANGE_PORT_ALIAS. + */ +static +VOID +NTAPI +IopArbPortAddAllocation( + _In_ PARBITER_INSTANCE Arbiter, + _Inout_ PARBITER_ALLOCATION_STATE ArbState) +{ + PARBITER_ALTERNATIVE Alternative = ArbState->CurrentAlternative; + PVOID Owner = ArbState->Entry ? ArbState->Entry->PhysicalDeviceObject : NULL; + ULONG Flags = RTL_RANGE_LIST_ADD_IF_CONFLICT; + UINT64 Alias; + UINT64 Span; + + PAGED_CODE(); + + if (Alternative != NULL && (Alternative->Flags & ARBITER_ALTERNATIVE_FLAG_SHARED)) + Flags |= RTL_RANGE_LIST_ADD_SHARED; + + RtlAddRange(Arbiter->PossibleAllocation, + ArbState->Start, + ArbState->End, + ArbState->RangeAttributes, + Flags, + NULL, + Owner); + + if (Alternative == NULL) + return; + + /* Alias the width actually granted, not the width asked for. */ + Span = ArbState->End - ArbState->Start; + + Alias = ArbState->Start; + while (IopArbPortGetNextAlias(Alternative->Descriptor->Flags, Alias, &Alias)) + { + RtlAddRange(Arbiter->PossibleAllocation, + Alias, + Alias + Span, + ArbState->RangeAttributes | ARBITER_RANGE_PORT_ALIAS, + Flags, + NULL, + Owner); + } +} + +/** + * @brief + * The Root Port arbiter's BacktrackAllocation: the exact inverse + * of IopArbPortAddAllocation. + * + * @param[in] Arbiter + * The Root Port arbiter instance. + * + * @param[in,out] ArbState + * The allocation state whose placement is being withdrawn. + * + * @remarks + * The alias chain is regenerated and removed along with the + * granted range. Leaving the aliases behind would quietly wall off + * the port space for every placement the engine tries afterwards. + */ +static +VOID +NTAPI +IopArbPortBacktrackAllocation( + _In_ PARBITER_INSTANCE Arbiter, + _Inout_ PARBITER_ALLOCATION_STATE ArbState) +{ + PARBITER_ALTERNATIVE Alternative = ArbState->CurrentAlternative; + PVOID Owner = ArbState->Entry ? ArbState->Entry->PhysicalDeviceObject : NULL; + UINT64 Alias; + UINT64 Span; + + PAGED_CODE(); + + if (Alternative != NULL) + { + Span = ArbState->End - ArbState->Start; + + Alias = ArbState->Start; + while (IopArbPortGetNextAlias(Alternative->Descriptor->Flags, Alias, &Alias)) + { + RtlDeleteRange(Arbiter->PossibleAllocation, Alias, Alias + Span, Owner); + } + } + + RtlDeleteRange(Arbiter->PossibleAllocation, ArbState->Start, ArbState->End, Owner); +} + +/** + * @brief Initialize the RootPortArbiter + * + * The root port arbiter owns the 16-bit x86 I/O port space and hands out + * sub-ranges of it. It is the fallback for every device whose ports no closer + * arbiter claims: root-enumerated and HAL-reported legacy hardware above all, + * which is also the hardware that partially decodes its address lines and so + * needs the aliasing this arbiter adds. + * + * @return NTSTATUS + * @retval STATUS_SUCCESS + * @retval STATUS_UNSUCCESSFUL + * @retval STATUS_INSUFFICIENT_RESOURCES + */ NTSTATUS NTAPI IopArbPortInitialize(VOID) { - NTSTATUS Status = STATUS_UNSUCCESSFUL; + NTSTATUS Status; PAGED_CODE(); + IopRootPortArbiter.Name = L"RootPort"; - IopRootPortArbiter.UnpackRequirement = IopPortMemUnpackRequirements; - IopRootPortArbiter.PackResource = IopPortMemPackResource; - IopRootPortArbiter.UnpackResource = IopPortMemUnpackResource; - IopRootPortArbiter.ScoreRequirement = IopPortMemScoreRequirement; + IopRootPortArbiter.UnpackRequirement = IopArbPortUnpackRequirements; + IopRootPortArbiter.PackResource = IopArbPortPackResource; + IopRootPortArbiter.UnpackResource = IopArbPortUnpackResource; + IopRootPortArbiter.ScoreRequirement = IopArbPortScoreRequirement; + + /* Port-specific placement: boot-config leniency and ISA decode aliasing. */ + IopRootPortArbiter.FindSuitableRange = IopArbPortFindSuitableRange; + IopRootPortArbiter.AddAllocation = IopArbPortAddAllocation; + IopRootPortArbiter.BacktrackAllocation = IopArbPortBacktrackAllocation; Status = ArbiterLibInitializeInstance(&IopRootPortArbiter, NULL, CmResourceTypePort, IopRootPortArbiter.Name, L"Root", - NULL); + IopArbPortTranslateOrdering); if (!NT_SUCCESS(Status)) { - DPRINT1("IopArbPortInitialize: Failed with %X", Status); + DPRINT1("IopArbPortInitialize: Failed with %X\n", Status); } return Status; diff --git a/ntoskrnl/ntoskrnl.spec b/ntoskrnl/ntoskrnl.spec index c33fd55225c..2556561b822 100644 --- a/ntoskrnl/ntoskrnl.spec +++ b/ntoskrnl/ntoskrnl.spec @@ -1149,7 +1149,7 @@ @ stdcall RtlFindMessage(ptr long long long ptr) @ stdcall RtlFindMostSignificantBit(long long) @ stdcall RtlFindNextForwardRunClear(ptr long ptr) -@ stdcall RtlFindRange(ptr long long long long long long long long ptr ptr ptr) +@ stdcall RtlFindRange(ptr int64 int64 int64 int64 long long ptr ptr ptr) @ stdcall RtlFindSetBits(ptr long long) @ stdcall RtlFindSetBitsAndClear(ptr long long) @ stdcall RtlFindUnicodePrefix(ptr ptr long) diff --git a/sdk/include/ndk/rtlfuncs.h b/sdk/include/ndk/rtlfuncs.h index 850a12eb2f9..8f36182f020 100644 --- a/sdk/include/ndk/rtlfuncs.h +++ b/sdk/include/ndk/rtlfuncs.h @@ -3772,8 +3772,8 @@ RtlFindRange( _In_ PRTL_RANGE_LIST RangeList, _In_ ULONGLONG Minimum, _In_ ULONGLONG Maximum, - _In_ ULONG Length, - _In_ ULONG Alignment, + _In_ ULONGLONG Length, + _In_ ULONGLONG Alignment, _In_ ULONG Flags, _In_ UCHAR AttributeAvailableMask, _In_opt_ PVOID Context, diff --git a/sdk/lib/drivers/arbiter/arbiter.h b/sdk/lib/drivers/arbiter/arbiter.h index c03a3e3fec6..8d57d99e9ad 100644 --- a/sdk/lib/drivers/arbiter/arbiter.h +++ b/sdk/lib/drivers/arbiter/arbiter.h @@ -37,9 +37,14 @@ * * ARBITER_RANGE_BOOT_ALLOCATED: * Marks a firmware boot configuration + * + * ARBITER_RANGE_PORT_ALIAS: + * Marks a phantom I/O-port range that a partially-decoding ISA card shadows, + * rather than one the device asked for. Set by the root port arbiter. */ -#define ARBITER_RANGE_SHARED_DRIVER 0x0 +#define ARBITER_RANGE_SHARED_DRIVER 0x02 #define ARBITER_RANGE_BOOT_ALLOCATED 0x04 +#define ARBITER_RANGE_PORT_ALIAS 0x10 /* ARBITER_ALLOCATION_STATE.Flags */ #define ARBITER_STATE_FLAG_NULL_CONFLICT_OK 0x0001 // a NULL-owner conflict is OK diff --git a/sdk/lib/drivers/arbiter/range.c b/sdk/lib/drivers/arbiter/range.c index 53946c0dfe4..329eaa84512 100644 --- a/sdk/lib/drivers/arbiter/range.c +++ b/sdk/lib/drivers/arbiter/range.c @@ -563,8 +563,8 @@ ArbiterLibFindSuitableRange( Status = RtlFindRange(Arbiter->PossibleAllocation, ArbState->CurrentMinimum, ArbState->CurrentMaximum, - (ULONG)Alternative->Length, - (ULONG)(Alternative->Alignment ? Alternative->Alignment : 1), + Alternative->Length, + max(Alternative->Alignment, 1), Flags, ArbState->RangeAvailableAttributes, Arbiter->ConflictCallbackContext, diff --git a/sdk/lib/rtl/rangelist.c b/sdk/lib/rtl/rangelist.c index 2a01d08679b..ee596cf878a 100644 --- a/sdk/lib/rtl/rangelist.c +++ b/sdk/lib/rtl/rangelist.c @@ -458,8 +458,8 @@ NTAPI RtlFindRange(IN PRTL_RANGE_LIST RangeList, IN ULONGLONG Minimum, IN ULONGLONG Maximum, - IN ULONG Length, - IN ULONG Alignment, + IN ULONGLONG Length, + IN ULONGLONG Alignment, IN ULONG Flags, IN UCHAR AttributeAvailableMask, IN PVOID Context OPTIONAL, @@ -476,12 +476,12 @@ RtlFindRange(IN PRTL_RANGE_LIST RangeList, } /* A window of Length can only end at Maximum if it also fits below it */ - if ((ULONGLONG)(Length - 1) > Maximum) + if (Length - 1 > Maximum) { return STATUS_RANGE_NOT_FOUND; } - Candidate = (Maximum - (Length - 1)) & ~((ULONGLONG)Alignment - 1); + Candidate = (Maximum - (Length - 1)) & ~(Alignment - 1); for (;;) { @@ -516,12 +516,12 @@ RtlFindRange(IN PRTL_RANGE_LIST RangeList, return STATUS_RANGE_NOT_FOUND; } - if ((ULONGLONG)(Length - 1) > ConflictStart - 1) + if (Length - 1 > ConflictStart - 1) { return STATUS_RANGE_NOT_FOUND; } - Candidate = ((ConflictStart - 1) - (Length - 1)) & ~((ULONGLONG)Alignment - 1); + Candidate = ((ConflictStart - 1) - (Length - 1)) & ~(Alignment - 1); } }