mirror of https://github.com/ARMmbed/mbed-os.git
Added more tests for error log and error reporting, updated doxygen comments
parent
9041b475c6
commit
839fef0ad1
|
@ -17,6 +17,8 @@
|
|||
#include "utest/utest.h"
|
||||
#include "unity/unity.h"
|
||||
#include "mbed.h"
|
||||
#include <LittleFileSystem.h>
|
||||
#include "HeapBlockDevice.h"
|
||||
|
||||
using utest::v1::Case;
|
||||
|
||||
|
@ -319,7 +321,7 @@ void test_error_logging()
|
|||
|
||||
}
|
||||
|
||||
#define NUM_TEST_THREADS 15
|
||||
#define NUM_TEST_THREADS 10
|
||||
|
||||
//Error logger threads
|
||||
void err_thread_func(MbedErrorStatus *error_status)
|
||||
|
@ -338,8 +340,7 @@ void test_error_logging_multithread()
|
|||
Thread errThread[NUM_TEST_THREADS];
|
||||
MbedErrorStatus error_status[NUM_TEST_THREADS] = {
|
||||
ERROR_INVALID_ARGUMENT, ERROR_INVALID_DATA, ERROR_INVALID_FORMAT, ERROR_INVALID_SIZE, ERROR_INVALID_OPERATION,
|
||||
ERROR_NOT_FOUND, ERROR_ACCESS_DENIED, ERROR_FAILED_OPERATION, ERROR_OPERATION_PROHIBITED, ERROR_OPERATION_ABORTED,
|
||||
ERROR_NO_RESPONSE, ERROR_SEMAPHORE_LOCK_FAILED, ERROR_MUTEX_LOCK_FAILED, ERROR_OPEN_FAILED, ERROR_CLOSE_FAILED
|
||||
ERROR_NOT_FOUND, ERROR_ACCESS_DENIED, ERROR_FAILED_OPERATION, ERROR_OPERATION_PROHIBITED, ERROR_OPERATION_ABORTED
|
||||
};
|
||||
|
||||
|
||||
|
@ -355,7 +356,11 @@ void test_error_logging_multithread()
|
|||
printf("\nError log count = %d\n", i+1);
|
||||
for(;i>=0;--i) {
|
||||
MbedErrorStatus status = get_error_log_info( i, &error_ctx );
|
||||
printf("\nError Status[%d] = 0x%08X Value = 0x%08X\n", i, error_ctx.error_status, error_ctx.error_value);
|
||||
if(status != ERROR_SUCCESS) {
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
printf("\nError Status[%d] = 0x%08X Value = 0x%08X\n", i, (unsigned int)error_ctx.error_status, (unsigned int)error_ctx.error_value);
|
||||
TEST_ASSERT_EQUAL_UINT((unsigned int)error_ctx.error_value, (unsigned int)error_ctx.error_status);
|
||||
}
|
||||
}
|
||||
|
@ -381,6 +386,94 @@ void test_error_hook()
|
|||
TEST_ASSERT(sem_status > 0);
|
||||
}
|
||||
|
||||
#ifdef MBED_TEST_SIM_BLOCKDEVICE
|
||||
|
||||
// test configuration
|
||||
#ifndef MBED_TEST_FILESYSTEM
|
||||
#define MBED_TEST_FILESYSTEM LittleFileSystem
|
||||
#endif
|
||||
|
||||
#ifndef MBED_TEST_FILESYSTEM_DECL
|
||||
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
|
||||
#endif
|
||||
|
||||
#ifndef MBED_TEST_BLOCK_COUNT
|
||||
#define MBED_TEST_BLOCK_COUNT 64
|
||||
#endif
|
||||
|
||||
#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL
|
||||
#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE fd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512)
|
||||
#endif
|
||||
|
||||
// declarations
|
||||
#define STRINGIZE(x) STRINGIZE2(x)
|
||||
#define STRINGIZE2(x) #x
|
||||
#define INCLUDE(x) STRINGIZE(x.h)
|
||||
|
||||
#include INCLUDE(MBED_TEST_FILESYSTEM)
|
||||
#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE)
|
||||
|
||||
MBED_TEST_FILESYSTEM_DECL;
|
||||
MBED_TEST_SIM_BLOCKDEVICE_DECL;
|
||||
|
||||
/** Test save error log
|
||||
*/
|
||||
void test_save_error_log()
|
||||
{
|
||||
//Log some errors
|
||||
SET_ERROR(ERROR_TIMEOUT, "Timeout error", 1 );
|
||||
SET_ERROR(ERROR_ALREADY_IN_USE, "Already in use error", 2 );
|
||||
SET_ERROR(ERROR_NOT_SUPPORTED, "Not supported error", 3 );
|
||||
SET_ERROR(ERROR_ACCESS_DENIED, "Access denied error", 4 );
|
||||
SET_ERROR(ERROR_NOT_FOUND, "Not found error", 5 );
|
||||
SET_ERROR(ERROR_INVALID_ARGUMENT, "Invalid argument error", 6 );
|
||||
SET_ERROR(ERROR_INVALID_SIZE, "Invalid size error", 7 );
|
||||
SET_ERROR(ERROR_INVALID_FORMAT, "Invalid format error", 8 );
|
||||
SET_ERROR(ERROR_INVALID_OPERATION, "Invalid operation", 9 );
|
||||
SET_ERROR(ERROR_NOT_READY, "Not ready error", 10 );
|
||||
|
||||
int error = 0;
|
||||
|
||||
error = MBED_TEST_FILESYSTEM::format(&fd);
|
||||
if(error < 0) {
|
||||
printf("Failed formatting");
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
error = fs.mount(&fd);
|
||||
if(error < 0) {
|
||||
printf("Failed mounting fs");
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
if(ERROR_SUCCESS != save_error_log("/fs/errors.log")) {
|
||||
printf("Failed saving error log");
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
FILE *error_file = fopen("/fs/errors.log", "r");
|
||||
if(error_file == NULL) {
|
||||
printf("Unable to find error log in fs");
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
char buff[64] = {0};
|
||||
while (!feof(error_file)){
|
||||
int size = fread(&buff[0], 1, 15, error_file);
|
||||
fwrite(&buff[0], 1, size, stdout);
|
||||
}
|
||||
printf("\r\n");
|
||||
fclose(error_file);
|
||||
|
||||
error = fs.unmount();
|
||||
if(error < 0) {
|
||||
printf("Failed unmounting fs");
|
||||
TEST_FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
utest::v1::status_t test_setup(const size_t number_of_cases)
|
||||
{
|
||||
GREENTEA_SETUP(100, "default_auto");
|
||||
|
@ -400,6 +493,9 @@ Case cases[] = {
|
|||
#ifndef MBED_CONF_ERROR_LOG_DISABLED
|
||||
Case("Test error logging", test_error_logging),
|
||||
Case("Test error handling multi-threaded", test_error_logging_multithread),
|
||||
#ifdef MBED_TEST_SIM_BLOCKDEVICE
|
||||
Case("Test error save log", test_save_error_log),
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -266,5 +266,66 @@ int get_error_log_count(void)
|
|||
{
|
||||
return mbed_log_get_error_log_count();
|
||||
}
|
||||
|
||||
MbedErrorStatus save_error_log(const char *path)
|
||||
{
|
||||
MbedErrorStatus ret = ERROR_SUCCESS;
|
||||
mbed_error_ctx ctx = {0};
|
||||
int log_count = mbed_log_get_error_log_count();
|
||||
FILE *error_log_file = NULL;
|
||||
|
||||
//Ensure path is valid
|
||||
if(path==NULL) {
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_INVALID_ARGUMENT);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//Open the file for saving the error log info
|
||||
if((error_log_file = fopen( path, "w" ) ) == NULL){
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_OPEN_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//First store the first and last errors
|
||||
if(fprintf(error_log_file, "\nFirst Error: Status:0x%x ThreadId:0x%x Address:0x%x Value:0x%x\n",
|
||||
(unsigned int)first_error_ctx.error_status,
|
||||
(unsigned int)first_error_ctx.thread_id,
|
||||
(unsigned int)first_error_ctx.error_address,
|
||||
(unsigned int)first_error_ctx.error_value) <= 0) {
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_WRITE_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if(fprintf(error_log_file, "\nLast Error: Status:0x%x ThreadId:0x%x Address:0x%x Value:0x%x\n",
|
||||
(unsigned int)current_error_ctx.error_status,
|
||||
(unsigned int)current_error_ctx.thread_id,
|
||||
(unsigned int)current_error_ctx.error_address,
|
||||
(unsigned int)current_error_ctx.error_value) <= 0) {
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_WRITE_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//Update with error log info
|
||||
while(--log_count >= 0) {
|
||||
mbed_log_get_error(log_count, &ctx);
|
||||
//first line of file will be error log count
|
||||
if(fprintf(error_log_file, "\n%d: Status:0x%x ThreadId:0x%x Address:0x%x Value:0x%x\n",
|
||||
log_count,
|
||||
(unsigned int)ctx.error_status,
|
||||
(unsigned int)ctx.thread_id,
|
||||
(unsigned int)ctx.error_address,
|
||||
(unsigned int)ctx.error_value) <= 0) {
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_WRITE_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
fclose(error_log_file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -162,7 +162,11 @@ typedef int MbedErrorStatus;
|
|||
* Since this macro is a wrapper for set_error_fatal API callers should process the return value from this macro which is the return value from calling set_error_fatal API.
|
||||
*
|
||||
*/
|
||||
#define SET_ERROR_FATAL( error_status, error_msg, error_value ) set_error_fatal( error_status, (const char *)error_msg, (uint32_t)error_value, (const char *)MBED_FILENAME, __LINE__ )
|
||||
#ifdef MBED_CONF_ERROR_FILENAME_CAPTURE_ENABLED
|
||||
#define SET_ERROR_FATAL( error_status, error_msg, error_value ) set_error_fatal( error_status, (const char *)error_msg, (uint32_t)error_value, (const char *)MBED_FILENAME, __LINE__ )
|
||||
#else
|
||||
#define SET_ERROR_FATAL( error_status, error_msg, error_value ) set_error_fatal( error_status, (const char *)error_msg, (uint32_t)error_value, NULL, 0 )
|
||||
#endif
|
||||
|
||||
//Error Type definition
|
||||
/** MbedErrorType definition
|
||||
|
@ -255,7 +259,142 @@ typedef enum _MbedEntityType
|
|||
* ERROR_CODE_EPERM = 1\n
|
||||
* ERROR_EPERM = -1\n
|
||||
* All Posix error codes currently supported by MbedOS(defined in mbed_retarget.h) are defined using the DEFINE_POSIX_ERROR macro.\n\n
|
||||
* Below are the Posic error codes and the description:\n
|
||||
* \verbatim
|
||||
EPERM 1 Operation not permitted
|
||||
ENOENT 2 No such file or directory
|
||||
ESRCH 3 No such process
|
||||
EINTR 4 Interrupted system call
|
||||
EIO 5 I/O error
|
||||
ENXIO 6 No such device or address
|
||||
E2BIG 7 Argument list too long
|
||||
ENOEXEC 8 Exec format error
|
||||
EBADF 9 Bad file number
|
||||
ECHILD 10 No child processes
|
||||
EAGAIN 11 Try again
|
||||
ENOMEM 12 Out of memory
|
||||
EACCES 13 Permission denied
|
||||
EFAULT 14 Bad address
|
||||
ENOTBLK 15 Block device required
|
||||
EBUSY 16 Device or resource busy
|
||||
EEXIST 17 File exists
|
||||
EXDEV 18 Cross-device link
|
||||
ENODEV 19 No such device
|
||||
ENOTDIR 20 Not a directory
|
||||
EISDIR 21 Is a directory
|
||||
EINVAL 22 Invalid argument
|
||||
ENFILE 23 File table overflow
|
||||
EMFILE 24 Too many open files
|
||||
ENOTTY 25 Not a typewriter
|
||||
ETXTBSY 26 Text file busy
|
||||
EFBIG 27 File too large
|
||||
ENOSPC 28 No space left on device
|
||||
ESPIPE 29 Illegal seek
|
||||
EROFS 30 Read-only file system
|
||||
EMLINK 31 Too many links
|
||||
EPIPE 32 Broken pipe
|
||||
EDOM 33 Math argument out of domain of func
|
||||
ERANGE 34 Math result not representable
|
||||
EDEADLK 35 Resource deadlock would occur
|
||||
ENAMETOOLONG 36 File name too long
|
||||
ENOLCK 37 No record locks available
|
||||
ENOSYS 38 Function not implemented
|
||||
ENOTEMPTY 39 Directory not empty
|
||||
ELOOP 40 Too many symbolic links encountered
|
||||
EWOULDBLOCK EAGAIN Operation would block
|
||||
ENOMSG 42 No message of desired type
|
||||
EIDRM 43 Identifier removed
|
||||
ECHRNG 44 Channel number out of range
|
||||
EL2NSYNC 45 Level 2 not synchronized
|
||||
EL3HLT 46 Level 3 halted
|
||||
EL3RST 47 Level 3 reset
|
||||
ELNRNG 48 Link number out of range
|
||||
EUNATCH 49 Protocol driver not attached
|
||||
ENOCSI 50 No CSI structure available
|
||||
EL2HLT 51 Level 2 halted
|
||||
EBADE 52 Invalid exchange
|
||||
EBADR 53 Invalid request descriptor
|
||||
EXFULL 54 Exchange full
|
||||
ENOANO 55 No anode
|
||||
EBADRQC 56 Invalid request code
|
||||
EBADSLT 57 Invalid slot
|
||||
EDEADLOCK EDEADLK Resource deadlock would occur
|
||||
EBFONT 59 Bad font file format
|
||||
ENOSTR 60 Device not a stream
|
||||
ENODATA 61 No data available
|
||||
ETIME 62 Timer expired
|
||||
ENOSR 63 Out of streams resources
|
||||
ENONET 64 Machine is not on the network
|
||||
ENOPKG 65 Package not installed
|
||||
EREMOTE 66 Object is remote
|
||||
ENOLINK 67 Link has been severed
|
||||
EADV 68 Advertise error
|
||||
ESRMNT 69 Srmount error
|
||||
ECOMM 70 Communication error on send
|
||||
EPROTO 71 Protocol error
|
||||
EMULTIHOP 72 Multihop attempted
|
||||
EDOTDOT 73 RFS specific error
|
||||
EBADMSG 74 Not a data message
|
||||
EOVERFLOW 75 Value too large for defined data type
|
||||
ENOTUNIQ 76 Name not unique on network
|
||||
EBADFD 77 File descriptor in bad state
|
||||
EREMCHG 78 Remote address changed
|
||||
ELIBACC 79 Can not access a needed shared library
|
||||
ELIBBAD 80 Accessing a corrupted shared library
|
||||
ELIBSCN 81 .lib section in a.out corrupted
|
||||
ELIBMAX 82 Attempting to link in too many shared libraries
|
||||
ELIBEXEC 83 Cannot exec a shared library directly
|
||||
EILSEQ 84 Illegal byte sequence
|
||||
ERESTART 85 Interrupted system call should be restarted
|
||||
ESTRPIPE 86 Streams pipe error
|
||||
EUSERS 87 Too many users
|
||||
ENOTSOCK 88 Socket operation on non-socket
|
||||
EDESTADDRREQ 89 Destination address required
|
||||
EMSGSIZE 90 Message too long
|
||||
EPROTOTYPE 91 Protocol wrong type for socket
|
||||
ENOPROTOOPT 92 Protocol not available
|
||||
EPROTONOSUPPORT 93 Protocol not supported
|
||||
ESOCKTNOSUPPORT 94 Socket type not supported
|
||||
EOPNOTSUPP 95 Operation not supported on transport endpoint
|
||||
EPFNOSUPPORT 96 Protocol family not supported
|
||||
EAFNOSUPPORT 97 Address family not supported by protocol
|
||||
EADDRINUSE 98 Address already in use
|
||||
EADDRNOTAVAIL 99 Cannot assign requested address
|
||||
ENETDOWN 100 Network is down
|
||||
ENETUNREACH 101 Network is unreachable
|
||||
ENETRESET 102 Network dropped connection because of reset
|
||||
ECONNABORTED 103 Software caused connection abort
|
||||
ECONNRESET 104 Connection reset by peer
|
||||
ENOBUFS 105 No buffer space available
|
||||
EISCONN 106 Transport endpoint is already connected
|
||||
ENOTCONN 107 Transport endpoint is not connected
|
||||
ESHUTDOWN 108 Cannot send after transport endpoint shutdown
|
||||
ETOOMANYREFS 109 Too many references: cannot splice
|
||||
ETIMEDOUT 110 Connection timed out
|
||||
ECONNREFUSED 111 Connection refused
|
||||
EHOSTDOWN 112 Host is down
|
||||
EHOSTUNREACH 113 No route to host
|
||||
EALREADY 114 Operation already in progress
|
||||
EINPROGRESS 115 Operation now in progress
|
||||
ESTALE 116 Stale NFS file handle
|
||||
EUCLEAN 117 Structure needs cleaning
|
||||
ENOTNAM 118 Not a XENIX named type file
|
||||
ENAVAIL 119 No XENIX semaphores available
|
||||
EISNAM 120 Is a named type file
|
||||
EREMOTEIO 121 Remote I/O error
|
||||
EDQUOT 122 Quota exceeded
|
||||
ENOMEDIUM 123 No medium found
|
||||
EMEDIUMTYPE 124 Wrong medium type
|
||||
ECANCELED 125 Operation Canceled
|
||||
ENOKEY 126 Required key not available
|
||||
EKEYEXPIRED 127 Key has expired
|
||||
EKEYREVOKED 128 Key has been revoked
|
||||
EKEYREJECTED 129 Key was rejected by service
|
||||
EOWNERDEAD 130 Owner died
|
||||
ENOTRECOVERABLE 131 State not recoverable
|
||||
\endverbatim
|
||||
*
|
||||
* @note
|
||||
* MbedOS System Error codes are defined using the macro DEFINE_SYSTEM_ERROR\n
|
||||
* For example DEFINE_SYSTEM_ERROR( INVALID_ARGUMENT ,1 ) macro defines the following values:\n
|
||||
* ERROR_CODE_INVALID_ARGUMENT = MBED_SYSTEM_ERROR_BASE+1\n
|
||||
|
@ -265,7 +404,76 @@ typedef enum _MbedEntityType
|
|||
* ERROR_INVALID_ARGUMENT = 0x80FF0001\n (Note that ENTITY field is set to ENTITY_UNKNOWN)
|
||||
* New System Error codes should be defined using DEFINE_SYSTEM_ERROR macro and must have an unique error code value\n
|
||||
* passed as the second argument in the DEFINE_SYSTEM_ERROR macro.\n\n
|
||||
* Below are the Mbed System error codes and the description:
|
||||
* \verbatim
|
||||
UNKNOWN 256 Unknown error
|
||||
INVALID_ARGUMENT 257 Invalid Argument
|
||||
INVALID_DATA 258 Invalid data
|
||||
INVALID_FORMAT 259 Invalid format
|
||||
INVALID_INDEX 260 Invalid Index
|
||||
INVALID_SIZE 261 Inavlid Size
|
||||
INVALID_OPERATION 262 Invalid Operation
|
||||
NOT_FOUND 263 Not Found
|
||||
ACCESS_DENIED 264 Access Denied
|
||||
NOT_SUPPORTED 265 Not supported
|
||||
BUFFER_FULL 266 Buffer Full
|
||||
MEDIA_FULL 267 Media/Disk Full
|
||||
ALREADY_IN_USE 268 Already in use
|
||||
TIMEOUT 269 Timeout error
|
||||
NOT_READY 270 Not Ready
|
||||
FAILED_OPERATION 271 Requested Operation failed
|
||||
OPERATION_PROHIBITED 272 Operation prohibited
|
||||
OPERATION_ABORTED 273 Operation failed
|
||||
WRITE_PROTECTED 274 Attempt to write to write-protected resource
|
||||
NO_RESPONSE 275 No response
|
||||
SEMAPHORE_LOCK_FAILED 276 Sempahore lock failed
|
||||
MUTEX_LOCK_FAILED 277 Mutex lock failed
|
||||
SEMAPHORE_UNLOCK_FAILED 278 Sempahore unlock failed
|
||||
MUTEX_UNLOCK_FAILED 279 Mutex unlock failed
|
||||
CRC_ERROR 280 CRC error or mismatch
|
||||
OPEN_FAILED 281 Open failed
|
||||
CLOSE_FAILED 282 Close failed
|
||||
READ_FAILED 283 Read failed
|
||||
WRITE_FAILED 284 Write failed
|
||||
INITIALIZATION_FAILED 285 Initialization failed
|
||||
BOOT_FAILURE 286 Boot failure
|
||||
OUT_OF_MEMORY 287 Out of memory
|
||||
OUT_OF_RESOURCES 288 Out of resources
|
||||
ALLOC_FAILED 289 Alloc failed
|
||||
FREE_FAILED 290 Free failed
|
||||
OVERFLOW 291 Overflow error
|
||||
UNDERFLOW 292 Underflow error
|
||||
STACK_OVERFLOW 293 Stack overflow error
|
||||
ISR_QUEUE_OVERFLOW 294 ISR queue overflow
|
||||
TIMER_QUEUE_OVERFLOW 295 Timer Queue overflow
|
||||
CLIB_SPACE_UNAVAILABLE 296 Standard library error - Space unavailable
|
||||
CLIB_EXCEPTION 297 Standard library error - Exception
|
||||
CLIB_MUTEX_INIT_FAILURE 298 Standard library error - Mutex Init failure
|
||||
CREATE_FAILED 299 Create failed
|
||||
DELETE_FAILED 300 Delete failed
|
||||
THREAD_CREATE_FAILED 301 Thread Create failed
|
||||
THREAD_DELETE_FAILED 302 Thread Delete failed
|
||||
PROHIBITED_IN_ISR_CONTEXT 303 Operation Prohibited in ISR context
|
||||
PINMAP_INVALID 304 Pinmap Invalid
|
||||
RTOS_EVENT 305 Unknown Rtos Error
|
||||
RTOS_THREAD_EVENT 306 Rtos Thread Error
|
||||
RTOS_MUTEX_EVENT 307 Rtos Mutex Error
|
||||
RTOS_SEMAPHORE_EVENT 308 Rtos Semaphore Error
|
||||
RTOS_MEMORY_POOL_EVENT 309 Rtos Memory Pool Error
|
||||
RTOS_TIMER_EVENT 310 Rtos Timer Error
|
||||
RTOS_EVENT_FLAGS_EVENT 311 Rtos Event flags Error
|
||||
RTOS_MESSAGE_QUEUE_EVENT 312 Rtos Message queue Error
|
||||
DEVICE_BUSY 313 Device Busy
|
||||
CONFIG_UNSUPPORTED 314 Configuration not supported
|
||||
CONFIG_MISMATCH 315 Configuration mismatch
|
||||
ALREADY_INITIALIZED 316 Already initialzied
|
||||
HARDFAULT_EXCEPTION 317 HardFault exception
|
||||
MEMMANAGE_EXCEPTION 318 MemManage exception
|
||||
BUSFAULT_EXCEPTION 319 BusFault exception
|
||||
USAGEFAULT_EXCEPTION 320 UsageFault exception
|
||||
\endverbatim
|
||||
*
|
||||
* @note
|
||||
* Custom Error codes can be defined using the macro DEFINE_CUSTOM_ERROR\n
|
||||
* This is mainly meant to capture non-generic error codes specific to a device.
|
||||
* For example DEFINE_CUSTOM_ERROR( MY_CUSTOM_ERROR ,1 ) macro defines the following values:\n
|
||||
|
@ -273,8 +481,24 @@ typedef enum _MbedEntityType
|
|||
* ERROR_MY_CUSTOM_ERROR = MAKE_MBED_ERROR(ERROR_TYPE_CUSTOM, ENTITY_UNKNOWN, ERROR_CODE_MY_CUSTOM_ERROR)\n
|
||||
* Its effectively equivalent to:\n
|
||||
* ERROR_CODE_MY_CUSTOM_ERROR = 4097\n
|
||||
* ERROR_MY_CUSTOM_ERROR = 0xA0FF1001\n (Note that ENTITY field is set to ENTITY_UNKNOWN)
|
||||
* ERROR_MY_CUSTOM_ERROR = 0xA0FF1001\n (Note that ENTITY field is set to ENTITY_UNKNOWN) \n\n
|
||||
*
|
||||
*
|
||||
* @note
|
||||
* Searching for error codes in mbed-os source tree. \n
|
||||
* If you get an error report as below which you want to search for mbed-os source tree first take note of "Error Code" number. \n
|
||||
* For example, the below error report has an error code of \b 259. Find the error name associated with the error code and in this case its \b INVALID_FORMAT. \n
|
||||
* Use that error name(\b INVALID_FORMAT) to search the source tree for code locations setting that specific error code. \n
|
||||
\verbatim
|
||||
++ MbedOS Error Info ++
|
||||
Error Status: 0x80FF0103
|
||||
Error Code: 259
|
||||
Error Message: Invalid format error
|
||||
Error Location: 0x00002CFF
|
||||
Error Value: 0x00000008
|
||||
Current Thread: Id: 0x200025AC EntryFn: 0x00009681 StackSize: 0x00001000 StackMem: 0x200025F8 SP: 0x2002FFD8
|
||||
-- MbedOS Error Info --
|
||||
\endverbatim
|
||||
*/
|
||||
|
||||
typedef enum _MbedErrorCode
|
||||
|
@ -417,72 +641,72 @@ typedef enum _MbedErrorCode
|
|||
|
||||
//Below are MBED SYSTEM ERROR CODE definitions
|
||||
//MBED SYSTEM ERROR CODE definitions starts at offset MBED_SYSTEM_ERROR_BASE, see above.
|
||||
// Error Name Error Code
|
||||
DEFINE_SYSTEM_ERROR( UNKNOWN ,0 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_ARGUMENT ,1 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_DATA ,2 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_FORMAT ,3 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_INDEX ,4 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_SIZE ,5 ),
|
||||
DEFINE_SYSTEM_ERROR( INVALID_OPERATION ,6 ),
|
||||
DEFINE_SYSTEM_ERROR( NOT_FOUND ,7 ),
|
||||
DEFINE_SYSTEM_ERROR( ACCESS_DENIED ,8 ),
|
||||
DEFINE_SYSTEM_ERROR( NOT_SUPPORTED ,9 ),
|
||||
DEFINE_SYSTEM_ERROR( BUFFER_FULL ,10 ),
|
||||
DEFINE_SYSTEM_ERROR( MEDIA_FULL ,11 ),
|
||||
DEFINE_SYSTEM_ERROR( ALREADY_IN_USE ,12 ),
|
||||
DEFINE_SYSTEM_ERROR( TIMEOUT ,13 ),
|
||||
DEFINE_SYSTEM_ERROR( NOT_READY ,14 ),
|
||||
DEFINE_SYSTEM_ERROR( FAILED_OPERATION ,15 ),
|
||||
DEFINE_SYSTEM_ERROR( OPERATION_PROHIBITED ,16 ),
|
||||
DEFINE_SYSTEM_ERROR( OPERATION_ABORTED ,17 ),
|
||||
DEFINE_SYSTEM_ERROR( WRITE_PROTECTED ,18 ),
|
||||
DEFINE_SYSTEM_ERROR( NO_RESPONSE ,19 ),
|
||||
DEFINE_SYSTEM_ERROR( SEMAPHORE_LOCK_FAILED ,20 ),
|
||||
DEFINE_SYSTEM_ERROR( MUTEX_LOCK_FAILED ,21 ),
|
||||
DEFINE_SYSTEM_ERROR( SEMAPHORE_UNLOCK_FAILED ,22 ),
|
||||
DEFINE_SYSTEM_ERROR( MUTEX_UNLOCK_FAILED ,23 ),
|
||||
DEFINE_SYSTEM_ERROR( CRC_ERROR ,24 ),
|
||||
DEFINE_SYSTEM_ERROR( OPEN_FAILED ,25 ),
|
||||
DEFINE_SYSTEM_ERROR( CLOSE_FAILED ,26 ),
|
||||
DEFINE_SYSTEM_ERROR( READ_FAILED ,27 ),
|
||||
DEFINE_SYSTEM_ERROR( WRITE_FAILED ,28 ),
|
||||
DEFINE_SYSTEM_ERROR( INITIALIZATION_FAILED ,29 ),
|
||||
DEFINE_SYSTEM_ERROR( BOOT_FAILURE ,30 ),
|
||||
DEFINE_SYSTEM_ERROR( OUT_OF_MEMORY ,31 ),
|
||||
DEFINE_SYSTEM_ERROR( OUT_OF_RESOURCES ,32 ),
|
||||
DEFINE_SYSTEM_ERROR( ALLOC_FAILED ,33 ),
|
||||
DEFINE_SYSTEM_ERROR( FREE_FAILED ,34 ),
|
||||
DEFINE_SYSTEM_ERROR( OVERFLOW ,35 ),
|
||||
DEFINE_SYSTEM_ERROR( UNDERFLOW ,36 ),
|
||||
DEFINE_SYSTEM_ERROR( STACK_OVERFLOW ,37 ),
|
||||
DEFINE_SYSTEM_ERROR( ISR_QUEUE_OVERFLOW ,38 ),
|
||||
DEFINE_SYSTEM_ERROR( TIMER_QUEUE_OVERFLOW ,39 ),
|
||||
DEFINE_SYSTEM_ERROR( CLIB_SPACE_UNAVAILABLE ,40 ),
|
||||
DEFINE_SYSTEM_ERROR( CLIB_EXCEPTION ,41 ),
|
||||
DEFINE_SYSTEM_ERROR( CLIB_MUTEX_INIT_FAILURE ,42 ),
|
||||
DEFINE_SYSTEM_ERROR( CREATE_FAILED ,43 ),
|
||||
DEFINE_SYSTEM_ERROR( DELETE_FAILED ,44 ),
|
||||
DEFINE_SYSTEM_ERROR( THREAD_CREATE_FAILED ,45 ),
|
||||
DEFINE_SYSTEM_ERROR( THREAD_DELETE_FAILED ,46 ),
|
||||
DEFINE_SYSTEM_ERROR( PROHIBITED_IN_ISR_CONTEXT ,47 ),
|
||||
DEFINE_SYSTEM_ERROR( PINMAP_INVALID ,48 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_EVENT ,49 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_THREAD_EVENT ,50 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MUTEX_EVENT ,51 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_SEMAPHORE_EVENT ,52 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MEMORY_POOL_EVENT ,53 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_TIMER_EVENT ,54 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_EVENT_FLAGS_EVENT ,55 ),
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MESSAGE_QUEUE_EVENT ,56 ),
|
||||
DEFINE_SYSTEM_ERROR( DEVICE_BUSY ,57 ),
|
||||
DEFINE_SYSTEM_ERROR( CONFIG_UNSUPPORTED ,58 ),
|
||||
DEFINE_SYSTEM_ERROR( CONFIG_MISMATCH ,59 ),
|
||||
DEFINE_SYSTEM_ERROR( ALREADY_INITIALIZED ,60 ),
|
||||
DEFINE_SYSTEM_ERROR( HARDFAULT_EXCEPTION ,61 ),
|
||||
DEFINE_SYSTEM_ERROR( MEMMANAGE_EXCEPTION ,62 ),
|
||||
DEFINE_SYSTEM_ERROR( BUSFAULT_EXCEPTION ,63 ),
|
||||
DEFINE_SYSTEM_ERROR( USAGEFAULT_EXCEPTION ,64 ),
|
||||
// Error Name Error Offset Error Code
|
||||
DEFINE_SYSTEM_ERROR( UNKNOWN ,0 ), /* 256 Unknown error */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_ARGUMENT ,1 ), /* 257 Invalid Argument */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_DATA ,2 ), /* 258 Invalid data */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_FORMAT ,3 ), /* 259 Invalid format */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_INDEX ,4 ), /* 260 Invalid Index */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_SIZE ,5 ), /* 261 Inavlid Size */
|
||||
DEFINE_SYSTEM_ERROR( INVALID_OPERATION ,6 ), /* 262 Invalid Operation */
|
||||
DEFINE_SYSTEM_ERROR( NOT_FOUND ,7 ), /* 263 Not Found */
|
||||
DEFINE_SYSTEM_ERROR( ACCESS_DENIED ,8 ), /* 264 Access Denied */
|
||||
DEFINE_SYSTEM_ERROR( NOT_SUPPORTED ,9 ), /* 265 Not supported */
|
||||
DEFINE_SYSTEM_ERROR( BUFFER_FULL ,10 ), /* 266 Buffer Full */
|
||||
DEFINE_SYSTEM_ERROR( MEDIA_FULL ,11 ), /* 267 Media/Disk Full */
|
||||
DEFINE_SYSTEM_ERROR( ALREADY_IN_USE ,12 ), /* 268 Already in use */
|
||||
DEFINE_SYSTEM_ERROR( TIMEOUT ,13 ), /* 269 Timeout error */
|
||||
DEFINE_SYSTEM_ERROR( NOT_READY ,14 ), /* 270 Not Ready */
|
||||
DEFINE_SYSTEM_ERROR( FAILED_OPERATION ,15 ), /* 271 Requested Operation failed */
|
||||
DEFINE_SYSTEM_ERROR( OPERATION_PROHIBITED ,16 ), /* 272 Operation prohibited */
|
||||
DEFINE_SYSTEM_ERROR( OPERATION_ABORTED ,17 ), /* 273 Operation failed */
|
||||
DEFINE_SYSTEM_ERROR( WRITE_PROTECTED ,18 ), /* 274 Attempt to write to write-protected resource */
|
||||
DEFINE_SYSTEM_ERROR( NO_RESPONSE ,19 ), /* 275 No response */
|
||||
DEFINE_SYSTEM_ERROR( SEMAPHORE_LOCK_FAILED ,20 ), /* 276 Sempahore lock failed */
|
||||
DEFINE_SYSTEM_ERROR( MUTEX_LOCK_FAILED ,21 ), /* 277 Mutex lock failed */
|
||||
DEFINE_SYSTEM_ERROR( SEMAPHORE_UNLOCK_FAILED ,22 ), /* 278 Sempahore unlock failed */
|
||||
DEFINE_SYSTEM_ERROR( MUTEX_UNLOCK_FAILED ,23 ), /* 279 Mutex unlock failed */
|
||||
DEFINE_SYSTEM_ERROR( CRC_ERROR ,24 ), /* 280 CRC error or mismatch */
|
||||
DEFINE_SYSTEM_ERROR( OPEN_FAILED ,25 ), /* 281 Open failed */
|
||||
DEFINE_SYSTEM_ERROR( CLOSE_FAILED ,26 ), /* 282 Close failed */
|
||||
DEFINE_SYSTEM_ERROR( READ_FAILED ,27 ), /* 283 Read failed */
|
||||
DEFINE_SYSTEM_ERROR( WRITE_FAILED ,28 ), /* 284 Write failed */
|
||||
DEFINE_SYSTEM_ERROR( INITIALIZATION_FAILED ,29 ), /* 285 Initialization failed */
|
||||
DEFINE_SYSTEM_ERROR( BOOT_FAILURE ,30 ), /* 286 Boot failure */
|
||||
DEFINE_SYSTEM_ERROR( OUT_OF_MEMORY ,31 ), /* 287 Out of memory */
|
||||
DEFINE_SYSTEM_ERROR( OUT_OF_RESOURCES ,32 ), /* 288 Out of resources */
|
||||
DEFINE_SYSTEM_ERROR( ALLOC_FAILED ,33 ), /* 289 Alloc failed */
|
||||
DEFINE_SYSTEM_ERROR( FREE_FAILED ,34 ), /* 290 Free failed */
|
||||
DEFINE_SYSTEM_ERROR( OVERFLOW ,35 ), /* 291 Overflow error */
|
||||
DEFINE_SYSTEM_ERROR( UNDERFLOW ,36 ), /* 292 Underflow error */
|
||||
DEFINE_SYSTEM_ERROR( STACK_OVERFLOW ,37 ), /* 293 Stack overflow error */
|
||||
DEFINE_SYSTEM_ERROR( ISR_QUEUE_OVERFLOW ,38 ), /* 294 ISR queue overflow */
|
||||
DEFINE_SYSTEM_ERROR( TIMER_QUEUE_OVERFLOW ,39 ), /* 295 Timer Queue overflow */
|
||||
DEFINE_SYSTEM_ERROR( CLIB_SPACE_UNAVAILABLE ,40 ), /* 296 Standard library error - Space unavailable */
|
||||
DEFINE_SYSTEM_ERROR( CLIB_EXCEPTION ,41 ), /* 297 Standard library error - Exception */
|
||||
DEFINE_SYSTEM_ERROR( CLIB_MUTEX_INIT_FAILURE ,42 ), /* 298 Standard library error - Mutex Init failure */
|
||||
DEFINE_SYSTEM_ERROR( CREATE_FAILED ,43 ), /* 299 Create failed */
|
||||
DEFINE_SYSTEM_ERROR( DELETE_FAILED ,44 ), /* 300 Delete failed */
|
||||
DEFINE_SYSTEM_ERROR( THREAD_CREATE_FAILED ,45 ), /* 301 Thread Create failed */
|
||||
DEFINE_SYSTEM_ERROR( THREAD_DELETE_FAILED ,46 ), /* 302 Thread Delete failed */
|
||||
DEFINE_SYSTEM_ERROR( PROHIBITED_IN_ISR_CONTEXT ,47 ), /* 303 Operation Prohibited in ISR context */
|
||||
DEFINE_SYSTEM_ERROR( PINMAP_INVALID ,48 ), /* 304 Pinmap Invalid */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_EVENT ,49 ), /* 305 Unknown Rtos Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_THREAD_EVENT ,50 ), /* 306 Rtos Thread Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MUTEX_EVENT ,51 ), /* 307 Rtos Mutex Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_SEMAPHORE_EVENT ,52 ), /* 308 Rtos Semaphore Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MEMORY_POOL_EVENT ,53 ), /* 309 Rtos Memory Pool Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_TIMER_EVENT ,54 ), /* 310 Rtos Timer Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_EVENT_FLAGS_EVENT ,55 ), /* 311 Rtos Event flags Error */
|
||||
DEFINE_SYSTEM_ERROR( RTOS_MESSAGE_QUEUE_EVENT ,56 ), /* 312 Rtos Message queue Error */
|
||||
DEFINE_SYSTEM_ERROR( DEVICE_BUSY ,57 ), /* 313 Device Busy */
|
||||
DEFINE_SYSTEM_ERROR( CONFIG_UNSUPPORTED ,58 ), /* 314 Configuration not supported */
|
||||
DEFINE_SYSTEM_ERROR( CONFIG_MISMATCH ,59 ), /* 315 Configuration mismatch */
|
||||
DEFINE_SYSTEM_ERROR( ALREADY_INITIALIZED ,60 ), /* 316 Already initialzied */
|
||||
DEFINE_SYSTEM_ERROR( HARDFAULT_EXCEPTION ,61 ), /* 317 HardFault exception */
|
||||
DEFINE_SYSTEM_ERROR( MEMMANAGE_EXCEPTION ,62 ), /* 318 MemManage exception */
|
||||
DEFINE_SYSTEM_ERROR( BUSFAULT_EXCEPTION ,63 ), /* 319 BusFault exception */
|
||||
DEFINE_SYSTEM_ERROR( USAGEFAULT_EXCEPTION ,64 ), /* 320 UsageFault exception*/
|
||||
|
||||
//Everytime you add a new system error code, you must update
|
||||
//Error documentation under Handbook to capture the info on
|
||||
|
@ -753,7 +977,6 @@ MbedErrorStatus clear_all_errors(void);
|
|||
*/
|
||||
MbedErrorStatus make_mbed_error(MbedErrorType error_type, MbedEntityType entity, MbedErrorCode error_code);
|
||||
|
||||
#ifndef MBED_CONF_ERROR_LOG_DISABLED
|
||||
/**
|
||||
* Returns the current number of entries in the error log, if there has been more than max number of errors logged the number returned will be max depth of error log.
|
||||
* @return Current number of entries in the error log.
|
||||
|
@ -773,7 +996,19 @@ int get_error_log_count(void);
|
|||
*
|
||||
*/
|
||||
MbedErrorStatus get_error_log_info(int index, mbed_error_ctx *error_info);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Saves the error log information to a file
|
||||
*
|
||||
* @param path path to the file in the filesystem
|
||||
* @return 0 or ERROR_SUCCESS on success.
|
||||
* ERROR_WRITE_FAILED if writing to file failed
|
||||
* ERROR_INVALID_ARGUMENT if path is not valid
|
||||
*
|
||||
* @note Filesystem support is required in order for this function to work.
|
||||
*
|
||||
*/
|
||||
MbedErrorStatus save_error_log(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -106,43 +106,4 @@ MbedErrorStatus mbed_log_reset()
|
|||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
#if DEVICE_LOCALFILESYSTEM
|
||||
MbedErrorStatus mbed_log_save_error_log(const char *path)
|
||||
{
|
||||
MbedErrorStatus ret = ERROR_SUCCESS;
|
||||
mbed_error_ctx ctx = {0};
|
||||
int log_count = mbed_log_get_error_log_count();
|
||||
FILE *error_log_file = NULL;
|
||||
|
||||
//Open the file for saving the error log info
|
||||
if((error_log_file = fopen( path, "w" ) ) == NULL){
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_OPEN_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//first line of file will be error log count
|
||||
if(fprintf(error_log_file, "\nError Log Count = %d\n", log_count)){
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_WRITE_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//Update with error log info
|
||||
while(log_count >= 0) {
|
||||
mbed_log_get_error(log_count, &ctx);
|
||||
//first line of file will be error log count
|
||||
if(fprintf(error_log_file, "\n%d: Status:0x%x ThreadId:0x%x Address:0x%x File:%s+%d\n", log_count, ctx.error_status, ctx.thread_id, ctx.error_address)) {
|
||||
ret = MAKE_ERROR(ENTITY_PLATFORM, ERROR_CODE_WRITE_FAILED);
|
||||
goto exit;
|
||||
}
|
||||
log_count--;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
exit:
|
||||
fclose(error_log_file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -41,6 +41,21 @@ static void value_to_hex_str(uint32_t value, char *hex_str)
|
|||
}
|
||||
}
|
||||
|
||||
/* Converts a uint32 to dec char string */
|
||||
static void value_to_dec_str(uint32_t value, char *dec_str)
|
||||
{
|
||||
char dec_char_map[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
int i = 0;
|
||||
|
||||
//Return without converting if hex_str is not provided
|
||||
if(dec_str == NULL) return;
|
||||
|
||||
while(value > 0) {
|
||||
dec_str[i++] = dec_char_map[value % 10];
|
||||
value = value / 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Limited print functionality which prints the string out to
|
||||
stdout/uart without using stdlib by directly calling serial-api
|
||||
and also uses less resources
|
||||
|
@ -54,7 +69,7 @@ void mbed_error_print(char *fmtstr, uint32_t *values)
|
|||
int i = 0;
|
||||
int idx = 0;
|
||||
int vidx = 0;
|
||||
char hex_str[9]={0};
|
||||
char num_str[9]={0};
|
||||
char *str=NULL;
|
||||
|
||||
/* Initializes std uart if not init-ed yet */
|
||||
|
@ -67,9 +82,16 @@ void mbed_error_print(char *fmtstr, uint32_t *values)
|
|||
i++;
|
||||
if(fmtstr[i]=='x') {
|
||||
//print the number in hex format
|
||||
value_to_hex_str(values[vidx++],hex_str);
|
||||
value_to_hex_str(values[vidx++],num_str);
|
||||
for(idx=7; idx>=0; idx--) {
|
||||
serial_putc(&stdio_uart, hex_str[idx]);
|
||||
serial_putc(&stdio_uart, num_str[idx]);
|
||||
}
|
||||
}
|
||||
else if(fmtstr[i]=='d') {
|
||||
//print the number in dec format
|
||||
value_to_dec_str(values[vidx++],num_str);
|
||||
for(idx=5; idx>=0; idx--) {
|
||||
serial_putc(&stdio_uart, num_str[idx]);
|
||||
}
|
||||
}
|
||||
else if(fmtstr[i]=='s') {
|
||||
|
@ -120,7 +142,7 @@ void mbed_report_error(const mbed_error_ctx *error_ctx, char *error_msg)
|
|||
int error_code = GET_MBED_ERROR_CODE(error_ctx->error_status);
|
||||
|
||||
mbed_error_print("\n\n++ MbedOS Error Info ++\nError Status: 0x%x", (uint32_t *)&error_ctx->error_status);
|
||||
mbed_error_print("\nError Message: ", NULL);
|
||||
mbed_error_print("\nError Code: %d\nError Message: ", (uint32_t *)&error_code);
|
||||
|
||||
//Report error info based on error code, some errors require different info
|
||||
if(error_code == ERROR_CODE_HARDFAULT_EXCEPTION ||
|
||||
|
|
Loading…
Reference in New Issue