patch-2.4.10 linux/Documentation/arm/SA1100/DMA
Next file: linux/Documentation/arm/SA1100/FreeBird
Previous file: linux/Documentation/arm/SA1100/Assabet
Back to the patch index
Back to the overall index
- Lines: 249
- Date:
Fri Sep 7 09:28:38 2001
- Orig file:
v2.4.9/linux/Documentation/arm/SA1100/DMA
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.4.9/linux/Documentation/arm/SA1100/DMA linux/Documentation/arm/SA1100/DMA
@@ -0,0 +1,248 @@
+Support functions for the SA11x0 internal DMA channels
+======================================================
+
+Nicolas Pitre <nico@cam.org>
+Last updated: 2001/07/15
+
+
+The DMA controller consists of six independent DMA channels. Each channel
+can be configured to service any of the serial controllers. Two channels
+are required to service a full-duplex serial controller. The DMA
+controller is intended to relieve the processor of the interrupt overhead
+in servicing these ports with programmed I/ O.
+
+If desired, any or all peripherals (except the UDC) may be serviced with
+programmed I/ O instead of DMA. Each peripheral is capable of requesting
+processor service through its own interrupt lines or through a DMA
+request.
+
+A set of functions is provided to support drivers working with DMA buffers
+through a generic interface for (wishfully) all DMA usages. Those
+functions will take care of buffer queueing and splitting, DMA register
+management, interrupt handling, etc.
+
+
+SA11x0 DMA API
+--------------
+
+Here is the description for the DMA API.
+
+
+int sa1100_request_dma( dmach_t *channel, const char *device_id,
+ dma_device_t device );
+
+This function will search for a free DMA channel and returns the channel
+number in '*channel'. 'device_id' should point to a string identifying
+the DMA usage or device (mainly for /proc). 'device' is the SA11x0
+peripheral's ports. Note that reading from a port and writing to the
+same port are actually considered as two different streams requiring
+two DMA channels with their own device type. All possible dma_device_t
+are defined in include/asm-arm/arch-sa1100/dma.h. If no channel is
+available, or if the desired device is already in use by another DMA
+channel, then an error code is returned. This function must be called
+before any other DMA calls.
+
+
+int sa1100_dma_queue_buffer( dmach_t channel, void *buf_id,
+ dma_addr_t data, int size );
+
+This function enqueue the specified buffer for DMA processing. The buffer
+will be transmitted or filled with incoming data depending on the channel
+configuration made through sa1100_dma_set_device(). If the queue is
+empty, DMA starts immediately on the given buffer.
+
+Arguments are:
+
+dmach_t channel: the channel number.
+void *buf_id: a buffer identification known by the caller.
+dma_addr_t data: the buffer's physical address.
+int size: the buffer size in bytes.
+
+Note here the dma_addr_t which is not the same as the virtual address as
+returned by kmalloc() and friends. The DMA controller must be given a
+physical address to a buffer which is not cached bye the CPU data cache.
+To get such address, the DMA mapping functions (see
+Documentation/DMA-mapping.txt) are recommended. The only relevant
+functions are pci_alloc_consistent(), pci_map_single() and their unmap
+counterparts. The PCI dev argument is NULL of course.
+
+There is no restriction on the buffer size. The DMA code will split it up
+internally to acommodate the DMA controller as needed. If the buffer
+can't be enqueued the appropriate error code is returned.
+
+
+int sa1100_dma_set_callback( dmach_t channel, dma_callback_t cb );
+
+As soon as the DMa completes with a buffer, a callback function is used to
+notify the driver which would have registered one. The callback function
+is prototyped as:
+
+void dma_callback( void *buf_id, int size );
+
+The 'buf_id' argument is the buffer identifier as passed to
+sa1100_dma_queue_buffer(). The 'size' argument is the number of bytes the
+DMA processed (should be the same as the buffer size).
+
+Note that this callback function is called while in interrupt context.
+So it has to be small and efficient while posponing more complex
+processing to a bottom-half function or similar. All
+restrictions for interrupt handlers still apply.
+
+
+int sa1100_dma_get_current( dmach_t channel, void **buf_id,
+ dma_addr_t *addr );
+
+This returns the buffer ID and the DMA address pointer within the buffer
+currently being processed. If no such buffer is currently processed, an
+error code is returned. This is useful for mmap()'ed buffers like in
+audio drivers.
+
+
+int sa1100_dma_stop( dmach_t channel );
+
+This call stops any DMA transfer on the given channel.
+
+
+int sa1100_dma_resume( dmach_t channel );
+
+This call resumes a DMA transfer which would have been stopped through
+sa1100_dma_stop().
+
+
+int sa1100_dma_flush_all( dmach_t channel );
+
+This completely flushes all queued buffers and on-going DMA transfers on a
+given channel. The next enqueued buffer following this call will be
+processed right away.
+
+
+int sa1100_dma_set_spin( dmach_t channel, dma_addr_t addr, int size );
+
+Because there is at least one device out there that uses its receive
+signal for its transmit clock reference, we need a mecanism to make the
+DMA "spin" on a certain buffer for when there is no more actual buffer to
+process. The 'addr' argument is the physical memory address to use, and
+the 'size' argument determines the spin DMA chunk. This size can't be
+larger than 8191 (if so, it is clamped to 4096). When the size is 0,
+the spin function is turned off.
+
+When activated, DMA will "spin" until there is any buffer in the queue.
+The current DMA chunk will terminate before a newly queued buffer is
+processed. The spin buffer will only be reused when there is no more
+acctual buffer to process.
+
+It is important not to choose a too small 'size' value since it will
+greatly increase the interrupt load required to restart the spin. Since
+this feature will typically be used on transmit DMAs, and because a buffer
+full of zeros is probably the best thing to spin out, the 'addr' argument
+may well be used with FLUSH_BASE_PHYS for which no allocation nor memory
+bus request are needed.
+
+The spinning DMA is affected by sa1100_dma_stop() and sa1100_dma_resume()
+but not bu sa1100_dma_flush_all().
+
+
+void sa1100_free_dma( dmach_t channel );
+
+This clears all activities on a given DMA channel and releases it for
+future requests.
+
+
+Buffer allocation
+-----------------
+
+Like mentionned above, it is the driver's responsibility to allocate, free
+and keep track of buffer space with dma_addr_t type addresses. However the
+driver must not change the state of any buffer after it has been sent to
+sa1100-dma_queue_buffer(). When that function has been called, the buffer
+becomes the DMA's ownership until one of these events occur:
+
+- The callback function is called by the DMA code with a buffer ID to
+ indicate that DMA processing terminated on that buffer. Then the
+ driver owns the buffer again.
+- The sa1100-dma_flush_all() function is called by the driver at which
+ point *all* queued buffers are owned by the driver again.
+- The sa1100-free_dma() does the same as sa1100-dma_flush_all().
+
+This doesn't mean that you can't change the content of a queued buffer in
+conjonction with the usage of pci_map_consistent() and
+sa1100_dma_get_current()... but then you must be sure you know what you're
+doing (this doesn't work with pci_map_single()).
+
+
+Examples
+--------
+
+A real example of audio ring buffers is implemented in the
+drivers/sound/sa1100-audio.c driver. The SA1110 USB client and the
+SA11x0 FIR drivers are also using this interface to implement packetized
+DMA.
+
+A transmit DMA for network packets could look like this (largely simplified):
+
+struct sk_buff *tx_ring_skb[RING_SIZE];
+dma_addr_t tx_ring_dma[RING_SIZE];
+int cur_tx;
+...
+
+transmit function:
+
+ tx_ring_skb[cur_tx] = skb;
+ tx_ring_dma[cur_tx] = pci_map_single(NULL, skb->data, skb->len,
+ PCI_DMA_TODEVICE);
+ sa1100_dma_queue_buffer(channel, (void*)cur_tx,
+ tx_ring_dma[cur_tx], skb->len);
+ cur_tx++; cur_tx %= RING_SIZE;
+ ...
+
+and the callback function:
+
+void tx_done_callback( void *buf_id, int size ) {
+ int done_tx = (int) buf_id;
+ struct sk_buff *skb = tx_ring_skb[done_tx];
+ pci_unmap_single(NULL, tx_ring_dma[done_tx], skb->len,
+ PCI_DMA_TODEVICE);
+ stats.tx_packets++;
+ stats.tx_bytes += size;
+ dev_kfree_skb_irq(skb);
+ tx_ring_skb[done_tx] = NULL;
+}
+
+
+For drivers expecting variable length packets i.e. USB client, it is
+necessary to register the appropriate IRQ to be notified when the receiver
+is idle, the packet is complete, etc. We could use one buffer at a time
+with its ID being the virtual address of the buffer.
+
+Then the sequence:
+
+ /* be sure DMA won't continue under our feet */
+ sa1100_dma_stop(channel);
+ /* get the actual DMA length */
+ sa1100_get_current(channel, &data, &dma_ptr);
+ /* acquire ownership for the buffer */
+ sa1100_dma_flush_all(channel);
+ /* unmap the DMA buffer (actually doing cache coherency on ARM) */
+ pci_unmap_single (NULL, dma_addr, MAX_PKT_SIZE, PCI_DMA_FROMDEVICE);
+ /* get remaining bytes from the fifo */
+ ptr = data + dma_ptr - dma_addr;
+ while (fifo_not_empty)
+ *ptr++ = get_byte_from_fifo;
+ /* feed another free buffer for the next packet */
+ dma_addr2 = pci_map_single(NULL, data2, MAX_PKT_SIZE,
+ PCI_DMA_FROMDEVICE);
+ sa1100_dma_queue_buffer(channel, data2, dma_addr2, MAX_PKT_SIZE);
+ /* process the current packet */
+ ...
+
+might do the trick. This looks a bit ugly but that's a starting point for
+improvements.
+
+
+TODO
+----
+
+- Create kernel-doc comments in the source to document the API and
+ let the documentation be generated automatically.
+
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)