Linux Device Drivers 2nd Edition (779877), страница 96
Текст из файла (страница 96)
Only after pci_unmap_single has been called is it safe for thedriver to access the contents of the buffer (with one exception that we’ll seeshortly). Among other things, this rule implies that a buffer being written to adevice cannot be mapped until it contains all the data to write.•The buffer must not be unmapped while DMA is still active, or serious systeminstability is guaranteed.You may be wondering why the driver can no longer work with a buffer once ithas been mapped. There are actually two reasons why this rule makes sense. First,when a buffer is mapped for DMA, the kernel must ensure that all of the data inthat buffer has actually been written to memory.
It is likely that some data willremain in the processor’s cache, and must be explicitly flushed. Data written to thebuffer by the processor after the flush may not be visible to the device.40822 June 2001 16:42http://openlib.org.uaDirect Memory Access and Bus MasteringSecond, consider what happens if the buffer to be mapped is in a region of memory that is not accessible to the device. Some architectures will simply fail in thiscase, but others will create a bounce buffer.
The bounce buffer is just a separateregion of memory that is accessible to the device. If a buffer is mapped with adirection of PCI_DMA_TODEVICE, and a bounce buffer is required, the contentsof the original buffer will be copied as part of the mapping operation. Clearly,changes to the original buffer after the copy will not be seen by the device. Similarly, PCI_DMA_FROMDEVICE bounce buffers are copied back to the originalbuffer by pci_unmap_single; the data from the device is not present until thatcopy has been done.Incidentally, bounce buffers are one reason why it is important to get the directionright. PCI_DMA_BIDIRECTIONAL bounce buffers are copied before and after theoperation, which is often an unnecessary waste of CPU cycles.Occasionally a driver will need to access the contents of a streaming DMA bufferwithout unmapping it. A call has been provided to make this possible:void pci_sync_single(struct pci_dev *pdev, dma_handle_t bus_addr,size_t size, int direction);This function should be called befor e the processor accesses aPCI_DMA_FROMDEVICE buffer, and after an access to a PCI_DMA_TODEVICEbuffer.Scatter-gather mappingsScatter-gather mappings are a special case of streaming DMA mappings.
Supposeyou have several buffers, all of which need to be transferred to or from the device.This situation can come about in several ways, including from a readv or writevsystem call, a clustered disk I/O request, or a list of pages in a mapped kernel I/Obuffer. You could simply map each buffer in turn and perform the required operation, but there are advantages to mapping the whole list at once.One reason is that some smart devices can accept a scatterlist of array pointersand lengths and transfer them all in one DMA operation; for example, ‘‘zero-copy’’networking is easier if packets can be built in multiple pieces.
Linux is likely totake much better advantage of such devices in the future. Another reason to mapscatterlists as a whole is to take advantage of systems that have mapping registersin the bus hardware. On such systems, physically discontiguous pages can beassembled into a single, contiguous array from the device’s point of view. Thistechnique works only when the entries in the scatterlist are equal to the page sizein length (except the first and last), but when it does work it can turn multipleoperations into a single DMA and speed things up accordingly.Finally, if a bounce buffer must be used, it makes sense to coalesce the entire listinto a single buffer (since it is being copied anyway).40922 June 2001 16:42http://openlib.org.uaChapter 13: mmap and DMASo now you’re convinced that mapping of scatterlists is worthwhile in some situations.
The first step in mapping a scatterlist is to create and fill in an array ofstruct scatterlist describing the buffers to be transferred. This structure isarchitecture dependent, and is described in <linux/scatterlist.h>. It willalways contain two fields, however:char *address;The address of a buffer used in the scatter/gather operationunsigned int length;The length of that bufferTo map a scatter/gather DMA operation, your driver should set the address andlength fields in a struct scatterlist entry for each buffer to be transferred. Then call:int pci_map_sg(struct pci_dev *pdev, struct scatterlist *list,int nents, int direction);The return value will be the number of DMA buffers to transfer; it may be lessthan nents, the number of scatterlist entries passed in.Your driver should transfer each buffer returned by pci_map_sg.
The bus addressand length of each buffer will be stored in the struct scatterlist entries,but their location in the structure varies from one architecture to the next. Twomacros have been defined to make it possible to write portable code:dma_addr_t sg_dma_address(struct scatterlist *sg);Returns the bus (DMA) address from this scatterlist entryunsigned int sg_dma_len(struct scatterlist *sg);Returns the length of this bufferAgain, remember that the address and length of the buffers to transfer may be different from what was passed in to pci_map_sg.Once the transfer is complete, a scatter-gather mapping is unmapped with a call topci_unmap_sg:void pci_unmap_sg(struct pci_dev *pdev, struct scatterlist *list,int nents, int direction);Note that nents must be the number of entries that you originally passed topci_map_sg, and not the number of DMA buffers that function returned to you.Scatter-gather mappings are streaming DMA mappings, and the same access rulesapply to them as to the single variety.
If you must access a mapped scatter-gatherlist, you must synchronize it first:void pci_dma_sync_sg(struct pci_dev *pdev, struct scatterlist *sg,int nents, int direction);41022 June 2001 16:42http://openlib.org.uaDirect Memory Access and Bus MasteringHow different architectures support PCI DMAAs we stated at the beginning of this section, DMA is a very hardware-specificoperation.
The PCI DMA interface we have just described attempts to abstract outas many hardware dependencies as possible. There are still some things that showthrough, however.M68KS/390Super-HThese architectures do not support the PCI bus as of 2.4.0.IA-32 (x86)MIPSPowerPCARMThese platforms support the PCI DMA interface, but it is mostly a false front.There are no mapping registers in the bus interface, so scatterlists cannot becombined and virtual addresses cannot be used. There is no bounce buffersupport, so mapping of high-memory addresses cannot be done. The mappingfunctions on the ARM architecture can sleep, which is not the case for theother platforms.IA-64The Itanium architecture also lacks mapping registers.
This 64-bit architecturecan easily generate addresses that PCI peripherals cannot use, though. ThePCI interface on this platform thus implements bounce buffers, allowing anyaddress to be (seemingly) used for DMA operations.AlphaMIPS64SPARCThese architectures support an I/O memory management unit. As of 2.4.0, theMIPS64 port does not actually make use of this capability, so its PCI DMAimplementation looks like that of the IA-32. The Alpha and SPARC ports,though, can do full-buffer mapping with proper scatter-gather support.The differences listed will not be problems for most driver writers, as long as theinterface guidelines are followed.A simple PCI DMA exampleThe actual form of DMA operations on the PCI bus is very dependent on thedevice being driven.
Thus, this example does not apply to any real device; instead,it is part of a hypothetical driver called dad (DMA Acquisition Device). A driver forthis device might define a transfer function like this:41122 June 2001 16:42http://openlib.org.uaChapter 13: mmap and DMAint 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;bus_addr = pci_map_single(dev->pci_dev, buffer, count,dev->dma_dir);dev->dma_addr = bus_addr;/* Set up the device */writeb(dev->registers.command, DAD_CMD_DISABLEDMA);writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD);writel(dev->registers.addr, cpu_to_le32(bus_addr));writel(dev->registers.len, cpu_to_le32(count));/* Start the operation */writeb(dev->registers.command, DAD_CMD_ENABLEDMA);return 0;}This function maps the buffer to be transferred and starts the device operation.The other half of the job must be done in the interrupt service routine, whichwould look something like this: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.
*/...}Obviously a great deal of detail has been left out of this example, including whatever steps may be required to prevent attempts to start multiple simultaneousDMA operations.A quick look at SBusSPARC-based systems have traditionally included a Sun-designed bus called theSBus. This bus is beyond the scope of this chapter, but a quick mention is worthwhile.