2015-08-25 16:34:37 +00:00
/* Implementation of libmain/app_main.o from the Espressif SDK.
*
* This contains most of the startup code for the SDK / OS , some event handlers ,
* etc .
*
* Part of esp - open - rtos
* Copyright ( C ) 2015 Superhouse Automation Pty Ltd
* BSD Licensed as described in the file LICENSE
*/
# include <string.h>
# include <FreeRTOS.h>
# include <task.h>
# include <lwip/tcpip.h>
# include "common_macros.h"
# include "xtensa_ops.h"
# include "esp/rom.h"
2015-10-06 06:06:56 +00:00
# include "esp/uart.h"
2015-08-25 16:34:37 +00:00
# include "esp/iomux_regs.h"
# include "esp/spi_regs.h"
# include "esp/dport_regs.h"
# include "esp/wdev_regs.h"
2016-06-29 22:18:10 +00:00
# include "esp/hwrand.h"
2015-08-25 16:34:37 +00:00
# include "os_version.h"
2015-08-29 00:45:38 +00:00
# include "espressif/esp_common.h"
2016-04-07 07:23:30 +00:00
# include "espressif/phy_info.h"
2015-08-25 16:34:37 +00:00
# include "sdk_internal.h"
2016-04-05 16:23:28 +00:00
# include "esplibs/libmain.h"
2016-08-18 10:07:46 +00:00
# include "sysparam.h"
2015-08-25 16:34:37 +00:00
/* This is not declared in any header file (but arguably should be) */
void user_init ( void ) ;
# define BOOT_INFO_SIZE 28
// These are the offsets of these values within the RTCMEM regions. It appears
// that the ROM saves them to RTCMEM before calling us, and we pull them out of
// there to display them in startup messages (not sure why it works that way).
# define RTCMEM_BACKUP_PHY_VER 31
# define RTCMEM_SYSTEM_PP_VER 62
2015-08-26 00:22:38 +00:00
extern uint32_t _bss_start ;
extern uint32_t _bss_end ;
2015-08-25 16:34:37 +00:00
// user_init_flag -- .bss+0x0
uint8_t sdk_user_init_flag ;
// info -- .bss+0x4
struct sdk_info_st sdk_info ;
// xUserTaskHandle -- .bss+0x28
2016-11-05 10:04:03 +00:00
TaskHandle_t sdk_xUserTaskHandle ;
2015-08-25 16:34:37 +00:00
// xWatchDogTaskHandle -- .bss+0x2c
2016-11-05 10:04:03 +00:00
TaskHandle_t sdk_xWatchDogTaskHandle ;
2015-08-25 16:34:37 +00:00
/* Static function prototypes */
static void IRAM get_otp_mac_address ( uint8_t * buf ) ;
static void IRAM set_spi0_divisor ( uint32_t divisor ) ;
static void zero_bss ( void ) ;
2016-04-07 07:23:30 +00:00
static void init_networking ( sdk_phy_info_t * phy_info , uint8_t * mac_addr ) ;
2015-08-25 16:34:37 +00:00
static void init_g_ic ( void ) ;
static void user_start_phase2 ( void ) ;
2015-08-30 19:11:08 +00:00
static void dump_flash_sector ( uint32_t start_sector , uint32_t length ) ;
static void dump_flash_config_sectors ( uint32_t start_sector ) ;
2015-08-25 16:34:37 +00:00
// .Lfunc001 -- .text+0x14
static void IRAM get_otp_mac_address ( uint8_t * buf ) {
uint32_t otp_flags ;
uint32_t otp_id0 , otp_id1 ;
uint32_t otp_vendor_id ;
otp_flags = DPORT . OTP_CHIPID ;
otp_id1 = DPORT . OTP_MAC1 ;
otp_id0 = DPORT . OTP_MAC0 ;
if ( ! ( otp_flags & 0x8000 ) ) {
//FIXME: do we really need this check?
printf ( " Firmware ONLY supports ESP8266!!! \n " ) ;
2016-05-07 08:21:13 +00:00
abort ( ) ;
2015-08-25 16:34:37 +00:00
}
if ( otp_id0 = = 0 & & otp_id1 = = 0 ) {
printf ( " empty otp \n " ) ;
2016-05-07 08:21:13 +00:00
abort ( ) ;
2015-08-25 16:34:37 +00:00
}
if ( otp_flags & 0x1000 ) {
// If bit 12 is set, it indicates that the vendor portion of the MAC
// address is stored in DPORT.OTP_MAC2.
otp_vendor_id = DPORT . OTP_MAC2 ;
buf [ 0 ] = otp_vendor_id > > 16 ;
buf [ 1 ] = otp_vendor_id > > 8 ;
buf [ 2 ] = otp_vendor_id ;
} else {
// If bit 12 is clear, there's no MAC vendor in DPORT.OTP_MAC2, so
// default to the Espressif MAC vendor prefix instead.
buf [ 1 ] = 0xfe ;
buf [ 0 ] = 0x18 ;
buf [ 2 ] = 0x34 ;
}
buf [ 3 ] = otp_id1 > > 8 ;
buf [ 4 ] = otp_id1 ;
buf [ 5 ] = otp_id0 > > 24 ;
}
// .Lfunc002 -- .text+0xa0
static void IRAM set_spi0_divisor ( uint32_t divisor ) {
int cycle_len , half_cycle_len , clkdiv ;
if ( divisor < 2 ) {
clkdiv = 0 ;
SPI ( 0 ) . CTRL0 | = SPI_CTRL0_CLOCK_EQU_SYS_CLOCK ;
IOMUX . CONF | = IOMUX_CONF_SPI0_CLOCK_EQU_SYS_CLOCK ;
} else {
cycle_len = divisor - 1 ;
half_cycle_len = ( divisor / 2 ) - 1 ;
clkdiv = VAL2FIELD ( SPI_CTRL0_CLOCK_NUM , cycle_len )
| VAL2FIELD ( SPI_CTRL0_CLOCK_HIGH , half_cycle_len )
| VAL2FIELD ( SPI_CTRL0_CLOCK_LOW , cycle_len ) ;
SPI ( 0 ) . CTRL0 & = ~ SPI_CTRL0_CLOCK_EQU_SYS_CLOCK ;
IOMUX . CONF & = ~ IOMUX_CONF_SPI0_CLOCK_EQU_SYS_CLOCK ;
}
SPI ( 0 ) . CTRL0 = SET_FIELD ( SPI ( 0 ) . CTRL0 , SPI_CTRL0_CLOCK , clkdiv ) ;
}
2015-10-06 07:14:05 +00:00
static void IRAM default_putc ( char c ) {
uart_putc ( 0 , c ) ;
2015-08-25 16:34:37 +00:00
}
// .text+0x258
void IRAM sdk_user_start ( void ) {
uint32_t buf32 [ sizeof ( struct sdk_g_ic_saved_st ) / 4 ] ;
uint8_t * buf8 = ( uint8_t * ) buf32 ;
uint32_t flash_speed_divisor ;
uint32_t flash_sectors ;
uint32_t flash_size ;
int boot_slot ;
uint32_t cksum_magic ;
uint32_t cksum_len ;
uint32_t cksum_value ;
uint32_t ic_flash_addr ;
2016-08-18 10:07:46 +00:00
uint32_t sysparam_addr ;
sysparam_status_t status ;
2015-08-25 16:34:37 +00:00
SPI ( 0 ) . USER0 | = SPI_USER0_CS_SETUP ;
sdk_SPIRead ( 0 , buf32 , 4 ) ;
switch ( buf8 [ 3 ] & 0x0f ) {
case 0xf : // 80 MHz
flash_speed_divisor = 1 ;
break ;
case 0x0 : // 40 MHz
flash_speed_divisor = 2 ;
break ;
case 0x1 : // 26 MHz
flash_speed_divisor = 3 ;
break ;
case 0x2 : // 20 MHz
flash_speed_divisor = 4 ;
break ;
default : // Invalid -- Assume 40 MHz
flash_speed_divisor = 2 ;
}
switch ( buf8 [ 3 ] > > 4 ) {
case 0x0 : // 4 Mbit (512 KByte)
flash_sectors = 128 ;
break ;
case 0x1 : // 2 Mbit (256 Kbyte)
flash_sectors = 64 ;
break ;
case 0x2 : // 8 Mbit (1 Mbyte)
flash_sectors = 256 ;
break ;
case 0x3 : // 16 Mbit (2 Mbyte)
flash_sectors = 512 ;
break ;
case 0x4 : // 32 Mbit (4 Mbyte)
flash_sectors = 1024 ;
break ;
default : // Invalid -- Assume 4 Mbit (512 KByte)
flash_sectors = 128 ;
}
//FIXME: we should probably calculate flash_sectors by starting with flash_size and dividing by sdk_flashchip.sector_size instead of vice-versa.
flash_size = flash_sectors * 4096 ;
sdk_flashchip . chip_size = flash_size ;
set_spi0_divisor ( flash_speed_divisor ) ;
2015-08-29 00:45:38 +00:00
sdk_SPIRead ( flash_size - 4096 , buf32 , BOOT_INFO_SIZE ) ;
2015-08-30 20:37:40 +00:00
boot_slot = buf8 [ 0 ] ? 1 : 0 ;
2015-08-25 16:34:37 +00:00
cksum_magic = buf32 [ 1 ] ;
cksum_len = buf32 [ 3 + boot_slot ] ;
cksum_value = buf32 [ 5 + boot_slot ] ;
2015-08-30 20:37:40 +00:00
ic_flash_addr = ( flash_sectors - 3 + boot_slot ) * sdk_flashchip . sector_size ;
2015-08-25 16:34:37 +00:00
sdk_SPIRead ( ic_flash_addr , buf32 , sizeof ( struct sdk_g_ic_saved_st ) ) ;
Cache_Read_Enable ( 0 , 0 , 1 ) ;
zero_bss ( ) ;
2015-10-06 07:14:05 +00:00
sdk_os_install_putc1 ( default_putc ) ;
2015-08-25 16:34:37 +00:00
if ( cksum_magic = = 0xffffffff ) {
// No checksum required
} else if ( ( cksum_magic = = 0x55aa55aa ) & &
( sdk_system_get_checksum ( buf8 , cksum_len ) = = cksum_value ) ) {
// Checksum OK
} else {
// Bad checksum or bad cksum_magic value
dump_flash_config_sectors ( flash_sectors - 4 ) ;
//FIXME: should we halt here? (original SDK code doesn't)
}
memcpy ( & sdk_g_ic . s , buf32 , sizeof ( struct sdk_g_ic_saved_st ) ) ;
2016-08-18 10:07:46 +00:00
// By default, put the sysparam region just below the config sectors at the
// top of the flash space
sysparam_addr = flash_size - ( 4 + DEFAULT_SYSPARAM_SECTORS ) * sdk_flashchip . sector_size ;
status = sysparam_init ( sysparam_addr , flash_size ) ;
if ( status = = SYSPARAM_NOTFOUND ) {
status = sysparam_create_area ( sysparam_addr , DEFAULT_SYSPARAM_SECTORS , false ) ;
if ( status = = SYSPARAM_OK ) {
status = sysparam_init ( sysparam_addr , 0 ) ;
}
}
if ( status ! = SYSPARAM_OK ) {
printf ( " WARNING: Could not initialize sysparams (%d)! \n " , status ) ;
}
2015-08-25 16:34:37 +00:00
user_start_phase2 ( ) ;
}
// .text+0x3a8
2016-11-05 10:04:03 +00:00
void IRAM vApplicationStackOverflowHook ( TaskHandle_t task , char * task_name ) {
2016-02-23 21:54:32 +00:00
printf ( " Task stack overflow (high water mark=%lu name= \" %s \" ) \n " , uxTaskGetStackHighWaterMark ( task ) , task_name ) ;
2015-08-25 16:34:37 +00:00
}
// .text+0x3d8
2015-08-26 00:22:38 +00:00
void IRAM vApplicationIdleHook ( void ) {
2015-09-20 21:09:30 +00:00
printf ( " idle %u \n " , WDEV . SYS_TIME ) ;
2015-08-25 16:34:37 +00:00
}
// .text+0x404
2015-08-26 00:22:38 +00:00
void IRAM vApplicationTickHook ( void ) {
2015-09-20 21:09:30 +00:00
printf ( " tick %u \n " , WDEV . SYS_TIME ) ;
2015-08-25 16:34:37 +00:00
}
// .Lfunc005 -- .irom0.text+0x8
static void zero_bss ( void ) {
uint32_t * addr ;
2015-08-26 00:22:38 +00:00
for ( addr = & _bss_start ; addr < & _bss_end ; addr + + ) {
2015-08-25 16:34:37 +00:00
* addr = 0 ;
}
}
// .Lfunc006 -- .irom0.text+0x70
2016-04-07 07:23:30 +00:00
static void init_networking ( sdk_phy_info_t * phy_info , uint8_t * mac_addr ) {
2015-08-25 16:34:37 +00:00
if ( sdk_register_chipv6_phy ( phy_info ) ) {
printf ( " FATAL: sdk_register_chipv6_phy failed " ) ;
2016-05-07 08:21:13 +00:00
abort ( ) ;
2015-08-25 16:34:37 +00:00
}
2015-10-06 12:04:19 +00:00
uart_set_baud ( 0 , 74906 ) ;
uart_set_baud ( 1 , 74906 ) ;
2015-08-25 16:34:37 +00:00
sdk_phy_disable_agc ( ) ;
sdk_ieee80211_phy_init ( sdk_g_ic . s . phy_mode ) ;
sdk_lmacInit ( ) ;
sdk_wDev_Initialize ( ) ;
sdk_pp_attach ( ) ;
2015-08-26 00:22:38 +00:00
sdk_ieee80211_ifattach ( & sdk_g_ic , mac_addr ) ;
2015-08-25 16:34:37 +00:00
_xt_isr_mask ( 1 ) ;
DPORT . DPORT0 = SET_FIELD ( DPORT . DPORT0 , DPORT_DPORT0_FIELD0 , 1 ) ;
sdk_pm_attach ( ) ;
sdk_phy_enable_agc ( ) ;
2015-08-26 00:22:38 +00:00
sdk_cnx_attach ( & sdk_g_ic ) ;
2015-08-25 16:34:37 +00:00
sdk_wDevEnableRx ( ) ;
}
// .Lfunc007 -- .irom0.text+0x148
static void init_g_ic ( void ) {
if ( sdk_g_ic . s . wifi_mode = = 0xff ) {
sdk_g_ic . s . wifi_mode = 2 ;
}
sdk_wifi_softap_set_default_ssid ( ) ;
if ( sdk_g_ic . s . _unknown30d < 1 | | sdk_g_ic . s . _unknown30d > 14 ) {
sdk_g_ic . s . _unknown30d = 1 ;
}
if ( sdk_g_ic . s . _unknown544 < 100 | | sdk_g_ic . s . _unknown544 > 60000 ) {
sdk_g_ic . s . _unknown544 = 100 ;
}
if ( sdk_g_ic . s . _unknown30e = = 1 | | sdk_g_ic . s . _unknown30e > 4 ) {
sdk_g_ic . s . _unknown30e = 0 ;
}
bzero ( sdk_g_ic . s . _unknown2ac , sizeof ( sdk_g_ic . s . _unknown2ac ) ) ;
if ( sdk_g_ic . s . _unknown30f > 1 ) {
sdk_g_ic . s . _unknown30f = 0 ;
}
if ( sdk_g_ic . s . _unknown310 > 4 ) {
sdk_g_ic . s . _unknown310 = 4 ;
}
if ( sdk_g_ic . s . _unknown1e4 . _unknown1e4 = = 0xffffffff ) {
bzero ( & sdk_g_ic . s . _unknown1e4 , sizeof ( sdk_g_ic . s . _unknown1e4 ) ) ;
bzero ( & sdk_g_ic . s . _unknown20f , sizeof ( sdk_g_ic . s . _unknown20f ) ) ;
}
sdk_g_ic . s . wifi_led_enable = 0 ;
if ( sdk_g_ic . s . _unknown281 > 1 ) {
sdk_g_ic . s . _unknown281 = 0 ;
}
if ( sdk_g_ic . s . ap_number > 5 ) {
sdk_g_ic . s . ap_number = 1 ;
}
if ( sdk_g_ic . s . phy_mode < 1 | | sdk_g_ic . s . phy_mode > 3 ) {
sdk_g_ic . s . phy_mode = PHY_MODE_11N ;
}
}
// .irom0.text+0x398
void sdk_wdt_init ( void ) {
WDT . CTRL & = ~ WDT_CTRL_ENABLE ;
DPORT . INT_ENABLE | = DPORT_INT_ENABLE_WDT ;
WDT . REG1 = 0x0000000b ;
WDT . REG2 = 0x0000000c ;
WDT . CTRL | = WDT_CTRL_FLAG3 | WDT_CTRL_FLAG4 | WDT_CTRL_FLAG5 ;
WDT . CTRL = SET_FIELD ( WDT . CTRL , WDT_CTRL_FIELD0 , 0 ) ;
WDT . CTRL | = WDT_CTRL_ENABLE ;
sdk_pp_soft_wdt_init ( ) ;
}
// .irom0.text+0x474
void sdk_user_init_task ( void * params ) {
int phy_ver , pp_ver ;
sdk_ets_timer_init ( ) ;
printf ( " \n ESP-Open-SDK ver: %s compiled @ %s %s \n " , OS_VERSION_STR , __DATE__ , __TIME__ ) ;
phy_ver = RTCMEM_BACKUP [ RTCMEM_BACKUP_PHY_VER ] > > 16 ;
printf ( " phy ver: %d, " , phy_ver ) ;
pp_ver = RTCMEM_SYSTEM [ RTCMEM_SYSTEM_PP_VER ] ;
printf ( " pp ver: %d.%d \n \n " , ( pp_ver > > 8 ) & 0xff , pp_ver & 0xff ) ;
user_init ( ) ;
sdk_user_init_flag = 1 ;
sdk_wifi_mode_set ( sdk_g_ic . s . wifi_mode ) ;
if ( sdk_g_ic . s . wifi_mode = = 1 ) {
sdk_wifi_station_start ( ) ;
netif_set_default ( sdk_g_ic . v . station_netif_info - > netif ) ;
}
if ( sdk_g_ic . s . wifi_mode = = 2 ) {
sdk_wifi_softap_start ( ) ;
netif_set_default ( sdk_g_ic . v . softap_netif_info - > netif ) ;
}
if ( sdk_g_ic . s . wifi_mode = = 3 ) {
sdk_wifi_station_start ( ) ;
sdk_wifi_softap_start ( ) ;
netif_set_default ( sdk_g_ic . v . softap_netif_info - > netif ) ;
}
if ( sdk_wifi_station_get_auto_connect ( ) ) {
sdk_wifi_station_connect ( ) ;
}
vTaskDelete ( NULL ) ;
}
2016-02-09 05:06:25 +00:00
extern void ( * __init_array_start ) ( void ) ;
extern void ( * __init_array_end ) ( void ) ;
2015-08-25 16:34:37 +00:00
// .Lfunc009 -- .irom0.text+0x5b4
2016-05-07 08:33:43 +00:00
static __attribute__ ( ( noinline ) ) void user_start_phase2 ( void ) {
2015-08-25 16:34:37 +00:00
uint8_t * buf ;
2016-04-07 07:23:30 +00:00
sdk_phy_info_t phy_info , default_phy_info ;
2015-08-25 16:34:37 +00:00
sdk_system_rtc_mem_read ( 0 , & sdk_rst_if , sizeof ( sdk_rst_if ) ) ;
2016-04-05 16:23:28 +00:00
if ( sdk_rst_if . reason > 3 ) {
// Bad reason. Probably garbage.
2015-08-25 16:34:37 +00:00
bzero ( & sdk_rst_if , sizeof ( sdk_rst_if ) ) ;
}
buf = malloc ( sizeof ( sdk_rst_if ) ) ;
bzero ( buf , sizeof ( sdk_rst_if ) ) ;
sdk_system_rtc_mem_write ( 0 , buf , sizeof ( sdk_rst_if ) ) ;
free ( buf ) ;
sdk_sleep_reset_analog_rtcreg_8266 ( ) ;
get_otp_mac_address ( sdk_info . sta_mac_addr ) ;
sdk_wifi_softap_cacl_mac ( sdk_info . softap_mac_addr , sdk_info . sta_mac_addr ) ;
sdk_info . _unknown0 = 0x0104a8c0 ;
2016-04-05 16:23:28 +00:00
sdk_info . _unknown4 = 0x00ffffff ;
sdk_info . _unknown8 = 0x0104a8c0 ;
2015-08-25 16:34:37 +00:00
init_g_ic ( ) ;
2016-04-07 07:23:30 +00:00
read_saved_phy_info ( & phy_info ) ;
get_default_phy_info ( & default_phy_info ) ;
if ( phy_info . version ! = default_phy_info . version ) {
/* Versions don't match, use default for PHY info
( may be a blank config sector , or a new default version . )
*/
memcpy ( & phy_info , & default_phy_info , sizeof ( sdk_phy_info_t ) ) ;
}
2015-08-25 16:34:37 +00:00
2015-10-10 22:21:30 +00:00
// Disable default buffering on stdout
setbuf ( stdout , NULL ) ;
2015-08-25 16:34:37 +00:00
// Wait for UARTs to finish sending anything in their queues.
2015-10-06 07:14:05 +00:00
uart_flush_txfifo ( 0 ) ;
uart_flush_txfifo ( 1 ) ;
2015-08-25 16:34:37 +00:00
2016-04-07 07:23:30 +00:00
init_networking ( & phy_info , sdk_info . sta_mac_addr ) ;
2016-04-20 23:54:15 +00:00
2016-06-29 22:18:10 +00:00
srand ( hwrand ( ) ) ; /* seed libc rng */
2016-06-29 23:22:17 +00:00
// Set intial CPU clock speed to 160MHz if necessary
_Static_assert ( configCPU_CLOCK_HZ = = 80000000 | | configCPU_CLOCK_HZ = = 160000000 , " FreeRTOSConfig must define initial clock speed as either 80MHz or 160MHz " ) ;
sdk_system_update_cpu_freq ( configCPU_CLOCK_HZ / 1000000 ) ;
2016-04-20 23:54:15 +00:00
// Call gcc constructor functions
void ( * * ctor ) ( void ) ;
for ( ctor = & __init_array_start ; ctor ! = & __init_array_end ; + + ctor ) {
( * ctor ) ( ) ;
}
2015-08-25 16:34:37 +00:00
tcpip_init ( NULL , NULL ) ;
sdk_wdt_init ( ) ;
2016-10-21 09:40:36 +00:00
xTaskCreate ( sdk_user_init_task , " uiT " , 1024 , 0 , 14 , & sdk_xUserTaskHandle ) ;
2015-08-29 00:45:38 +00:00
vTaskStartScheduler ( ) ;
2015-08-25 16:34:37 +00:00
}
// .Lfunc010 -- .irom0.text+0x710
2015-08-30 19:11:08 +00:00
static void dump_flash_sector ( uint32_t start_sector , uint32_t length ) {
2015-08-25 16:34:37 +00:00
uint8_t * buf ;
int bufsize , i ;
bufsize = ( length + 3 ) & 0xfffc ;
buf = malloc ( bufsize ) ;
2015-08-30 19:11:08 +00:00
sdk_spi_flash_read ( start_sector * sdk_flashchip . sector_size , ( uint32_t * ) buf
, bufsize ) ;
for ( i = 0 ; i < length ; i + + ) {
if ( ( i & 0xf ) = = 0 ) {
if ( i ) {
printf ( " \n " ) ;
}
printf ( " %04x: " , i ) ;
2015-08-25 16:34:37 +00:00
}
2015-08-30 19:11:08 +00:00
printf ( " %02x " , buf [ i ] ) ;
2015-08-25 16:34:37 +00:00
}
printf ( " \n " ) ;
free ( buf ) ;
}
// .Lfunc011 -- .irom0.text+0x790
2016-05-07 08:33:43 +00:00
static __attribute__ ( ( noinline ) ) void dump_flash_config_sectors ( uint32_t start_sector ) {
2015-08-25 16:34:37 +00:00
printf ( " system param error \n " ) ;
2015-08-30 19:11:08 +00:00
// Note: original SDK code didn't dump PHY info
printf ( " phy_info: \n " ) ;
2016-04-07 07:23:30 +00:00
dump_flash_sector ( start_sector , sizeof ( sdk_phy_info_t ) ) ;
2015-08-30 19:11:08 +00:00
printf ( " \n g_ic saved 0: \n " ) ;
dump_flash_sector ( start_sector + 1 , sizeof ( struct sdk_g_ic_saved_st ) ) ;
printf ( " \n g_ic saved 1: \n " ) ;
dump_flash_sector ( start_sector + 2 , sizeof ( struct sdk_g_ic_saved_st ) ) ;
printf ( " \n boot info: \n " ) ;
dump_flash_sector ( start_sector + 3 , BOOT_INFO_SIZE ) ;
2015-08-25 16:34:37 +00:00
}