| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * QEMU Cryptodev backend for QEMU cipher APIs | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Authors: | 
					
						
							|  |  |  |  *    Gonglei <arei.gonglei@huawei.com> | 
					
						
							|  |  |  |  *    Jay Zhou <jianjay.zhou@huawei.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This library is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU Lesser General Public | 
					
						
							|  |  |  |  * License as published by the Free Software Foundation; either | 
					
						
							| 
									
										
										
										
											2020-10-14 13:37:22 +00:00
										 |  |  |  * version 2.1 of the License, or (at your option) any later version. | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * This library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
					
						
							|  |  |  |  * Lesser General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU Lesser General Public | 
					
						
							|  |  |  |  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "qemu/osdep.h"
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  | #include "hw/virtio/virtio-bus.h"
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | #include "sysemu/cryptodev-vhost.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_VHOST_CRYPTO
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  | #include "qapi/error.h"
 | 
					
						
							|  |  |  | #include "qemu/error-report.h"
 | 
					
						
							|  |  |  | #include "hw/virtio/virtio-crypto.h"
 | 
					
						
							|  |  |  | #include "sysemu/cryptodev-vhost-user.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | uint64_t | 
					
						
							|  |  |  | cryptodev_vhost_get_max_queues( | 
					
						
							|  |  |  |                         CryptoDevBackendVhost *crypto) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return crypto->dev.max_queues; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_cleanup(CryptoDevBackendVhost *crypto) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     vhost_dev_cleanup(&crypto->dev); | 
					
						
							|  |  |  |     g_free(crypto); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct CryptoDevBackendVhost * | 
					
						
							|  |  |  | cryptodev_vhost_init( | 
					
						
							|  |  |  |              CryptoDevBackendVhostOptions *options) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int r; | 
					
						
							|  |  |  |     CryptoDevBackendVhost *crypto; | 
					
						
							| 
									
										
										
										
											2021-06-09 08:46:52 -07:00
										 |  |  |     Error *local_err = NULL; | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     crypto = g_new(CryptoDevBackendVhost, 1); | 
					
						
							|  |  |  |     crypto->dev.max_queues = 1; | 
					
						
							|  |  |  |     crypto->dev.nvqs = 1; | 
					
						
							|  |  |  |     crypto->dev.vqs = crypto->vqs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     crypto->cc = options->cc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     crypto->dev.protocol_features = 0; | 
					
						
							|  |  |  |     crypto->backend = -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /* vhost-user needs vq_index to initiate a specific queue pair */ | 
					
						
							|  |  |  |     crypto->dev.vq_index = crypto->cc->queue_index * crypto->dev.nvqs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-09 08:46:52 -07:00
										 |  |  |     r = vhost_dev_init(&crypto->dev, options->opaque, options->backend_type, 0, | 
					
						
							|  |  |  |                        &local_err); | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  |     if (r < 0) { | 
					
						
							| 
									
										
										
										
											2021-06-09 08:46:52 -07:00
										 |  |  |         error_report_err(local_err); | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  |         goto fail; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return crypto; | 
					
						
							|  |  |  | fail: | 
					
						
							|  |  |  |     g_free(crypto); | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  | static int | 
					
						
							|  |  |  | cryptodev_vhost_start_one(CryptoDevBackendVhost *crypto, | 
					
						
							|  |  |  |                                   VirtIODevice *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int r; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     crypto->dev.nvqs = 1; | 
					
						
							|  |  |  |     crypto->dev.vqs = crypto->vqs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     r = vhost_dev_enable_notifiers(&crypto->dev, dev); | 
					
						
							|  |  |  |     if (r < 0) { | 
					
						
							|  |  |  |         goto fail_notifiers; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-30 11:24:36 +00:00
										 |  |  |     r = vhost_dev_start(&crypto->dev, dev, false); | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  |     if (r < 0) { | 
					
						
							|  |  |  |         goto fail_start; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | fail_start: | 
					
						
							|  |  |  |     vhost_dev_disable_notifiers(&crypto->dev, dev); | 
					
						
							|  |  |  | fail_notifiers: | 
					
						
							|  |  |  |     return r; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | cryptodev_vhost_stop_one(CryptoDevBackendVhost *crypto, | 
					
						
							|  |  |  |                                  VirtIODevice *dev) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2022-11-30 11:24:36 +00:00
										 |  |  |     vhost_dev_stop(&crypto->dev, dev, false); | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  |     vhost_dev_disable_notifiers(&crypto->dev, dev); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CryptoDevBackendVhost * | 
					
						
							|  |  |  | cryptodev_get_vhost(CryptoDevBackendClient *cc, | 
					
						
							|  |  |  |                             CryptoDevBackend *b, | 
					
						
							|  |  |  |                             uint16_t queue) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     CryptoDevBackendVhost *vhost_crypto = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!cc) { | 
					
						
							|  |  |  |         return NULL; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (cc->type) { | 
					
						
							|  |  |  | #if defined(CONFIG_VHOST_USER) && defined(CONFIG_LINUX)
 | 
					
						
							| 
									
										
										
										
											2023-03-01 18:58:36 +08:00
										 |  |  |     case QCRYPTODEV_BACKEND_TYPE_VHOST_USER: | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  |         vhost_crypto = cryptodev_vhost_user_get_vhost(cc, b, queue); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return vhost_crypto; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | cryptodev_vhost_set_vq_index(CryptoDevBackendVhost *crypto, | 
					
						
							|  |  |  |                                      int vq_index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     crypto->dev.vq_index = vq_index; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | vhost_set_vring_enable(CryptoDevBackendClient *cc, | 
					
						
							|  |  |  |                             CryptoDevBackend *b, | 
					
						
							|  |  |  |                             uint16_t queue, int enable) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     CryptoDevBackendVhost *crypto = | 
					
						
							|  |  |  |                        cryptodev_get_vhost(cc, b, queue); | 
					
						
							|  |  |  |     const VhostOps *vhost_ops; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cc->vring_enable = enable; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!crypto) { | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vhost_ops = crypto->dev.vhost_ops; | 
					
						
							|  |  |  |     if (vhost_ops->vhost_set_vring_enable) { | 
					
						
							|  |  |  |         return vhost_ops->vhost_set_vring_enable(&crypto->dev, enable); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int cryptodev_vhost_start(VirtIODevice *dev, int total_queues) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev); | 
					
						
							|  |  |  |     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(dev))); | 
					
						
							|  |  |  |     VirtioBusState *vbus = VIRTIO_BUS(qbus); | 
					
						
							|  |  |  |     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus); | 
					
						
							|  |  |  |     int r, e; | 
					
						
							|  |  |  |     int i; | 
					
						
							|  |  |  |     CryptoDevBackend *b = vcrypto->cryptodev; | 
					
						
							|  |  |  |     CryptoDevBackendVhost *vhost_crypto; | 
					
						
							|  |  |  |     CryptoDevBackendClient *cc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!k->set_guest_notifiers) { | 
					
						
							|  |  |  |         error_report("binding does not support guest notifiers"); | 
					
						
							|  |  |  |         return -ENOSYS; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (i = 0; i < total_queues; i++) { | 
					
						
							|  |  |  |         cc = b->conf.peers.ccs[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         vhost_crypto = cryptodev_get_vhost(cc, b, i); | 
					
						
							|  |  |  |         cryptodev_vhost_set_vq_index(vhost_crypto, i); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* Suppress the masking guest notifiers on vhost user
 | 
					
						
							|  |  |  |          * because vhost user doesn't interrupt masking/unmasking | 
					
						
							|  |  |  |          * properly. | 
					
						
							|  |  |  |          */ | 
					
						
							| 
									
										
										
										
											2023-03-01 18:58:36 +08:00
										 |  |  |         if (cc->type == QCRYPTODEV_BACKEND_TYPE_VHOST_USER) { | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  |             dev->use_guest_notifier_mask = false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |      } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     r = k->set_guest_notifiers(qbus->parent, total_queues, true); | 
					
						
							|  |  |  |     if (r < 0) { | 
					
						
							|  |  |  |         error_report("error binding guest notifier: %d", -r); | 
					
						
							|  |  |  |         goto err; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (i = 0; i < total_queues; i++) { | 
					
						
							|  |  |  |         cc = b->conf.peers.ccs[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         vhost_crypto = cryptodev_get_vhost(cc, b, i); | 
					
						
							|  |  |  |         r = cryptodev_vhost_start_one(vhost_crypto, dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (r < 0) { | 
					
						
							|  |  |  |             goto err_start; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (cc->vring_enable) { | 
					
						
							|  |  |  |             /* restore vring enable state */ | 
					
						
							|  |  |  |             r = vhost_set_vring_enable(cc, b, i, cc->vring_enable); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (r < 0) { | 
					
						
							|  |  |  |                 goto err_start; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | err_start: | 
					
						
							|  |  |  |     while (--i >= 0) { | 
					
						
							|  |  |  |         cc = b->conf.peers.ccs[i]; | 
					
						
							|  |  |  |         vhost_crypto = cryptodev_get_vhost(cc, b, i); | 
					
						
							|  |  |  |         cryptodev_vhost_stop_one(vhost_crypto, dev); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     e = k->set_guest_notifiers(qbus->parent, total_queues, false); | 
					
						
							|  |  |  |     if (e < 0) { | 
					
						
							|  |  |  |         error_report("vhost guest notifier cleanup failed: %d", e); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | err: | 
					
						
							|  |  |  |     return r; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_stop(VirtIODevice *dev, int total_queues) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(dev))); | 
					
						
							|  |  |  |     VirtioBusState *vbus = VIRTIO_BUS(qbus); | 
					
						
							|  |  |  |     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus); | 
					
						
							|  |  |  |     VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev); | 
					
						
							|  |  |  |     CryptoDevBackend *b = vcrypto->cryptodev; | 
					
						
							|  |  |  |     CryptoDevBackendVhost *vhost_crypto; | 
					
						
							|  |  |  |     CryptoDevBackendClient *cc; | 
					
						
							|  |  |  |     size_t i; | 
					
						
							|  |  |  |     int r; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (i = 0; i < total_queues; i++) { | 
					
						
							|  |  |  |         cc = b->conf.peers.ccs[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         vhost_crypto = cryptodev_get_vhost(cc, b, i); | 
					
						
							|  |  |  |         cryptodev_vhost_stop_one(vhost_crypto, dev); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     r = k->set_guest_notifiers(qbus->parent, total_queues, false); | 
					
						
							|  |  |  |     if (r < 0) { | 
					
						
							|  |  |  |         error_report("vhost guest notifier cleanup failed: %d", r); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     assert(r >= 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_virtqueue_mask(VirtIODevice *dev, | 
					
						
							|  |  |  |                                            int queue, | 
					
						
							|  |  |  |                                            int idx, bool mask) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev); | 
					
						
							|  |  |  |     CryptoDevBackend *b = vcrypto->cryptodev; | 
					
						
							|  |  |  |     CryptoDevBackendVhost *vhost_crypto; | 
					
						
							|  |  |  |     CryptoDevBackendClient *cc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     assert(queue < MAX_CRYPTO_QUEUE_NUM); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cc = b->conf.peers.ccs[queue]; | 
					
						
							|  |  |  |     vhost_crypto = cryptodev_get_vhost(cc, b, queue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vhost_virtqueue_mask(&vhost_crypto->dev, dev, idx, mask); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool cryptodev_vhost_virtqueue_pending(VirtIODevice *dev, | 
					
						
							|  |  |  |                                               int queue, int idx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev); | 
					
						
							|  |  |  |     CryptoDevBackend *b = vcrypto->cryptodev; | 
					
						
							|  |  |  |     CryptoDevBackendVhost *vhost_crypto; | 
					
						
							|  |  |  |     CryptoDevBackendClient *cc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     assert(queue < MAX_CRYPTO_QUEUE_NUM); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cc = b->conf.peers.ccs[queue]; | 
					
						
							|  |  |  |     vhost_crypto = cryptodev_get_vhost(cc, b, queue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return vhost_virtqueue_pending(&vhost_crypto->dev, idx); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | #else
 | 
					
						
							|  |  |  | uint64_t | 
					
						
							|  |  |  | cryptodev_vhost_get_max_queues(CryptoDevBackendVhost *crypto) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_cleanup(CryptoDevBackendVhost *crypto) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct CryptoDevBackendVhost * | 
					
						
							|  |  |  | cryptodev_vhost_init(CryptoDevBackendVhostOptions *options) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:29 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | CryptoDevBackendVhost * | 
					
						
							|  |  |  | cryptodev_get_vhost(CryptoDevBackendClient *cc, | 
					
						
							|  |  |  |                     CryptoDevBackend *b, | 
					
						
							|  |  |  |                     uint16_t queue) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int cryptodev_vhost_start(VirtIODevice *dev, int total_queues) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_stop(VirtIODevice *dev, int total_queues) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cryptodev_vhost_virtqueue_mask(VirtIODevice *dev, | 
					
						
							|  |  |  |                                     int queue, | 
					
						
							|  |  |  |                                     int idx, bool mask) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool cryptodev_vhost_virtqueue_pending(VirtIODevice *dev, | 
					
						
							|  |  |  |                                        int queue, int idx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-01 21:46:28 +08:00
										 |  |  | #endif
 |