2021-07-21 23:51:15 +02:00
/* firmware for STM8S-microcontroller-based HDMI firewall programmer
* Copyright ( C ) 2019 - 2021 King Kévin < kingkevin @ cuvoodoo . info >
2020-09-30 16:59:31 +02:00
* SPDX - License - Identifier : GPL - 3.0 - or - later
*/
# include <stdint.h>
# include <stdbool.h>
2021-07-21 23:39:08 +02:00
# include <stdlib.h>
2020-09-30 16:59:31 +02:00
# include "stm8s.h"
2021-07-21 23:39:08 +02:00
# include "main.h"
2021-08-17 11:31:42 +02:00
# include "softi2c_master.h"
2021-07-21 23:51:15 +02:00
# include "eeprom_blockprog.h"
2021-07-21 23:39:08 +02:00
2021-07-21 23:51:15 +02:00
// blink RUN LED to show error
static bool led_error = false ;
2020-09-30 16:59:31 +02:00
2021-07-21 23:51:15 +02:00
// set when the button is pressed
static volatile bool rw_button_pressed = false ;
// AWU tick count
static volatile uint8_t awu_tick = 0 ;
// to store the current E-EDID (EDID + extension)
static uint8_t edid [ 256 ] ;
// function RAM (code in RAM)
uint8_t f_ram [ 112 + 5 ] ; // use RAM_SEG size in main.map (plus some margin)
// function for saving EDID + extension to EEPROM, to put in RAM
bool ( * ram_eeprom_blockprog ) ( const uint8_t * data , uint16_t length ) ;
// size of RAM segment
volatile uint8_t RAM_SEG_LEN ;
// get the size of the RAM segment
2021-08-17 08:46:16 +02:00
static inline void get_ram_section_length ( ) {
2021-07-21 23:51:15 +02:00
__asm__ ( " mov _RAM_SEG_LEN, #l_RAM_SEG " ) ;
}
// copy functions to RAM
2021-08-17 08:46:16 +02:00
static bool ram_cpy ( ) {
2021-07-21 23:51:15 +02:00
get_ram_section_length ( ) ;
if ( RAM_SEG_LEN > ARRAY_LENGTH ( f_ram ) ) {
return false ;
}
for ( uint8_t i = 0 ; i < RAM_SEG_LEN ; i + + ) {
f_ram [ i ] = ( ( uint8_t * ) eeprom_blockprog ) [ i ] ;
}
ram_eeprom_blockprog = ( bool ( * ) ( const uint8_t * data , uint16_t length ) ) & f_ram ;
return true ;
}
2020-09-30 16:59:31 +02:00
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
static void wait_10us ( uint32_t us10 )
{
us10 = ( ( us10 / ( 1 < < CLK - > CKDIVR . fields . HSIDIV ) ) * 1000 ) / 206 ; // calibrated for 1 ms
2021-08-17 08:47:46 +02:00
while ( us10 - - ) ; // burn energy
2020-09-30 16:59:31 +02:00
}
2021-08-17 08:51:00 +02:00
void putc ( char c )
{
while ( ! UART1 - > SR . fields . TXE ) ; // wait until TX buffer is empty
UART1 - > DR . reg = c ; // put character in buffer to be transmitted
// don't wait until the transmission is complete
}
void puts ( const char * s )
{
if ( NULL = = s ) {
return ;
}
while ( * s ) {
putc ( * s + + ) ;
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
}
}
void putn ( uint8_t n )
{
n & = 0x0f ; // ensure it's a nibble
if ( n < 0xa ) {
putc ( ' 0 ' + n ) ;
} else {
putc ( ' a ' + ( n - 0x0a ) ) ;
}
}
void puth ( uint8_t h )
{
putn ( h > > 4 ) ;
putn ( h & 0x0f ) ;
}
2021-07-21 23:51:15 +02:00
// verify (E-)EDID checksums
// the sum of the bytes (including checksum at the end) must be 0
static bool checksum_ok ( const uint8_t * data , uint16_t length )
{
uint8_t checksum = 0 ;
for ( uint16_t i = 0 ; i < length ; i + + ) {
checksum + = data [ i ] ;
}
return ( 0 = = checksum ) ;
}
// return if the current EDID is valid
/* from HDMI 1.3a specification
* All Sinks shall contain an CEA - 861 - D compliant E - EDID data structure .
* A Source shall read the EDID 1.3 and first CEA Extension to determine the capabilities supported
by the Sink .
* The first 128 bytes of the E - EDID shall contain an EDID 1.3 structure . The contents of this
structure shall also meet the requirements of CEA - 861 - D .
*
* HDMI 2.1 uses CTA - 861 - G
* this uses EDID 1.4 structure
* EDID 1.3 / 1.4 is 128 bytes long , and can point to 128 bytes extension
* EDID 2.0 with its 256 bytes does not seem to be used in HDMI at all
* DisplayID with its variable - length structure meant to replace E - EDID only seems to be used in DisplayPort
* I have no idea how more than 1 extension is supported since technically the ROM is limited to 256 bytes
*/
static uint16_t edid_length ( void )
{
2021-08-17 08:53:09 +02:00
puts ( " EDID check: " ) ;
2021-07-21 23:51:15 +02:00
// check EDID 1.3/1.4 fixed pattern header
if ( 0x00 ! = edid [ 0 ] | | 0xff ! = edid [ 1 ] | | 0xff ! = edid [ 2 ] | | 0xff ! = edid [ 3 ] | | 0xff ! = edid [ 4 ] | | 0xff ! = edid [ 5 ] | | 0xff ! = edid [ 6 ] | | 0x00 ! = edid [ 7 ] ) {
2021-08-17 08:53:09 +02:00
puts ( " invalid header \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 0 ;
}
if ( 1 = = edid [ 18 ] ) { // EDID 1.3/1.4 128-byte structure
if ( checksum_ok ( & edid [ 0 ] , 128 ) ) {
if ( 0 = = edid [ 126 ] ) { // no extension
2021-08-17 08:53:09 +02:00
puts ( " 128 bytes \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 128 ;
} else { // extension available
// the usual extension is CEA EDID Timing Extension (with extension tag 02), but we allow others
// no idea how more than 1 extension is supported
if ( checksum_ok ( & edid [ 128 ] , 128 ) ) {
2021-08-17 08:53:09 +02:00
puts ( " 256 bytes (with extension) \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 256 ;
} else {
2021-08-17 08:53:09 +02:00
puts ( " extension CRC error \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 0 ; // EDID is broken
}
}
} else {
2021-08-17 08:53:09 +02:00
puts ( " CRC error \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 0 ;
}
} else if ( 2 = = edid [ 18 ] ) { // EDID 2.0 256-byte structure
if ( checksum_ok ( & edid [ 0 ] , 256 ) ) {
2021-08-17 08:53:09 +02:00
puts ( " 256 bytes (no extension) \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 256 ;
} else {
2021-08-17 08:53:09 +02:00
puts ( " CRC error \r \n " ) ;
2021-07-21 23:51:15 +02:00
return 0 ;
}
}
return 0 ;
}
// load EDID + extension from EEPROM
static void load_edid ( void )
{
for ( uint16_t i = 0 ; i < ARRAY_LENGTH ( edid ) ; i + + ) {
edid [ i ] = * ( uint8_t * ) ( EEPROM_ADDR + i ) ;
}
}
// save EDID + extension in EEPROM
static bool save_edid ( void )
{
2021-08-23 17:34:55 +02:00
// modify EDID to include the character indicating the firewall
const char firewall_indicator = ' | ' ; // sun character to indicate the firewall
// ensure descriptor 4 is for Display name
if ( ( 0 = = edid [ 108 ] ) & & ( 0 = = edid [ 109 ] ) & & ( 0 = = edid [ 110 ] ) & & ( 0xfc = = edid [ 111 ] ) & & ( 0 = = edid [ 112 ] ) ) { // ensure descriptor 4 is for Display name
uint8_t last_c ; // position of last character
for ( last_c = 113 ; last_c < 126 & & edid [ last_c ] ! = ' \n ' ; last_c + + ) ; // find position for inserting our character
if ( firewall_indicator ! = edid [ last_c - 1 ] ) { // the last character is not yet the sun
if ( last_c > 125 ) { // ensure we insert as the last possible character
last_c = 125 ;
}
edid [ last_c + + ] = firewall_indicator ; // insert sun
if ( last_c < 126 ) {
edid [ last_c + + ] = ' \n ' ; // insert LF to terminate string
}
while ( last_c < 126 ) {
edid [ last_c + + ] = ' ' ; // insert padding space
}
// calculate new checksum
uint8_t checksum = 0 ;
for ( uint8_t i = 0 ; i < 127 ; i + + ) {
checksum + = edid [ i ] ;
}
edid [ 127 ] = ( 256 - checksum ) ;
}
}
2021-07-21 23:51:15 +02:00
return ram_eeprom_blockprog ( edid , edid_length ( ) ) ;
}
2021-08-17 08:54:17 +02:00
// size in byte of a page in the I²C EEPROM (for faster page write)
# define I2C_EEPROM_PAGE_SIZE 16U
2021-07-21 23:51:15 +02:00
// read EDID from I²C memory
// return if succeeded
static bool read_edid ( void )
{
2021-08-17 11:31:42 +02:00
if ( ! softi2c_master_setup ( 10 ) ) {
2021-08-17 08:55:23 +02:00
puts ( " I²C setup failed " ) ;
2021-07-21 23:51:15 +02:00
return false ;
}
2021-08-17 08:55:23 +02:00
// read all pages
// in theory I could read the whole address space at once, but in practice short clock pulses appear after some bytes, corrupting the data flow. I have no idea what the cause of theses glitches is (I even used WFI to reduce EMI as recommended in the errata)
for ( uint16_t i = 0 ; i < ARRAY_LENGTH ( edid ) ; i + = I2C_EEPROM_PAGE_SIZE ) {
const uint8_t address [ ] = { i } ; // address of page to read
uint8_t data [ I2C_EEPROM_PAGE_SIZE ] ; // data from page
bool same = false ; // if the data read is the same
while ( ! same ) { // read until the data is the same
2021-08-17 11:31:42 +02:00
const bool rc = softi2c_master_address_read ( I2C_SLAVE , address , ARRAY_LENGTH ( address ) , data , ARRAY_LENGTH ( data ) ) ; // read I²C EEPROM data
if ( ! rc ) {
puts ( " I²C read failed " ) ;
2021-08-17 08:55:23 +02:00
return false ;
}
// check if the data is the same and copy it to EDID
same = true ;
for ( uint16_t j = 0 ; j < I2C_EEPROM_PAGE_SIZE ; j + + ) {
if ( data [ j ] ! = edid [ i + j ] ) {
same = false ;
}
edid [ i + j ] = data [ j ] ; // save last read data
}
}
2021-07-21 23:51:15 +02:00
}
2021-08-17 11:31:42 +02:00
softi2c_master_release ( ) ; // release I²C again
2021-08-17 08:55:23 +02:00
2021-07-21 23:51:15 +02:00
return true ;
}
// write EDID to I²C memory
// return if succeeded
static bool write_edid ( void )
{
2021-08-17 11:31:42 +02:00
if ( ! softi2c_master_setup ( 10 ) ) {
2021-08-17 08:54:17 +02:00
puts ( " I²C setup failed " ) ;
2021-07-21 23:51:15 +02:00
return false ;
}
2021-08-17 08:54:17 +02:00
// write all page
const uint16_t length = edid_length ( ) ;
for ( uint16_t i = 0 ; i < length ; i + = I2C_EEPROM_PAGE_SIZE ) {
const uint8_t address [ ] = { i } ;
2021-08-17 11:31:42 +02:00
const bool rc = softi2c_master_address_write ( I2C_SLAVE , address , ARRAY_LENGTH ( address ) , & edid [ i ] , I2C_EEPROM_PAGE_SIZE ) ; // write I²C EEPROM page
if ( ! rc ) {
puts ( " I²C write failed " ) ;
2021-08-17 08:54:17 +02:00
return false ;
}
wait_10us ( ( 5 + 1 ) * 100 ) ; // wait 5 ms for the page write to complete
2021-07-21 23:51:15 +02:00
}
2021-08-17 11:31:42 +02:00
softi2c_master_release ( ) ; // release I²C again
2021-07-21 23:51:15 +02:00
return true ;
}
2020-09-30 16:59:31 +02:00
void main ( void )
{
sim ( ) ; // disable interrupts (while we reconfigure them)
CLK - > CKDIVR . fields . HSIDIV = CLK_CKDIVR_HSIDIV_DIV0 ; // don't divide internal 16 MHz clock
CLK - > CKDIVR . fields . CPUDIV = CLK_CKDIVR_CPUDIV_DIV0 ; // don't divide CPU frequency to 16 MHz
while ( ! CLK - > ICKR . fields . HSIRDY ) ; // wait for internal oscillator to be ready
2021-07-21 23:51:15 +02:00
// save power by disabling unused peripheral
2021-08-17 08:49:48 +02:00
CLK_PCKENR1 = CLK_PCKENR1_I2C | CLK_PCKENR1_UART1234 ; // keep I²C and UART
2021-07-21 23:51:15 +02:00
CLK_PCKENR2 = CLK_PCKENR2_AWU ; // keep AWU
// configure LEDs
EDID_LED_PORT - > DDR . reg | = EDID_LED_PIN ; // switch pin to output
EDID_LED_PORT - > CR1 . reg & = ~ EDID_LED_PIN ; // use in open-drain mode
edid_led_off ( ) ; // start with LED off
RUN_LED_PORT - > DDR . reg | = RUN_LED_PIN ; // switch pin to output
RUN_LED_PORT - > CR1 . reg & = ~ RUN_LED_PIN ; // use in open-drain mode
run_led_off ( ) ; // start with LED off
// configure read/write button
RW_BUTTON_PORT - > DDR . reg & = ~ RW_BUTTON_PIN ; // switch pin to input
RW_BUTTON_PORT - > CR1 . reg | = RW_BUTTON_PIN ; // pull up
RW_BUTTON_PORT - > CR2 . reg | = RW_BUTTON_PIN ; // enable external interrupt
EXTI - > CR1 . fields . PDIS = EXTI_FALLING_EDGE ; // interrupt only on falling edges (WARNING hard coded port)
2020-09-30 16:59:31 +02:00
// configure auto-wakeup (AWU) to be able to refresh the watchdog
// 128 kHz LSI used by default in option bytes CKAWUSEL
// we skip measuring the LS clock frequency since there is no need to be precise
AWU - > TBR . fields . AWUTB = 10 ; // interval range: 128-256 ms
AWU - > APR . fields . APR = 0x3e ; // set time to 256 ms
AWU_CSR | = AWU_CSR_AWUEN ; // enable AWU (start only when entering wait or active halt mode)
2021-08-17 08:49:48 +02:00
// configure UART for debug output
UART1 - > CR1 . fields . M = 0 ; // 8 data bits
UART1 - > CR3 . fields . STOP = 0 ; // 1 stop bit
UART1 - > BRR2 . reg = 0x0B ; // set baud rate to 115200 (at 16 MHz)
UART1 - > BRR1 . reg = 0x08 ; // set baud rate to 115200 (at 16 MHz)
UART1 - > CR2 . fields . TEN = 1 ; // enable TX
2021-07-21 23:51:15 +02:00
// load function in RAM
ram_cpy ( ) ;
2020-09-30 16:59:31 +02:00
// configure independent watchdog (very loose, just it case the firmware hangs)
IWDG - > KR . fields . KEY = IWDG_KR_KEY_REFRESH ; // reset watchdog
IWDG - > KR . fields . KEY = IWDG_KR_KEY_ENABLE ; // start watchdog
IWDG - > KR . fields . KEY = IWDG_KR_KEY_ACCESS ; // allows changing the prescale
IWDG - > PR . fields . PR = IWDG_PR_DIV256 ; // set prescale to longest time (1.02s)
IWDG - > KR . fields . KEY = IWDG_KR_KEY_REFRESH ; // reset watchdog
2021-08-17 08:53:09 +02:00
puts ( " \r \n CuVoodoo HDMI firewall programmer ready \r \n " ) ;
2021-07-21 23:51:15 +02:00
// erase saved EDID when button is pressed on boot
if ( 0 = = ( RW_BUTTON_PORT - > IDR . reg & RW_BUTTON_PIN ) ) { // button is pressed while booting
for ( uint16_t i = 0 ; i < ARRAY_LENGTH ( edid ) ; i + + ) {
edid [ i ] = 0 ; // create empty EDID
}
ram_eeprom_blockprog ( edid , ARRAY_LENGTH ( edid ) ) ; // erase EDID
2021-08-17 08:53:09 +02:00
puts ( " EEPROM EDID erased \r \n " ) ;
2021-07-21 23:51:15 +02:00
}
load_edid ( ) ; // load EDID from EEPROM
bool edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid
2021-08-17 08:53:09 +02:00
if ( edid_valid ) {
puts ( " EEPROM EDID valid \r \n " ) ;
} else {
puts ( " EEPROM EDID not valid \r \n " ) ;
}
2021-07-21 23:51:15 +02:00
2020-09-30 16:59:31 +02:00
rim ( ) ; // re-enable interrupts
bool action = false ; // if an action has been performed
while ( true ) {
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
2021-07-21 23:51:15 +02:00
// handle button press
if ( rw_button_pressed ) {
2021-08-23 17:34:25 +02:00
wait_10us ( 10000 ) ; // wait 100 ms for the noise to be gone
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
2021-07-21 23:51:15 +02:00
if ( 0 = = ( RW_BUTTON_PORT - > IDR . reg & RW_BUTTON_PIN ) ) { // ensure the button is pressed (the pull-up is really weak)
2021-08-17 08:49:04 +02:00
run_led_off ( ) ; // clear RUN LED to see result at the end
2021-07-21 23:51:15 +02:00
uint8_t press_duration = 1 ; // start counting how long the button is pressed
while ( 0 = = ( RW_BUTTON_PORT - > IDR . reg & RW_BUTTON_PIN ) ) { // wait until button is depressed
wait_10us ( 10000 ) ; // wait for 100 ms
press_duration + + ;
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
2021-08-09 11:21:43 +02:00
if ( press_duration > = 30 ) { // 3 seconds already passed
break ; // stop waiting for button to be released
}
2021-07-21 23:51:15 +02:00
}
led_error = false ; // reset error state
if ( press_duration < 30 ) { // less than 3 sec
2021-08-17 08:53:09 +02:00
puts ( " read I²C EDID: " ) ;
2021-07-21 23:51:15 +02:00
run_led_on ( ) ; // indicate we started
if ( read_edid ( ) ) { // read EDID from I²C
2021-08-17 08:53:09 +02:00
puts ( " OK \r \n " ) ;
2021-07-21 23:51:15 +02:00
edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid
if ( edid_valid ) { // read EDID is valid
2021-08-17 08:53:09 +02:00
puts ( " I²C EDID valid \r \n " ) ;
2021-07-21 23:51:15 +02:00
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
2021-08-17 08:56:33 +02:00
/*
puts ( " EDID data: \r \n " ) ;
for ( uint16_t i = 0 ; i < ARRAY_LENGTH ( edid ) ; i + + ) {
puth ( edid [ i ] ) ;
putc ( ' ' ) ;
}
puts ( " \r \n " ) ;
*/
if ( save_edid ( ) ) { ; // save to EEPROM
puts ( " I²C EDID saved to EEPROM \r \n " ) ;
} else {
led_error = true ; // indicate write error
puts ( " could not save EDID to EEPROM \r \n " ) ;
load_edid ( ) ; // re-load EDID from EEPROM
edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid
}
2021-07-21 23:51:15 +02:00
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
} else { // read EDID is not valid
2021-08-17 08:53:09 +02:00
puts ( " I²C EDID not valid, reloading from EEPROM \r \n " ) ;
2021-07-21 23:51:15 +02:00
led_error = true ; // indicate read error
load_edid ( ) ; // re-load EDID from EEPROM
edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid
}
} else { // read error
2021-08-17 08:53:09 +02:00
puts ( " \r \n " ) ; // error should have been printed
2021-07-21 23:51:15 +02:00
led_error = true ; // indicate read error
load_edid ( ) ; // re-load EDID from EEPROM
edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid
}
} else { // button pressed > 3s
run_led_on ( ) ; // indicate we started
2021-08-17 08:57:48 +02:00
if ( edid_valid ) { // the current EDID we have is valid and is ready to be written
led_error = true ; // error indication will be cleared if everything succeeded
puts ( " writing I²C EDID: " ) ;
if ( write_edid ( ) ) { // write EDID to I²C EEPROM succeeded
puts ( " OK \r \n " ) ;
const uint16_t target_length = edid_length ( ) ; // remember the length of the EDID we programmed
puts ( " reading back EDID: " ) ;
if ( read_edid ( ) ) { // read EDID back from I²C
puts ( " OK \r \n " ) ;
if ( edid_length ( ) = = target_length ) { // ensure the EDID length between what we programmed and read is the same
puts ( " verifying EDID: " ) ;
bool identical_edid = true ; // to find out if EDID is identical
for ( uint16_t i = 0 ; i < target_length ; i + + ) { // compare EDID
if ( edid [ i ] ! = * ( uint8_t * ) ( EEPROM_ADDR + i ) ) { // ensure the data is the same
identical_edid = false ; // EDID is not identical
break ; // stop comparing
}
}
if ( identical_edid ) { // EDID has been successfully programmed
puts ( " OK \r \n " ) ;
led_error = false ; // no error happened
} else {
puts ( " failed (wrong data) \r \n " ) ;
}
} else {
puts ( " failed (not length) \r \n " ) ;
}
} else {
puts ( " \r \n " ) ; // error should have been printed
}
puts ( " reloading EEPROM EDID \r \n " ) ;
load_edid ( ) ; // re-load EDID from EEPROM after it has been overwritten from the read
edid_valid = ( 0 ! = edid_length ( ) ) ; // verify if EDID is valid (should be as before
} else {
puts ( " \r \n " ) ; // error should have been printed
}
2021-07-21 23:51:15 +02:00
} else {
2021-08-17 08:53:09 +02:00
puts ( " \r \n " ) ; // error should have been printed
2021-07-21 23:51:15 +02:00
led_error = true ; // we can't program an invalid EDID
}
}
}
action = true ; // remember we performed an action
rw_button_pressed = false ; // clear flag
}
// update LED
if ( ( awu_tick & 0x7 ) < 4 ) { // on period of blink (every second)
edid_led_on ( ) ; // on period
if ( led_error ) {
run_led_on ( ) ; // start blinking
}
} else {
if ( ! edid_valid ) { // EDID not valid
edid_led_off ( ) ; // blink to let user know
}
if ( led_error ) {
run_led_off ( ) ; // blink to indicate error
}
}
2020-09-30 16:59:31 +02:00
if ( action ) { // something has been performed, check if other flags have been set meanwhile
action = false ; // clear flag
} else { // nothing down
2021-07-21 23:51:15 +02:00
wfi ( ) ; // go to sleep (wait for any interrupt, also starting AWU)
2020-09-30 16:59:31 +02:00
}
}
}
void awu ( void ) __interrupt ( IRQ_AWU ) // auto wakeup
{
volatile uint8_t awuf = AWU_CSR ; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization)
2021-07-21 23:51:15 +02:00
awu_tick + + ; // increment tick count
2020-09-30 16:59:31 +02:00
// let the main loop kick the dog
}
2021-07-21 23:51:15 +02:00
void rw_button_isr ( void ) __interrupt ( IRQ_EXTI3 ) // button pressed
{
rw_button_pressed = true ; // notify main loop
}