# DMA Wrapper Module

## Overview

The DMA wrapper module provides a C++ object-oriented interface for ESP-IDF GDMA (General DMA) functionality. This module encapsulates the ESP-IDF GDMA driver functions and provides a clean, easy-to-use API for high-performance data transfers between memory and peripherals.

## Features

- **Channel Management**: Dynamic allocation and deallocation of DMA channels
- **Memory Management**: DMA-capable memory allocation and management
- **Descriptor Management**: Creation and linking of DMA descriptors
- **Transfer Control**: Start, stop, and reset DMA transfers
- **Callback Support**: Event-driven callbacks for transfer completion
- **Memory Copy**: High-performance DMA-based memory copy operations
- **Error Handling**: Comprehensive error checking and logging

## Architecture

### Class Diagram

```
┌─────────────────────────────────────┐
│              Dma                    │
├─────────────────────────────────────┤
│ - m_channels_[8]: gdma_handle_t     │
│ - m_channelAllocated_[8]: bool      │
├─────────────────────────────────────┤
│ + Dma()                             │
│ + ~Dma()                            │
│ + allocateChannel(cfg, hdl): bool   │
│ + deallocateChannel(hdl): bool      │
│ + configureTransfer(hdl, cfg): bool │
│ + startTransfer(hdl): bool          │
│ + stopTransfer(hdl): bool           │
│ + resetChannel(hdl): bool           │
│ + allocateDmaMemory(size): void*    │
│ + freeDmaMemory(ptr): void          │
│ + createDescriptor(...): Desc*      │
│ + linkDescriptors(d1, d2): bool     │
│ + freeDescriptor(desc): void        │
│ + setCallback(hdl, cb, arg): bool   │
│ + enableChannel(hdl): bool          │
│ + disableChannel(hdl): bool         │
│ + getChannelState(hdl): State       │
│ + memcpy(dst, src, size): bool      │
│ + getDefaultChannelConfig(): Config │
│ + getDefaultTransferConfig(): Config│
│ + isDmaCapable(addr): bool          │
│ - findFreeChannelSlot(): int        │
│ - findChannelSlot(hdl): int         │
│ - convertDirection(dir): gdma_dir   │
│ - convertPriority(pri): int         │
└─────────────────────────────────────┘
```

### Enumerations

#### DmaDirection
- `MEM_TO_MEM`: Memory to memory transfer
- `MEM_TO_PERIPH`: Memory to peripheral transfer
- `PERIPH_TO_MEM`: Peripheral to memory transfer
- `PERIPH_TO_PERIPH`: Peripheral to peripheral transfer

#### DmaTransferType
- `SINGLE`: Single transfer
- `BLOCK`: Block transfer
- `LINKED_LIST`: Linked list transfer

#### DmaPriority
- `LOW`: Low priority (0)
- `MEDIUM`: Medium priority (1)
- `HIGH`: High priority (2)
- `HIGHEST`: Highest priority (3)

### Configuration Structures

#### DmaChannelConfig
```cpp
struct DmaChannelConfig {
    DmaPriority priority;       // Channel priority
    uint32_t flags;             // Configuration flags
};
```

#### DmaTransferConfig
```cpp
struct DmaTransferConfig {
    void* srcAddr;              // Source address
    void* dstAddr;              // Destination address
    size_t dataSize;            // Size of data to transfer
    DmaDirection direction;     // Transfer direction
    DmaTransferType type;       // Transfer type
    DmaPriority priority;       // Transfer priority
    bool autoReload;            // Auto-reload mode
    bool enableInterrupt;       // Enable transfer complete interrupt
};
```

#### DmaDescriptor
```cpp
struct DmaDescriptor {
    uint32_t size;          // Size of data to transfer
    uint32_t length;        // Actual length of valid data
    uint32_t sosf : 1;      // Start of sub-frame
    uint32_t eof : 1;       // End of frame
    uint32_t owner : 1;     // Owner (0: CPU, 1: DMA)
    uint32_t reserved : 29; // Reserved bits
    void* buffer;           // Pointer to data buffer
    DmaDescriptor* next;    // Pointer to next descriptor
};
```

## Usage Examples

### Basic DMA Channel Allocation

```cpp
#include "dma.hpp"

// DMA transfer completion callback
void dmaCallback(gdma_channel_handle_t channel, gdma_event_data_t* eventData, void* userData) {
    printf("DMA transfer completed!\n");
}

// Create DMA instance
Dma dma;

// Allocate DMA channel
DmaChannelConfig config = Dma::getDefaultChannelConfig();
config.priority = DmaPriority::HIGH;

gdma_channel_handle_t channel;
dma.allocateChannel(config, &channel);

// Set callback
dma.setCallback(channel, dmaCallback, nullptr);
```

### Memory-to-Memory Transfer

```cpp
// Allocate DMA-capable memory
size_t dataSize = 1024;
void* srcBuffer = dma.allocateDmaMemory(dataSize);
void* dstBuffer = dma.allocateDmaMemory(dataSize);

// Fill source buffer with test data
memset(srcBuffer, 0xAA, dataSize);

// Perform DMA copy
bool success = dma.memcpy(dstBuffer, srcBuffer, dataSize, channel);

if (success) {
    printf("DMA copy completed successfully\n");
}

// Clean up
dma.freeDmaMemory(srcBuffer);
dma.freeDmaMemory(dstBuffer);
dma.deallocateChannel(channel);
```

### Descriptor-Based Transfer

```cpp
// Create source and destination buffers
uint8_t* srcData = (uint8_t*)dma.allocateDmaMemory(512);
uint8_t* dstData = (uint8_t*)dma.allocateDmaMemory(512);

// Fill source with test pattern
for (int i = 0; i < 512; i++) {
    srcData[i] = i & 0xFF;
}

// Create DMA descriptors
DmaDescriptor* desc1 = dma.createDescriptor(srcData, 256, false);
DmaDescriptor* desc2 = dma.createDescriptor(srcData + 256, 256, true);

// Link descriptors for chained transfer
dma.linkDescriptors(desc1, desc2);

// Configure and start transfer
DmaTransferConfig transferConfig = Dma::getDefaultTransferConfig();
transferConfig.srcAddr = srcData;
transferConfig.dstAddr = dstData;
transferConfig.dataSize = 512;
transferConfig.direction = DmaDirection::MEM_TO_MEM;

dma.configureTransfer(channel, transferConfig);
dma.startTransfer(channel);

// Wait for completion (in real application, use callback)
// ...

// Clean up descriptors
dma.freeDescriptor(desc1);
dma.freeDescriptor(desc2);
```

### Large Data Transfer with Chunking

```cpp
void transferLargeData(Dma& dma, void* src, void* dst, size_t totalSize) {
    const size_t chunkSize = 4096;  // 4KB chunks
    size_t remaining = totalSize;
    uint8_t* srcPtr = (uint8_t*)src;
    uint8_t* dstPtr = (uint8_t*)dst;
    
    gdma_channel_handle_t channel;
    DmaChannelConfig config = Dma::getDefaultChannelConfig();
    dma.allocateChannel(config, &channel);
    
    while (remaining > 0) {
        size_t currentChunk = (remaining > chunkSize) ? chunkSize : remaining;
        
        // Perform DMA transfer for current chunk
        bool success = dma.memcpy(dstPtr, srcPtr, currentChunk, channel);
        
        if (!success) {
            printf("DMA transfer failed for chunk\n");
            break;
        }
        
        srcPtr += currentChunk;
        dstPtr += currentChunk;
        remaining -= currentChunk;
        
        printf("Transferred %zu bytes, %zu remaining\n", currentChunk, remaining);
    }
    
    dma.deallocateChannel(channel);
}
```

### Peripheral-to-Memory Transfer

```cpp
// Example: DMA transfer from SPI peripheral to memory
void setupSpiDmaTransfer(Dma& dma) {
    // Allocate receive buffer
    uint8_t* rxBuffer = (uint8_t*)dma.allocateDmaMemory(1024);
    
    // Allocate DMA channel
    gdma_channel_handle_t channel;
    DmaChannelConfig config = Dma::getDefaultChannelConfig();
    dma.allocateChannel(config, &channel);
    
    // Configure transfer from SPI to memory
    DmaTransferConfig transferConfig = {};
    transferConfig.srcAddr = nullptr;  // Will be set by SPI driver
    transferConfig.dstAddr = rxBuffer;
    transferConfig.dataSize = 1024;
    transferConfig.direction = DmaDirection::PERIPH_TO_MEM;
    transferConfig.enableInterrupt = true;
    
    dma.configureTransfer(channel, transferConfig);
    
    // SPI driver would typically handle the actual peripheral configuration
    // and trigger the DMA transfer
    
    // Clean up when done
    dma.freeDmaMemory(rxBuffer);
    dma.deallocateChannel(channel);
}
```

## API Reference

### Constructor/Destructor

- **Dma()**: Initialize DMA wrapper instance
- **~Dma()**: Clean up resources and deallocate all channels

### Channel Management

- **allocateChannel(config, handle)**: Allocate DMA channel
- **deallocateChannel(handle)**: Deallocate DMA channel
- **enableChannel(handle)**: Enable DMA channel
- **disableChannel(handle)**: Disable DMA channel
- **resetChannel(handle)**: Reset DMA channel
- **getChannelState(handle)**: Get current channel state

### Transfer Control

- **configureTransfer(handle, config)**: Configure DMA transfer
- **startTransfer(handle)**: Start DMA transfer
- **stopTransfer(handle)**: Stop DMA transfer

### Memory Management

- **allocateDmaMemory(size, caps)**: Allocate DMA-capable memory
- **freeDmaMemory(ptr)**: Free DMA-capable memory
- **isDmaCapable(addr)**: Check if address is DMA-capable

### Descriptor Management

- **createDescriptor(buffer, size, eof)**: Create DMA descriptor
- **linkDescriptors(desc1, desc2)**: Link two descriptors
- **freeDescriptor(descriptor)**: Free DMA descriptor

### Callback and Events

- **setCallback(handle, callback, userData)**: Set transfer completion callback

### Utility Methods

- **memcpy(dst, src, size, handle)**: High-performance DMA memory copy
- **getDefaultChannelConfig()**: Get default channel configuration
- **getDefaultTransferConfig()**: Get default transfer configuration

## Error Handling

The module provides comprehensive error handling:
- Channel handle validation
- Memory allocation checks
- ESP-IDF error codes are caught and logged
- Return values indicate success/failure for all operations
- Automatic cleanup of allocated resources

## Dependencies

- ESP-IDF GDMA driver (`driver/gdma.h`)
- ESP-IDF DMA utilities (`esp_dma_utils.h`)
- ESP-IDF hardware support (`esp_hw_support`)
- ESP-IDF error handling (`esp_err.h`)
- ESP-IDF logging (`esp_log.h`)
- ESP-IDF heap capabilities (`esp_heap_caps.h`)

## Thread Safety

The DMA wrapper uses ESP-IDF's thread-safe GDMA driver. Multiple tasks can safely use different DMA channels simultaneously.

## Memory Usage

- Fixed memory footprint per instance
- Channel handles stored in fixed-size array
- DMA descriptors allocated from DMA-capable memory
- No hidden dynamic allocations

## Performance Considerations

- Direct ESP-IDF GDMA calls for optimal performance
- DMA transfers are asynchronous and non-blocking
- Use callbacks for transfer completion notification
- DMA-capable memory required for source/destination buffers
- Descriptor chaining for large transfers

## DMA Memory Requirements

### DMA-Capable Memory
- Source and destination buffers must be in DMA-capable memory
- Use `allocateDmaMemory()` or `MALLOC_CAP_DMA` flag
- Check memory capability with `isDmaCapable()`

### Memory Alignment
- Some transfers may require specific alignment
- Descriptors must be in DMA-capable memory
- Buffer addresses should be word-aligned for best performance

## Limitations

- Maximum 8 DMA channels can be managed simultaneously
- Memory buffers must be DMA-capable
- Transfer size limitations depend on ESP32 variant
- Some peripherals have specific DMA requirements
- Descriptor chains limited by available DMA memory

## Use Cases

### High-Speed Data Transfer
- Large memory copies
- Image processing
- Audio/video streaming
- Network packet processing

### Peripheral Integration
- SPI high-speed transfers
- I2S audio streaming
- UART bulk data transfer
- ADC continuous sampling

### Background Processing
- Asynchronous data movement
- Buffer management
- Real-time data streaming
- Multi-buffer ping-pong operations

## Troubleshooting

### Common Issues

1. **Memory Allocation Failures**: Ensure sufficient DMA-capable memory
2. **Transfer Failures**: Check buffer alignment and DMA capability
3. **Performance Issues**: Use appropriate transfer sizes and descriptor chaining
4. **Channel Exhaustion**: Monitor channel allocation and deallocation
5. **Callback Issues**: Ensure callback functions are in IRAM for interrupt context