#include "dma.h"
#include "dbg.h"

/* Bitmap tracking free pages in DMA region.
 * 4096 pages = 4096 bits = 128 uint32_t = 512 bytes */
#define DMA_NUM_PAGES   (DMA_SIZE / DMA_PAGE_SIZE)
#define DMA_BITMAP_WORDS ((DMA_NUM_PAGES + 31) / 32)

static uint32_t dma_bitmap[DMA_BITMAP_WORDS];
static uint32_t dma_alloc_count;  /* for stats */

/* Identity-mapped region: virt = phys + 0xFFFF800000000000 (high half) */
#define DMA_VIRT(phys)  ((void *)(uintptr_t)((phys) + 0xFFFF800000000000ULL))
#define DMA_PHYS(virt)  ((uint32_t)(uintptr_t)(virt) - 0xFFFF800000000000ULL)

static inline void dma_set_bit(uint32_t idx)
{
    dma_bitmap[idx / 32] |= (1u << (idx % 32));
}

static inline void dma_clear_bit(uint32_t idx)
{
    dma_bitmap[idx / 32] &= ~(1u << (idx % 32));
}

static inline int dma_test_bit(uint32_t idx)
{
    return (dma_bitmap[idx / 32] >> (idx % 32)) & 1;
}

void dma_init(void)
{
    /* Mark all pages as free */
    for(uint32_t i = 0; i < DMA_BITMAP_WORDS; i++)
        dma_bitmap[i] = 0;

    dma_alloc_count = 0;
    dbg_printf("[dma] DMA allocator: base=0x%x size=%u (%u pages)\n",
               DMA_BASE, DMA_SIZE, DMA_NUM_PAGES);
}

/* Find N contiguous free pages */
static uint32_t dma_find_contiguous(uint32_t num)
{
    if(num == 0) return DMA_NUM_PAGES;  /* invalid */
    if(num == 1) {
        /* Quick scan for single page */
        for(uint32_t i = 0; i < DMA_NUM_PAGES; i++) {
            if(!dma_test_bit(i)) return i;
        }
        return DMA_NUM_PAGES;
    }

    /* Multi-page: scan for contiguous run */
    uint32_t run = 0;
    uint32_t start = 0;
    for(uint32_t i = 0; i < DMA_NUM_PAGES; i++) {
        if(!dma_test_bit(i)) {
            if(run == 0) start = i;
            run++;
            if(run == num) return start;
        } else {
            run = 0;
        }
    }
    return DMA_NUM_PAGES;
}

void *dma_alloc_pages(uint32_t num_pages, uint32_t *phys_out)
{
    if(num_pages == 0) return 0;

    uint32_t idx = dma_find_contiguous(num_pages);
    if(idx >= DMA_NUM_PAGES) {
        dbg_printf("[dma] alloc FAILED: %u pages\n", num_pages);
        return 0;
    }

    /* Mark pages as used */
    for(uint32_t i = 0; i < num_pages; i++)
        dma_set_bit(idx + i);

    dma_alloc_count += num_pages;

    uint32_t phys = DMA_BASE + idx * DMA_PAGE_SIZE;
    if(phys_out) *phys_out = phys;
    return DMA_VIRT(phys);
}

void *dma_alloc_page(uint32_t *phys_out)
{
    return dma_alloc_pages(1, phys_out);
}

void dma_free(void *addr, uint32_t num_pages)
{
    if(!addr || num_pages == 0) return;

    uint32_t phys = DMA_PHYS(addr);
    if(phys < DMA_BASE || phys + num_pages * DMA_PAGE_SIZE > DMA_BASE + DMA_SIZE) {
        dbg_printf("[dma] free out of range: 0x%x\n", phys);
        return;
    }

    uint32_t idx = (phys - DMA_BASE) / DMA_PAGE_SIZE;
    for(uint32_t i = 0; i < num_pages; i++)
        dma_clear_bit(idx + i);

    dma_alloc_count -= num_pages;
}

void *dma_alloc(uint32_t size, uint32_t *phys_out)
{
    uint32_t pages = (size + DMA_PAGE_SIZE - 1) / DMA_PAGE_SIZE;
    return dma_alloc_pages(pages, phys_out);
}

void dma_free_size(void *addr, uint32_t size)
{
    uint32_t pages = (size + DMA_PAGE_SIZE - 1) / DMA_PAGE_SIZE;
    dma_free(addr, pages);
}
