From e6dc1927ce61a16359477bb93a2292caade5df75 Mon Sep 17 00:00:00 2001 From: patacongo Date: Sun, 17 Mar 2013 00:40:49 +0000 Subject: Add support for calling to and returning from signal handlers in in user-mode threads git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@5750 42af7a65-404d-4744-a932-0658087f49c3 --- nuttx/arch/arm/include/armv6-m/irq.h | 8 +++ nuttx/arch/arm/include/armv6-m/syscall.h | 26 +++++++ nuttx/arch/arm/include/armv7-m/irq.h | 8 +++ nuttx/arch/arm/include/armv7-m/syscall.h | 26 +++++++ nuttx/arch/arm/src/armv6-m/svcall.h | 39 +++++++--- nuttx/arch/arm/src/armv6-m/up_sigdeliver.c | 2 +- nuttx/arch/arm/src/armv6-m/up_svcall.c | 74 ++++++++++++++++++- nuttx/arch/arm/src/armv7-m/svcall.h | 39 +++++++--- nuttx/arch/arm/src/armv7-m/up_sigdeliver.c | 2 +- nuttx/arch/arm/src/armv7-m/up_svcall.c | 74 ++++++++++++++++++- nuttx/arch/arm/src/common/up_signal_handler.c | 100 ++++++++++++++++++++++++++ nuttx/arch/arm/src/common/up_task_start.c | 3 +- nuttx/arch/arm/src/lpc17xx/Make.defs | 3 + nuttx/arch/arm/src/sam3u/Make.defs | 3 + 14 files changed, 380 insertions(+), 27 deletions(-) create mode 100644 nuttx/arch/arm/src/common/up_signal_handler.c (limited to 'nuttx/arch/arm') diff --git a/nuttx/arch/arm/include/armv6-m/irq.h b/nuttx/arch/arm/include/armv6-m/irq.h index db5c82913..75aed8ff1 100644 --- a/nuttx/arch/arm/include/armv6-m/irq.h +++ b/nuttx/arch/arm/include/armv6-m/irq.h @@ -161,6 +161,14 @@ struct xcptcontext uint32_t saved_pc; uint32_t saved_basepri; uint32_t saved_xpsr; + +# ifdef CONFIG_NUTTX_KERNEL + /* This is the saved address to use when returning from a user-space + * signal handler. + */ + + uint32_t sigreturn; +# endif #endif #ifdef CONFIG_NUTTX_KERNEL diff --git a/nuttx/arch/arm/include/armv6-m/syscall.h b/nuttx/arch/arm/include/armv6-m/syscall.h index f92dea7e8..9548ae494 100644 --- a/nuttx/arch/arm/include/armv6-m/syscall.h +++ b/nuttx/arch/arm/include/armv6-m/syscall.h @@ -54,8 +54,22 @@ * Pro-processor Definitions ****************************************************************************/ +/* This is the value used as the argument to the SVC instruction. It is not + * used. + */ + #define SYS_syscall 0x00 +/* The SYS_signal_handler_return is executed here... its value is not always + * available in this context and so is assumed to be 7. + */ + +#ifndef SYS_signal_handler_return +# define SYS_signal_handler_return (7) +#elif SYS_signal_handler_return != 7 +# error "SYS_signal_handler_return was assumed to be 7" +#endif + /**************************************************************************** * Public Types ****************************************************************************/ @@ -218,6 +232,18 @@ static inline uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1, return reg0; } +/* This inline function is used by user-space code in order to return from + * a signal handler. + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(__KERNEL__) +static inline void signal_handler_return(void) noreturn_function; +static inline void signal_handler_return(void) +{ + sys_call0(SYS_signal_handler_return); +} +#endif + /**************************************************************************** * Public Variables ****************************************************************************/ diff --git a/nuttx/arch/arm/include/armv7-m/irq.h b/nuttx/arch/arm/include/armv7-m/irq.h index 7556d0747..afeebdb4f 100644 --- a/nuttx/arch/arm/include/armv7-m/irq.h +++ b/nuttx/arch/arm/include/armv7-m/irq.h @@ -124,6 +124,14 @@ struct xcptcontext uint32_t saved_primask; #endif uint32_t saved_xpsr; + +# ifdef CONFIG_NUTTX_KERNEL + /* This is the saved address to use when returning from a user-space + * signal handler. + */ + + uint32_t sigreturn; +# endif #endif #ifdef CONFIG_NUTTX_KERNEL diff --git a/nuttx/arch/arm/include/armv7-m/syscall.h b/nuttx/arch/arm/include/armv7-m/syscall.h index 4278c3a36..91220f359 100644 --- a/nuttx/arch/arm/include/armv7-m/syscall.h +++ b/nuttx/arch/arm/include/armv7-m/syscall.h @@ -54,8 +54,22 @@ * Pro-processor Definitions ****************************************************************************/ +/* This is the value used as the argument to the SVC instruction. It is not + * used. + */ + #define SYS_syscall 0x00 +/* The SYS_signal_handler_return is executed here... its value is not always + * available in this context and so is assumed to be 7. + */ + +#ifndef SYS_signal_handler_return +# define SYS_signal_handler_return (7) +#elif SYS_signal_handler_return != 7 +# error "SYS_signal_handler_return was assumed to be 7" +#endif + /**************************************************************************** * Public Types ****************************************************************************/ @@ -218,6 +232,18 @@ static inline uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1, return reg0; } +/* This inline function is used by user-space code in order to return from + * a signal handler. + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(__KERNEL__) +static inline void signal_handler_return(void) noreturn_function; +static inline void signal_handler_return(void) +{ + sys_call0(SYS_signal_handler_return); +} +#endif + /**************************************************************************** * Public Variables ****************************************************************************/ diff --git a/nuttx/arch/arm/src/armv6-m/svcall.h b/nuttx/arch/arm/src/armv6-m/svcall.h index c7f2013fc..daa70a0c0 100644 --- a/nuttx/arch/arm/src/armv6-m/svcall.h +++ b/nuttx/arch/arm/src/armv6-m/svcall.h @@ -57,9 +57,9 @@ #ifdef CONFIG_NUTTX_KERNEL # ifndef CONFIG_SYS_RESERVED -# error "CONFIG_SYS_RESERVED must be defined to the value 6" -# elif CONFIG_SYS_RESERVED != 6 -# error "CONFIG_SYS_RESERVED must have the value 6" +# error "CONFIG_SYS_RESERVED must be defined to have the value 8" +# elif CONFIG_SYS_RESERVED != 8 +# error "CONFIG_SYS_RESERVED must have the value 8" # endif #endif @@ -70,21 +70,21 @@ * int up_saveusercontext(uint32_t *saveregs); */ -#define SYS_save_context (0) +#define SYS_save_context (0) /* SYS call 1: * * void up_fullcontextrestore(uint32_t *restoreregs) noreturn_function; */ -#define SYS_restore_context (1) +#define SYS_restore_context (1) /* SYS call 2: * * void up_switchcontext(uint32_t *saveregs, uint32_t *restoreregs); */ -#define SYS_switch_context (2) +#define SYS_switch_context (2) #ifdef CONFIG_NUTTX_KERNEL /* SYS call 3: @@ -92,21 +92,38 @@ * void up_syscall_return(void); */ -#define SYS_syscall_return (3) +#define SYS_syscall_return (3) /* SYS call 4: * - * void up_task_start(main_t taskentry, int argc, FAR char *argv[]) noreturn_function; + * void up_task_start(main_t taskentry, int argc, FAR char *argv[]) + * noreturn_function; */ -#define SYS_task_start (4) +#define SYS_task_start (4) /* SYS call 5: * - * void up_pthread_start(pthread_startroutine_t entrypt, pthread_addr_t arg) noreturn_function + * void up_pthread_start(pthread_startroutine_t entrypt, pthread_addr_t arg) + * noreturn_function */ -#define SYS_pthread_start (5) +#define SYS_pthread_start (5) + +/* SYS call 6: + * + * void signal_handler(_sa_sigaction_t sighand, int signo, FAR siginfo_t *info, + * FAR void *ucontext); + */ + +#define SYS_signal_handler (6) + +/* SYS call 7: + * + * void signal_handler_return(void); + */ + +#define SYS_signal_handler_return (7) #endif /************************************************************************************ diff --git a/nuttx/arch/arm/src/armv6-m/up_sigdeliver.c b/nuttx/arch/arm/src/armv6-m/up_sigdeliver.c index ca60b68c5..83a064e12 100644 --- a/nuttx/arch/arm/src/armv6-m/up_sigdeliver.c +++ b/nuttx/arch/arm/src/armv6-m/up_sigdeliver.c @@ -122,7 +122,7 @@ void up_sigdeliver(void) irqrestore((uint8_t)regs[REG_BASEPRI]); - /* Deliver the signals */ + /* Deliver the signal */ sigdeliver(rtcb); diff --git a/nuttx/arch/arm/src/armv6-m/up_svcall.c b/nuttx/arch/arm/src/armv6-m/up_svcall.c index ac99cabe0..745800532 100644 --- a/nuttx/arch/arm/src/armv6-m/up_svcall.c +++ b/nuttx/arch/arm/src/armv6-m/up_svcall.c @@ -344,6 +344,78 @@ int up_svcall(int irq, FAR void *context) break; #endif + /* R0=SYS_signal_handler: This a user signal handler callback + * + * void signal_handler(_sa_sigaction_t sighand, int signo, + * FAR siginfo_t *info, FAR void *ucontext); + * + * At this point, the following values are saved in context: + * + * R0 = SYS_signal_handler + * R1 = sighand + * R2 = signo + * R3 = info + * ucontext (on the stack) + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(CONFIG_DISABLE_SIGNALS) + case SYS_signal_handler: + { + struct tcb_s *rtcb = sched_self(); + + /* Remember the caller's return address */ + + DEBUGASSERT(rtcb->xcp.sigreturn == 0); + rtcb->xcp.sigreturn = regs[REG_PC]; + + /* Set up to return to the user-space pthread start-up function in + * unprivileged mode. + */ + + regs[REG_PC] = (uint32_t)USERSPACE->signal_handler; + regs[REG_EXC_RETURN] = EXC_RETURN_UNPRIVTHR; + + /* Change the parameter ordering to match the expectation of struct + * userpace_s signal_handler. + */ + + regs[REG_R0] = regs[REG_R1]; /* sighand */ + regs[REG_R1] = regs[REG_R2]; /* signal */ + regs[REG_R2] = regs[REG_R3]; /* info */ + + /* The last parameter, arg, is trickier. The arg parameter will + * reside at an offset of 4 from the stack pointer. + */ + + regs[REG_R3] = *(uint32_t*)(regs[REG_SP+4]); + } + break; +#endif + + /* R0=SYS_signal_handler_return: This a user signal handler callback + * + * void signal_handler_return(void); + * + * At this point, the following values are saved in context: + * + * R0 = SYS_signal_handler_return + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(CONFIG_DISABLE_SIGNALS) + case SYS_signal_handler_return: + { + struct tcb_s *rtcb = sched_self(); + + /* Set up to return to the kernel-mode signal dispatching logic. */ + + DEBUGASSERT(rtcb->xcp.sigreturn != 0); + + regs[REG_PC] = rtcb->xcp.sigreturn; + regs[REG_EXC_RETURN] = EXC_RETURN_PRIVTHR; + } + break; +#endif + /* This is not an architecture-specific system call. If NuttX is built * as a standalone kernel with a system call interface, then all of the * additional system calls must be handled as in the default case. @@ -356,7 +428,7 @@ int up_svcall(int irq, FAR void *context) /* Verify that the SYS call number is within range */ - DEBUGASSERT(cmd < SYS_maxsyscall); + DEBUGASSERT(cmd >= CONFIG_SYS_RESERVED && cmd < SYS_maxsyscall); /* Make sure that there is a no saved syscall return address. We * cannot yet handle nested system calls. diff --git a/nuttx/arch/arm/src/armv7-m/svcall.h b/nuttx/arch/arm/src/armv7-m/svcall.h index 333dfb6d0..2c4735335 100644 --- a/nuttx/arch/arm/src/armv7-m/svcall.h +++ b/nuttx/arch/arm/src/armv7-m/svcall.h @@ -57,9 +57,9 @@ #ifdef CONFIG_NUTTX_KERNEL # ifndef CONFIG_SYS_RESERVED -# error "CONFIG_SYS_RESERVED must be defined to the value 6" -# elif CONFIG_SYS_RESERVED != 6 -# error "CONFIG_SYS_RESERVED must have the value 6" +# error "CONFIG_SYS_RESERVED must be defined to have the value 8" +# elif CONFIG_SYS_RESERVED != 8 +# error "CONFIG_SYS_RESERVED must have the value 8" # endif #endif @@ -70,21 +70,21 @@ * int up_saveusercontext(uint32_t *saveregs); */ -#define SYS_save_context (0) +#define SYS_save_context (0) /* SYS call 1: * * void up_fullcontextrestore(uint32_t *restoreregs) noreturn_function; */ -#define SYS_restore_context (1) +#define SYS_restore_context (1) /* SYS call 2: * * void up_switchcontext(uint32_t *saveregs, uint32_t *restoreregs); */ -#define SYS_switch_context (2) +#define SYS_switch_context (2) #ifdef CONFIG_NUTTX_KERNEL /* SYS call 3: @@ -92,21 +92,38 @@ * void up_syscall_return(void); */ -#define SYS_syscall_return (3) +#define SYS_syscall_return (3) /* SYS call 4: * - * void up_task_start(main_t taskentry, int argc, FAR char *argv[]) noreturn_function; + * void up_task_start(main_t taskentry, int argc, FAR char *argv[]) + * noreturn_function; */ -#define SYS_task_start (4) +#define SYS_task_start (4) /* SYS call 5: * - * void up_pthread_start(pthread_startroutine_t entrypt, pthread_addr_t arg) noreturn_function + * void up_pthread_start(pthread_startroutine_t entrypt, pthread_addr_t arg) + * noreturn_function */ -#define SYS_pthread_start (5) +#define SYS_pthread_start (5) + +/* SYS call 6: + * + * void signal_handler(_sa_sigaction_t sighand, int signo, FAR siginfo_t *info, + * FAR void *ucontext); + */ + +#define SYS_signal_handler (6) + +/* SYS call 7: + * + * void signal_handler_return(void); + */ + +#define SYS_signal_handler_return (7) #endif /************************************************************************************ diff --git a/nuttx/arch/arm/src/armv7-m/up_sigdeliver.c b/nuttx/arch/arm/src/armv7-m/up_sigdeliver.c index 2e75d254a..e9c7a9523 100644 --- a/nuttx/arch/arm/src/armv7-m/up_sigdeliver.c +++ b/nuttx/arch/arm/src/armv7-m/up_sigdeliver.c @@ -125,7 +125,7 @@ void up_sigdeliver(void) irqrestore((uint16_t)regs[REG_PRIMASK]); #endif - /* Deliver the signals */ + /* Deliver the signal */ sigdeliver(rtcb); diff --git a/nuttx/arch/arm/src/armv7-m/up_svcall.c b/nuttx/arch/arm/src/armv7-m/up_svcall.c index 1a153dead..ab313671c 100644 --- a/nuttx/arch/arm/src/armv7-m/up_svcall.c +++ b/nuttx/arch/arm/src/armv7-m/up_svcall.c @@ -348,6 +348,78 @@ int up_svcall(int irq, FAR void *context) break; #endif + /* R0=SYS_signal_handler: This a user signal handler callback + * + * void signal_handler(_sa_sigaction_t sighand, int signo, + * FAR siginfo_t *info, FAR void *ucontext); + * + * At this point, the following values are saved in context: + * + * R0 = SYS_signal_handler + * R1 = sighand + * R2 = signo + * R3 = info + * ucontext (on the stack) + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(CONFIG_DISABLE_SIGNALS) + case SYS_signal_handler: + { + struct tcb_s *rtcb = sched_self(); + + /* Remember the caller's return address */ + + DEBUGASSERT(rtcb->xcp.sigreturn == 0); + rtcb->xcp.sigreturn = regs[REG_PC]; + + /* Set up to return to the user-space pthread start-up function in + * unprivileged mode. + */ + + regs[REG_PC] = (uint32_t)USERSPACE->signal_handler; + regs[REG_EXC_RETURN] = EXC_RETURN_UNPRIVTHR; + + /* Change the parameter ordering to match the expectation of struct + * userpace_s signal_handler. + */ + + regs[REG_R0] = regs[REG_R1]; /* sighand */ + regs[REG_R1] = regs[REG_R2]; /* signal */ + regs[REG_R2] = regs[REG_R3]; /* info */ + + /* The last parameter, arg, is trickier. The arg parameter will + * reside at an offset of 4 from the stack pointer. + */ + + regs[REG_R3] = *(uint32_t*)(regs[REG_SP+4]); + } + break; +#endif + + /* R0=SYS_signal_handler_return: This a user signal handler callback + * + * void signal_handler_return(void); + * + * At this point, the following values are saved in context: + * + * R0 = SYS_signal_handler_return + */ + +#if defined(CONFIG_NUTTX_KERNEL) && !defined(CONFIG_DISABLE_SIGNALS) + case SYS_signal_handler_return: + { + struct tcb_s *rtcb = sched_self(); + + /* Set up to return to the kernel-mode signal dispatching logic. */ + + DEBUGASSERT(rtcb->xcp.sigreturn != 0); + + regs[REG_PC] = rtcb->xcp.sigreturn; + regs[REG_EXC_RETURN] = EXC_RETURN_PRIVTHR; + } + break; +#endif + /* This is not an architecture-specific system call. If NuttX is built * as a standalone kernel with a system call interface, then all of the * additional system calls must be handled as in the default case. @@ -360,7 +432,7 @@ int up_svcall(int irq, FAR void *context) /* Verify that the SYS call number is within range */ - DEBUGASSERT(cmd < SYS_maxsyscall); + DEBUGASSERT(cmd >= CONFIG_SYS_RESERVED && cmd < SYS_maxsyscall); /* Make sure that there is a no saved syscall return address. We * cannot yet handle nested system calls. diff --git a/nuttx/arch/arm/src/common/up_signal_handler.c b/nuttx/arch/arm/src/common/up_signal_handler.c new file mode 100644 index 000000000..34407885e --- /dev/null +++ b/nuttx/arch/arm/src/common/up_signal_handler.c @@ -0,0 +1,100 @@ +/**************************************************************************** + * arch/arm/src/common/up_task_start.c + * + * Copyright (C) 2013 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "svcall.h" +#include "up_internal.h" + +#if defined(CONFIG_NUTTX_KERNEL) && defined(__KERNEL__) && !defined(CONFIG_DISABLE_SIGNALS) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_signal_handler + * + * Description: + * In this kernel mode build, this function will be called to execute a + * a signal handler in user-space. When the signal is delivered, a + * kernel-mode stub will first run to perform some housekeeping functions. + * This kernel-mode stub will then be called transfer control to the user + * mode signal handler by calling this function. + * + * Normally the a user-mode signalling handling stub will also execute + * before the ultimate signal handler is called. See + * libc/signal/signal_handler.c This function is the user-space, signal + * handler trampoline function. It is called from up_signal_handler() in + * user-mode. + * + * Inputs: + * sighand - The address user-space signal handling function + * signo, info, and ucontext - Standard arguments to be passed to the + * signal handling function. + * + * Return: + * None. This function does not return in the normal sense. It returns + * via signal_handler_return (below) + * + ****************************************************************************/ + +void up_signal_handler(_sa_sigaction_t sighand, int signo, + FAR siginfo_t *info, FAR void *ucontext) +{ + /* Let sys_call4() do all of the work */ + + sys_call4(SYS_signal_handler, (uintptr_t)sighand, (uintptr_t)signo, + (uintptr_t)info, (uintptr_t)ucontext); +} + +#endif /* CONFIG_NUTTX_KERNEL && __KERNEL__ && !CONFIG_DISABLE_SIGNALS */ diff --git a/nuttx/arch/arm/src/common/up_task_start.c b/nuttx/arch/arm/src/common/up_task_start.c index c006f9595..0a3f3c4a4 100644 --- a/nuttx/arch/arm/src/common/up_task_start.c +++ b/nuttx/arch/arm/src/common/up_task_start.c @@ -90,7 +90,8 @@ void up_task_start(main_t taskentry, int argc, FAR char *argv[]) { /* Let sys_call3() do all of the work */ - sys_call3(SYS_task_start, (uintptr_t)taskentry, (uintptr_t)argc, (uintptr_t)argv); + sys_call3(SYS_task_start, (uintptr_t)taskentry, (uintptr_t)argc, + (uintptr_t)argv); } #endif /* CONFIG_NUTTX_KERNEL */ diff --git a/nuttx/arch/arm/src/lpc17xx/Make.defs b/nuttx/arch/arm/src/lpc17xx/Make.defs index e6d007902..2bedbf5b3 100644 --- a/nuttx/arch/arm/src/lpc17xx/Make.defs +++ b/nuttx/arch/arm/src/lpc17xx/Make.defs @@ -65,6 +65,9 @@ endif ifeq ($(CONFIG_NUTTX_KERNEL),y) CMN_CSRCS += up_mpu.c up_task_start.c up_pthread_start.c +ifneq ($(CONFIG_DISABLE_SIGNALS),y) +CMN_CSRCS += up_signal_handler.c +endif endif ifeq ($(CONFIG_NET),y) diff --git a/nuttx/arch/arm/src/sam3u/Make.defs b/nuttx/arch/arm/src/sam3u/Make.defs index a2047a78a..14cc68d49 100644 --- a/nuttx/arch/arm/src/sam3u/Make.defs +++ b/nuttx/arch/arm/src/sam3u/Make.defs @@ -57,6 +57,9 @@ endif ifeq ($(CONFIG_NUTTX_KERNEL),y) CMN_CSRCS += up_mpu.c up_task_start.c up_pthread_start.c +ifneq ($(CONFIG_DISABLE_SIGNALS),y) +CMN_CSRCS += up_signal_handler.c +endif endif ifeq ($(CONFIG_ELF),y) -- cgit v1.2.3