diff options
author | Ben Skeggs <bskeggs@redhat.com> | 2015-08-20 14:54:18 +1000 |
---|---|---|
committer | Ben Skeggs <bskeggs@redhat.com> | 2015-08-28 12:40:38 +1000 |
commit | 898a2b32138da26ed8f7abc0cc8232741ca03de7 (patch) | |
tree | 074fe2ff37d9c2d872f8a4c3d8b3b1e3f00b5930 /drivers | |
parent | cd459e7776c2c08e3771e20fca7de96272f2c9cd (diff) | |
download | linux-898a2b32138da26ed8f7abc0cc8232741ca03de7.tar.bz2 |
drm/nouveau/sw: turn flip completion into an event
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Diffstat (limited to 'drivers')
18 files changed, 240 insertions, 53 deletions
diff --git a/drivers/gpu/drm/nouveau/include/nvif/class.h b/drivers/gpu/drm/nouveau/include/nvif/class.h index 4dd2c6ec0ec3..d8bad60642ac 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/class.h +++ b/drivers/gpu/drm/nouveau/include/nvif/class.h @@ -584,6 +584,8 @@ struct nv50_disp_overlay_v0 { * software ******************************************************************************/ +#define NVSW_NTFY_UEVENT 0x00 + #define NV04_NVSW_GET_REF 0x00 struct nv04_nvsw_get_ref_v0 { diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/sw.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/sw.h index 8dae95829f66..b06140af0a05 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/engine/sw.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/sw.h @@ -1,27 +1,5 @@ #ifndef __NVKM_SW_H__ #define __NVKM_SW_H__ -#include <core/engctx.h> - -struct nvkm_sw_chan { - struct nvkm_engctx base; - - int (*flip)(void *); - void *flip_data; -}; - -#define nvkm_sw_context_create(p,e,c,d) \ - nvkm_engctx_create((p), (e), (c), (p), 0, 0, 0, (d)) -#define nvkm_sw_context_destroy(d) \ - nvkm_engctx_destroy(&(d)->base) -#define nvkm_sw_context_init(d) \ - nvkm_engctx_init(&(d)->base) -#define nvkm_sw_context_fini(d,s) \ - nvkm_engctx_fini(&(d)->base, (s)) - -#define _nvkm_sw_context_dtor _nvkm_engctx_dtor -#define _nvkm_sw_context_init _nvkm_engctx_init -#define _nvkm_sw_context_fini _nvkm_engctx_fini - #include <core/engine.h> struct nvkm_sw { diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 7f3e821e235d..4a13bda1475b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_chan.c +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c @@ -293,7 +293,6 @@ nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart) struct nvif_device *device = chan->device; struct nouveau_cli *cli = (void *)chan->user.client; struct nvkm_mmu *mmu = nvxx_mmu(device); - struct nvkm_sw_chan *swch; struct nv_dma_v0 args = {}; int ret, i; @@ -382,10 +381,6 @@ nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart) if (ret) return ret; - swch = (void *)nvxx_object(&chan->nvsw)->parent; - swch->flip = nouveau_flip_complete; - swch->flip_data = chan; - ret = RING_SPACE(chan, 2); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index d913074c952f..5553caa16b9c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -358,6 +358,7 @@ int nouveau_display_init(struct drm_device *dev) { struct nouveau_display *disp = nouveau_display(dev); + struct nouveau_drm *drm = nouveau_drm(dev); struct drm_connector *connector; int ret; @@ -374,6 +375,8 @@ nouveau_display_init(struct drm_device *dev) nvif_notify_get(&conn->hpd); } + /* enable flip completion events */ + nvif_notify_get(&drm->flip); return ret; } @@ -381,6 +384,7 @@ void nouveau_display_fini(struct drm_device *dev) { struct nouveau_display *disp = nouveau_display(dev); + struct nouveau_drm *drm = nouveau_drm(dev); struct drm_connector *connector; int head; @@ -388,6 +392,9 @@ nouveau_display_fini(struct drm_device *dev) for (head = 0; head < dev->mode_config.num_crtc; head++) drm_vblank_off(dev, head); + /* disable flip completion events */ + nvif_notify_put(&drm->flip); + /* disable hotplug interrupts */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { struct nouveau_connector *conn = nouveau_connector(connector); @@ -847,10 +854,10 @@ nouveau_finish_page_flip(struct nouveau_channel *chan, } int -nouveau_flip_complete(void *data) +nouveau_flip_complete(struct nvif_notify *notify) { - struct nouveau_channel *chan = data; - struct nouveau_drm *drm = chan->drm; + struct nouveau_drm *drm = container_of(notify, typeof(*drm), flip); + struct nouveau_channel *chan = drm->channel; struct nouveau_page_flip_state state; if (!nouveau_finish_page_flip(chan, &state)) { @@ -861,7 +868,7 @@ nouveau_flip_complete(void *data) } } - return 0; + return NVIF_NOTIFY_KEEP; } int diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index ebcbe7bbdddf..d04d08cc546d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -139,6 +139,7 @@ nouveau_cli_destroy(struct nouveau_cli *cli) static void nouveau_accel_fini(struct nouveau_drm *drm) { + nvif_notify_fini(&drm->flip); nouveau_channel_del(&drm->channel); nvif_object_fini(&drm->ntfy); nvkm_gpuobj_del(&drm->notify); @@ -240,7 +241,6 @@ nouveau_accel_init(struct nouveau_drm *drm) ret = nvif_object_init(&drm->channel->user, NVDRM_NVSW, nouveau_abi16_swclass(drm), NULL, 0, &drm->nvsw); if (ret == 0) { - struct nvkm_sw_chan *swch; ret = RING_SPACE(drm->channel, 2); if (ret == 0) { if (device->info.family < NV_DEVICE_INFO_V0_FERMI) { @@ -252,9 +252,16 @@ nouveau_accel_init(struct nouveau_drm *drm) OUT_RING (drm->channel, 0x001f0000); } } - swch = (void *)nvxx_object(&drm->nvsw)->parent; - swch->flip = nouveau_flip_complete; - swch->flip_data = drm->channel; + + ret = nvif_notify_init(&drm->nvsw, nouveau_flip_complete, + false, NVSW_NTFY_UEVENT, NULL, 0, 0, + &drm->flip); + if (ret == 0) + ret = nvif_notify_get(&drm->flip); + if (ret) { + nouveau_accel_fini(drm); + return; + } } if (ret) { diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.h b/drivers/gpu/drm/nouveau/nouveau_drm.h index d1a4c120b71b..f18710afcfd3 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.h +++ b/drivers/gpu/drm/nouveau/nouveau_drm.h @@ -150,6 +150,7 @@ struct nouveau_drm { struct nouveau_fbdev *fbcon; struct nvif_object nvsw; struct nvif_object ntfy; + struct nvif_notify flip; /* nv10-nv40 tiling regions */ struct { diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.h b/drivers/gpu/drm/nouveau/nouveau_fence.h index d9241d8247fb..2e3a62d38fe9 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.h +++ b/drivers/gpu/drm/nouveau/nouveau_fence.h @@ -85,7 +85,7 @@ int nv50_fence_create(struct nouveau_drm *); int nv84_fence_create(struct nouveau_drm *); int nvc0_fence_create(struct nouveau_drm *); -int nouveau_flip_complete(void *chan); +int nouveau_flip_complete(struct nvif_notify *); struct nv84_fence_chan { struct nouveau_fence_chan base; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/sw/Kbuild index bdc3a05907d5..1144d84e46de 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/Kbuild @@ -2,3 +2,7 @@ nvkm-y += nvkm/engine/sw/nv04.o nvkm-y += nvkm/engine/sw/nv10.o nvkm-y += nvkm/engine/sw/nv50.o nvkm-y += nvkm/engine/sw/gf100.o + +nvkm-y += nvkm/engine/sw/chan.o + +nvkm-y += nvkm/engine/sw/nvsw.o diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.c new file mode 100644 index 000000000000..97c0d96463a2 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.c @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ +#include "chan.h" + +#include <core/notify.h> + +#include <nvif/event.h> +#include <nvif/unpack.h> + +static int +nvkm_sw_chan_event_ctor(struct nvkm_object *object, void *data, u32 size, + struct nvkm_notify *notify) +{ + union { + struct nvif_notify_uevent_req none; + } *req = data; + int ret; + + if (nvif_unvers(req->none)) { + notify->size = sizeof(struct nvif_notify_uevent_rep); + notify->types = 1; + notify->index = 0; + } + + return ret; +} + +static const struct nvkm_event_func +nvkm_sw_chan_event = { + .ctor = nvkm_sw_chan_event_ctor, +}; + +void +nvkm_sw_chan_dtor(struct nvkm_object *base) +{ + struct nvkm_sw_chan *chan = (void *)base; + nvkm_event_fini(&chan->event); + nvkm_engctx_destroy(&chan->base); +} + +int +nvkm_sw_chan_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, int length, void **pobject) +{ + struct nvkm_sw_chan *chan; + int ret; + + ret = nvkm_engctx_create_(parent, engine, oclass, parent, + 0, 0, 0, length, pobject); + chan = *pobject; + if (ret) + return ret; + + return nvkm_event_init(&nvkm_sw_chan_event, 1, 1, &chan->event); +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.h b/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.h new file mode 100644 index 000000000000..b5cf4c517a95 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/chan.h @@ -0,0 +1,25 @@ +#ifndef __NVKM_SW_CHAN_H__ +#define __NVKM_SW_CHAN_H__ +#include "priv.h" +#include <core/engctx.h> +#include <core/event.h> + +struct nvkm_sw_chan { + struct nvkm_engctx base; + struct nvkm_event event; +}; + +#define nvkm_sw_context_create(p,e,c,d) \ + nvkm_sw_chan_ctor((p), (e), (c), sizeof(**d), (void **)d) +int nvkm_sw_chan_ctor(struct nvkm_object *, struct nvkm_object *, + struct nvkm_oclass *, int, void **); +void nvkm_sw_chan_dtor(struct nvkm_object *); +#define nvkm_sw_context_init(d) \ + nvkm_engctx_init(&(d)->base) +#define nvkm_sw_context_fini(d,s) \ + nvkm_engctx_fini(&(d)->base, (s)) + +#define _nvkm_sw_context_dtor nvkm_sw_chan_dtor +#define _nvkm_sw_context_init _nvkm_engctx_init +#define _nvkm_sw_context_fini _nvkm_engctx_fini +#endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/gf100.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/gf100.c index 061ea72f5d0f..d57052cdcad3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/gf100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/gf100.c @@ -89,7 +89,7 @@ gf100_sw_omthds[] = { static struct nvkm_oclass gf100_sw_sclass[] = { - { NVIF_IOCTL_NEW_V0_SW_GF100, &nvkm_object_ofuncs, gf100_sw_omthds }, + { NVIF_IOCTL_NEW_V0_SW_GF100, &nvkm_nvsw_ofuncs, gf100_sw_omthds }, {} }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv04.c index a2ffe2ccdbe5..08b59f198c73 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv04.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv04.c @@ -21,7 +21,9 @@ * * Authors: Ben Skeggs */ -#include <engine/sw.h> +#include "priv.h" +#include "chan.h" +#include "nvsw.h" #include <nvif/class.h> #include <nvif/ioctl.h> @@ -48,9 +50,8 @@ static int nv04_sw_flip(struct nvkm_object *object, u32 mthd, void *args, u32 size) { struct nvkm_sw_chan *chan = (void *)nv_engctx(object->parent); - if (chan->flip) - return chan->flip(chan->flip_data); - return -EINVAL; + nvkm_event_send(&chan->event, 1, 0, NULL, 0); + return 0; } static struct nvkm_omthds @@ -90,7 +91,7 @@ nv04_sw_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) static struct nvkm_ofuncs nv04_sw_ofuncs = { - .ctor = _nvkm_object_ctor, + .ctor = nvkm_nvsw_ctor, .dtor = nvkm_object_destroy, .init = _nvkm_object_init, .fini = _nvkm_object_fini, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv10.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv10.c index 26ccd690bea8..d0d4339b857c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv10.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv10.c @@ -21,7 +21,9 @@ * * Authors: Ben Skeggs */ -#include <engine/sw.h> +#include "priv.h" +#include "chan.h" +#include "nvsw.h" #include <nvif/ioctl.h> @@ -33,9 +35,8 @@ static int nv10_sw_flip(struct nvkm_object *object, u32 mthd, void *args, u32 size) { struct nvkm_sw_chan *chan = (void *)nv_engctx(object->parent); - if (chan->flip) - return chan->flip(chan->flip_data); - return -EINVAL; + nvkm_event_send(&chan->event, 1, 0, NULL, 0); + return 0; } static struct nvkm_omthds @@ -46,7 +47,7 @@ nv10_sw_omthds[] = { static struct nvkm_oclass nv10_sw_sclass[] = { - { NVIF_IOCTL_NEW_V0_SW_NV10, &nvkm_object_ofuncs, nv10_sw_omthds }, + { NVIF_IOCTL_NEW_V0_SW_NV10, &nvkm_nvsw_ofuncs, nv10_sw_omthds }, {} }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.c index ca4b8a02b971..9322bde518d2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.c @@ -92,9 +92,8 @@ int nv50_sw_mthd_flip(struct nvkm_object *object, u32 mthd, void *args, u32 size) { struct nv50_sw_chan *chan = (void *)nv_engctx(object->parent); - if (chan->base.flip) - return chan->base.flip(chan->base.flip_data); - return -EINVAL; + nvkm_event_send(&chan->base.event, 1, 0, NULL, 0); + return 0; } static struct nvkm_omthds @@ -109,7 +108,7 @@ nv50_sw_omthds[] = { static struct nvkm_oclass nv50_sw_sclass[] = { - { NVIF_IOCTL_NEW_V0_SW_NV50, &nvkm_object_ofuncs, nv50_sw_omthds }, + { NVIF_IOCTL_NEW_V0_SW_NV50, &nvkm_nvsw_ofuncs, nv50_sw_omthds }, {} }; @@ -150,7 +149,7 @@ nv50_sw_context_dtor(struct nvkm_object *object) for (i = 0; i < ARRAY_SIZE(chan->vblank.notify); i++) nvkm_notify_fini(&chan->vblank.notify[i]); - nvkm_sw_context_destroy(&chan->base); + nvkm_sw_chan_dtor(&chan->base.base.gpuobj.object); } int diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.h b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.h index 1c9101bff894..f3bf451546b1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nv50.h @@ -1,6 +1,8 @@ #ifndef __NVKM_SW_NV50_H__ #define __NVKM_SW_NV50_H__ -#include <engine/sw.h> +#include "priv.h" +#include "chan.h" +#include "nvsw.h" #include <core/notify.h> struct nv50_sw_oclass { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.c b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.c new file mode 100644 index 000000000000..c3c6829fd001 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.c @@ -0,0 +1,74 @@ +/* + * Copyright 2015 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ +#define nvkm_nvsw(p) container_of((p), struct nvkm_nvsw, object) +#include "nvsw.h" +#include "chan.h" + +#include <nvif/class.h> + +struct nvkm_nvsw { + struct nvkm_object object; + struct nvkm_sw_chan *chan; +}; + +static int +nvkm_nvsw_ntfy(struct nvkm_object *base, u32 mthd, struct nvkm_event **pevent) +{ + struct nvkm_nvsw *nvsw = nvkm_nvsw(base); + switch (mthd) { + case NVSW_NTFY_UEVENT: + *pevent = &nvsw->chan->event; + return 0; + default: + break; + } + return -EINVAL; +} + +int +nvkm_nvsw_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nvkm_sw_chan *chan = (void *)parent; + struct nvkm_nvsw *nvsw; + int ret; + + ret = nvkm_object_create(parent, engine, oclass, 0, &nvsw); + *pobject = &nvsw->object; + if (ret) + return ret; + + nvsw->chan = chan; + return 0; +} + +struct nvkm_ofuncs +nvkm_nvsw_ofuncs = { + .ctor = nvkm_nvsw_ctor, + .dtor = nvkm_object_destroy, + .init = _nvkm_object_init, + .fini = _nvkm_object_fini, + .ntfy = nvkm_nvsw_ntfy, +}; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.h b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.h new file mode 100644 index 000000000000..c54a12798e84 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/nvsw.h @@ -0,0 +1,10 @@ +#ifndef __NVKM_NVSW_H__ +#define __NVKM_NVSW_H__ +#include "priv.h" + +extern struct nvkm_ofuncs nvkm_nvsw_ofuncs; +int +nvkm_nvsw_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject); +#endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sw/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/sw/priv.h new file mode 100644 index 000000000000..42707355eb92 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sw/priv.h @@ -0,0 +1,5 @@ +#ifndef __NVKM_SW_PRIV_H__ +#define __NVKM_SW_PRIV_H__ +#include <engine/sw.h> + +#endif |