From: Logan Gunthorpe <email@example.com> To: Bjorn Helgaas <firstname.lastname@example.org> Cc: email@example.com, firstname.lastname@example.org, Kit Chow <email@example.com>, Yinghai Lu <firstname.lastname@example.org> Subject: Re: [PATCH 1/2] PCI: Prevent 64-bit resources from being counted in 32-bit bridge region Date: Mon, 4 Mar 2019 12:21:09 -0700 Message-ID: <email@example.com> (raw) In-Reply-To: <20190304002351.GA26569@google.com> On 2019-03-03 5:23 p.m., Bjorn Helgaas wrote: > Sorry for the delay. This code gives a headache. I still remember > my headache from the last time we touched it. Help me understand > what's going on here. Yes, this code gave me a headache debugging it too. And it's not the first time I've tried to figure out what's going on with it because it often just prints noisy messages that look like errors. I think I understand it better now but it's something that's a bit fleeting and easy to forget the details of. There may also be other solutions to this problem. > On Thu, Feb 14, 2019 at 10:00:27AM -0700, Logan Gunthorpe wrote: >> The possible situations where this can happen will be quite varied and >> depend highly on the exact hierarchy and how the realloc code ends up >> trying to assign the regions. It's known to at least require a >> large 64-bit BAR (>1GB) below a PCI bridge. > > I guess the bug is that some BAR or window is unset when we actually > have space for it? We need to make this more concrete, e.g., with a > minimal example of a failure case, and then connect this code change > specifically with that. The system we hit this bug on is quite large and complex with multiple layers of switches though I suspect I might have seen it on a completely different system but never had time to dig into it. I guess I could try to find a case in which qemu can hit it. > "Ignored BARs" doesn't seem like the best terminology here. Can we > just say they're "unset" as you do for windows? Even that's a little > squishy because there's really no such thing as a clearly "unset" or > invalid value for a BAR. All we can say is that Linux *thinks* it's > unset because it happens to be zero (technically still a valid BAR > value) or it conflicts with another device. Yes. I used the "ignored" term because that's the terminology lspci uses when it sees a resource like this. I'm not sure I like the "unset" term because, in fact, the BAR registers in the configuration space are typically still set to whatever the bios set them to[*1]. It's just that the kernel doesn't create a struct resource for them and thus you wont see a corresponding entry in /sys/bus/pci/.../resources or /proc/bus/pci/devices. For example, here's the lspci and hex dump of the config space for an example case; as you can see Region 0, is "ignored", but the BAR register is still set to 0xf7100000. 05:00.0 PCI bridge: PLX Technology, Inc. Device 8733 (rev ca) (prog-if 00 [Normal decode]) Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx- Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx- Latency: 0, Cache Line Size: 32 bytes Interrupt: pin A routed to IRQ 24 NUMA node: 0 Region 0: Memory at <ignored> (32-bit, non-prefetchable) [size=256K] Bus: primary=05, secondary=06, subordinate=0a, sec-latency=0 05:00.0 PCI bridge: PLX Technology, Inc. Device 8733 (rev ca) 00: b5 10 33 87 07 01 10 00 ca 00 04 06 08 00 81 00 10: 00 00 10 f7 00 00 00 00 05 06 0a 00 11 21 00 00 f7100000 20: f0 ff 00 00 01 00 f1 ff 20 02 00 00 7f 02 00 00 > Strictly speaking, the result is that we can't enable decoding for > that BAR type. Often that does mean the device is unusable, but in > some cases, e.g., an I/O BAR being unset and a driver using > pci_enable_device_mem(), the device *is* usable. Yes, though I think this bug will always apply to non-prefetchable 32-bit MEM BARs and not I/O BARs. So if the driver needs such a BAR (which I expect is common) it will not function. > Surely realloc can fail even without a large 64-bit BAR? I don't > think there's a magic threshold at 1GB. Maybe an example would > illustrate the problem better. No, to hit this bug, we do require a large 64-bit BAR. Though the threshold isn't necessarily 1GB, its more complicated (see below). This is just really hard to explain because the code is so tricky. I'll try to clarify it more below. Once we can clear it up so you understand it I'll try to update my commit message to be clearer. > There are actually three calls to pbus_size_mem(): > > 1) If bridge has a 64-bit prefetchable window, find the size of all > 64-bit prefetchable resources below the bridge > > 2) If bridge has no 64-bit prefetchable window, find the size of all > prefetchable resources below the bridge > > 3) Find the size of everything else (non-prefetchable resources plus > any prefetchable ones that couldn't be accommodated above) Yes, this is technically correct. I was just over-simplifying because, to hit this bug, there must be a 64-bit prefetchable window and there are no prefetchable 32-bit resources so (2) is irrelevant. >> There are only two reasons for pbus_size_mem() to fail: if there is no >> 64-bit/prefetchable bridge window, or if that window is already >> assigned (in other words, its resource already has a parent set). We know >> the former case can't be true because, in __pci_bus_size_bridges(), it's >> existence is checked before making the call. So if the pbus_size_mem() >> call in question fails, the window must already be assigned, and in this >> case, we still do not want 64-bit resources trying to be sized into the >> 32-bit catch-all resource. > > I guess this question of putting a 64-bit resource in the 32-bit > non-prefetchable window (legal but undesirable) is a secondary thing, > not the chief complaint you're fixing? Uh, yeah no. The 64-bit resource(s) are typically correctly assigned to the 64-bit window. However the bug causes victim 32-bit Non-prefetchable resources to not be assigned because when we calculate the size the code to inadvertently thinks the 64-bit resource must fit into the 32-bit non-prefetchable window. -- Ok, so let me try to walk through this a bit more step by step. Lets make the following assumptions: 1) Let's say we have a bridge that has a 64-bit prefetchable window, such that (b_res.flags & IORESOURCE_MEM_64) is true. So the bridge has three windows: the I/O window, the non-preftechable 32-bit window and the prefetchable 64-bit window. 2) Let's also say that, under the bridge, we have a resource that's larger than we'd expect to fit into the 32-bit window. (So, the actual limit depends on the maximum size of that window, which is hardware dependant, and the size of everything else that goes in it, but for simplicity I estimated this to be at least 1GB). For the purposes of this example, lets say it's very large: 64GB. 3) Now, the tricky thing is that this code tries to do things in multiple passes, unassigning resources that didn't seem to fit and recalculating window sizes on multiple passes. So lets say we are on the second pass where, previously, the 64-bit prefetchable window on this bridge was successfully assigned but, for whatever reason, this bridge failed and the resources were released (see pci_bus_release_bridge_resources()). In this case (bres.parent != NULL) and the large 64-bit resource now has (res->parent == NULL). Walking through __pci_bus_size_bridges(): i) Per (1), we do have a prefetchable window, so the first call to pbus_size_mem() happens. However, the first thing that function does is call find_free_bus_resource() which should find bres, but does not because, per (3) its parent is not NULL (see the comment above find_free_bus_resource() which makes it seem important that parent not be set). Thus this call to pbus_size_mem() fails with -ENOSPC, and 'type2' and 'type3' remain unset. ii) Seeing type2 is unset, we go to the second call to pbus_size_mem(). This call fails because per (1), there is no 32-bit prefetchable resource to find. So find_free_bus_resource() fails, as it should. 'type2' and 'type3' are now set to IORESOURCE_MEM. iii) Next we do the third call to pbus_size_mem() for the non-prefetchable window, however, because the first two calls failed, it will calculate the size for the 32-bit window to be greater than 64GB. As the code recurses up into the rest of the PCI tree, nothing will fit into the 32-bit window seeing it's calculated to be much larger than it needs to be so none of the 32-bit prefetchable BARs will be assigned and thus will not have struct resources and they will be reported as ignored by lspci. Drivers that try to use these resources will also fail and there will be addresses in the IOVA space that may get routed to the wrong PCI address. My solution to this bug was to notice that pbus_size_mem() can only fail for one of two reasons: either the resource doesn't exist at all or it is already assigned (ie. the resource's parent is still set). However, for the first call in (i), we know the resource does exist because we check for it (ie. the condition in (1)). Therefore we can say that if pbus_size_mem() fails in (i) there is a 64-bit window and we should not try to size the 32-bit window to fit 64-bit resources. We do this simply by setting 'mask', 'type2' and 'type3' even when pbus_size_mem() fails. Another potential solution would be to always unassign the windows for the failing bridges by setting it's resources parents to NULL. But this makes me much more nervous because I don't understand what all the implications would be and the comment above find_free_bus_resource() makes me think this is important. Let me know if this makes more sense or you have further questions. Also, the second patch in this series is a similar bug with *very* similar symptoms but I think it's easier to understand. It's a completely different cause and only happens on one particular piece of hardware that I'm aware of; and the driver for that hardware doesn't use the BAR at all right now so the only real negative effect is on the IOVA address space. Thanks, Logan [*1] This is probably another big bug due to its affect on the IOVA address space, but I'm not sure what to do about it and with these patches we don't have any further issues. However, maybe we should be scanning the tree after everything is said and done and unset any BARs that do not have corresponding resources. That way, if there are other bugs that can cause ignored regions, or if our algorithm does something that doesn't match the bios there aren't random failures when using the IOMMU.
next prev parent reply index Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-02-14 17:00 Logan Gunthorpe 2019-02-14 17:00 ` [PATCH 2/2] PCI: Fix disabling of bridge BARs when assigning bus resources Logan Gunthorpe 2019-03-04 0:23 ` [PATCH 1/2] PCI: Prevent 64-bit resources from being counted in 32-bit bridge region Bjorn Helgaas 2019-03-04 19:21 ` Logan Gunthorpe [this message] 2019-03-04 20:11 ` Bjorn Helgaas 2019-03-04 20:21 ` Logan Gunthorpe 2019-03-04 20:29 ` Bjorn Helgaas 2019-03-04 20:39 ` Logan Gunthorpe 2019-04-01 19:22 ` Logan Gunthorpe 2019-03-04 23:58 ` Logan Gunthorpe
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --firstname.lastname@example.org \ --email@example.com \ --firstname.lastname@example.org \ --email@example.com \ --firstname.lastname@example.org \ --email@example.com \ --firstname.lastname@example.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Linux-PCI Archive on lore.kernel.org Archives are clonable: git clone --mirror https://lore.kernel.org/linux-pci/0 linux-pci/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 linux-pci linux-pci/ https://lore.kernel.org/linux-pci \ email@example.com public-inbox-index linux-pci Example config snippet for mirrors Newsgroup available over NNTP: nntp://nntp.lore.kernel.org/org.kernel.vger.linux-pci AGPL code for this site: git clone https://public-inbox.org/public-inbox.git