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); } }