1 /*
2  * Freescale hypervisor call interface
3  *
4  * Copyright 2008-2010 Freescale Semiconductor, Inc.
5  *
6  * Author: Timur Tabi <timur@freescale.com>
7  *
8  * This file is provided under a dual BSD/GPL license.  When using or
9  * redistributing this file, you may do so under either license.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are met:
13  *     * Redistributions of source code must retain the above copyright
14  *       notice, this list of conditions and the following disclaimer.
15  *     * Redistributions in binary form must reproduce the above copyright
16  *       notice, this list of conditions and the following disclaimer in the
17  *       documentation and/or other materials provided with the distribution.
18  *     * Neither the name of Freescale Semiconductor nor the
19  *       names of its contributors may be used to endorse or promote products
20  *       derived from this software without specific prior written permission.
21  *
22  *
23  * ALTERNATIVELY, this software may be distributed under the terms of the
24  * GNU General Public License ("GPL") as published by the Free Software
25  * Foundation, either version 2 of that License or (at your option) any
26  * later version.
27  *
28  * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
29  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31  * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
32  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
35  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #ifndef _FSL_HCALLS_H
41 #define _FSL_HCALLS_H
42 
43 #include <linux/types.h>
44 #include <linux/errno.h>
45 #include <asm/byteorder.h>
46 #include <asm/epapr_hcalls.h>
47 
48 #define FH_API_VERSION			1
49 
50 #define FH_ERR_GET_INFO			1
51 #define FH_PARTITION_GET_DTPROP		2
52 #define FH_PARTITION_SET_DTPROP		3
53 #define FH_PARTITION_RESTART		4
54 #define FH_PARTITION_GET_STATUS		5
55 #define FH_PARTITION_START		6
56 #define FH_PARTITION_STOP		7
57 #define FH_PARTITION_MEMCPY		8
58 #define FH_DMA_ENABLE			9
59 #define FH_DMA_DISABLE			10
60 #define FH_SEND_NMI			11
61 #define FH_VMPIC_GET_MSIR		12
62 #define FH_SYSTEM_RESET			13
63 #define FH_GET_CORE_STATE		14
64 #define FH_ENTER_NAP			15
65 #define FH_EXIT_NAP			16
66 #define FH_CLAIM_DEVICE			17
67 #define FH_PARTITION_STOP_DMA		18
68 
69 /* vendor ID: Freescale Semiconductor */
70 #define FH_HCALL_TOKEN(num)		_EV_HCALL_TOKEN(EV_FSL_VENDOR_ID, num)
71 
72 /*
73  * We use "uintptr_t" to define a register because it's guaranteed to be a
74  * 32-bit integer on a 32-bit platform, and a 64-bit integer on a 64-bit
75  * platform.
76  *
77  * All registers are either input/output or output only.  Registers that are
78  * initialized before making the hypercall are input/output.  All
79  * input/output registers are represented with "+r".  Output-only registers
80  * are represented with "=r".  Do not specify any unused registers.  The
81  * clobber list will tell the compiler that the hypercall modifies those
82  * registers, which is good enough.
83  */
84 
85 /**
86  * fh_send_nmi - send NMI to virtual cpu(s).
87  * @vcpu_mask: send NMI to virtual cpu(s) specified by this mask.
88  *
89  * Returns 0 for success, or EINVAL for invalid vcpu_mask.
90  */
91 static inline unsigned int fh_send_nmi(unsigned int vcpu_mask)
92 {
93 	register uintptr_t r11 __asm__("r11");
94 	register uintptr_t r3 __asm__("r3");
95 
96 	r11 = FH_HCALL_TOKEN(FH_SEND_NMI);
97 	r3 = vcpu_mask;
98 
99 	asm volatile("bl	epapr_hypercall_start"
100 		: "+r" (r11), "+r" (r3)
101 		: : EV_HCALL_CLOBBERS1
102 	);
103 
104 	return r3;
105 }
106 
107 /* Arbitrary limits to avoid excessive memory allocation in hypervisor */
108 #define FH_DTPROP_MAX_PATHLEN 4096
109 #define FH_DTPROP_MAX_PROPLEN 32768
110 
111 /**
112  * fh_partition_get_dtprop - get a property from a guest device tree.
113  * @handle: handle of partition whose device tree is to be accessed
114  * @dtpath_addr: physical address of device tree path to access
115  * @propname_addr: physical address of name of property
116  * @propvalue_addr: physical address of property value buffer
117  * @propvalue_len: length of buffer on entry, length of property on return
118  *
119  * Returns zero on success, non-zero on error.
120  */
121 static inline unsigned int fh_partition_get_dtprop(int handle,
122 						   uint64_t dtpath_addr,
123 						   uint64_t propname_addr,
124 						   uint64_t propvalue_addr,
125 						   uint32_t *propvalue_len)
126 {
127 	register uintptr_t r11 __asm__("r11");
128 	register uintptr_t r3 __asm__("r3");
129 	register uintptr_t r4 __asm__("r4");
130 	register uintptr_t r5 __asm__("r5");
131 	register uintptr_t r6 __asm__("r6");
132 	register uintptr_t r7 __asm__("r7");
133 	register uintptr_t r8 __asm__("r8");
134 	register uintptr_t r9 __asm__("r9");
135 	register uintptr_t r10 __asm__("r10");
136 
137 	r11 = FH_HCALL_TOKEN(FH_PARTITION_GET_DTPROP);
138 	r3 = handle;
139 
140 #ifdef CONFIG_PHYS_64BIT
141 	r4 = dtpath_addr >> 32;
142 	r6 = propname_addr >> 32;
143 	r8 = propvalue_addr >> 32;
144 #else
145 	r4 = 0;
146 	r6 = 0;
147 	r8 = 0;
148 #endif
149 	r5 = (uint32_t)dtpath_addr;
150 	r7 = (uint32_t)propname_addr;
151 	r9 = (uint32_t)propvalue_addr;
152 	r10 = *propvalue_len;
153 
154 	asm volatile("bl	epapr_hypercall_start"
155 		: "+r" (r11),
156 		  "+r" (r3), "+r" (r4), "+r" (r5), "+r" (r6), "+r" (r7),
157 		  "+r" (r8), "+r" (r9), "+r" (r10)
158 		: : EV_HCALL_CLOBBERS8
159 	);
160 
161 	*propvalue_len = r4;
162 	return r3;
163 }
164 
165 /**
166  * Set a property in a guest device tree.
167  * @handle: handle of partition whose device tree is to be accessed
168  * @dtpath_addr: physical address of device tree path to access
169  * @propname_addr: physical address of name of property
170  * @propvalue_addr: physical address of property value
171  * @propvalue_len: length of property
172  *
173  * Returns zero on success, non-zero on error.
174  */
175 static inline unsigned int fh_partition_set_dtprop(int handle,
176 						   uint64_t dtpath_addr,
177 						   uint64_t propname_addr,
178 						   uint64_t propvalue_addr,
179 						   uint32_t propvalue_len)
180 {
181 	register uintptr_t r11 __asm__("r11");
182 	register uintptr_t r3 __asm__("r3");
183 	register uintptr_t r4 __asm__("r4");
184 	register uintptr_t r6 __asm__("r6");
185 	register uintptr_t r8 __asm__("r8");
186 	register uintptr_t r5 __asm__("r5");
187 	register uintptr_t r7 __asm__("r7");
188 	register uintptr_t r9 __asm__("r9");
189 	register uintptr_t r10 __asm__("r10");
190 
191 	r11 = FH_HCALL_TOKEN(FH_PARTITION_SET_DTPROP);
192 	r3 = handle;
193 
194 #ifdef CONFIG_PHYS_64BIT
195 	r4 = dtpath_addr >> 32;
196 	r6 = propname_addr >> 32;
197 	r8 = propvalue_addr >> 32;
198 #else
199 	r4 = 0;
200 	r6 = 0;
201 	r8 = 0;
202 #endif
203 	r5 = (uint32_t)dtpath_addr;
204 	r7 = (uint32_t)propname_addr;
205 	r9 = (uint32_t)propvalue_addr;
206 	r10 = propvalue_len;
207 
208 	asm volatile("bl	epapr_hypercall_start"
209 		: "+r" (r11),
210 		  "+r" (r3), "+r" (r4), "+r" (r5), "+r" (r6), "+r" (r7),
211 		  "+r" (r8), "+r" (r9), "+r" (r10)
212 		: : EV_HCALL_CLOBBERS8
213 	);
214 
215 	return r3;
216 }
217 
218 /**
219  * fh_partition_restart - reboot the current partition
220  * @partition: partition ID
221  *
222  * Returns an error code if reboot failed.  Does not return if it succeeds.
223  */
224 static inline unsigned int fh_partition_restart(unsigned int partition)
225 {
226 	register uintptr_t r11 __asm__("r11");
227 	register uintptr_t r3 __asm__("r3");
228 
229 	r11 = FH_HCALL_TOKEN(FH_PARTITION_RESTART);
230 	r3 = partition;
231 
232 	asm volatile("bl	epapr_hypercall_start"
233 		: "+r" (r11), "+r" (r3)
234 		: : EV_HCALL_CLOBBERS1
235 	);
236 
237 	return r3;
238 }
239 
240 #define FH_PARTITION_STOPPED	0
241 #define FH_PARTITION_RUNNING	1
242 #define FH_PARTITION_STARTING	2
243 #define FH_PARTITION_STOPPING	3
244 #define FH_PARTITION_PAUSING	4
245 #define FH_PARTITION_PAUSED	5
246 #define FH_PARTITION_RESUMING	6
247 
248 /**
249  * fh_partition_get_status - gets the status of a partition
250  * @partition: partition ID
251  * @status: returned status code
252  *
253  * Returns 0 for success, or an error code.
254  */
255 static inline unsigned int fh_partition_get_status(unsigned int partition,
256 	unsigned int *status)
257 {
258 	register uintptr_t r11 __asm__("r11");
259 	register uintptr_t r3 __asm__("r3");
260 	register uintptr_t r4 __asm__("r4");
261 
262 	r11 = FH_HCALL_TOKEN(FH_PARTITION_GET_STATUS);
263 	r3 = partition;
264 
265 	asm volatile("bl	epapr_hypercall_start"
266 		: "+r" (r11), "+r" (r3), "=r" (r4)
267 		: : EV_HCALL_CLOBBERS2
268 	);
269 
270 	*status = r4;
271 
272 	return r3;
273 }
274 
275 /**
276  * fh_partition_start - boots and starts execution of the specified partition
277  * @partition: partition ID
278  * @entry_point: guest physical address to start execution
279  *
280  * The hypervisor creates a 1-to-1 virtual/physical IMA mapping, so at boot
281  * time, guest physical address are the same as guest virtual addresses.
282  *
283  * Returns 0 for success, or an error code.
284  */
285 static inline unsigned int fh_partition_start(unsigned int partition,
286 	uint32_t entry_point, int load)
287 {
288 	register uintptr_t r11 __asm__("r11");
289 	register uintptr_t r3 __asm__("r3");
290 	register uintptr_t r4 __asm__("r4");
291 	register uintptr_t r5 __asm__("r5");
292 
293 	r11 = FH_HCALL_TOKEN(FH_PARTITION_START);
294 	r3 = partition;
295 	r4 = entry_point;
296 	r5 = load;
297 
298 	asm volatile("bl	epapr_hypercall_start"
299 		: "+r" (r11), "+r" (r3), "+r" (r4), "+r" (r5)
300 		: : EV_HCALL_CLOBBERS3
301 	);
302 
303 	return r3;
304 }
305 
306 /**
307  * fh_partition_stop - stops another partition
308  * @partition: partition ID
309  *
310  * Returns 0 for success, or an error code.
311  */
312 static inline unsigned int fh_partition_stop(unsigned int partition)
313 {
314 	register uintptr_t r11 __asm__("r11");
315 	register uintptr_t r3 __asm__("r3");
316 
317 	r11 = FH_HCALL_TOKEN(FH_PARTITION_STOP);
318 	r3 = partition;
319 
320 	asm volatile("bl	epapr_hypercall_start"
321 		: "+r" (r11), "+r" (r3)
322 		: : EV_HCALL_CLOBBERS1
323 	);
324 
325 	return r3;
326 }
327 
328 /**
329  * struct fh_sg_list: definition of the fh_partition_memcpy S/G list
330  * @source: guest physical address to copy from
331  * @target: guest physical address to copy to
332  * @size: number of bytes to copy
333  * @reserved: reserved, must be zero
334  *
335  * The scatter/gather list for fh_partition_memcpy() is an array of these
336  * structures.  The array must be guest physically contiguous.
337  *
338  * This structure must be aligned on 32-byte boundary, so that no single
339  * strucuture can span two pages.
340  */
341 struct fh_sg_list {
342 	uint64_t source;   /**< guest physical address to copy from */
343 	uint64_t target;   /**< guest physical address to copy to */
344 	uint64_t size;     /**< number of bytes to copy */
345 	uint64_t reserved; /**< reserved, must be zero */
346 } __attribute__ ((aligned(32)));
347 
348 /**
349  * fh_partition_memcpy - copies data from one guest to another
350  * @source: the ID of the partition to copy from
351  * @target: the ID of the partition to copy to
352  * @sg_list: guest physical address of an array of &fh_sg_list structures
353  * @count: the number of entries in @sg_list
354  *
355  * Returns 0 for success, or an error code.
356  */
357 static inline unsigned int fh_partition_memcpy(unsigned int source,
358 	unsigned int target, phys_addr_t sg_list, unsigned int count)
359 {
360 	register uintptr_t r11 __asm__("r11");
361 	register uintptr_t r3 __asm__("r3");
362 	register uintptr_t r4 __asm__("r4");
363 	register uintptr_t r5 __asm__("r5");
364 	register uintptr_t r6 __asm__("r6");
365 	register uintptr_t r7 __asm__("r7");
366 
367 	r11 = FH_HCALL_TOKEN(FH_PARTITION_MEMCPY);
368 	r3 = source;
369 	r4 = target;
370 	r5 = (uint32_t) sg_list;
371 
372 #ifdef CONFIG_PHYS_64BIT
373 	r6 = sg_list >> 32;
374 #else
375 	r6 = 0;
376 #endif
377 	r7 = count;
378 
379 	asm volatile("bl	epapr_hypercall_start"
380 		: "+r" (r11),
381 		  "+r" (r3), "+r" (r4), "+r" (r5), "+r" (r6), "+r" (r7)
382 		: : EV_HCALL_CLOBBERS5
383 	);
384 
385 	return r3;
386 }
387 
388 /**
389  * fh_dma_enable - enable DMA for the specified device
390  * @liodn: the LIODN of the I/O device for which to enable DMA
391  *
392  * Returns 0 for success, or an error code.
393  */
394 static inline unsigned int fh_dma_enable(unsigned int liodn)
395 {
396 	register uintptr_t r11 __asm__("r11");
397 	register uintptr_t r3 __asm__("r3");
398 
399 	r11 = FH_HCALL_TOKEN(FH_DMA_ENABLE);
400 	r3 = liodn;
401 
402 	asm volatile("bl	epapr_hypercall_start"
403 		: "+r" (r11), "+r" (r3)
404 		: : EV_HCALL_CLOBBERS1
405 	);
406 
407 	return r3;
408 }
409 
410 /**
411  * fh_dma_disable - disable DMA for the specified device
412  * @liodn: the LIODN of the I/O device for which to disable DMA
413  *
414  * Returns 0 for success, or an error code.
415  */
416 static inline unsigned int fh_dma_disable(unsigned int liodn)
417 {
418 	register uintptr_t r11 __asm__("r11");
419 	register uintptr_t r3 __asm__("r3");
420 
421 	r11 = FH_HCALL_TOKEN(FH_DMA_DISABLE);
422 	r3 = liodn;
423 
424 	asm volatile("bl	epapr_hypercall_start"
425 		: "+r" (r11), "+r" (r3)
426 		: : EV_HCALL_CLOBBERS1
427 	);
428 
429 	return r3;
430 }
431 
432 
433 /**
434  * fh_vmpic_get_msir - returns the MPIC-MSI register value
435  * @interrupt: the interrupt number
436  * @msir_val: returned MPIC-MSI register value
437  *
438  * Returns 0 for success, or an error code.
439  */
440 static inline unsigned int fh_vmpic_get_msir(unsigned int interrupt,
441 	unsigned int *msir_val)
442 {
443 	register uintptr_t r11 __asm__("r11");
444 	register uintptr_t r3 __asm__("r3");
445 	register uintptr_t r4 __asm__("r4");
446 
447 	r11 = FH_HCALL_TOKEN(FH_VMPIC_GET_MSIR);
448 	r3 = interrupt;
449 
450 	asm volatile("bl	epapr_hypercall_start"
451 		: "+r" (r11), "+r" (r3), "=r" (r4)
452 		: : EV_HCALL_CLOBBERS2
453 	);
454 
455 	*msir_val = r4;
456 
457 	return r3;
458 }
459 
460 /**
461  * fh_system_reset - reset the system
462  *
463  * Returns 0 for success, or an error code.
464  */
465 static inline unsigned int fh_system_reset(void)
466 {
467 	register uintptr_t r11 __asm__("r11");
468 	register uintptr_t r3 __asm__("r3");
469 
470 	r11 = FH_HCALL_TOKEN(FH_SYSTEM_RESET);
471 
472 	asm volatile("bl	epapr_hypercall_start"
473 		: "+r" (r11), "=r" (r3)
474 		: : EV_HCALL_CLOBBERS1
475 	);
476 
477 	return r3;
478 }
479 
480 
481 /**
482  * fh_err_get_info - get platform error information
483  * @queue id:
484  * 0 for guest error event queue
485  * 1 for global error event queue
486  *
487  * @pointer to store the platform error data:
488  * platform error data is returned in registers r4 - r11
489  *
490  * Returns 0 for success, or an error code.
491  */
492 static inline unsigned int fh_err_get_info(int queue, uint32_t *bufsize,
493 	uint32_t addr_hi, uint32_t addr_lo, int peek)
494 {
495 	register uintptr_t r11 __asm__("r11");
496 	register uintptr_t r3 __asm__("r3");
497 	register uintptr_t r4 __asm__("r4");
498 	register uintptr_t r5 __asm__("r5");
499 	register uintptr_t r6 __asm__("r6");
500 	register uintptr_t r7 __asm__("r7");
501 
502 	r11 = FH_HCALL_TOKEN(FH_ERR_GET_INFO);
503 	r3 = queue;
504 	r4 = *bufsize;
505 	r5 = addr_hi;
506 	r6 = addr_lo;
507 	r7 = peek;
508 
509 	asm volatile("bl	epapr_hypercall_start"
510 		: "+r" (r11), "+r" (r3), "+r" (r4), "+r" (r5), "+r" (r6),
511 		  "+r" (r7)
512 		: : EV_HCALL_CLOBBERS5
513 	);
514 
515 	*bufsize = r4;
516 
517 	return r3;
518 }
519 
520 
521 #define FH_VCPU_RUN	0
522 #define FH_VCPU_IDLE	1
523 #define FH_VCPU_NAP	2
524 
525 /**
526  * fh_get_core_state - get the state of a vcpu
527  *
528  * @handle: handle of partition containing the vcpu
529  * @vcpu: vcpu number within the partition
530  * @state:the current state of the vcpu, see FH_VCPU_*
531  *
532  * Returns 0 for success, or an error code.
533  */
534 static inline unsigned int fh_get_core_state(unsigned int handle,
535 	unsigned int vcpu, unsigned int *state)
536 {
537 	register uintptr_t r11 __asm__("r11");
538 	register uintptr_t r3 __asm__("r3");
539 	register uintptr_t r4 __asm__("r4");
540 
541 	r11 = FH_HCALL_TOKEN(FH_GET_CORE_STATE);
542 	r3 = handle;
543 	r4 = vcpu;
544 
545 	asm volatile("bl	epapr_hypercall_start"
546 		: "+r" (r11), "+r" (r3), "+r" (r4)
547 		: : EV_HCALL_CLOBBERS2
548 	);
549 
550 	*state = r4;
551 	return r3;
552 }
553 
554 /**
555  * fh_enter_nap - enter nap on a vcpu
556  *
557  * Note that though the API supports entering nap on a vcpu other
558  * than the caller, this may not be implmented and may return EINVAL.
559  *
560  * @handle: handle of partition containing the vcpu
561  * @vcpu: vcpu number within the partition
562  *
563  * Returns 0 for success, or an error code.
564  */
565 static inline unsigned int fh_enter_nap(unsigned int handle, unsigned int vcpu)
566 {
567 	register uintptr_t r11 __asm__("r11");
568 	register uintptr_t r3 __asm__("r3");
569 	register uintptr_t r4 __asm__("r4");
570 
571 	r11 = FH_HCALL_TOKEN(FH_ENTER_NAP);
572 	r3 = handle;
573 	r4 = vcpu;
574 
575 	asm volatile("bl	epapr_hypercall_start"
576 		: "+r" (r11), "+r" (r3), "+r" (r4)
577 		: : EV_HCALL_CLOBBERS2
578 	);
579 
580 	return r3;
581 }
582 
583 /**
584  * fh_exit_nap - exit nap on a vcpu
585  * @handle: handle of partition containing the vcpu
586  * @vcpu: vcpu number within the partition
587  *
588  * Returns 0 for success, or an error code.
589  */
590 static inline unsigned int fh_exit_nap(unsigned int handle, unsigned int vcpu)
591 {
592 	register uintptr_t r11 __asm__("r11");
593 	register uintptr_t r3 __asm__("r3");
594 	register uintptr_t r4 __asm__("r4");
595 
596 	r11 = FH_HCALL_TOKEN(FH_EXIT_NAP);
597 	r3 = handle;
598 	r4 = vcpu;
599 
600 	asm volatile("bl	epapr_hypercall_start"
601 		: "+r" (r11), "+r" (r3), "+r" (r4)
602 		: : EV_HCALL_CLOBBERS2
603 	);
604 
605 	return r3;
606 }
607 /**
608  * fh_claim_device - claim a "claimable" shared device
609  * @handle: fsl,hv-device-handle of node to claim
610  *
611  * Returns 0 for success, or an error code.
612  */
613 static inline unsigned int fh_claim_device(unsigned int handle)
614 {
615 	register uintptr_t r11 __asm__("r11");
616 	register uintptr_t r3 __asm__("r3");
617 
618 	r11 = FH_HCALL_TOKEN(FH_CLAIM_DEVICE);
619 	r3 = handle;
620 
621 	asm volatile("bl	epapr_hypercall_start"
622 		: "+r" (r11), "+r" (r3)
623 		: : EV_HCALL_CLOBBERS1
624 	);
625 
626 	return r3;
627 }
628 
629 /**
630  * Run deferred DMA disabling on a partition's private devices
631  *
632  * This applies to devices which a partition owns either privately,
633  * or which are claimable and still actively owned by that partition,
634  * and which do not have the no-dma-disable property.
635  *
636  * @handle: partition (must be stopped) whose DMA is to be disabled
637  *
638  * Returns 0 for success, or an error code.
639  */
640 static inline unsigned int fh_partition_stop_dma(unsigned int handle)
641 {
642 	register uintptr_t r11 __asm__("r11");
643 	register uintptr_t r3 __asm__("r3");
644 
645 	r11 = FH_HCALL_TOKEN(FH_PARTITION_STOP_DMA);
646 	r3 = handle;
647 
648 	asm volatile("bl	epapr_hypercall_start"
649 		: "+r" (r11), "+r" (r3)
650 		: : EV_HCALL_CLOBBERS1
651 	);
652 
653 	return r3;
654 }
655 #endif
656