xref: /netbsd-src/sys/arch/amd64/include/frameasm.h (revision 376bcb54d12937980e62a3bbac6e2c4fd894095e)
1*376bcb54Sriastradh /*	$NetBSD: frameasm.h,v 1.55 2022/07/30 14:11:00 riastradh Exp $	*/
281918bf8Sfvdl 
381918bf8Sfvdl #ifndef _AMD64_MACHINE_FRAMEASM_H
481918bf8Sfvdl #define _AMD64_MACHINE_FRAMEASM_H
591ca02fcSad 
691ca02fcSad #ifdef _KERNEL_OPT
74e541343Sbouyer #include "opt_xen.h"
8da72fac8Smaxv #include "opt_svs.h"
9e7e5aa03Smaxv #include "opt_kcov.h"
1010c5b023Smaxv #include "opt_kmsan.h"
1191ca02fcSad #endif
1281918bf8Sfvdl 
1381918bf8Sfvdl /*
1481918bf8Sfvdl  * Macros to define pushing/popping frames for interrupts, traps
1581918bf8Sfvdl  * and system calls. Currently all the same; will diverge later.
1681918bf8Sfvdl  */
1781918bf8Sfvdl 
18427af037Scherry #ifdef XENPV
194e541343Sbouyer #define HYPERVISOR_iret hypercall_page + (__HYPERVISOR_iret * 32)
204e541343Sbouyer /* Xen do not need swapgs, done by hypervisor */
214e541343Sbouyer #define swapgs
224e541343Sbouyer #define iretq	pushq $0 ; jmp HYPERVISOR_iret
2353a9a10fSdsl #define	XEN_ONLY2(x,y)	x,y
2453a9a10fSdsl #define	NOT_XEN(x)
2553a9a10fSdsl 
2653a9a10fSdsl #define CLI(temp_reg) \
2753a9a10fSdsl  	movq CPUVAR(VCPU),%r ## temp_reg ;			\
2853a9a10fSdsl 	movb $1,EVTCHN_UPCALL_MASK(%r ## temp_reg);
2953a9a10fSdsl 
3053a9a10fSdsl #define STI(temp_reg) \
3153a9a10fSdsl  	movq CPUVAR(VCPU),%r ## temp_reg ;			\
3253a9a10fSdsl 	movb $0,EVTCHN_UPCALL_MASK(%r ## temp_reg);
3353a9a10fSdsl 
34c24c993fSbouyer #define PUSHF(temp_reg) \
35c24c993fSbouyer  	movq CPUVAR(VCPU),%r ## temp_reg ;			\
36c24c993fSbouyer 	movzbl EVTCHN_UPCALL_MASK(%r ## temp_reg), %e ## temp_reg; \
37c24c993fSbouyer 	pushq %r ## temp_reg
38c24c993fSbouyer 
39c24c993fSbouyer #define POPF \
40c24c993fSbouyer 	popq %rdi; \
41c24c993fSbouyer 	call _C_LABEL(xen_write_psl)
42c24c993fSbouyer 
43c24c993fSbouyer 
44427af037Scherry #else /* XENPV */
4553a9a10fSdsl #define	XEN_ONLY2(x,y)
4653a9a10fSdsl #define	NOT_XEN(x)	x
4753a9a10fSdsl #define CLI(temp_reg) cli
4853a9a10fSdsl #define STI(temp_reg) sti
49c24c993fSbouyer #define PUSHF(temp_reg) pushf
50c24c993fSbouyer #define POPL popl
51c1ac8a41Sbouyer #endif	/* XENPV */
524e541343Sbouyer 
5399d8611cSmaxv #define HP_NAME_CLAC		1
5499d8611cSmaxv #define HP_NAME_STAC		2
559ef803feSmaxv #define HP_NAME_NOLOCK		3
566fcf3f0aSmaxv #define HP_NAME_RETFENCE	4
57ebc1f703Smaxv #define HP_NAME_SVS_ENTER	5
58ebc1f703Smaxv #define HP_NAME_SVS_LEAVE	6
59ebc1f703Smaxv #define HP_NAME_SVS_ENTER_ALT	7
60ebc1f703Smaxv #define HP_NAME_SVS_LEAVE_ALT	8
610223f0c8Smaxv #define HP_NAME_IBRS_ENTER	9
620223f0c8Smaxv #define HP_NAME_IBRS_LEAVE	10
638b7b3795Smaxv #define HP_NAME_SVS_ENTER_NMI	11
648b7b3795Smaxv #define HP_NAME_SVS_LEAVE_NMI	12
6574b8eea5Smaxv #define HP_NAME_MDS_LEAVE	13
6699d8611cSmaxv 
6799d8611cSmaxv #define HOTPATCH(name, size) \
6899d8611cSmaxv 123:						; \
6903af2da1Smaxv 	.pushsection	.rodata.hotpatch, "a"	; \
7099d8611cSmaxv 	.byte		name			; \
7199d8611cSmaxv 	.byte		size			; \
7299d8611cSmaxv 	.quad		123b			; \
7303af2da1Smaxv 	.popsection
7499d8611cSmaxv 
7599d8611cSmaxv #define SMAP_ENABLE \
7699d8611cSmaxv 	HOTPATCH(HP_NAME_CLAC, 3)		; \
7799d8611cSmaxv 	.byte 0x0F, 0x1F, 0x00			; \
7899d8611cSmaxv 
7999d8611cSmaxv #define SMAP_DISABLE \
8099d8611cSmaxv 	HOTPATCH(HP_NAME_STAC, 3)		; \
8199d8611cSmaxv 	.byte 0x0F, 0x1F, 0x00			; \
8299d8611cSmaxv 
830223f0c8Smaxv /*
840223f0c8Smaxv  * IBRS
850223f0c8Smaxv  */
860223f0c8Smaxv 
87125e142fSmaxv #define IBRS_ENTER_BYTES	12
880223f0c8Smaxv #define IBRS_ENTER \
890223f0c8Smaxv 	HOTPATCH(HP_NAME_IBRS_ENTER, IBRS_ENTER_BYTES)		; \
900223f0c8Smaxv 	NOIBRS_ENTER
910223f0c8Smaxv #define NOIBRS_ENTER \
920223f0c8Smaxv 	.byte 0xEB, (IBRS_ENTER_BYTES-2)	/* jmp */	; \
930223f0c8Smaxv 	.fill	(IBRS_ENTER_BYTES-2),1,0xCC
940223f0c8Smaxv 
95125e142fSmaxv #define IBRS_LEAVE_BYTES	12
960223f0c8Smaxv #define IBRS_LEAVE \
970223f0c8Smaxv 	HOTPATCH(HP_NAME_IBRS_LEAVE, IBRS_LEAVE_BYTES)		; \
980223f0c8Smaxv 	NOIBRS_LEAVE
990223f0c8Smaxv #define NOIBRS_LEAVE \
1000223f0c8Smaxv 	.byte 0xEB, (IBRS_LEAVE_BYTES-2)	/* jmp */	; \
1010223f0c8Smaxv 	.fill	(IBRS_LEAVE_BYTES-2),1,0xCC
1020223f0c8Smaxv 
10374b8eea5Smaxv /*
10474b8eea5Smaxv  * MDS
10574b8eea5Smaxv  */
10674b8eea5Smaxv 
107125e142fSmaxv #define MDS_LEAVE_BYTES	10
10874b8eea5Smaxv #define MDS_LEAVE \
10974b8eea5Smaxv 	HOTPATCH(HP_NAME_MDS_LEAVE, MDS_LEAVE_BYTES)		; \
11074b8eea5Smaxv 	NOMDS_LEAVE
11174b8eea5Smaxv #define NOMDS_LEAVE \
11274b8eea5Smaxv 	.byte 0xEB, (MDS_LEAVE_BYTES-2)	/* jmp */		; \
11374b8eea5Smaxv 	.fill	(MDS_LEAVE_BYTES-2),1,0xCC
11474b8eea5Smaxv 
115c578e8d2Sdsl #define	SWAPGS	NOT_XEN(swapgs)
116c578e8d2Sdsl 
11781918bf8Sfvdl /*
11881918bf8Sfvdl  * These are used on interrupt or trap entry or exit.
11981918bf8Sfvdl  */
12081918bf8Sfvdl #define INTR_SAVE_GPRS \
12181918bf8Sfvdl 	movq	%rdi,TF_RDI(%rsp)	; \
12281918bf8Sfvdl 	movq	%rsi,TF_RSI(%rsp)	; \
12381918bf8Sfvdl 	movq	%rdx,TF_RDX(%rsp)	; \
12481918bf8Sfvdl 	movq	%rcx,TF_RCX(%rsp)	; \
12589a3551aSdsl 	movq	%r8,TF_R8(%rsp)		; \
12689a3551aSdsl 	movq	%r9,TF_R9(%rsp)		; \
12789a3551aSdsl 	movq	%r10,TF_R10(%rsp)	; \
12889a3551aSdsl 	movq	%r11,TF_R11(%rsp)	; \
12989a3551aSdsl 	movq	%r12,TF_R12(%rsp)	; \
13089a3551aSdsl 	movq	%r13,TF_R13(%rsp)	; \
13189a3551aSdsl 	movq	%r14,TF_R14(%rsp)	; \
13289a3551aSdsl 	movq	%r15,TF_R15(%rsp)	; \
13389a3551aSdsl 	movq	%rbp,TF_RBP(%rsp)	; \
13489a3551aSdsl 	movq	%rbx,TF_RBX(%rsp)	; \
135aa64020bSmaxv 	movq	%rax,TF_RAX(%rsp)
13681918bf8Sfvdl 
13781918bf8Sfvdl #define	INTR_RESTORE_GPRS \
13881918bf8Sfvdl 	movq	TF_RDI(%rsp),%rdi	; \
13981918bf8Sfvdl 	movq	TF_RSI(%rsp),%rsi	; \
14081918bf8Sfvdl 	movq	TF_RDX(%rsp),%rdx	; \
14181918bf8Sfvdl 	movq	TF_RCX(%rsp),%rcx	; \
14289a3551aSdsl 	movq	TF_R8(%rsp),%r8		; \
14389a3551aSdsl 	movq	TF_R9(%rsp),%r9		; \
14489a3551aSdsl 	movq	TF_R10(%rsp),%r10	; \
14589a3551aSdsl 	movq	TF_R11(%rsp),%r11	; \
14689a3551aSdsl 	movq	TF_R12(%rsp),%r12	; \
14789a3551aSdsl 	movq	TF_R13(%rsp),%r13	; \
14889a3551aSdsl 	movq	TF_R14(%rsp),%r14	; \
14989a3551aSdsl 	movq	TF_R15(%rsp),%r15	; \
15089a3551aSdsl 	movq	TF_RBP(%rsp),%rbp	; \
15189a3551aSdsl 	movq	TF_RBX(%rsp),%rbx	; \
15289a3551aSdsl 	movq	TF_RAX(%rsp),%rax
15381918bf8Sfvdl 
154cea874c7Smaxv #define TEXT_USER_BEGIN	.pushsection	.text.user, "ax"
155cea874c7Smaxv #define TEXT_USER_END	.popsection
156cea874c7Smaxv 
157da72fac8Smaxv #ifdef SVS
15868bdfac8Smaxv 
15968bdfac8Smaxv /* XXX: put this somewhere else */
160afb643e1Smaxv #define SVS_UTLS		0xffffff0000000000 /* PMAP_PCPU_BASE */
16168bdfac8Smaxv #define UTLS_KPDIRPA		0
16268bdfac8Smaxv #define UTLS_SCRATCH		8
16368bdfac8Smaxv #define UTLS_RSP0		16
16468bdfac8Smaxv 
165ebc1f703Smaxv #define SVS_ENTER_BYTES	22
1665ab8a4e7Smaxv #define NOSVS_ENTER \
167ebc1f703Smaxv 	.byte 0xEB, (SVS_ENTER_BYTES-2)	/* jmp */	; \
168ebc1f703Smaxv 	.fill	(SVS_ENTER_BYTES-2),1,0xCC
1695ab8a4e7Smaxv #define SVS_ENTER \
1705ab8a4e7Smaxv 	HOTPATCH(HP_NAME_SVS_ENTER, SVS_ENTER_BYTES)	; \
1715ab8a4e7Smaxv 	NOSVS_ENTER
17268bdfac8Smaxv 
173125e142fSmaxv #define SVS_LEAVE_BYTES	21
1745ab8a4e7Smaxv #define NOSVS_LEAVE \
175ebc1f703Smaxv 	.byte 0xEB, (SVS_LEAVE_BYTES-2)	/* jmp */	; \
176ebc1f703Smaxv 	.fill	(SVS_LEAVE_BYTES-2),1,0xCC
1775ab8a4e7Smaxv #define SVS_LEAVE \
1785ab8a4e7Smaxv 	HOTPATCH(HP_NAME_SVS_LEAVE, SVS_LEAVE_BYTES)	; \
1795ab8a4e7Smaxv 	NOSVS_LEAVE
18068bdfac8Smaxv 
181ebc1f703Smaxv #define SVS_ENTER_ALT_BYTES	23
1825ab8a4e7Smaxv #define NOSVS_ENTER_ALTSTACK \
183ebc1f703Smaxv 	.byte 0xEB, (SVS_ENTER_ALT_BYTES-2)	/* jmp */	; \
184ebc1f703Smaxv 	.fill	(SVS_ENTER_ALT_BYTES-2),1,0xCC
1855ab8a4e7Smaxv #define SVS_ENTER_ALTSTACK \
1865ab8a4e7Smaxv 	HOTPATCH(HP_NAME_SVS_ENTER_ALT, SVS_ENTER_ALT_BYTES)	; \
1875ab8a4e7Smaxv 	NOSVS_ENTER_ALTSTACK
18868bdfac8Smaxv 
189ebc1f703Smaxv #define SVS_LEAVE_ALT_BYTES	22
1905ab8a4e7Smaxv #define NOSVS_LEAVE_ALTSTACK \
191ebc1f703Smaxv 	.byte 0xEB, (SVS_LEAVE_ALT_BYTES-2)	/* jmp */	; \
192ebc1f703Smaxv 	.fill	(SVS_LEAVE_ALT_BYTES-2),1,0xCC
1935ab8a4e7Smaxv #define SVS_LEAVE_ALTSTACK \
1945ab8a4e7Smaxv 	HOTPATCH(HP_NAME_SVS_LEAVE_ALT, SVS_LEAVE_ALT_BYTES)	; \
1955ab8a4e7Smaxv 	NOSVS_LEAVE_ALTSTACK
196ebc1f703Smaxv 
1978b7b3795Smaxv #define SVS_ENTER_NMI_BYTES	22
1988b7b3795Smaxv #define NOSVS_ENTER_NMI \
1998b7b3795Smaxv 	.byte 0xEB, (SVS_ENTER_NMI_BYTES-2)	/* jmp */	; \
2008b7b3795Smaxv 	.fill	(SVS_ENTER_NMI_BYTES-2),1,0xCC
2018b7b3795Smaxv #define SVS_ENTER_NMI \
2028b7b3795Smaxv 	HOTPATCH(HP_NAME_SVS_ENTER_NMI, SVS_ENTER_NMI_BYTES)	; \
2038b7b3795Smaxv 	NOSVS_ENTER_NMI
2048b7b3795Smaxv 
2058b7b3795Smaxv #define SVS_LEAVE_NMI_BYTES	11
2068b7b3795Smaxv #define NOSVS_LEAVE_NMI \
2078b7b3795Smaxv 	.byte 0xEB, (SVS_LEAVE_NMI_BYTES-2)	/* jmp */	; \
2088b7b3795Smaxv 	.fill	(SVS_LEAVE_NMI_BYTES-2),1,0xCC
2098b7b3795Smaxv #define SVS_LEAVE_NMI \
2108b7b3795Smaxv 	HOTPATCH(HP_NAME_SVS_LEAVE_NMI, SVS_LEAVE_NMI_BYTES)	; \
2118b7b3795Smaxv 	NOSVS_LEAVE_NMI
2128b7b3795Smaxv 
213da72fac8Smaxv #else
214da72fac8Smaxv #define SVS_ENTER	/* nothing */
2152681a041Smartin #define SVS_ENTER_NMI	/* nothing */
216da72fac8Smaxv #define SVS_LEAVE	/* nothing */
2172681a041Smartin #define SVS_LEAVE_NMI	/* nothing */
21868bdfac8Smaxv #define SVS_ENTER_ALTSTACK	/* nothing */
21968bdfac8Smaxv #define SVS_LEAVE_ALTSTACK	/* nothing */
220da72fac8Smaxv #endif
221da72fac8Smaxv 
22210c5b023Smaxv #ifdef KMSAN
223248fe10bSad /* XXX this belongs somewhere else. */
22410c5b023Smaxv #define KMSAN_ENTER	\
22510c5b023Smaxv 	movq	%rsp,%rdi		; \
22610c5b023Smaxv 	movq	$TF_REGSIZE+16+40,%rsi	; \
22710c5b023Smaxv 	xorq	%rdx,%rdx		; \
22810c5b023Smaxv 	callq	kmsan_mark		; \
22910c5b023Smaxv 	callq	kmsan_intr_enter
23010c5b023Smaxv #define KMSAN_LEAVE	\
23110c5b023Smaxv 	pushq	%rbp			; \
23210c5b023Smaxv 	movq	%rsp,%rbp		; \
23310c5b023Smaxv 	callq	kmsan_intr_leave	; \
23410c5b023Smaxv 	popq	%rbp
23510c5b023Smaxv #define KMSAN_INIT_ARG(sz)	\
23610c5b023Smaxv 	pushq	%rax			; \
23710c5b023Smaxv 	pushq	%rcx			; \
23810c5b023Smaxv 	pushq	%rdx			; \
23910c5b023Smaxv 	pushq	%rsi			; \
24010c5b023Smaxv 	pushq	%rdi			; \
24110c5b023Smaxv 	pushq	%r8			; \
24210c5b023Smaxv 	pushq	%r9			; \
24310c5b023Smaxv 	pushq	%r10			; \
24410c5b023Smaxv 	pushq	%r11			; \
24510c5b023Smaxv 	movq	$sz,%rdi		; \
24610c5b023Smaxv 	callq	_C_LABEL(kmsan_init_arg); \
24710c5b023Smaxv 	popq	%r11			; \
24810c5b023Smaxv 	popq	%r10			; \
24910c5b023Smaxv 	popq	%r9			; \
25010c5b023Smaxv 	popq	%r8			; \
25110c5b023Smaxv 	popq	%rdi			; \
25210c5b023Smaxv 	popq	%rsi			; \
25310c5b023Smaxv 	popq	%rdx			; \
25410c5b023Smaxv 	popq	%rcx			; \
25510c5b023Smaxv 	popq	%rax
25610c5b023Smaxv #define KMSAN_INIT_RET(sz)	\
25710c5b023Smaxv 	pushq	%rax			; \
25810c5b023Smaxv 	pushq	%rcx			; \
25910c5b023Smaxv 	pushq	%rdx			; \
26010c5b023Smaxv 	pushq	%rsi			; \
26110c5b023Smaxv 	pushq	%rdi			; \
26210c5b023Smaxv 	pushq	%r8			; \
26310c5b023Smaxv 	pushq	%r9			; \
26410c5b023Smaxv 	pushq	%r10			; \
26510c5b023Smaxv 	pushq	%r11			; \
26610c5b023Smaxv 	movq	$sz,%rdi		; \
26710c5b023Smaxv 	callq	_C_LABEL(kmsan_init_ret); \
26810c5b023Smaxv 	popq	%r11			; \
26910c5b023Smaxv 	popq	%r10			; \
27010c5b023Smaxv 	popq	%r9			; \
27110c5b023Smaxv 	popq	%r8			; \
27210c5b023Smaxv 	popq	%rdi			; \
27310c5b023Smaxv 	popq	%rsi			; \
27410c5b023Smaxv 	popq	%rdx			; \
27510c5b023Smaxv 	popq	%rcx			; \
27610c5b023Smaxv 	popq	%rax
27710c5b023Smaxv #else
27810c5b023Smaxv #define KMSAN_ENTER		/* nothing */
27910c5b023Smaxv #define KMSAN_LEAVE		/* nothing */
28010c5b023Smaxv #define KMSAN_INIT_ARG(sz)	/* nothing */
28110c5b023Smaxv #define KMSAN_INIT_RET(sz)	/* nothing */
28210c5b023Smaxv #endif
28310c5b023Smaxv 
284e7e5aa03Smaxv #ifdef KCOV
285e7e5aa03Smaxv #define KCOV_DISABLE			\
286e7e5aa03Smaxv 	incl	CPUVAR(IDEPTH)
287e7e5aa03Smaxv #define KCOV_ENABLE			\
288e7e5aa03Smaxv 	decl	CPUVAR(IDEPTH)
289e7e5aa03Smaxv #else
290e7e5aa03Smaxv #define KCOV_DISABLE		/* nothing */
291e7e5aa03Smaxv #define KCOV_ENABLE		/* nothing */
292e7e5aa03Smaxv #endif
293e7e5aa03Smaxv 
294ccc038a8Smaxv #define	INTRENTRY \
29589a3551aSdsl 	subq	$TF_REGSIZE,%rsp	; \
29653a9a10fSdsl 	INTR_SAVE_GPRS			; \
297aa64020bSmaxv 	cld				; \
29899d8611cSmaxv 	SMAP_ENABLE			; \
29953a9a10fSdsl 	testb	$SEL_UPL,TF_CS(%rsp)	; \
300ccc038a8Smaxv 	je	98f			; \
301c578e8d2Sdsl 	SWAPGS				; \
3020223f0c8Smaxv 	IBRS_ENTER			; \
303da72fac8Smaxv 	SVS_ENTER			; \
30489a3551aSdsl 	movw	%gs,TF_GS(%rsp)		; \
30589a3551aSdsl 	movw	%fs,TF_FS(%rsp)		; \
30689a3551aSdsl 	movw	%es,TF_ES(%rsp)		; \
307ccc038a8Smaxv 	movw	%ds,TF_DS(%rsp)		; \
30810c5b023Smaxv 98:	KMSAN_ENTER
30953a9a10fSdsl 
31081918bf8Sfvdl #define INTRFASTEXIT \
311faf0f614Smaxv 	jmp	intrfastexit
31281918bf8Sfvdl 
31381918bf8Sfvdl #define INTR_RECURSE_HWFRAME \
31481918bf8Sfvdl 	movq	%rsp,%r10		; \
31581918bf8Sfvdl 	movl	%ss,%r11d		; \
31681918bf8Sfvdl 	pushq	%r11			; \
31781918bf8Sfvdl 	pushq	%r10			; \
31881918bf8Sfvdl 	pushfq				; \
3192e43b1e6Smaxv 	pushq	$GSEL(GCODE_SEL,SEL_KPL); \
32053a9a10fSdsl /* XEN: We must fixup CS, as even kernel mode runs at CPL 3 */ \
32132a80534Sdsl  	XEN_ONLY2(andb	$0xfc,(%rsp);)	  \
32281918bf8Sfvdl 	pushq	%r13			;
32381918bf8Sfvdl 
3247318ea98Smaxv #define INTR_RECURSE_ENTRY \
3257318ea98Smaxv 	subq	$TF_REGSIZE,%rsp	; \
3267318ea98Smaxv 	INTR_SAVE_GPRS			; \
32710c5b023Smaxv 	cld				; \
32810c5b023Smaxv 	KMSAN_ENTER
3297318ea98Smaxv 
33024a1632cSyamt #define	CHECK_DEFERRED_SWITCH \
331ffa744f4Schs 	cmpl	$0, CPUVAR(WANT_PMAPLOAD)
33281918bf8Sfvdl 
3336c336cd7Syamt #define CHECK_ASTPENDING(reg)	cmpl	$0, L_MD_ASTPENDING(reg)
334b07ec3fcSad #define CLEAR_ASTPENDING(reg)	movl	$0, L_MD_ASTPENDING(reg)
33581918bf8Sfvdl 
336560337f7Smaxv /*
337560337f7Smaxv  * If the FPU state is not in the CPU, restore it. Executed with interrupts
338560337f7Smaxv  * disabled.
339560337f7Smaxv  *
340560337f7Smaxv  *     %r14 is curlwp, must not be modified
341560337f7Smaxv  *     %rbx must not be modified
342560337f7Smaxv  */
343560337f7Smaxv #define HANDLE_DEFERRED_FPU	\
344560337f7Smaxv 	testl	$MDL_FPU_IN_CPU,L_MD_FLAGS(%r14)	; \
345560337f7Smaxv 	jnz	1f					; \
346560337f7Smaxv 	call	_C_LABEL(fpu_handle_deferred)		; \
347560337f7Smaxv 	orl	$MDL_FPU_IN_CPU,L_MD_FLAGS(%r14)	; \
348560337f7Smaxv 1:
349560337f7Smaxv 
35081918bf8Sfvdl #endif /* _AMD64_MACHINE_FRAMEASM_H */
351