From nobody Tue Feb 10 05:25:51 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 97137C77B7E for ; Mon, 29 May 2023 17:14:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229633AbjE2ROf (ORCPT ); Mon, 29 May 2023 13:14:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51506 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229455AbjE2ROX (ORCPT ); Mon, 29 May 2023 13:14:23 -0400 Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id D1015D2 for ; Mon, 29 May 2023 10:14:19 -0700 (PDT) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8Bxq+pr3XRkr0kCAA--.916S3; Tue, 30 May 2023 01:14:19 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8DxD79p3XRkLUJ_AA--.10654S7; Tue, 30 May 2023 01:14:18 +0800 (CST) From: Sui Jingfeng To: Lucas Stach , Russell King , Christian Gmeiner , David Airlie , Daniel Vetter Cc: linux-kernel@vger.kernel.org, etnaviv@lists.freedesktop.org, dri-devel@lists.freedesktop.org, loongson-kernel@lists.loongnix.cn, Li Yi Subject: [PATCH v4 5/6] drm/etnaviv: expand driver support for the PCI devices Date: Tue, 30 May 2023 01:14:16 +0800 Message-Id: <20230529171417.2146323-6-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230529171417.2146323-1-suijingfeng@loongson.cn> References: <20230529171417.2146323-1-suijingfeng@loongson.cn> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: AQAAf8DxD79p3XRkLUJ_AA--.10654S7 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBjvAXoWfGF1xGr4UZrW8tF4kZFW8JFb_yoW8Xw4xuo WxZrnxXa1rGFZ3t398AF12gFy3Zw4DC3s8C3Z8tws09ayvqF15tw13G3W3tF1Yvr1FgrWf Zw1Utw1rJF4xJFn5n29KB7ZKAUJUUUUx529EdanIXcx71UUUUU7KY7ZEXasCq-sGcSsGvf J3Ic02F40EFcxC0VAKzVAqx4xG6I80ebIjqfuFe4nvWSU5nxnvy29KBjDU0xBIdaVrnRJU UUB2b4IE77IF4wAFF20E14v26r1j6r4UM7CY07I20VC2zVCF04k26cxKx2IYs7xG6rWj6s 0DM7CIcVAFz4kK6r1Y6r17M28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Cr0_Gr1UM2 8EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v26r4j6r4UJwAa w2AFwI0_JF0_Jw1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqjxCEc2xF0cIa020Ex4CE44 I27wAqx4xG64xvF2IEw4CE5I8CrVC2j2WlYx0E2Ix0cI8IcVAFwI0_Wrv_ZF1lYx0Ex4A2 jsIE14v26r4j6F4UMcvjeVCFs4IE7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwCY1x0262 kKe7AKxVWUAVWUtwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwCFI7km 07C267AKxVWUAVWUtwC20s026c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r 1rMI8E67AF67kF1VAFwI0_Jw0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW5 JVW7JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Cr0_Gr1UMIIF0xvE42xK8VAvwI8IcIk0rV WUJVWUCwCI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4j6r4U JbIYCTnIWIevJa73UjIFyTuYvjxUVWrXDUUUU Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This patch adds PCI driver support on top of what already have. Take the GC1000 in LS7A1000/LS2K1000 as the first instance of the PCI device driver. There is only one GPU core for the GC1000 in the LS7A1000 and LS2K1000. Therefore, component frameworks can be avoided. Because we want to bind the DRM driver service to the PCI driver manually. =C2=A0 =C2=A0=C2=A0 We avoid using the component framework because the virtual master device will not be used without a force override. X server and Mesa will try to find the PCI device to use by default. Creating a virtual master device for PCI GPUs cause unnecessary troubles. =C2=A0 =C2=A0=C2=A0 Using the component framework with a PCI device is still possible; it is just that the solo PCI device should be the master. A platform with a single GPU core could also try the non-component code path. Signed-off-by: Sui Jingfeng --- drivers/gpu/drm/etnaviv/Makefile | 1 + drivers/gpu/drm/etnaviv/etnaviv_drv.c | 62 ++++++++++++--- drivers/gpu/drm/etnaviv/etnaviv_drv.h | 3 + drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 97 ++++++++++++++++------- drivers/gpu/drm/etnaviv/etnaviv_gpu.h | 12 +++ drivers/gpu/drm/etnaviv/etnaviv_pci_drv.c | 88 ++++++++++++++++++++ drivers/gpu/drm/etnaviv/etnaviv_pci_drv.h | 10 +++ 7 files changed, 232 insertions(+), 41 deletions(-) create mode 100644 drivers/gpu/drm/etnaviv/etnaviv_pci_drv.c create mode 100644 drivers/gpu/drm/etnaviv/etnaviv_pci_drv.h diff --git a/drivers/gpu/drm/etnaviv/Makefile b/drivers/gpu/drm/etnaviv/Mak= efile index 46e5ffad69a6..3f8b99664a58 100644 --- a/drivers/gpu/drm/etnaviv/Makefile +++ b/drivers/gpu/drm/etnaviv/Makefile @@ -13,6 +13,7 @@ etnaviv-y :=3D \ etnaviv_iommu_v2.o \ etnaviv_iommu.o \ etnaviv_mmu.o \ + etnaviv_pci_drv.o \ etnaviv_perfmon.o \ etnaviv_sched.o =20 diff --git a/drivers/gpu/drm/etnaviv/etnaviv_drv.c b/drivers/gpu/drm/etnavi= v/etnaviv_drv.c index 56c98711f8e1..7ff795c5cc79 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_drv.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.c @@ -7,6 +7,7 @@ #include #include #include +#include #include =20 #include @@ -21,6 +22,7 @@ #include "etnaviv_gpu.h" #include "etnaviv_gem.h" #include "etnaviv_mmu.h" +#include "etnaviv_pci_drv.h" #include "etnaviv_perfmon.h" =20 /* @@ -525,6 +527,16 @@ static int etnaviv_alloc_private(struct device *dev, return ret; } =20 + /* + * Loongson Mips and LoongArch CPU(ls3a5000, ls3c5000, ls2k1000la) + * maintain cache coherency by hardware + */ + if (IS_ENABLED(CONFIG_CPU_LOONGSON64) || IS_ENABLED(CONFIG_LOONGARCH)) + priv->has_cached_coherent =3D true; + + dev_info(dev, "Cached coherent mode is %s\n", + priv->has_cached_coherent ? "support" : "not support"); + *ppriv =3D priv; =20 return 0; @@ -539,10 +551,9 @@ static void etnaviv_free_private(struct etnaviv_drm_pr= ivate *priv) kfree(priv); } =20 -/* - * Platform driver: - */ -static int etnaviv_bind(struct device *dev) +static struct etnaviv_drm_private *etna_private_s; + +int etnaviv_drm_bind(struct device *dev, bool component) { struct etnaviv_drm_private *priv; struct drm_device *drm; @@ -558,12 +569,15 @@ static int etnaviv_bind(struct device *dev) =20 priv->drm =3D drm; drm->dev_private =3D priv; + etna_private_s =3D priv; =20 dma_set_max_seg_size(dev, SZ_2G); =20 - dev_set_drvdata(dev, drm); + if (component) + ret =3D component_bind_all(dev, drm); + else + ret =3D etnaviv_gpu_bind(dev, NULL, drm); =20 - ret =3D component_bind_all(dev, drm); if (ret < 0) goto out_free_priv; =20 @@ -585,14 +599,17 @@ static int etnaviv_bind(struct device *dev) return ret; } =20 -static void etnaviv_unbind(struct device *dev) +void etnaviv_drm_unbind(struct device *dev, bool component) { - struct drm_device *drm =3D dev_get_drvdata(dev); - struct etnaviv_drm_private *priv =3D drm->dev_private; + struct etnaviv_drm_private *priv =3D etna_private_s; + struct drm_device *drm =3D priv->drm; =20 drm_dev_unregister(drm); =20 - component_unbind_all(dev, drm); + if (component) + component_unbind_all(dev, drm); + else + etnaviv_gpu_unbind(dev, NULL, drm); =20 etnaviv_free_private(priv); =20 @@ -601,9 +618,22 @@ static void etnaviv_unbind(struct device *dev) drm_dev_put(drm); } =20 +/* + * Platform driver: + */ +static int etnaviv_master_bind(struct device *dev) +{ + return etnaviv_drm_bind(dev, true); +} + +static void etnaviv_master_unbind(struct device *dev) +{ + return etnaviv_drm_unbind(dev, true); +} + static const struct component_master_ops etnaviv_master_ops =3D { - .bind =3D etnaviv_bind, - .unbind =3D etnaviv_unbind, + .bind =3D etnaviv_master_bind, + .unbind =3D etnaviv_master_unbind, }; =20 static int etnaviv_pdev_probe(struct platform_device *pdev) @@ -723,10 +753,14 @@ static int __init etnaviv_init(void) if (ret !=3D 0) return ret; =20 - ret =3D platform_driver_register(&etnaviv_platform_driver); + ret =3D pci_register_driver(&etnaviv_pci_driver); if (ret !=3D 0) goto unregister_gpu_driver; =20 + ret =3D platform_driver_register(&etnaviv_platform_driver); + if (ret !=3D 0) + goto unregister_pci_driver; + /* * If the DT contains at least one available GPU device, instantiate * the DRM platform device. @@ -748,6 +782,8 @@ static int __init etnaviv_init(void) =20 unregister_platform_driver: platform_driver_unregister(&etnaviv_platform_driver); +unregister_pci_driver: + pci_unregister_driver(&etnaviv_pci_driver); unregister_gpu_driver: platform_driver_unregister(&etnaviv_gpu_driver); return ret; diff --git a/drivers/gpu/drm/etnaviv/etnaviv_drv.h b/drivers/gpu/drm/etnavi= v/etnaviv_drv.h index 87fb52c03c5e..934fc3744389 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_drv.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.h @@ -86,6 +86,9 @@ bool etnaviv_cmd_validate_one(struct etnaviv_gpu *gpu, u32 *stream, unsigned int size, struct drm_etnaviv_gem_submit_reloc *relocs, unsigned int reloc_size); =20 +int etnaviv_drm_bind(struct device *dev, bool component); +void etnaviv_drm_unbind(struct device *dev, bool component); + #ifdef CONFIG_DEBUG_FS void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv, struct seq_file *m); diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnavi= v/etnaviv_gpu.c index 4937580551a5..700f2414b87d 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -1565,10 +1565,11 @@ static irqreturn_t irq_handler(int irq, void *data) return ret; } =20 -static int etnaviv_gpu_clk_get(struct etnaviv_gpu *gpu) +static int etnaviv_gpu_clk_get(struct etnaviv_gpu *gpu, bool no_clk) { struct device *dev =3D gpu->dev; =20 + gpu->no_clk =3D no_clk; if (gpu->no_clk) return 0; =20 @@ -1746,8 +1747,7 @@ static const struct thermal_cooling_device_ops coolin= g_ops =3D { .set_cur_state =3D etnaviv_gpu_cooling_set_cur_state, }; =20 -static int etnaviv_gpu_bind(struct device *dev, struct device *master, - void *data) +int etnaviv_gpu_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm =3D data; struct etnaviv_drm_private *priv =3D drm->dev_private; @@ -1778,7 +1778,6 @@ static int etnaviv_gpu_bind(struct device *dev, struc= t device *master, if (ret < 0) goto out_sched; =20 - gpu->drm =3D drm; gpu->fence_context =3D dma_fence_context_alloc(1); xa_init_flags(&gpu->user_fences, XA_FLAGS_ALLOC); @@ -1807,8 +1806,7 @@ static int etnaviv_gpu_bind(struct device *dev, struc= t device *master, return ret; } =20 -static void etnaviv_gpu_unbind(struct device *dev, struct device *master, - void *data) +void etnaviv_gpu_unbind(struct device *dev, struct device *master, void *d= ata) { struct etnaviv_gpu *gpu =3D dev_get_drvdata(dev); =20 @@ -1878,9 +1876,37 @@ static int etnaviv_gpu_register_irq(struct etnaviv_g= pu *gpu, int irq) return 0; } =20 -static int etnaviv_gpu_platform_probe(struct platform_device *pdev) +static int etnaviv_gpu_plat_drv_init(struct etnaviv_gpu *gpu, bool compone= nt) +{ + struct device *dev =3D gpu->dev; + struct platform_device *pdev =3D to_platform_device(dev); + int err; + + /* Map registers: */ + gpu->mmio =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gpu->mmio)) + return PTR_ERR(gpu->mmio); + + if (component) { + err =3D component_add(dev, &gpu_ops); + if (err < 0) { + dev_err(dev, "failed to register component: %d\n", err); + return err; + } + } + + return 0; +} + +static void etnaviv_gpu_plat_drv_fini(struct etnaviv_gpu *gpu, bool compon= ent) +{ + if (component) + component_del(gpu->dev, &gpu_ops); +} + +int etnaviv_gpu_driver_create(struct device *dev, int irq, bool component, + bool no_clk, pfn_gpu_init_t gpu_post_init) { - struct device *dev =3D &pdev->dev; struct etnaviv_gpu *gpu; int err; =20 @@ -1888,22 +1914,16 @@ static int etnaviv_gpu_platform_probe(struct platfo= rm_device *pdev) if (!gpu) return -ENOMEM; =20 - gpu->dev =3D &pdev->dev; + gpu->dev =3D dev; mutex_init(&gpu->lock); mutex_init(&gpu->sched_lock); =20 - /* Map registers: */ - gpu->mmio =3D devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(gpu->mmio)) - return PTR_ERR(gpu->mmio); - /* Get Interrupt: */ - err =3D etnaviv_gpu_register_irq(gpu, platform_get_irq(pdev, 0)); + err =3D etnaviv_gpu_register_irq(gpu, irq); if (err) return err; =20 - /* Get Clocks: */ - err =3D etnaviv_gpu_clk_get(gpu); + err =3D etnaviv_gpu_clk_get(gpu, no_clk); if (err) return err; =20 @@ -1915,23 +1935,44 @@ static int etnaviv_gpu_platform_probe(struct platfo= rm_device *pdev) * autosuspend delay is rather arbitary: no measurements have * yet been performed to determine an appropriate value. */ - pm_runtime_use_autosuspend(gpu->dev); - pm_runtime_set_autosuspend_delay(gpu->dev, 200); - pm_runtime_enable(gpu->dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 200); + pm_runtime_enable(dev); =20 - err =3D component_add(&pdev->dev, &gpu_ops); - if (err < 0) { - dev_err(&pdev->dev, "failed to register component: %d\n", err); - return err; - } + gpu_post_init(gpu, component); =20 return 0; } =20 +void etnaviv_gpu_driver_destroy(struct device *dev, bool component, + pfn_gpu_fini_t gpu_early_fini) +{ + struct etnaviv_gpu *gpu =3D dev_get_drvdata(dev); + + if (!gpu) { + dev_err(dev, "device not initialized properly\n"); + return; + } + + gpu_early_fini(gpu, component); + + pm_runtime_disable(dev); +} + +static int etnaviv_gpu_platform_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + int irq =3D platform_get_irq(pdev, 0); + + return etnaviv_gpu_driver_create(dev, irq, true, false, etnaviv_gpu_plat_= drv_init); +} + static int etnaviv_gpu_platform_remove(struct platform_device *pdev) { - component_del(&pdev->dev, &gpu_ops); - pm_runtime_disable(&pdev->dev); + struct device *dev =3D &pdev->dev; + + etnaviv_gpu_driver_destroy(dev, true, etnaviv_gpu_plat_drv_fini); + return 0; } =20 @@ -1978,7 +2019,7 @@ static int etnaviv_gpu_rpm_resume(struct device *dev) return 0; } =20 -static const struct dev_pm_ops etnaviv_gpu_pm_ops =3D { +const struct dev_pm_ops etnaviv_gpu_pm_ops =3D { RUNTIME_PM_OPS(etnaviv_gpu_rpm_suspend, etnaviv_gpu_rpm_resume, NULL) }; =20 diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnavi= v/etnaviv_gpu.h index 6da5209a7d64..cfcdc5dde9d9 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h @@ -207,6 +207,18 @@ void etnaviv_gpu_pm_put(struct etnaviv_gpu *gpu); int etnaviv_gpu_wait_idle(struct etnaviv_gpu *gpu, unsigned int timeout_ms= ); void etnaviv_gpu_start_fe(struct etnaviv_gpu *gpu, u32 address, u16 prefet= ch); =20 +typedef int (*pfn_gpu_init_t)(struct etnaviv_gpu *gpu, bool component); +typedef void (*pfn_gpu_fini_t)(struct etnaviv_gpu *gpu, bool component); + +int etnaviv_gpu_driver_create(struct device *dev, int irq, bool component, + bool no_clk, pfn_gpu_init_t post_init); +void etnaviv_gpu_driver_destroy(struct device *dev, bool component, + pfn_gpu_fini_t early_fini); + +int etnaviv_gpu_bind(struct device *dev, struct device *master, void *data= ); +void etnaviv_gpu_unbind(struct device *dev, struct device *master, void *d= ata); + extern struct platform_driver etnaviv_gpu_driver; +extern const struct dev_pm_ops etnaviv_gpu_pm_ops; =20 #endif /* __ETNAVIV_GPU_H__ */ diff --git a/drivers/gpu/drm/etnaviv/etnaviv_pci_drv.c b/drivers/gpu/drm/et= naviv/etnaviv_pci_drv.c new file mode 100644 index 000000000000..e64a03061d09 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_pci_drv.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "etnaviv_drv.h" +#include "etnaviv_gpu.h" +#include "etnaviv_pci_drv.h" + +enum etnaviv_pci_gpu_family { + GC1000_IN_LS7A1000 =3D 0, + GC1000_IN_LS2K1000 =3D 1, +}; + +static int etnaviv_gpu_pci_init(struct etnaviv_gpu *gpu, bool component) +{ + struct pci_dev *pdev =3D to_pci_dev(gpu->dev); + + /* Map registers, assume the PCI bar 0 contain the registers */ + gpu->mmio =3D pcim_iomap(pdev, 0, 0); + if (IS_ERR(gpu->mmio)) + return PTR_ERR(gpu->mmio); + + gpu->no_clk =3D true; + + return 0; +} + +static void etnaviv_gpu_pci_fini(struct etnaviv_gpu *gpu, bool component) +{ + struct pci_dev *pdev =3D to_pci_dev(gpu->dev); + + pci_clear_master(pdev); + + dev_dbg(gpu->dev, "component is %s\n", + component ? "enabled" : "disabled"); +} + +static int etnaviv_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct device *dev =3D &pdev->dev; + int ret; + + ret =3D pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to enable\n"); + return ret; + } + + pci_set_master(pdev); + + ret =3D dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /* Create a GPU driver instance for the PCI device itself */ + ret =3D etnaviv_gpu_driver_create(dev, pdev->irq, false, true, + etnaviv_gpu_pci_init); + if (ret) + return ret; + + return etnaviv_drm_bind(dev, false); +} + +static void etnaviv_pci_remove(struct pci_dev *pdev) +{ + struct device *dev =3D &pdev->dev; + + etnaviv_drm_unbind(dev, false); + + etnaviv_gpu_driver_destroy(dev, false, etnaviv_gpu_pci_fini); +} + +static const struct pci_device_id etnaviv_pci_id_lists[] =3D { + {0x0014, 0x7a15, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GC1000_IN_LS7A1000}, + {0x0014, 0x7a05, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GC1000_IN_LS2K1000}, + {0, 0, 0, 0, 0, 0, 0} +}; + +struct pci_driver etnaviv_pci_driver =3D { + .name =3D "etnaviv", + .id_table =3D etnaviv_pci_id_lists, + .probe =3D etnaviv_pci_probe, + .remove =3D etnaviv_pci_remove, + .driver.pm =3D pm_ptr(&etnaviv_gpu_pm_ops), +}; + +MODULE_DEVICE_TABLE(pci, etnaviv_pci_id_lists); diff --git a/drivers/gpu/drm/etnaviv/etnaviv_pci_drv.h b/drivers/gpu/drm/et= naviv/etnaviv_pci_drv.h new file mode 100644 index 000000000000..6e50ec5f32b7 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_pci_drv.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __ETNAVIV_PCI_DRV_H__ +#define __ETNAVIV_PCI_DRV_H__ + +#include + +extern struct pci_driver etnaviv_pci_driver; + +#endif --=20 2.25.1