Analysis of DMA in Linux
DMA is a hardware mechanism that enables bidirectional data transmission between peripherals and system memory without the involvement of the CPU.. Using DMA can remove the system CPU from the actual I/O data transmission process, thus greatly improving the system throughput. DMA is often closely related to the hardware architecture, especially the peripheral bus technology.
I. DMA controller hardware structure
DMA allows direct transmission of I/O data between the peripheral device and the main memory. DMA depends on the system. Each architecture has different DMA transmission and different programming interfaces.
Data transmission can be triggered in either of the following ways:Software request data, AnotherAsynchronous transmission by hardware.
A -- software request data
The call steps can be summarized as follows (read is used as an example ):
(1) When a process calls read, the driver method allocates a DMA buffer and then instructs the hardware to transmit its data. The process goes to sleep.
(2) Hardware writes data to the DMA buffer and causes an interruption upon completion.
(3) The Interrupt Processing Program obtains the input data, responds to the interruption, and finally wakes up the process. The process can now read data.
B: asynchronous transmission by hardware
This occurs when DMA is used asynchronously. Take the data collection device as an example:
(1) The hardware sends out an interruption to notify that new data has arrived.
(2) The interrupt handler allocates a DMA buffer.
(3) The peripheral device writes data to the buffer zone and then sends another interrupt upon completion.
(4) The processing program uses DMA to distribute new data and wake up any related process.
The same is true for Nic transmission. The NIC hasLoop buffer (usually called DMA ring buffer)Built on memory shared with the processor. Each input packet is placed in the next available buffer zone in the ring buffer and is interrupted. The driver then transmits the network data packet to other parts of the kernel for processing and places a new DMA buffer in the ring buffer.
Drivers allocate DMA buffers during initialization and use them until they stop running..
Ii. address used by the DMA Channel
DMA ChannelDma_chan structure arrayThis structure is listed in kernel/dma. c as follows:
struct dma_chan {int lock;const char *device_id;}; static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {[4] = { 1, "cascade" },};
If dma_chan_busy [n]. lock! = 0 indicates busy, DMA0 is reserved for DRAM update, and DMA4 is used for Cascade. The main problem with the DMA buffer is that when it is larger than one page, it must occupy consecutive pages in the physical memory.
Since DMA requires continuous memory, the memory is allocated during boot or the top of the physical RAM is reserved for the buffer zone. You can pass the "mem =" parameter to the kernel during boot to reserve the top of the RAM. For example, if the system has 32 MB memory, the parameter "mem = 31 M" prevents the kernel from using the top 1 MB. Later, the module can use the following code to access the reserved memory:
Dmabuf = ioremap (0x1F00000/* 31 M */, 0x100000/* 1 M */);
Method for allocating DMA space, Code callKmalloc (GFP_ATOMIC)Wait until the failure occurs, wait for the kernel to release several pages, and then allocate again. The appearance of the DMA buffer composed of consecutive pages will eventually be discovered.
A device driver using DMA usually communicates with hardware connected to the interface bus, which uses physical addresses while program code uses virtual addresses. DMA-based hardware uses bus addresses instead of physical addresses. Sometimes, the interface bus is connected by ing I/O addresses to Bridge Circuits with different physical addresses. Some systems even have a page ing scheme that enables any page to behave consecutively on the peripheral bus.
When the driver needs to send address information to an I/O device (such as an expansion board or DMA controller), it must use the virt_to_bus conversion to receive address information from the hardware connected to the bus, bus_to_virt must be used.
Iii. DMA Operation Functions
The main tasks of writing a DMA driver include:: DMA channel application, DMA interrupt application, control register settings, mounted DMA wait queue, clear DMA interrupt, release DMA Channel
Because the DMA controller is a system-level resource, the kernel assists in processing this resource. Kernel usageDMA RegistryA request/release mechanism is provided for the DMA channel, and a set of functions are provided to configure channel information in the DMA controller.
Key functions (linux/arch/arm/mach-s3c2410/dma. c)
Int s3c2410_request_dma (const char * device_id, dmach_t channel, dma_callback_t write_cb, dma_callback_t read_cb) (bytes);/* Function Description: Apply for a channel's DMA resource and fill in the shard data structure, apply for a DMA interrupt. Input parameters: device_id DMA device name; channel number; callback function after write_cb DMA write operation is completed; callback function after read_cb DMA read operation is completed output parameters: If the channel is used, otherwise, 0 */int s3c2410_dma_queue_buffer (dmach_t channel, void * buf_id, dma_addr_t data, int size, int write) (s3c2410_dma_stop) is returned./* function description: this is the most critical function for DMA operations. It completes a series of actions: allocates and initializes a DMA kernel buffer control structure, inserts it into the DMA wait queue, and sets the content of the DMA control register, wait for the DMA Operation to trigger input parameters: channel number; buf_id; buffer ID dma_addr_t data DMA data buffer start physical address; size DMA data buffer size; write or Read operation output parameter: if the operation is successful, 0 is returned. Otherwise, error code */int s3c2410_dma_stop (dmach_t channel) is returned. // Function Description: Stop the DMA Operation. Int s3c2410_dma_flush_all (dmach_t channel) // Function Description: release all memory resources applied by the DMA channel void s3c2410_free_dma (dmach_t channel) // Function Description: Release the DMA channel
Iv. DMA ing
A DMA ing is a combined operation that allocates a DMA buffer and generates a device-accessible address for the buffer.. Generally, the virt_to_bus function is called to address the address on the device bus, but some hardware ing registers are also set in the bus hardware. Mapping register is a virtual memory equivalent to a peripheral device. On systems using these registers, the peripheral device has a relatively small dedicated address segment, where DMA can be executed. Through the ing register, these addresses are remapped to the system RAM. The ing register has some good features, including making the Scattered pages appear continuous in the device address space. However, not all architectures have ing registers. In particular, the PC platform does not have ing registers.
In some cases, setting a valid address for the device also means creating a bounce buffer. For example, when the driver attempts to execute DMA on an address (a high-end memory address) that cannot be accessed by the peripheral device, the rebound buffer is created. Then, data is copied to the rebound buffer or from the rebound buffer as needed.
Based on the expected retention time of the DMA buffer, the PCI Code distinguishes two types of DMA ing:
A -- consistent DMA ing
They exist in the driver's lifecycle. A buffer with consistent ing must be accessible to both the CPU and peripheral devices. When the buffer is written by the processor, it can be immediately read by the device without the cache effect, and vice versa, use the pci_alloc_consistent function to create a consistent ing.
B -- stream DMA ing
Streaming DMA ing is set for a single operation. It maps an address of the processor virtual space so that it can be accessed by devices. Streaming ing instead of consistent ing should be used whenever possible. This is because on systems that support consistent ing, each DMA ing uses one or more ing registers on the bus. Consistent mappings with a long life cycle occupy these registers for a long time, even if they are not used. Use the dma_map_single function to create a streaming ing.
1. Establish consistent DMA ing
FunctionPci_alloc_consistentHandle the allocation and ing of the buffer, function analysis is as follows (in include/asm-generic/pci-dma-compat.h ):
static inline void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle){return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev, size, dma_handle, GFP_ATOMIC);}
StructureDma_coherent_memDefines the address, size, and Id of memory for DMA consistent ing. The structure dma_coherent_mem is listed as follows (in arch/i386/kernel/pci-dma.c ):
struct dma_coherent_mem {void*virt_base;u32device_base;intsize;intflags;unsigned long*bitmap;};
FunctionDma_alloc_coherentThe size byte is allocated to the same memory in the region. The obtained dma_handle is the address pointer to the allocated region. This address serves as the physical base address of the region. Dma_handle is an unsigned integer with the same bit width as the bus. The dma_alloc_coherent function is analyzed as follows (in arch/i386/kernel/pci-dma.c ):
Void * dma_alloc_coherent (struct device * dev, size_t size, dma_addr_t * dma_handle, int green) {void * ret; // if the device is used, obtain the dma memory area of the device, that is, mem = dev-> dma_memstruct dma_coherent_mem * mem = dev? Dev-> dma_mem: NULL; int order = get_order (size); // convert the size to order, that is, // ignore a specific region and ignore the two identifiers ~ (_ GFP_DMA | _ GFP_HIGHMEM); if (mem) {// The DMA ing of the device, mem = dev-> dma_mem // find the page int page corresponding to mem = bitmap_find_free_region (mem-> bitmap, mem-> size, order); if (page> = 0) {* dma_handle = mem-> device_base + (page <PAGE_SHIFT); ret = mem-> pai_base + (page <PAGE_SHIFT); memset (ret, 0, size ); return ret;} if (mem-> flags & DMA_MEMORY_EXCLUSIVE) return NULL;} // if (dev = NULL | (dev-> coherent_dma_mask < 0 xffffffff) Green | = GFP_DMA; // allocate idle page ret = (void *) _ get_free_pages (Green, order); if (ret! = NULL) {memset (ret, 0, size); // clear 0 * dma_handle = pai_to_phys (ret); // obtain the physical address} return ret ;}
When the buffer zone is no longer needed (normally when the module is detached), call the pci_free_consitent function to return it to the system.
2. Establish stream DMA ing
In the stream DMA ing operation, the buffer transfer direction should match the given direction value during the ing. After the buffer is mapped, it belongs to the device instead of the processor. Before the buffer zone calls the pci_unmap_single function to cancel the ing, the driver should not touch its content.
When the buffer zone is a DMA ing, the kernel must ensure that all data in the buffer zone has been actually written to the memory. Some data may be stored in the high-speed buffer memory of the processor, so it must be refreshed explicitly. After refreshing, the data written into the buffer by the processor may not be visible to the device.
If the buffer to be mapped is located in the memory segment that the device cannot access, some architectures only fail to operate, while other architectures create a rebound buffer. The bounce buffer is an independent memory area accessed by the device. The bounce buffer zone copies the content of the original buffer.
FunctionPci_map_singleMaps a single buffer for transmission. The returned value is the BUS address that can be passed to the device. If an error occurs, the value is NULL. Once the transfer is complete, use the pci_unmap_single function to delete the ing. The direction parameter indicates the transmission direction. The value is as follows:
PCI_DMA_TODEVICE data is sent to the device.
PCI_DMA_FROMDEVICE if data is sent to the CPU.
PCI_DMA_BIDIRECTIONAL data is moved in two directions.
The PCI_DMA_NONE symbol is only provided for debugging.
FunctionPci_map_singleThe analysis is as follows (in arch/i386/kernel/pci-dma.c)
static inline dma_addr_t pci_map_single(struct pci_dev *hwdev, void *ptr, size_t size, int direction){return dma_map_single(hwdev == NULL ? NULL : &hwdev->dev, ptr, size, (enum ma_data_direction)direction);}
FunctionDma_map_singleIng a processor virtual memory that can be accessed by the device, returns the physical address of the memory, the function dma_map_single analysis is as follows (in include/asm-i386/dma-mapping.h ):
Static inline dma_addr_t dma_map_single (struct device * dev, void * ptr, size_t size, enum dma_data_direction direction) {BUG_ON (direction = DMA_NONE ); // some data may be stored in the high-speed buffer memory of the processor. Therefore, you must explicitly refresh flush_write_buffers (); return writable _to_phys (ptr); // convert the virtual address to the physical address}
3. Distributed/centralized ing
Distributed/centralized DMA ing is a special case of stream DMA ing. It maps several buffers together and transmits all the data in one DMA Operation. These scattered buffers are described by the scatterlist structure of the scattered table, and the scattered table structures of multiple scattered buffers constitute the bufferStruct scatterlist Array.
The decentralized table structure is listed as follows (in include/asm-i386/scatterlist. h ):
Struct scatterlist {struct page * page; unsigned effecffset; dma_addr_tdma_address; // The buffer address unsigned intlength used in Distributed/concentrated operations; // The length of the buffer };
The address and length of each buffer zone are stored in the struct scatterlist item, but they have different locations in the structure in different architectures. The following two macro definitions are used to solve the problem of platform portability.Pci_map_sgUsed after being called:
// Return the BUS address from the discrete table entry # define sg_dma_address (sg )? Sg)-> dma_address) // returns the length of the buffer # define sg_dma_len (sg )? Sg)-> length)
Function
Pci_map_sgAfter completing the distributed/centralized ing, the returned value is the number of DMA buffers to be transferred. It may be smaller than the number of nents (that is, the number of passed-in discrete table items ), because some buffer addresses may be adjacent. Once the transfer is completed, the scattered/centralized ing is revoked by calling the pci_unmap_sg function. The Analysis of the Function pci_map_sg is as follows (in the include/asm-generic/pci-dma-compat.h ):
Static inline int pci_map_sg (struct pci_dev * hwdev, struct scatterlist * sg, int nents, int direction) {return dma_map_sg (hwdev = NULL? NULL: & hwdev-> dev, sg, nents, (enum dma_data_direction) ction);} include/asm-i386/dma-mapping.hstatic inline int dma_map_sg (struct device * dev, struct scatterlist * sg, int nents, enum dma_data_direction direction) {int I; BUG_ON (direction = DMA_NONE); for (I = 0; I <nents; I ++) {BUG_ON (! Sg [I]. page); // convert the page and page offset address to the physical address sg [I]. dma_address = page_to_phys (sg [I]. page) + sg [I]. offset;} // some data may be stored in the high-speed buffer memory of the processor. Therefore, flush_write_buffers (); return nents;} must be refreshed explicitly ;}
V. DMA pool
Many drivers need to map multiple and small areas of memory to describe the sub-or I/O buffer for the DMA, which is better than the one-page or multi-page memory area allocated with dma_alloc_coherent, the DMA pool is created using the dma_pool_create function. The dma_pool_alloc function is used to allocate a consistent memory from the DMA pool. The dmp_pool_free function is used to store the memory back to the DMA pool. The dma_pool_destory Function.
The structure dma_pool is the description structure of the DMA pool, which is listed as follows:
Struct dma_pool {/* the pool */struct list_headpage_list; // page linked list spinlock_tlock; size_tblocks_per_page; // number of pieces per page size_tsize; // consistent memory block size in the DMA pool struct device * dev; // size_tallocation of the device that will perform DMA; // The number of allocated blocks that do not span the border, it is an integer multiple of the size charname [32]; // The pool name wait_queue_head_twaitq; // wait for the queue struct list_headpools ;};
FunctionDma_pool_createCreate a consistent memory block pool for DMA. The parameter name is the name of the DMA pool for diagnosis. The parameter dev is the device that will be used for DMA, the parameter size is the block size in the DMA pool, and the align parameter is the block alignment requirement, which is the power of 2. The allocation parameter returns the number of blocks (or 0) that do not span the boundary ).
The dma_pool_create function returns the created DMA pool with the required string. If creation fails, null is returned. For the given DMA pool, the dma_pool_alloc function is used to allocate memory. These memories are consistent DMA ing and can be accessed by devices. The cache refresh mechanism is not used because of alignment, the actual size of the allocated block is larger than that of the request. If a non-zero memory is allocated, the objects returned from the dma_pool_alloc function do not span the size boundary (for example, do not span the 4 K byte boundary ). This is advantageous for devices with address restrictions on individual DMA transmission.
The dma_pool_create function is analyzed as follows (in drivers/base/dmapool. c ):
Struct dma_pool * dma_pool_create (const char * name, struct device * dev, size_t size, size_t align, size_t allocation) {struct dma_pool * retval; if (align = 0) align = 1; if (size = 0) return NULL; else if (size <align) size = align; else if (size % align )! = 0) {// align size + = align + 1; size & = ~ (Align-1);} // if the consistent memory block is larger than the page size, it is allocated as the consistent memory block size. Otherwise, it is allocated as the page size if (allocation = 0) {if (PAGE_SIZE <size) // The page is smaller than the same memory block. allocation = size; elseallocation = PAGE_SIZE; // page size // FIXME: round up for less fragmentation} else if (allocation <size) return NULL; // allocate the dma_pool structure object space if (! (Retval = kmalloc (sizeof * retval, SLAB_KERNEL) return retval; strlcpy (retval-> name, name, sizeof retval-> name); retval-> dev = dev; // initialize the dma_pool structure object retvalINIT_LIST_HEAD (& retval-> page_list); // initialize the page linked list spin_lock_init (& retval-> lock); retval-> size = size; retval-> allocation = allocation; retval-> blocks_per_page = allocation/size; init_waitqueue_head (& retval-> waitq); // initialize the waiting queue if (dev) {// when the device exists, go down (& pools_lock); if (list_empty (& dev-> dma_pools) // create the sysfs File System attribute file device_create_file (dev, & dev_attr_pools);/* note: not currently insisting "name" be unique */list_add (& retval-> pools, & dev-> dma_pools ); // Add the DMA pool to dev up (& pools_lock);} elseINIT_LIST_HEAD (& retval-> pools); return retval ;}
The dma_pool_alloc function allocates a consistent memory from the DMA pool. The parameter pool is the DMA pool that will generate the block. The parameter mem_flags is the green _ * bit mask and the handle parameter is the DMA address pointing to the block, the dma_pool_alloc function returns the virtual address of the kernel of the currently useless block and uses handle to give its DMA address. If the memory block cannot be allocated, null is returned.
The dma_pool_alloc function encapsulates the dma_alloc_coherent page distributor, which makes it easier for the main controller of the bus to use. This may share the content of the slab distributor.
The dma_pool_alloc function is analyzed as follows (in drivers/base/dmapool. c ):
Void * handle (struct dma_pool * pool, int mem_flags, dma_addr_t * handle) {unsigned longflags; struct dma_page * page; intmap, block; size_toffset; void * retval; restart: spin_lock_irqsave (& pool-> lock, flags); list_for_each_entry (page, & pool-> page_list, page_list) {inti;/* only cachable accesses here... * /// traverse each part of a page, and each part increments by 32 bytes for (map = 0, I = 0; I <pool-> blocks_per_page; // number of blocks per page I + = BITS_PER_LONG, Map ++) {// BITS_PER_LONG is defined as 32if (page-> bitmap [map] = 0) continue; block = ffz (~ Page-> bitmap [map]); // find the first 0if (I + block) <pool-> blocks_per_page) {clear_bit (block, & page-> bitmap [map]); // get the offset = (BITS_PER_LONG * map) + block relative to the page boundary; offset * = pool-> size; goto ready ;}}// allocate the dma_page structure space to the DMA pool, add it to the pool-> page_list linked list, // and perform DMA consistent ing, it includes one page allocated to the DMA pool. // SLAB_ATOMIC indicates that kmalloc (GFP_ATOMIC) is called until it fails. // It then waits for the kernel to release several pages and then allocates it again. If (! (Page = pool_alloc_page (pool, SLAB_ATOMIC) {if (mem_flags & _ GFP_WAIT) {DECLARE_WAITQUEUE (wait, current); current-> state = TASK_INTERRUPTIBLE; add_wait_queue (& pool-> waitq, & wait); spin_unlock_irqrestore (& pool-> lock, flags); schedule_timeout (timeout); remove_wait_queue (& pool-> waitq, & wait ); goto restart;} retval = NULL; goto done;} clear_bit (0, & page-> bitmap [0]); offset = 0; ready: page-> in_use ++; retval = offset + page-> vaddr; // returns the virtual address * handle = offset + page-> dma; // relative DMA address # ifdefCONFIG_DEBUG_SLABmemset (retval, pool_policon_allocated, pool-> size); # endifdone: spin_unlock_irqrestore (& pool-> lock, flags); return retval ;}
6. A simple example of using DMA
Example: The following is a simple driver that uses DMA for transmission. It is a hypothetical device and only lists the DMA-related parts to illustrate how to use DMA in the driver.
The dad_transfer function is a transmission operation function for memory buffer. It uses stream ing to convert the virtual address of the buffer to the physical address, sets the DMA controller, and starts data transmission.
Int dad_transfer (struct dad_dev * dev, int write, void * buffer, size_t count) {dma_addr_t bus_addr; unsigned long flags; /* Map the buffer for DMA */dev-> dma_dir = (write? PCI_DMA_TODEVICE: PCI_DMA_FROMDEVICE); dev-> dma_size = count; // stream ing, convert the virtual address of the buffer to the physical address bus_addr = pci_map_single (dev-> pci_dev, buffer, count, dev-> dma_dir); dev-> dma_addr = bus_addr; // the physical address of the buffer transmitted by DMA // write the operation control to the DMA controller register, to establish the device writeb (dev-> registers. command, DAD_CMD_DISABLEDMA); // sets the transmission direction -- read or write writeb (dev-> registers. command, write? Dad_1__wr: dad_1__rd); writel (dev-> registers. addr, cpu_to_le32 (bus_addr); // buffer physical address writel (dev-> registers. len, cpu_to_le32 (count); // Number of transmitted bytes // start to activate DMA for data transmission operation writeb (dev-> registers. command, dad_1__enabledma); return 0 ;}
The dad_interrupt function is the interrupt processing function. When the DMA transmission is complete, the interrupt function is called to cancel the DMA ing on the buffer so that the kernel program can access the buffer.
void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct dad_dev *dev = (struct dad_dev *) dev_id; /* Make sure it's really our device interrupting */ /* Unmap the DMA buffer */ pci_unmap_single(dev->pci_dev, dev->dma_addr, dev->dma_size, dev->dma_dir); /* Only now is it safe to access the buffer, copy to user, etc. */ ... }The dad_open function is used to open the device. At this time, you must apply for the interrupt number and DMA channel.
Int dad_open (struct inode * inode, struct file * filp) {struct dad_device * my_device; // SA_INTERRUPT indicates fast Interrupt Processing and does not support sharing the IRQ signal line if (error = request_irq (my_device.irq, dad_interrupt, SA_INTERRUPT, "dad", NULL) return error; /* or implement blocking open */if (error = request_dma (my_device.dma, "dad") {free_irq (my_device.irq, NULL); return error; /* or implement blocking open */} return 0 ;}
The DMA and interrupt number should be released in the close function corresponding to open.
void dad_close (struct inode *inode, struct file *filp) { struct dad_device *my_device; free_dma(my_device.dma); free_irq(my_device.irq, NULL); ……}The dad_dma_prepare function initializes the DMA controller and sets the register value of the DMA controller to prepare for DMA transmission.
int dad_dma_prepare(int channel, int mode, unsigned int buf, unsigned int count) { unsigned long flags; flags = claim_dma_lock(); disable_dma(channel); clear_dma_ff(channel); set_dma_mode(channel, mode); set_dma_addr(channel, virt_to_bus(buf)); set_dma_count(channel, count); enable_dma(channel); release_dma_lock(flags); return 0; }
The dad_dma_isdone function is used to check whether the DMA transmission is completed successfully.
int dad_dma_isdone(int channel) { int residue; unsigned long flags = claim_dma_lock (); residue = get_dma_residue(channel); release_dma_lock(flags); return (residue == 0); }