Re: [PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
In fact, the changes over the last patch are more complex than the current patch. Just for reference, that's how enable_ddw() currently patches: @@ -1087,7 +1119,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) struct device_node *dn; u32 ddw_avail[DDW_APPLICABLE_SIZE]; struct direct_window *window; - struct property *win64; + struct property *win64, *default_win = NULL; struct dynamic_dma_window_prop *ddwprop; struct failed_ddw_pdn *fpdn; @@ -1133,14 +1165,38 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) if (ret != 0) goto out_failed; + /* +* If there is no window available, remove the default DMA window, +* if it's present. This will make all the resources available to the +* new DDW window. +* If anything fails after this, we need to restore it, so also check +* for extensions presence. +*/ if (query.windows_available == 0) { - /* -* no additional windows are available for this device. -* We might be able to reallocate the existing window, -* trading in for a larger page size. -*/ - dev_dbg(>dev, "no free dynamic windows"); - goto out_failed; + int reset_win_ext; + + default_win = of_find_property(pdn, "ibm,dma-window", NULL); + if (!default_win) + goto out_failed; + + reset_win_ext = ddw_read_ext(pdn, DDW_EXT_RESET_DMA_WIN, NULL); + if (reset_win_ext) { + default_win = NULL; + goto out_failed; + } + + remove_dma_window(pdn, ddw_avail, default_win); + + /* Query again, to check if the window is available */ + ret = query_ddw(dev, ddw_avail, , pdn); + if (ret != 0) + goto out_failed; + + if (query.windows_available == 0) { + /* no windows are available for this device. */ + dev_dbg(>dev, "no free dynamic windows"); + goto out_failed; + } } if (query.page_size & 4) { page_shift = 24; /* 16MB */ @@ -1231,6 +1287,8 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) kfree(win64); out_failed: + if (default_win) + reset_dma_window(dev, pdn); fpdn = kzalloc(sizeof(*fpdn), GFP_KERNEL); if (!fpdn)
Re: [PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
On Tue, 2020-07-14 at 14:52 +1000, Alexey Kardashevskiy wrote: > > On 14/07/2020 12:40, Leonardo Bras wrote: > > Thank you for this feedback Alexey! > > > > On Mon, 2020-07-13 at 17:33 +1000, Alexey Kardashevskiy wrote: > > > [...] > > > > - int len, ret; > > > > + int len, ret, reset_win_ext; > > > > > > Make it "reset_token". > > > > Oh, it's not a token here, it just checks if the reset_win extension > > exists. The token would be returned in *value, but since we did not > > need it here, it's not copied. > > ah right, so it is a bool actually. In fact I did it a int, as it's the return value of ddw_read_ext(), which can return 0 on success and -error otherwise. > > > > [...] > > > > -out_failed: > > > > +out_restore_defwin: > > > > + if (default_win && reset_win_ext == 0) > > > > > > reset_win_ext potentially may be uninitialized here. Yeah I know it is > > > tied to default_win but still. > > > > I can't see it being used uninitialized here, as you said it's tied to > > default_win. > > Where it is declared - it is not initialized so in theory it can skip > "if (query.windows_available == 0)". Humm, I thought doing if (default_win && reset_win_ext == 0) would guarantee default_win to be tested before reset_win_ext is ever tested, so I could control it using default_win. > > > > Could you please tell me how it can be used uninitialized here, or what > > is bad by doing this way? > > > > > After looking at this function for a few minutes, it could use some > > > refactoring (way too many gotos) such as: > > > > Yes, I agree. > > > > > 1. move (query.page_size & xx) checks before "if > > > (query.windows_available == 0)" > > > > Moving 'page_size selection' above 'checking windows available' will > > need us to duplicate the 'page_size selection' after the new query, > > inside the if. > > page_size selection is not going to change, why? In theory, a query after freeing the default DMA window could have a different (bigger) page size, so we should test again. > > > > I mean, as query will be done again, it will need to get the (new) page > > size. > > > > > 2. move "win64 = kzalloc(sizeof(struct property), GFP_KERNEL)" before > > > "if (query.windows_available == 0)" > > > 3. call "reset_dma_window(dev, pdn)" inside the "if > > > (query.windows_available == 0)" branch. > > > Then you can drop all "goto out_restore_defwin" and move default_win and > > > reset_win_ext inside "if (query.windows_available == 0)". > > > > I did all changes suggested locally and did some analysis in the > > result: > > > > I did not see a way to put default_win and reset_win_ext inside > > "if (query.windows_available == 0)", because if we still need a way to > > know if the default window was removed, and if so, restore in case > > anything ever fails ahead (like creating the node property). > > Ah, I missed that new out_restore_defwin label is between other exit > labels. Sorry :-/ > > > > reset_win_ext = ddw_read_ext(pdn, > > DDW_EXT_RESET_DMA_WIN, NULL); > > - if (reset_win_ext) > > + if (reset_win_ext){ > > + default_win = NULL; > > goto out_failed; > > + } > > This says "if we can reset, then we fail", no? Here ddw_read_ext() should return 0 if extension was found, and (-EINVAL, -ENODATA or -EOVERFLOW) otherwise. So it should return nonzero if we can't find the extension, in which case we should fail. > > > remove_dma_window(pdn, ddw_avail, default_win); > > I think you can do "default_win=NULL" here and later at > out_restore_defwin check if it is NULL - then call reset. Currently I initialize 'default_win = NULL', and it only changes when I read the default DMA window. If reset is not available I restore it to NULL, so it will be not-NULL only when the have removed the default DMA window. If I make it NULL here, we either never reset the default DMA window (as it is now "if (default_win)" ) or we may always reset it (in case "if (default_win == NULL)"). If you think it's better, I can create a bool variable like "default_win_removed", initialized with 'false', which can be assigned here with 'true' and test in the end if(default_win_removed) reset(); This would allow to move default_win inside this 'if block'. What do you think? > > [...] > > > > -out_restore_defwin: > > - if (default_win && reset_win_ext == 0) > > +out_failed: > > + if (default_win) > > reset_dma_window(dev, pdn); > > > > -out_failed: > > fpdn = kzalloc(sizeof(*fpdn), GFP_KERNEL); > > if (!fpdn) > > goto out_unlock; > > > > # > > > > What do you think? > > > > > > > > > The rest of the series is good as it is, > > > > Thank you :) > > > > > however it may conflict with > > > https://patchwork.ozlabs.org/project/linuxppc-dev/patch/20200713062348.100552-1-...@ozlabs.ru/ > > > and the patchset
Re: [PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
On 14/07/2020 12:40, Leonardo Bras wrote: > Thank you for this feedback Alexey! > > On Mon, 2020-07-13 at 17:33 +1000, Alexey Kardashevskiy wrote: >> [...] >>> - int len, ret; >>> + int len, ret, reset_win_ext; >> >> Make it "reset_token". > > Oh, it's not a token here, it just checks if the reset_win extension > exists. The token would be returned in *value, but since we did not > need it here, it's not copied. ah right, so it is a bool actually. > >>> [...] >>> -out_failed: >>> +out_restore_defwin: >>> + if (default_win && reset_win_ext == 0) >> >> reset_win_ext potentially may be uninitialized here. Yeah I know it is >> tied to default_win but still. > > I can't see it being used uninitialized here, as you said it's tied to > default_win. Where it is declared - it is not initialized so in theory it can skip "if (query.windows_available == 0)". > Could you please tell me how it can be used uninitialized here, or what > is bad by doing this way? > >> After looking at this function for a few minutes, it could use some >> refactoring (way too many gotos) such as: > > Yes, I agree. > >> 1. move (query.page_size & xx) checks before "if >> (query.windows_available == 0)" > > Moving 'page_size selection' above 'checking windows available' will > need us to duplicate the 'page_size selection' after the new query, > inside the if. page_size selection is not going to change, why? > I mean, as query will be done again, it will need to get the (new) page > size. > >> 2. move "win64 = kzalloc(sizeof(struct property), GFP_KERNEL)" before >> "if (query.windows_available == 0)" > >> 3. call "reset_dma_window(dev, pdn)" inside the "if >> (query.windows_available == 0)" branch. > >> Then you can drop all "goto out_restore_defwin" and move default_win and >> reset_win_ext inside "if (query.windows_available == 0)". > > I did all changes suggested locally and did some analysis in the > result: > > I did not see a way to put default_win and reset_win_ext inside > "if (query.windows_available == 0)", because if we still need a way to > know if the default window was removed, and if so, restore in case > anything ever fails ahead (like creating the node property). Ah, I missed that new out_restore_defwin label is between other exit labels. Sorry :-/ > But from that analysis I noted it's possible to remove all the new > "goto out_restore_defwin", if we do default_win = NULL if > ddw_read_ext() fails. > > So testing only default_win should always be enough to say if the > default window was deleted, and reset_win_ext could be moved inside "if > (query.windows_available == 0)". > Also, it would avoid reset_win_ext being 'used uninitialized' and > "out_restore_defwin:" would not be needed. > > Against the current patch, we would have something like this: > > # > > static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) > { > - int len, ret, reset_win_ext; > + int len, ret; > struct ddw_query_response query; > struct ddw_create_response create; > int page_shift; > @@ -1173,25 +1173,28 @@ static u64 enable_ddw(struct pci_dev *dev, > struct device_node *pdn) > * for extensions presence. > */ > if (query.windows_available == 0) { > + int reset_win_ext; > default_win = of_find_property(pdn, "ibm,dma-window", > NULL); > if (!default_win) > goto out_failed; > > reset_win_ext = ddw_read_ext(pdn, > DDW_EXT_RESET_DMA_WIN, NULL); > - if (reset_win_ext) > + if (reset_win_ext){ > + default_win = NULL; > goto out_failed; > + } This says "if we can reset, then we fail", no? > remove_dma_window(pdn, ddw_avail, default_win); I think you can do "default_win=NULL" here and later at out_restore_defwin check if it is NULL - then call reset. > /* Query again, to check if the window is available */ > ret = query_ddw(dev, ddw_avail, , pdn); > if (ret != 0) > - goto out_restore_defwin; > + goto out_failed; > > if (query.windows_available == 0) { > /* no windows are available for this device. */ > dev_dbg(>dev, "no free dynamic windows"); > - goto out_restore_defwin; > + goto out_failed; > } > } > if (query.page_size & 4) { > @@ -1203,7 +1206,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct > device_node *pdn) > } else { > dev_dbg(>dev, "no supported direct page size in > mask %x", > query.page_size); > - goto out_restore_defwin; > + goto out_failed; > } > /* verify the window * number of ptes will map the
Re: [PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
Thank you for this feedback Alexey! On Mon, 2020-07-13 at 17:33 +1000, Alexey Kardashevskiy wrote: > [...] > > - int len, ret; > > + int len, ret, reset_win_ext; > > Make it "reset_token". Oh, it's not a token here, it just checks if the reset_win extension exists. The token would be returned in *value, but since we did not need it here, it's not copied. > > [...] > > -out_failed: > > +out_restore_defwin: > > + if (default_win && reset_win_ext == 0) > > reset_win_ext potentially may be uninitialized here. Yeah I know it is > tied to default_win but still. I can't see it being used uninitialized here, as you said it's tied to default_win. Could you please tell me how it can be used uninitialized here, or what is bad by doing this way? > After looking at this function for a few minutes, it could use some > refactoring (way too many gotos) such as: Yes, I agree. > 1. move (query.page_size & xx) checks before "if > (query.windows_available == 0)" Moving 'page_size selection' above 'checking windows available' will need us to duplicate the 'page_size selection' after the new query, inside the if. I mean, as query will be done again, it will need to get the (new) page size. > 2. move "win64 = kzalloc(sizeof(struct property), GFP_KERNEL)" before > "if (query.windows_available == 0)" > 3. call "reset_dma_window(dev, pdn)" inside the "if > (query.windows_available == 0)" branch. > Then you can drop all "goto out_restore_defwin" and move default_win and > reset_win_ext inside "if (query.windows_available == 0)". I did all changes suggested locally and did some analysis in the result: I did not see a way to put default_win and reset_win_ext inside "if (query.windows_available == 0)", because if we still need a way to know if the default window was removed, and if so, restore in case anything ever fails ahead (like creating the node property). But from that analysis I noted it's possible to remove all the new "goto out_restore_defwin", if we do default_win = NULL if ddw_read_ext() fails. So testing only default_win should always be enough to say if the default window was deleted, and reset_win_ext could be moved inside "if (query.windows_available == 0)". Also, it would avoid reset_win_ext being 'used uninitialized' and "out_restore_defwin:" would not be needed. Against the current patch, we would have something like this: # static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) { - int len, ret, reset_win_ext; + int len, ret; struct ddw_query_response query; struct ddw_create_response create; int page_shift; @@ -1173,25 +1173,28 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) * for extensions presence. */ if (query.windows_available == 0) { + int reset_win_ext; default_win = of_find_property(pdn, "ibm,dma-window", NULL); if (!default_win) goto out_failed; reset_win_ext = ddw_read_ext(pdn, DDW_EXT_RESET_DMA_WIN, NULL); - if (reset_win_ext) + if (reset_win_ext){ + default_win = NULL; goto out_failed; + } remove_dma_window(pdn, ddw_avail, default_win); /* Query again, to check if the window is available */ ret = query_ddw(dev, ddw_avail, , pdn); if (ret != 0) - goto out_restore_defwin; + goto out_failed; if (query.windows_available == 0) { /* no windows are available for this device. */ dev_dbg(>dev, "no free dynamic windows"); - goto out_restore_defwin; + goto out_failed; } } if (query.page_size & 4) { @@ -1203,7 +1206,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) } else { dev_dbg(>dev, "no supported direct page size in mask %x", query.page_size); - goto out_restore_defwin; + goto out_failed; } /* verify the window * number of ptes will map the partition */ /* check largest block * page size > max memory hotplug addr */ @@ -1212,14 +1215,14 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) dev_dbg(>dev, "can't map partition max 0x%llx with %llu " "%llu-sized pages\n", max_addr, query.largest_available_block, 1ULL << page_shift); - goto out_restore_defwin; + goto out_failed; } len = order_base_2(max_addr); win64 = kzalloc(sizeof(struct property), GFP_KERNEL); if (!win64) { dev_info(>dev, "couldn't allocate property for 64bit dma window\n"); -
Re: [PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
On 03/07/2020 16:18, Leonardo Bras wrote: > On LoPAR "DMA Window Manipulation Calls", it's recommended to remove the > default DMA window for the device, before attempting to configure a DDW, > in order to make the maximum resources available for the next DDW to be > created. > > This is a requirement for using DDW on devices in which hypervisor > allows only one DMA window. > > If setting up a new DDW fails anywhere after the removal of this > default DMA window, it's needed to restore the default DMA window. > For this, an implementation of ibm,reset-pe-dma-windows rtas call is > needed: > > Platforms supporting the DDW option starting with LoPAR level 2.7 implement > ibm,ddw-extensions. The first extension available (index 2) carries the > token for ibm,reset-pe-dma-windows rtas call, which is used to restore > the default DMA window for a device, if it has been deleted. > > It does so by resetting the TCE table allocation for the PE to it's > boot time value, available in "ibm,dma-window" device tree node. > > Signed-off-by: Leonardo Bras > --- > arch/powerpc/platforms/pseries/iommu.c | 83 +- > 1 file changed, 69 insertions(+), 14 deletions(-) > > diff --git a/arch/powerpc/platforms/pseries/iommu.c > b/arch/powerpc/platforms/pseries/iommu.c > index 4e33147825cc..5b520ac354c6 100644 > --- a/arch/powerpc/platforms/pseries/iommu.c > +++ b/arch/powerpc/platforms/pseries/iommu.c > @@ -1066,6 +1066,38 @@ static phys_addr_t ddw_memory_hotplug_max(void) > return max_addr; > } > > +/* > + * Platforms supporting the DDW option starting with LoPAR level 2.7 > implement > + * ibm,ddw-extensions, which carries the rtas token for > + * ibm,reset-pe-dma-windows. > + * That rtas-call can be used to restore the default DMA window for the > device. > + */ > +static void reset_dma_window(struct pci_dev *dev, struct device_node *par_dn) > +{ > + int ret; > + u32 cfg_addr, reset_dma_win; > + u64 buid; > + struct device_node *dn; > + struct pci_dn *pdn; > + > + ret = ddw_read_ext(par_dn, DDW_EXT_RESET_DMA_WIN, _dma_win); > + if (ret) > + return; > + > + dn = pci_device_to_OF_node(dev); > + pdn = PCI_DN(dn); > + buid = pdn->phb->buid; > + cfg_addr = ((pdn->busno << 16) | (pdn->devfn << 8)); > + > + ret = rtas_call(reset_dma_win, 3, 1, NULL, cfg_addr, BUID_HI(buid), > + BUID_LO(buid)); > + if (ret) > + dev_info(>dev, > + "ibm,reset-pe-dma-windows(%x) %x %x %x returned %d ", > + reset_dma_win, cfg_addr, BUID_HI(buid), BUID_LO(buid), > + ret); > +} > + > /* > * If the PE supports dynamic dma windows, and there is space for a table > * that can map all pages in a linear offset, then setup such a table, > @@ -1079,7 +,7 @@ static phys_addr_t ddw_memory_hotplug_max(void) > */ > static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) > { > - int len, ret; > + int len, ret, reset_win_ext; Make it "reset_token". > struct ddw_query_response query; > struct ddw_create_response create; > int page_shift; > @@ -1087,7 +1119,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct > device_node *pdn) > struct device_node *dn; > u32 ddw_avail[DDW_APPLICABLE_SIZE]; > struct direct_window *window; > - struct property *win64; > + struct property *win64, *default_win = NULL; > struct dynamic_dma_window_prop *ddwprop; > struct failed_ddw_pdn *fpdn; > > @@ -1122,7 +1154,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct > device_node *pdn) > if (ret) > goto out_failed; > > - /* > + /* >* Query if there is a second window of size to map the >* whole partition. Query returns number of windows, largest >* block assigned to PE (partition endpoint), and two bitmasks > @@ -1133,14 +1165,34 @@ static u64 enable_ddw(struct pci_dev *dev, struct > device_node *pdn) > if (ret != 0) > goto out_failed; > > + /* > + * If there is no window available, remove the default DMA window, > + * if it's present. This will make all the resources available to the > + * new DDW window. > + * If anything fails after this, we need to restore it, so also check > + * for extensions presence. > + */ > if (query.windows_available == 0) { > - /* > - * no additional windows are available for this device. > - * We might be able to reallocate the existing window, > - * trading in for a larger page size. > - */ > - dev_dbg(>dev, "no free dynamic windows"); > - goto out_failed; > + default_win = of_find_property(pdn, "ibm,dma-window", NULL); > + if (!default_win) > + goto out_failed; > + > + reset_win_ext = ddw_read_ext(pdn,
[PATCH v3 4/6] powerpc/pseries/iommu: Remove default DMA window before creating DDW
On LoPAR "DMA Window Manipulation Calls", it's recommended to remove the default DMA window for the device, before attempting to configure a DDW, in order to make the maximum resources available for the next DDW to be created. This is a requirement for using DDW on devices in which hypervisor allows only one DMA window. If setting up a new DDW fails anywhere after the removal of this default DMA window, it's needed to restore the default DMA window. For this, an implementation of ibm,reset-pe-dma-windows rtas call is needed: Platforms supporting the DDW option starting with LoPAR level 2.7 implement ibm,ddw-extensions. The first extension available (index 2) carries the token for ibm,reset-pe-dma-windows rtas call, which is used to restore the default DMA window for a device, if it has been deleted. It does so by resetting the TCE table allocation for the PE to it's boot time value, available in "ibm,dma-window" device tree node. Signed-off-by: Leonardo Bras --- arch/powerpc/platforms/pseries/iommu.c | 83 +- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/arch/powerpc/platforms/pseries/iommu.c b/arch/powerpc/platforms/pseries/iommu.c index 4e33147825cc..5b520ac354c6 100644 --- a/arch/powerpc/platforms/pseries/iommu.c +++ b/arch/powerpc/platforms/pseries/iommu.c @@ -1066,6 +1066,38 @@ static phys_addr_t ddw_memory_hotplug_max(void) return max_addr; } +/* + * Platforms supporting the DDW option starting with LoPAR level 2.7 implement + * ibm,ddw-extensions, which carries the rtas token for + * ibm,reset-pe-dma-windows. + * That rtas-call can be used to restore the default DMA window for the device. + */ +static void reset_dma_window(struct pci_dev *dev, struct device_node *par_dn) +{ + int ret; + u32 cfg_addr, reset_dma_win; + u64 buid; + struct device_node *dn; + struct pci_dn *pdn; + + ret = ddw_read_ext(par_dn, DDW_EXT_RESET_DMA_WIN, _dma_win); + if (ret) + return; + + dn = pci_device_to_OF_node(dev); + pdn = PCI_DN(dn); + buid = pdn->phb->buid; + cfg_addr = ((pdn->busno << 16) | (pdn->devfn << 8)); + + ret = rtas_call(reset_dma_win, 3, 1, NULL, cfg_addr, BUID_HI(buid), + BUID_LO(buid)); + if (ret) + dev_info(>dev, +"ibm,reset-pe-dma-windows(%x) %x %x %x returned %d ", +reset_dma_win, cfg_addr, BUID_HI(buid), BUID_LO(buid), +ret); +} + /* * If the PE supports dynamic dma windows, and there is space for a table * that can map all pages in a linear offset, then setup such a table, @@ -1079,7 +,7 @@ static phys_addr_t ddw_memory_hotplug_max(void) */ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) { - int len, ret; + int len, ret, reset_win_ext; struct ddw_query_response query; struct ddw_create_response create; int page_shift; @@ -1087,7 +1119,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) struct device_node *dn; u32 ddw_avail[DDW_APPLICABLE_SIZE]; struct direct_window *window; - struct property *win64; + struct property *win64, *default_win = NULL; struct dynamic_dma_window_prop *ddwprop; struct failed_ddw_pdn *fpdn; @@ -1122,7 +1154,7 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) if (ret) goto out_failed; - /* + /* * Query if there is a second window of size to map the * whole partition. Query returns number of windows, largest * block assigned to PE (partition endpoint), and two bitmasks @@ -1133,14 +1165,34 @@ static u64 enable_ddw(struct pci_dev *dev, struct device_node *pdn) if (ret != 0) goto out_failed; + /* +* If there is no window available, remove the default DMA window, +* if it's present. This will make all the resources available to the +* new DDW window. +* If anything fails after this, we need to restore it, so also check +* for extensions presence. +*/ if (query.windows_available == 0) { - /* -* no additional windows are available for this device. -* We might be able to reallocate the existing window, -* trading in for a larger page size. -*/ - dev_dbg(>dev, "no free dynamic windows"); - goto out_failed; + default_win = of_find_property(pdn, "ibm,dma-window", NULL); + if (!default_win) + goto out_failed; + + reset_win_ext = ddw_read_ext(pdn, DDW_EXT_RESET_DMA_WIN, NULL); + if (reset_win_ext) + goto out_failed; + + remove_dma_window(pdn, ddw_avail, default_win); + + /* Query again,