Fix Memory leak in addApbChangeCallback() (#3560)

* `ledcWriteTone()` added a `apbcallback()` evertime the tone value was non zero.  
* `addApbChangeCallback()` did not detect duplicate callbacks.
* changed the apbcallback list to a double link to support roll forward, roll back execution.  This made the sequences of clock change callback start with the newest registered -> to oldest on the `before` then oldest -> newest after the clock change.  This made the UART debug log output have minimal gibberish during the clock change.
* change how the UART callback handled the MUTEX because if any `apbchangeCallback()` executed a `log_x()` a deadlock would occur.

This fixes #3555
This commit is contained in:
chuck todd 2019-12-30 12:35:29 -07:00 committed by Me No Dev
parent cec3fca4ad
commit 9ad860758c
3 changed files with 76 additions and 53 deletions

View File

@ -28,6 +28,7 @@
#include "esp32-hal-cpu.h" #include "esp32-hal-cpu.h"
typedef struct apb_change_cb_s { typedef struct apb_change_cb_s {
struct apb_change_cb_s * prev;
struct apb_change_cb_s * next; struct apb_change_cb_s * next;
void * arg; void * arg;
apb_change_cb_t cb; apb_change_cb_t cb;
@ -53,10 +54,20 @@ static void triggerApbChangeCallback(apb_change_ev_t ev_type, uint32_t old_apb,
initApbChangeCallback(); initApbChangeCallback();
xSemaphoreTake(apb_change_lock, portMAX_DELAY); xSemaphoreTake(apb_change_lock, portMAX_DELAY);
apb_change_t * r = apb_change_callbacks; apb_change_t * r = apb_change_callbacks;
if( r != NULL ){
if(ev_type == APB_BEFORE_CHANGE )
while(r != NULL){ while(r != NULL){
r->cb(r->arg, ev_type, old_apb, new_apb); r->cb(r->arg, ev_type, old_apb, new_apb);
r=r->next; r=r->next;
} }
else { // run backwards through chain
while(r->next != NULL) r = r->next; // find first added
while( r != NULL){
r->cb(r->arg, ev_type, old_apb, new_apb);
r=r->prev;
}
}
}
xSemaphoreGive(apb_change_lock); xSemaphoreGive(apb_change_lock);
} }
@ -68,6 +79,7 @@ bool addApbChangeCallback(void * arg, apb_change_cb_t cb){
return false; return false;
} }
c->next = NULL; c->next = NULL;
c->prev = NULL;
c->arg = arg; c->arg = arg;
c->cb = cb; c->cb = cb;
xSemaphoreTake(apb_change_lock, portMAX_DELAY); xSemaphoreTake(apb_change_lock, portMAX_DELAY);
@ -75,18 +87,20 @@ bool addApbChangeCallback(void * arg, apb_change_cb_t cb){
apb_change_callbacks = c; apb_change_callbacks = c;
} else { } else {
apb_change_t * r = apb_change_callbacks; apb_change_t * r = apb_change_callbacks;
if(r->cb != cb || r->arg != arg){ // look for duplicate callbacks
while(r->next){ while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
r = r->next; if (r) {
if(r->cb == cb && r->arg == arg){ log_e("duplicate func=%08X arg=%08X",c->cb,c->arg);
free(c); free(c);
goto unlock_and_exit; xSemaphoreGive(apb_change_lock);
return false;
}
else {
c->next = apb_change_callbacks;
apb_change_callbacks-> prev = c;
apb_change_callbacks = c;
} }
} }
r->next = c;
}
}
unlock_and_exit:
xSemaphoreGive(apb_change_lock); xSemaphoreGive(apb_change_lock);
return true; return true;
} }
@ -95,24 +109,21 @@ bool removeApbChangeCallback(void * arg, apb_change_cb_t cb){
initApbChangeCallback(); initApbChangeCallback();
xSemaphoreTake(apb_change_lock, portMAX_DELAY); xSemaphoreTake(apb_change_lock, portMAX_DELAY);
apb_change_t * r = apb_change_callbacks; apb_change_t * r = apb_change_callbacks;
// look for matching callback
while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
if ( r == NULL ) { if ( r == NULL ) {
log_e("not found func=%08X arg=%08X",cb,arg);
xSemaphoreGive(apb_change_lock); xSemaphoreGive(apb_change_lock);
return false; return false;
} }
if(r->cb == cb && r->arg == arg){ else {
// patch links
if(r->prev) r->prev->next = r->next;
else { // this is first link
apb_change_callbacks = r->next; apb_change_callbacks = r->next;
}
if(r->next) r->next->prev = r->prev;
free(r); free(r);
} else {
while(r->next && (r->next->cb != cb || r->next->arg != arg)){
r = r->next;
}
if(r->next == NULL || r->next->cb != cb || r->next->arg != arg){
xSemaphoreGive(apb_change_lock);
return false;
}
apb_change_t * c = r->next;
r->next = c->next;
free(c);
} }
xSemaphoreGive(apb_change_lock); xSemaphoreGive(apb_change_lock);
return true; return true;

View File

@ -28,7 +28,7 @@
#else #else
#define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS) #define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS)
#define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock) #define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock)
xSemaphoreHandle _ledc_sys_lock; xSemaphoreHandle _ledc_sys_lock = NULL;
#endif #endif
/* /*
@ -55,18 +55,19 @@ xSemaphoreHandle _ledc_sys_lock;
static void _on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){ static void _on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){
if(ev_type == APB_AFTER_CHANGE && old_apb != new_apb){ if(ev_type == APB_AFTER_CHANGE && old_apb != new_apb){
uint32_t iarg = (uint32_t)arg; uint16_t iarg = *(uint16_t*)arg;
uint8_t chan = iarg; uint8_t chan = 0;
uint8_t group=(chan/8), timer=((chan/2)%4);
old_apb /= 1000000; old_apb /= 1000000;
new_apb /= 1000000; new_apb /= 1000000;
while(iarg){ // run though all active channels, adjusting timing configurations
if(iarg & 1) {// this channel is active
uint8_t group=(chan/8), timer=((chan/2)%4);
if(LEDC_TIMER(group, timer).conf.tick_sel){ if(LEDC_TIMER(group, timer).conf.tick_sel){
LEDC_MUTEX_LOCK(); LEDC_MUTEX_LOCK();
uint32_t old_div = LEDC_TIMER(group, timer).conf.clock_divider; uint32_t old_div = LEDC_TIMER(group, timer).conf.clock_divider;
uint32_t div_num = (new_apb * old_div) / old_apb; uint32_t div_num = (new_apb * old_div) / old_apb;
if(div_num > LEDC_DIV_NUM_HSTIMER0_V){ if(div_num > LEDC_DIV_NUM_HSTIMER0_V){
new_apb = REF_CLK_FREQ / 1000000; div_num = ((REF_CLK_FREQ /1000000) * old_div) / old_apb;
div_num = (new_apb * old_div) / old_apb;
if(div_num > LEDC_DIV_NUM_HSTIMER0_V) { if(div_num > LEDC_DIV_NUM_HSTIMER0_V) {
div_num = LEDC_DIV_NUM_HSTIMER0_V;//lowest clock possible div_num = LEDC_DIV_NUM_HSTIMER0_V;//lowest clock possible
} }
@ -77,6 +78,13 @@ static void _on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old_apb
LEDC_TIMER(group, timer).conf.clock_divider = div_num; LEDC_TIMER(group, timer).conf.clock_divider = div_num;
LEDC_MUTEX_UNLOCK(); LEDC_MUTEX_UNLOCK();
} }
else {
log_d("using REF_CLK chan=%d",chan);
}
}
iarg = iarg >> 1;
chan++;
}
} }
} }
@ -85,11 +93,14 @@ static void _ledcSetupTimer(uint8_t chan, uint32_t div_num, uint8_t bit_num, boo
{ {
uint8_t group=(chan/8), timer=((chan/2)%4); uint8_t group=(chan/8), timer=((chan/2)%4);
static bool tHasStarted = false; static bool tHasStarted = false;
static uint16_t _activeChannels = 0;
if(!tHasStarted) { if(!tHasStarted) {
tHasStarted = true; tHasStarted = true;
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_LEDC_CLK_EN); DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_LEDC_CLK_EN);
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_LEDC_RST); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_LEDC_RST);
LEDC.conf.apb_clk_sel = 1;//LS use apb clock LEDC.conf.apb_clk_sel = 1;//LS use apb clock
addApbChangeCallback((void*)&_activeChannels, _on_apb_change);
#if !CONFIG_DISABLE_HAL_LOCKS #if !CONFIG_DISABLE_HAL_LOCKS
_ledc_sys_lock = xSemaphoreCreateMutex(); _ledc_sys_lock = xSemaphoreCreateMutex();
#endif #endif
@ -105,8 +116,7 @@ static void _ledcSetupTimer(uint8_t chan, uint32_t div_num, uint8_t bit_num, boo
LEDC_TIMER(group, timer).conf.rst = 1;//This bit is used to reset timer the counter will be 0 after reset. LEDC_TIMER(group, timer).conf.rst = 1;//This bit is used to reset timer the counter will be 0 after reset.
LEDC_TIMER(group, timer).conf.rst = 0; LEDC_TIMER(group, timer).conf.rst = 0;
LEDC_MUTEX_UNLOCK(); LEDC_MUTEX_UNLOCK();
uint32_t iarg = chan; _activeChannels |= (1 << chan); // mark as active for APB callback
addApbChangeCallback((void*)iarg, _on_apb_change);
} }
//max div_num 0x3FFFF (262143) //max div_num 0x3FFFF (262143)

View File

@ -217,7 +217,6 @@ uart_t* uartBegin(uint8_t uart_nr, uint32_t baudrate, uint32_t config, int8_t rx
if(txPin != -1) { if(txPin != -1) {
uartAttachTx(uart, txPin, inverted); uartAttachTx(uart, txPin, inverted);
} }
addApbChangeCallback(uart, uart_on_apb_change); addApbChangeCallback(uart, uart_on_apb_change);
return uart; return uart;
} }
@ -377,18 +376,21 @@ static void uart_on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old
uart->dev->int_clr.val = 0xffffffff; uart->dev->int_clr.val = 0xffffffff;
// read RX fifo // read RX fifo
uint8_t c; uint8_t c;
BaseType_t xHigherPriorityTaskWoken; // BaseType_t xHigherPriorityTaskWoken;
while(uart->dev->status.rxfifo_cnt != 0 || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) { while(uart->dev->status.rxfifo_cnt != 0 || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
c = uart->dev->fifo.rw_byte; c = uart->dev->fifo.rw_byte;
if(uart->queue != NULL && !xQueueIsQueueFullFromISR(uart->queue)) { if(uart->queue != NULL ) {
xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken); xQueueSend(uart->queue, &c, 1); //&xHigherPriorityTaskWoken);
} }
} }
UART_MUTEX_UNLOCK();
// wait TX empty // wait TX empty
while(uart->dev->status.txfifo_cnt || uart->dev->status.st_utx_out); while(uart->dev->status.txfifo_cnt || uart->dev->status.st_utx_out);
} else { } else {
//todo: //todo:
// set baudrate // set baudrate
UART_MUTEX_LOCK();
uint32_t clk_div = (uart->dev->clk_div.div_int << 4) | (uart->dev->clk_div.div_frag & 0x0F); uint32_t clk_div = (uart->dev->clk_div.div_int << 4) | (uart->dev->clk_div.div_frag & 0x0F);
uint32_t baud_rate = ((old_apb<<4)/clk_div); uint32_t baud_rate = ((old_apb<<4)/clk_div);
clk_div = ((new_apb<<4)/baud_rate); clk_div = ((new_apb<<4)/baud_rate);