| /* Memory Debugging */ |
| #ifdef DWC_DEBUG_MEMORY |
| |
| #include "dwc_os.h" |
| #include "dwc_list.h" |
| |
| struct allocation { |
| void *addr; |
| void *ctx; |
| char *func; |
| int line; |
| uint32_t size; |
| int dma; |
| DWC_CIRCLEQ_ENTRY(allocation) entry; |
| }; |
| |
| DWC_CIRCLEQ_HEAD(allocation_queue, allocation); |
| |
| struct allocation_manager { |
| void *mem_ctx; |
| struct allocation_queue allocations; |
| |
| /* statistics */ |
| int num; |
| int num_freed; |
| int num_active; |
| uint32_t total; |
| uint32_t cur; |
| uint32_t max; |
| }; |
| |
| static struct allocation_manager *manager = NULL; |
| |
| static int add_allocation(void *ctx, uint32_t size, char const *func, int line, void *addr, |
| int dma) |
| { |
| struct allocation *a; |
| |
| DWC_ASSERT(manager != NULL, "manager not allocated"); |
| |
| a = __DWC_ALLOC_ATOMIC(manager->mem_ctx, sizeof(*a)); |
| if (!a) { |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| a->func = __DWC_ALLOC_ATOMIC(manager->mem_ctx, DWC_STRLEN(func) + 1); |
| if (!a->func) { |
| __DWC_FREE(manager->mem_ctx, a); |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| DWC_MEMCPY(a->func, func, DWC_STRLEN(func) + 1); |
| a->addr = addr; |
| a->ctx = ctx; |
| a->line = line; |
| a->size = size; |
| a->dma = dma; |
| DWC_CIRCLEQ_INSERT_TAIL(&manager->allocations, a, entry); |
| |
| /* Update stats */ |
| manager->num++; |
| manager->num_active++; |
| manager->total += size; |
| manager->cur += size; |
| |
| if (manager->max < manager->cur) { |
| manager->max = manager->cur; |
| } |
| |
| return 0; |
| } |
| |
| static struct allocation *find_allocation(void *ctx, void *addr) |
| { |
| struct allocation *a; |
| |
| DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { |
| if (a->ctx == ctx && a->addr == addr) { |
| return a; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void free_allocation(void *ctx, void *addr, char const *func, int line) |
| { |
| struct allocation *a = find_allocation(ctx, addr); |
| |
| if (!a) { |
| DWC_ASSERT(0, |
| "Free of address %p that was never allocated or already freed %s:%d", |
| addr, func, line); |
| return; |
| } |
| |
| DWC_CIRCLEQ_REMOVE(&manager->allocations, a, entry); |
| |
| manager->num_active--; |
| manager->num_freed++; |
| manager->cur -= a->size; |
| __DWC_FREE(manager->mem_ctx, a->func); |
| __DWC_FREE(manager->mem_ctx, a); |
| } |
| |
| int dwc_memory_debug_start(void *mem_ctx) |
| { |
| DWC_ASSERT(manager == NULL, "Memory debugging has already started\n"); |
| |
| if (manager) { |
| return -DWC_E_BUSY; |
| } |
| |
| manager = __DWC_ALLOC(mem_ctx, sizeof(*manager)); |
| if (!manager) { |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| DWC_CIRCLEQ_INIT(&manager->allocations); |
| manager->mem_ctx = mem_ctx; |
| manager->num = 0; |
| manager->num_freed = 0; |
| manager->num_active = 0; |
| manager->total = 0; |
| manager->cur = 0; |
| manager->max = 0; |
| |
| return 0; |
| } |
| |
| void dwc_memory_debug_stop(void) |
| { |
| struct allocation *a; |
| |
| dwc_memory_debug_report(); |
| |
| DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { |
| DWC_ERROR("Memory leaked from %s:%d\n", a->func, a->line); |
| free_allocation(a->ctx, a->addr, NULL, -1); |
| } |
| |
| __DWC_FREE(manager->mem_ctx, manager); |
| } |
| |
| void dwc_memory_debug_report(void) |
| { |
| struct allocation *a; |
| |
| DWC_PRINTF("\n\n\n----------------- Memory Debugging Report -----------------\n\n"); |
| DWC_PRINTF("Num Allocations = %d\n", manager->num); |
| DWC_PRINTF("Freed = %d\n", manager->num_freed); |
| DWC_PRINTF("Active = %d\n", manager->num_active); |
| DWC_PRINTF("Current Memory Used = %d\n", manager->cur); |
| DWC_PRINTF("Total Memory Used = %d\n", manager->total); |
| DWC_PRINTF("Maximum Memory Used at Once = %d\n", manager->max); |
| DWC_PRINTF("Unfreed allocations:\n"); |
| |
| DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { |
| DWC_PRINTF(" addr=%p, size=%d from %s:%d, DMA=%d\n", |
| a->addr, a->size, a->func, a->line, a->dma); |
| } |
| } |
| |
| /* The replacement functions */ |
| void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line) |
| { |
| void *addr = __DWC_ALLOC(mem_ctx, size); |
| |
| if (!addr) { |
| return NULL; |
| } |
| |
| if (add_allocation(mem_ctx, size, func, line, addr, 0)) { |
| __DWC_FREE(mem_ctx, addr); |
| return NULL; |
| } |
| |
| return addr; |
| } |
| |
| void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func, |
| int line) |
| { |
| void *addr = __DWC_ALLOC_ATOMIC(mem_ctx, size); |
| |
| if (!addr) { |
| return NULL; |
| } |
| |
| if (add_allocation(mem_ctx, size, func, line, addr, 0)) { |
| __DWC_FREE(mem_ctx, addr); |
| return NULL; |
| } |
| |
| return addr; |
| } |
| |
| void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line) |
| { |
| free_allocation(mem_ctx, addr, func, line); |
| __DWC_FREE(mem_ctx, addr); |
| } |
| |
| void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, |
| char const *func, int line) |
| { |
| void *addr = __DWC_DMA_ALLOC(dma_ctx, size, dma_addr); |
| |
| if (!addr) { |
| return NULL; |
| } |
| |
| if (add_allocation(dma_ctx, size, func, line, addr, 1)) { |
| __DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr); |
| return NULL; |
| } |
| |
| return addr; |
| } |
| |
| void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size, |
| dwc_dma_t *dma_addr, char const *func, int line) |
| { |
| void *addr = __DWC_DMA_ALLOC_ATOMIC(dma_ctx, size, dma_addr); |
| |
| if (!addr) { |
| return NULL; |
| } |
| |
| if (add_allocation(dma_ctx, size, func, line, addr, 1)) { |
| __DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr); |
| return NULL; |
| } |
| |
| return addr; |
| } |
| |
| void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr, |
| dwc_dma_t dma_addr, char const *func, int line) |
| { |
| free_allocation(dma_ctx, virt_addr, func, line); |
| __DWC_DMA_FREE(dma_ctx, size, virt_addr, dma_addr); |
| } |
| |
| #endif /* DWC_DEBUG_MEMORY */ |