DMA 4GB boundary protection
PATCH 1/1 DMA 4GB boundary protection
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives us dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
This patch checks to see if the mapping spans a 4 gig boundary, and if it does, retries the allocation. It tries the next allocation at the start of the crossed 4 gig boundary.
---
arch/powerpc/kernel/iommu.c | 10 ++++++++++ 1 files changed, 10 insertions(+)
arch/powerpc/kernel/iommu.c +10 -0
@@ -76,6 +76,7 @@ static unsigned long iommu_range_alloc(s unsigned int align_order) {unsigned long n, end, i, start; +unsigned long start_addr, end_addr;unsigned long limit;int largealloc = npages >15 ;int pass =0 ;
@@ -146,6 +147,15 @@ static unsigned long iommu_range_alloc(s } } +/* DMA cannot cross 4 GB boundary */ + start_addr = (n + tbl->it_offset) <<PAGE_SHIFT ; + end_addr = (end + tbl->it_offset) <<PAGE_SHIFT ; +if ((start_addr >>32 ) != (end_addr >>32 )) { + end_addr &=0xffffffff00000000l ; + start = (end_addr >>PAGE_SHIFT ) - tbl->it_offset; + goto again; + } +for (i = n; i < end; i++)if (test_bit (i, tbl->it_map)) { start = i+1 ;
Quoted 10 lines · Jake Moilanen
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives us dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
Sorry, but you need to fix the drivers. They might be used on a system without an IOMMU, or with direct mapping. It's particularily likely in the case of DAC-capable devices, and that's the only case for which you can cross a 4GB boundary in the first place.
-Olof
Quoted 8 lines · Olof Johansson
Quoted 7 lines · Jake Moilanen
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives us dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
Quoted 8 lines
Quoted 3 lines
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
Sorry, but you need to fix the drivers. They might be used on a system without an IOMMU, or with direct mapping. It's particularily likely in the case of DAC-capable devices, and that's the only case for which you can cross a 4GB boundary in the first place.
Hrm... ok, that would be ideal, _but_
- We currently don't have platforms that DMA > 32 bits and don't have an iommu
- Drivers are broken -today- and I doubt they can all be audited and fixed (and fixes bacported to distros) quickly
- That problem is very common and can be very hard to debug when it happens.
So while I agree, the logical fix would be in the drivers, I think that -also- making sure the iommu code will not hand off sections crossing 4G boundaries does make some sense. It will at least reduce the likehood of strange and hard to debug problems on those setups that do have an iommu and >4G of RAM, which is pretty much all of them at the moment on ppc64.
Can you double check the correctness of the patch ? I'll have a better look myself next week hopefully.
Ben.
Quoted 18 lines · Benjamin Herrenschmidt
Quoted 8 lines · Olof Johansson
Quoted 7 lines · Jake Moilanen
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives us dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
Quoted 8 lines
Quoted 3 lines
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
Sorry, but you need to fix the drivers. They might be used on a system without an IOMMU, or with direct mapping. It's particularily likely in the case of DAC-capable devices, and that's the only case for which you can cross a 4GB boundary in the first place.
Quoted 4 lines
Hrm... ok, that would be ideal, _but_
- We currently don't have platforms that DMA > 32 bits and don't have an iommu
Up until this week, we didn't have a platform that did DMA > 32, period.
On PPC I suppose there's only one right now that can do it without IOMMU, and that's 1682M. I'd expect there to be more in the future. On non-PPC I'm sure there's several.
Quoted 2 lines
- Drivers are broken -today- and I doubt they can all be audited and fixed (and fixes bacported to distros) quickly
Yes, and the chance of them getting audited and fixed suddenly drops to 0% once we put this in instead. The same problem applies to this patch with respect to backporting it to distros.
Quoted 2 lines
- That problem is very common and can be very hard to debug when it happens.
So let's just make someone else suffer instead?
Quoted 5 lines
So while I agree, the logical fix would be in the drivers, I think that -also- making sure the iommu code will not hand off sections crossing 4G boundaries does make some sense. It will at least reduce the likehood of strange and hard to debug problems on those setups that do have an iommu and >4G of RAM, which is pretty much all of them at the moment on ppc64.
Even though most ppc64 platforms have more than 4GB of ram, the IOMMUs have up until now not done more than 32-bit addresses. This 4GB boundary bug won't affect the majority of users out there, only those on platforms whose firmware gives out address ranges that crosses the 4GB boundary.
Besides virtualization, there's no need to use an IOMMU with devices that can address the full memory range of the system anyway. In most cases it just hurts performance.
Quoted 2 lines
Can you double check the correctness of the patch ? I'll have a better look myself next week hopefully.
It's incorrect as posted, and I think it can be done in a nicer manner. I'll post feedback to the patch.
-Olof
Quoted 2 lines · Benjamin Herrenschmidt
- Drivers are broken -today- and I doubt they can all be audited and fixed (and fixes bacported to distros) quickly
Which drivers? All requests coming from the block layer make sure you never span the 4GB boundary, and IIRC that same is true for networking. Similarly dma_alloc_coherent and pci_alloc_consinstant only allocate from the lower 4G unless told otherwise. A driver writer would have to be extra-ordinarily stuid to get this wrong. (And yes, some driver writers are..)
Quoted 8 lines · Christoph Hellwig
Quoted 2 lines · Benjamin Herrenschmidt
- Drivers are broken -today- and I doubt they can all be audited and fixed (and fixes bacported to distros) quickly
Which drivers? All requests coming from the block layer make sure you never span the 4GB boundary, and IIRC that same is true for networking. Similarly
dma_alloc_coherentandpci_alloc_consinstantonly allocate from the lower 4G unless told otherwise.
Since the IOMMU layer adds a completely independent address space, any mapping might end up straddling the boundary if you're unlucky. But, with the above info, adding the restrictions in the iommu code seems safer -- at least it won't cause problems on non-translated enviromnents if the block and network code makes sure it can't happen there.
Quoted 3 lines
A driver writer
would have to be extra-ordinarily stuid to get this wrong. (And yes, some driver writers are..)
Any pci_map_single or friends should be able to end up there based on how the iommu allocator works. It doesn't take into consideration any kind of alignment of the physical regions being mapped.
-Olof
Quoted 3 lines · Jake Moilanen
This normally is not an issue as firmware gives us dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
Above 4 gigs, or that crosses the 4GB boundary? There's a difference.
Quoted 3 lines
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
The drawback of this patch is that it adds code to every single allocation. Instead, you should just mark the last entry before the 4GB boundary as allocated when you setup the bitmaps for the table. That way, no allocation will ever be able to cross over.
Even nicer would be to only do it when a boot option is specified, so we actually have a chance to expose and find the driver bugs instead of papering them over.
Also, remember to use IOMMU_PAGE_SHIFT instead of PAGE_SHIFT, they might be different.
-Olof
Quoted 4 lines
The drawback of this patch is that it adds code to every single allocation. Instead, you should just mark the last entry before the 4GB boundary as allocated when you setup the bitmaps for the table. That way, no allocation will ever be able to cross over.
Jake said that this bug happens when crossing _any_ 4GB boundary, so that means reserving a few more blocks.
Quoted 3 lines
Even nicer would be to only do it when a boot option is specified, so we actually have a chance to expose and find the driver bugs instead of papering them over.
Almost all drivers (*) that can do DAC already avoid crossing the SAC-to-DAC boundary. I have never heard about a card having the bug on _any_ 4GB crossing, I doubt they are that common. Just fix the drivers :-)
(*) Well, amongst those that matter and have this bug.
Segher
Quoted 8 lines · Segher Boessenkool
Quoted 5 lines
The drawback of this patch is that it adds code to every single allocation.
Instead, you should just mark the last entry before the 4GB boundary as allocated when you setup the bitmaps for the table. That way, no allocation will ever be able to cross over.Jake said that this bug happens when crossing _any_ 4GB boundary, so that means reserving a few more blocks.
Yes, sorry. I was presuming that the table provided by firmware was less than 4GB in size. He would obviously need to mark at each boundary.
Quoted 8 lines
Quoted 3 lines
Even nicer would be to only do it when a boot option is specified, so we actually have a chance to expose and find the driver bugs instead of papering them over.
Almost all drivers (*) that can do DAC already avoid crossing the SAC-to-DAC boundary. I have never heard about a card having the bug on _any_ 4GB crossing, I doubt they are that common. Just fix the drivers :-)
I can see the point with dealig with backports. Having a boot option that is disabled by default would help working around it until a fix is available, but it'd still help expose driver bugs.
-Olof
Quoted 8 lines
Quoted 3 lines
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
The drawback of this patch is that it adds code to every single allocation. Instead, you should just mark the last entry before the 4GB boundary as allocated when you setup the bitmaps for the table. That way, no allocation will ever be able to cross over.
Agreed. While it's not an issue at 4KB, when we go to say 64k, or 16MB TCE sizes, this extra space will be wasted. But, I believe we'll need a little code for this anyways.
Quoted 3 lines
Even nicer would be to only do it when a boot option is specified, so we actually have a chance to expose and find the driver bugs instead of papering them over.
Done.
Quoted 2 lines
Also, remember to use
IOMMU_PAGE_SHIFTinstead ofPAGE_SHIFT, they might be different.
Yup...
Here's a patch addressing Olof's concerns.
--- arch/powerpc/kernel/iommu.c | 32 +++++++++++++++++++++++++++++++- 1 files changed, 31 insertions(+), 1 deletion(-)
arch/powerpc/kernel/iommu.c +28 -1
@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {
@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))
@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,
@@ -429,6 +442,8 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_addr, end_addr; +unsigned long index;static int welcomed =0 ;struct page *page;
@@ -450,7 +465,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/*
@@ -480,6 +495,21 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark first entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + start_addr = tbl->it_offset <<IOMMU_PAGE_SHIFT ; +/* go up to next 4GB boundary */ + start_addr = (start_addr +0x00000000ffffffffl ) >>32 ; + end_addr = (tbl->it_offset + tbl->it_size) <<IOMMU_PAGE_SHIFT ; +for (index = start_addr; index < end_addr; index += (1l <<32 )) { +/* Reserve 4GB entry */ + __set_bit((index >> IOMMU_PAGE_SHIFT) - tbl->it_offset,
tbl->it_map);
+ }
+ }
+
if (!welcomed) {
printk(KERN_INFO "IOMMU table initialized, virtual merging %s\n",
novmerge ? "disabled" : "enabled");
Quoted 4 lines · Jake Moilanen
Quoted 4 lines
Quoted 3 lines
I propose fixing it in the IOMMU allocation instead of making each driver protect against it as it is more efficient, and won't require changing every driver which has not considered this issue.
Quoted 8 lines
Quoted 4 lines
The drawback of this patch is that it adds code to every single allocation. Instead, you should just mark the last entry before the 4GB boundary as allocated when you setup the bitmaps for the table. That way, no allocation will ever be able to cross over.
Agreed. While it's not an issue at 4KB, when we go to say 64k, or 16MB TCE sizes, this extra space will be wasted. But, I believe we'll need a little code for this anyways.
Doing 16MB mappings will have it's challenges anyway, especially for things such as network and file I/O. It's similar to what happens for 64kB base page size and the buffer cache: lots and lots of waste, and the risk of running out of address space is high (depending on how big the window is per device, of course).
Quoted 4 lines
Quoted 3 lines
Even nicer would be to only do it when a boot option is specified, so we actually have a chance to expose and find the driver bugs instead of papering them over.
Quoted 8 lines
Done.
Quoted 2 lines
Also, remember to use
IOMMU_PAGE_SHIFTinstead ofPAGE_SHIFT, they might be different.Yup...
Here's a patch addressing Olof's concerns.
Thanks, I'll have a closer look at it tonight.
-Olof
Quoted 10 lines · Jake Moilanen
arch/powerpc/kernel/iommu.c +6
-0@@ -480,6 +495,21 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark first entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + start_addr = tbl->it_offset <<IOMMU_PAGE_SHIFT ;
arch/powerpc/kernel/iommu.c struct iommu_table *iommu_init_table(str · line 513
Quoted 10 lines
arch/powerpc/kernel/iommu.c +9
-0+/* go up to next 4GB boundary */ + start_addr = (start_addr +0x00000000ffffffffl ) >>32 ; + end_addr = (tbl->it_offset + tbl->it_size) <<IOMMU_PAGE_SHIFT ; +for (index = start_addr; index < end_addr; index += (1l <<32 )) { +/* Reserve 4GB entry */ +__set_bit ((index >>IOMMU_PAGE_SHIFT ) - tbl->it_offset, tbl->it_map); + } + } +
This is done a bit more complicated than it has to be. The >> 32 is a red flag as well.
I would also like it to be the last page in the range, not the first (since otherwise you'll reserve even if the window is less than 4GB.
Something like (untested):
entries_per_4g = 0x100000000 >> IOMMU_PAGE_SHIFT; /* Mark the last bit before a 4GB boundary as used */ start_index = tbl->it_offset | (entries_per_4g - 1); end_index = tbl->it_offset + tbl->it_size; for (index = start_index; index < end_index; index += entries_per_4g) __set_bit(index, tbl->it_map);
Is easier to follow.
-Olof
Quoted 41 lines · Olof Johansson
Quoted 10 lines · Jake Moilanen
arch/powerpc/kernel/iommu.c +6
-0@@ -480,6 +495,21 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark first entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + start_addr = tbl->it_offset <<IOMMU_PAGE_SHIFT ;arch/powerpc/kernel/iommu.c struct iommu_table *iommu_init_table(str · line 513
Quoted 10 lines
arch/powerpc/kernel/iommu.c +9
-0+/* go up to next 4GB boundary */ + start_addr = (start_addr +0x00000000ffffffffl ) >>32 ; + end_addr = (tbl->it_offset + tbl->it_size) <<IOMMU_PAGE_SHIFT ; +for (index = start_addr; index < end_addr; index += (1l <<32 )) { +/* Reserve 4GB entry */ +__set_bit ((index >>IOMMU_PAGE_SHIFT ) - tbl->it_offset, tbl->it_map); + } + } +This is done a bit more complicated than it has to be. The >> 32 is a red flag as well.
I would also like it to be the last page in the range, not the first (since otherwise you'll reserve even if the window is less than 4GB.
Something like (untested):
entries_per_4g = 0x100000000 >> IOMMU_PAGE_SHIFT; /* Mark the last bit before a 4GB boundary as used */ start_index = tbl->it_offset | (entries_per_4g - 1); end_index = tbl->it_offset + tbl->it_size; for (index = start_index; index < end_index; index += entries_per_4g) __set_bit(index, tbl->it_map);
Quoted 1 line
Is easier to follow.
Yup, I like that better. I'll give it a run.
Jake
Quoted 5 lines
I would also like it to be the last page in the range, not the first (since otherwise you'll reserve even if the window is less than 4GB.
Something like (untested):
Quoted 8 lines
entries_per_4g = 0x100000000 >> IOMMU_PAGE_SHIFT; /* Mark the last bit before a 4GB boundary as used */ start_index = tbl->it_offset | (entries_per_4g - 1); end_index = tbl->it_offset + tbl->it_size; for (index = start_index; index < end_index; index += entries_per_4g) __set_bit(index, tbl->it_map);
As a very minor optimisation, avoid reserving the last page of the I/O window if it ends on a 4GB boundary?
Segher
Quoted 32 lines · Olof Johansson
Quoted 10 lines · Jake Moilanen
(quoted diff) +6
-0@@ -480,6 +495,21 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free(tbl, tbl->it_offset, tbl->it_size); #endif + /* + * DMA cannot cross 4 GB boundary. Mark first entry of each 4 + * GB chunk as reserved. + */ + if (protect4gb) { + start_addr = tbl->it_offset << IOMMU_PAGE_SHIFT;Review struct iommu_table *iommu_init_table(str · line 513
Quoted 10 lines
(quoted diff) +9
-0+ /* go up to next 4GB boundary */ + start_addr = (start_addr + 0x00000000ffffffffl) >> 32; + end_addr = (tbl->it_offset + tbl->it_size) << IOMMU_PAGE_SHIFT; + for (index = start_addr; index < end_addr; index += (1l << 32)) { + /* Reserve 4GB entry */ + __set_bit((index >> IOMMU_PAGE_SHIFT) - tbl->it_offset, tbl->it_map); + } + } +This is done a bit more complicated than it has to be. The >> 32 is a red flag as well.
I would also like it to be the last page in the range, not the first (since otherwise you'll reserve even if the window is less than 4GB.
Something like (untested):
Quoted 8 lines
entries_per_4g = 0x100000000 >> IOMMU_PAGE_SHIFT; /* Mark the last bit before a 4GB boundary as used */ start_index = tbl->it_offset | (entries_per_4g - 1); end_index = tbl->it_offset + tbl->it_size; for (index = start_index; index < end_index; index += entries_per_4g) __set_bit(index, tbl->it_map);
I just had to realign the start_index in case it didn't start at a 4GB boundary. Other than that, it worked like a champ. I also changed to for loop to exit if it's the last entry.
--- arch/powerpc/kernel/iommu.c | 35 ++++++++++++++++++++++++++++++++++- 1 files changed, 34 insertions(+), 1 deletion(-)
arch/powerpc/kernel/iommu.c +31 -1
@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {
@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))
@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,
@@ -429,6 +442,9 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_index, end_index; +unsigned long entries_per_4g; +unsigned long index;static int welcomed =0 ;struct page *page;
@@ -450,7 +466,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/*
@@ -480,6 +496,23 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ + if (protect4gb) { + entries_per_4g = 0x100000000l >> IOMMU_PAGE_SHIFT; + + /* Mark the last bit before a 4GB boundary as used */ + start_index = (tbl->it_offset << IOMMU_PAGE_SHIFT) >> 32; + start_index |= (entries_per_4g - 1); + + end_index = tbl->it_offset + tbl->it_size; + + for (index = start_index; index < end_index - 1; index +=
entries_per_4g)
+ __set_bit(index, tbl->it_map);
+ }
+
if (!welcomed) {
printk(KERN_INFO "IOMMU table initialized, virtual merging %s\n",
novmerge ? "disabled" : "enabled");
Quoted 5 lines · Jake Moilanen
I just had to realign the
start_indexin case it didn't start at a 4GB boundary. Other than that, it worked like a champ. I also changed to forloop to exit if it's the last entry.Signed-off-by Jake Moilanen <[email protected]>
patch got mangled ...
Quoted 93 lines
--- arch/powerpc/kernel/iommu.c | 35 ++++++++++++++++++++++++++++++++++- 1 files changed, 34 insertions(+), 1 deletion(-)Index:
powerpc/arch/powerpc/kernel/iommu.c
===================================================================arch/powerpc/kernel/iommu.c +34
-1@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,@@ -429,6 +442,9 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_index, end_index; +unsigned long entries_per_4g; +unsigned long index;static int welcomed =0 ;struct page *page;@@ -450,7 +466,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/* @@ -480,6 +496,23 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + entries_per_4g =0x100000000l >>IOMMU_PAGE_SHIFT ; + +/* Mark the last bit before a 4GB boundary as used */ + start_index = (tbl->it_offset <<IOMMU_PAGE_SHIFT ) >>32 ; + start_index |= (entries_per_4g -1 ); + + end_index = tbl->it_offset + tbl->it_size; + +for (index = start_index; index < end_index -1 ; index += entries_per_4g) +__set_bit (index, tbl->it_map); + } +if (!welcomed) { printk(KERN_INFO "IOMMU table initialized, virtual merging %s\n",novmerge ? "disabled" : "enabled");
_______________________________________________
Linuxppc-dev mailing list
[email protected]
https://ozlabs.org/mailman/listinfo/linuxppc-dev
Quoted 3 lines · Jake Moilanen
I just had to realign the
start_indexin case it didn't start at a 4GB boundary. Other than that, it worked like a champ. I also changed to for loop to exit if it's the last entry.
If this is now suitable for going upstream, please re-send with a proper patch description, e.g. the original patch description modified or augmented as necessary for the changes you've made. A description of what has changed since the previous version of the patch is not suitable as the commit text in the git repository.
Paul.
Quoted 3 lines · Jake Moilanen
I just had to realign the
start_indexin case it didn't start at a 4GB boundary. Other than that, it worked like a champ. I also changed to for loop to exit if it's the last entry.
Hmm. See below.
arch/powerpc/kernel/iommu.c struct iommu_table *iommu_init_table(str · line 508
Quoted 10 lines
arch/powerpc/kernel/iommu.c +10
-0+/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + entries_per_4g =0x100000000l >>IOMMU_PAGE_SHIFT ; + +/* Mark the last bit before a 4GB boundary as used */ + start_index = (tbl->it_offset <<IOMMU_PAGE_SHIFT ) >>32 ; + start_index |= (entries_per_4g -1 );
This looks broken.
The idea is to make start_index the last page before the first 4GB boundary after it_offset. If that happens to be beyond end_index the for loop below will never run. If it's below that, every last page in the 4GB ranges will be marked in the loop. This will work even if the table starts at i.e. 2GB and goes until 10GB.
With the first line above, your start_index will always be 0xfffff (unless the offset is waay up there in the address space).
The logic I had was:
start_index = tbl->it_offset | (entries_per_4g - 1);
This is also broken, since it doesn't consider it_offset in the loop below. That was my bad, and I guess was what you tried to fix above.
What you really want is:
start_index = tbl->it_offset | (entries_per_4g - 1); start_index -= tbl->it_offset; end_index = tbl->it_size;
Say that it_offset is at 3GB, with 4KB pages that means the value is 0xc0000. entries_per_4g is 0x100000, i.e. the logic becomes: 0xc0000 | 0xfffff = 0xfffff (- 0xc0000 = 0x3ffff), which indeed is the last page before 4GB.
If it_offset is at 9GB, i.e. 0x240000, then we get start_index at 0x2fffff (- 0x240000 = 0xbffff) , i.e. yet again last page before the 12GB wrap.
Review
Quoted 6 lines
arch/powerpc/kernel/iommu.c +4
-0+ + end_index = tbl->it_offset + tbl->it_size; + + for (index = start_index; index < end_index - 1; index +=
entries_per_4g)(quoted diff) +1
-0+ __set_bit(index, tbl->it_map);
-Olof
Quoted 29 lines
Review
Quoted 10 lines
(quoted diff) +10
-0+ /* + * DMA cannot cross 4 GB boundary. Mark last entry of each 4 + * GB chunk as reserved. + */ + if (protect4gb) { + entries_per_4g = 0x100000000l >> IOMMU_PAGE_SHIFT; + + /* Mark the last bit before a 4GB boundary as used */ + start_index = (tbl->it_offset << IOMMU_PAGE_SHIFT) >> 32; + start_index |= (entries_per_4g - 1);This looks broken.
The idea is to make
start_indexthe last page before the first 4GB boundary afterit_offset. If that happens to be beyondend_indexthe for loop below will never run. If it's below that, every last page in the 4GB ranges will be marked in the loop. This will work even if the table starts at i.e. 2GB and goes until 10GB.With the first line above, your
start_indexwill always be0xfffff(unless the offset is waay up there in the address space).The logic I had was:
start_index = tbl->it_offset | (entries_per_4g - 1);This is also broken, since it doesn't consider
it_offsetin the loop below. That was my bad, and I guess was what you tried to fix above.
Quoted 6 lines
What you really want is:
start_index = tbl->it_offset | (entries_per_4g - 1); start_index -= tbl->it_offset; end_index = tbl->it_size;
Yup.
Quoted 7 lines
Say that
it_offsetis at 3GB, with 4KB pages that means the value is0xc0000.entries_per_4gis0x100000, i.e. the logic becomes:0xc0000|0xfffff=0xfffff(-0xc0000=0x3ffff), which indeed is the last page before 4GB.If
it_offsetis at 9GB, i.e.0x240000, then we getstart_indexat0x2fffff(-0x240000=0xbffff) , i.e. yet again last page before the 12GB wrap.
One more try.
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
During initialization of the IOMMU tables, the last entry at each 4GB boundary is marked as used. Thus no mappings can cross the boundary. If a table ends at a 4GB boundary, the entry is not marked as used.
A boot option to remove this 4GB protection is given w/ protect4gb=off. This exposes the potential issue for driver and hardware development purposes.
--- arch/powerpc/kernel/iommu.c | 35 ++++++++++++++++++++++++++++++++++- 1 files changed, 34 insertions(+), 1 deletion(-)
arch/powerpc/kernel/iommu.c +31 -1
@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {
@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))
@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,
@@ -429,6 +442,9 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_index, end_index; +unsigned long entries_per_4g; +unsigned long index;static int welcomed =0 ;struct page *page;
@@ -450,7 +466,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/*
@@ -480,6 +496,23 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ + if (protect4gb) { + entries_per_4g = 0x100000000l >> IOMMU_PAGE_SHIFT; + + /* Mark the last bit before a 4GB boundary as used */ + start_index = tbl->it_offset | (entries_per_4g - 1); + start_index -= tbl->it_offset; + + end_index = tbl->it_size; + + for (index = start_index; index < end_index - 1; index +=
entries_per_4g)
+ __set_bit(index, tbl->it_map);
+ }
+
if (!welcomed) {
printk(KERN_INFO "IOMMU table initialized, virtual merging %s\n",
novmerge ? "disabled" : "enabled");
Quoted 1 line · Jake Moilanen
One more try.
And patch is still mangled....
Quoted 108 lines
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
During initialization of the IOMMU tables, the last entry at each 4GB boundary is marked as used. Thus no mappings can cross the boundary. If a table ends at a 4GB boundary, the entry is not marked as used.
A boot option to remove this 4GB protection is given w/ protect4gb=off. This exposes the potential issue for driver and hardware development purposes.
Signed-off-by Jake Moilanen <[email protected]>--- arch/powerpc/kernel/iommu.c | 35 ++++++++++++++++++++++++++++++++++- 1 files changed, 34 insertions(+), 1 deletion(-)Index:
powerpc/arch/powerpc/kernel/iommu.c
===================================================================arch/powerpc/kernel/iommu.c +34
-1@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,@@ -429,6 +442,9 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_index, end_index; +unsigned long entries_per_4g; +unsigned long index;static int welcomed =0 ;struct page *page;@@ -450,7 +466,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/* @@ -480,6 +496,23 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + entries_per_4g =0x100000000l >>IOMMU_PAGE_SHIFT ; + +/* Mark the last bit before a 4GB boundary as used */ + start_index = tbl->it_offset | (entries_per_4g -1 ); + start_index -= tbl->it_offset; + + end_index = tbl->it_size; + +for (index = start_index; index < end_index -1 ; index += entries_per_4g) +__set_bit (index, tbl->it_map); + } +if (!welcomed) { printk(KERN_INFO "IOMMU table initialized, virtual merging %s\n",novmerge ? "disabled" : "enabled");
_______________________________________________
Linuxppc-dev mailing list
[email protected]
https://ozlabs.org/mailman/listinfo/linuxppc-dev
Quoted 5 lines · Benjamin Herrenschmidt
Quoted 1 line · Jake Moilanen
One more try.
And patch is still mangled....
Hrm....I should just go back to Sylpheed.
Unmangled version:
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
During initialization of the IOMMU tables, the last entry at each 4GB boundary is marked as used. Thus no mappings can cross the boundary. If a table ends at a 4GB boundary, the entry is not marked as used.
A boot option to remove this 4GB protection is given w/ protect4gb=off. This exposes the potential issue for driver and hardware development purposes.
---
arch/powerpc/kernel/iommu.c | 35 ++++++++++++++++++++++++++++++++++- 1 files changed, 34 insertions(+), 1 deletion(-)
arch/powerpc/kernel/iommu.c +34 -1
@@ -47,6 +47,8 @@ static int novmerge = 0;static int novmerge =1 ;#endif +static int protect4gb =1 ; +static inline unsigned long iommu_num_pages (unsigned long vaddr,unsigned long slen) {
@@ -58,6 +60,16 @@ static inline unsigned long iommu_num_pareturn npages; } +static int __init setup_protect4gb (char *str) +{ +if (strcmp (str,"on" ) ==0 ) + protect4gb =1 ; +else if (strcmp (str,"off" ) ==0 ) + protect4gb =0 ; + +return 1 ; +} +static int __init setup_iommu (char *str) {if (!strcmp (str,"novmerge" ))
@@ -67,6 +79,7 @@ static int __init setup_iommu(char *str)return 1 ; } +__setup ("protect4gb=" , setup_protect4gb);__setup ("iommu=" , setup_iommu); static unsigned long iommu_range_alloc(struct iommu_table *tbl,
@@ -429,6 +442,9 @@ void iommu_unmap_sg(struct iommu_table *struct iommu_table *iommu_init_table (struct iommu_table *tbl,int nid) {unsigned long sz; +unsigned long start_index, end_index; +unsigned long entries_per_4g; +unsigned long index;static int welcomed =0 ;struct page *page;
@@ -450,7 +466,7 @@ struct iommu_table *iommu_init_table(str#ifdef CONFIG_CRASH_DUMP if (ppc_md.tce_get) { -unsigned long index, tceval; +unsigned long tceval;unsigned long tcecount =0 ;/*
@@ -480,6 +496,23 @@ struct iommu_table *iommu_init_table(str ppc_md.tce_free (tbl, tbl->it_offset, tbl->it_size);#endif +/* +* DMA cannot cross 4 GB boundary. Mark last entry of each 4 +* GB chunk as reserved. +*/ +if (protect4gb) { + entries_per_4g =0x100000000l >>IOMMU_PAGE_SHIFT ; + +/* Mark the last bit before a 4GB boundary as used */ + start_index = tbl->it_offset | (entries_per_4g -1 ); + start_index -= tbl->it_offset; + + end_index = tbl->it_size; + +for (index = start_index; index < end_index -1 ; index += entries_per_4g) +__set_bit (index, tbl->it_map); + } +if (!welcomed) {printk (KERN_INFO "IOMMU table initialized, virtual merging %s\n" , novmerge ?"disabled" :"enabled" );
Olof Johansson
Acked-byQuoted 23 lines · Jake Moilanen
Quoted 5 lines · Benjamin Herrenschmidt
Quoted 1 line · Jake Moilanen
One more try.
And patch is still mangled....
Hrm....I should just go back to Sylpheed.
Unmangled version:
There are many adapters which can not handle DMAing acrosss any 4 GB boundary. For instance the latest Emulex adapters.
This normally is not an issue as firmware gives dma-windows under 4gigs. However, some of the new System-P boxes have dma-windows above 4gigs, and this present a problem.
During initialization of the IOMMU tables, the last entry at each 4GB boundary is marked as used. Thus no mappings can cross the boundary. If a table ends at a 4GB boundary, the entry is not marked as used.
Quoted 5 lines
A boot option to remove this 4GB protection is given w/ protect4gb=off. This exposes the potential issue for driver and hardware development purposes.
Signed-off-by Jake Moilanen <[email protected]>
-Olof
Quoted 8 lines · Jake Moilanen
Quoted 5 lines · Benjamin Herrenschmidt
Quoted 1 line · Jake Moilanen
One more try.
And patch is still mangled....
Hrm....I should just go back to Sylpheed.
Heh, I never have problems with Evolution... just make sure you use "Preformat" as a style.
Cheers,
Ben.
Jake,
It looks like I have applied both the final version of this patch (569975591c5530fdc9c7a3c45122e5e46f075a74) and the original version (618d3adc351a24c4c48437c767befb88ca2d199d) that modified iommu_range_alloc().
Should I revert the original version?
Paul.
Quoted 8 lines · Paul Mackerras
Jake,
It looks like I have applied both the final version of this patch (
569975591c5530fdc9c7a3c45122e5e46f075a74) and the original version (618d3adc351a24c4c48437c767befb88ca2d199d) that modifiediommu_range_alloc().Should I revert the original version?
(Since I didn't see a reply from Jake today):
Yes, please do. It's redundant given the better patch that went in later.
Thanks,
-Olof