📄 pcibuf.c.new
字号:
/* * linux/arch/arm/mach-s3c2410/pcibuf.c * * Special pci_{map/unmap/dma_sync}_* routines for S3C2410. * * These functions utilize bouncer buffers to compensate for a bug in * the S3C2410 hardware which don't allow DMA to/from addresses * certain addresses above 1MB. * * Re-written by kingmonkey <kangdazhi@163.com> * Re-written by Christopher Hoover <ch@murgatroid.com> * Original version by Brad Parker (brad@heeltoe.com) * * Copyright (C) 2002 Hewlett Packard Company. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * */#include <linux/module.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/pci.h>#include <linux/list.h>#include "pcipool.h"//#define PCI_BUF_DEBUG 1/* * 26 bits of the S3C2410 address bus are available to the S3C2410. * Use these when feeding target addresses to the DMA engines. */#define S3C2410_ADDR_WIDTH (26)#define S3C2410_ADDR_MASK ((1<<S3C2410_ADDR_WIDTH)-1)#define S3C2410_DMA_ADDR(x) ((x)&S3C2410_ADDR_MASK)struct safe_buffer { struct list_head node; /* original request */ void *ptr; size_t size; int direction; /* safe buffer info */ struct pci_pool *pool; void *safe; dma_addr_t safe_dma_addr;};LIST_HEAD(safe_buffers);#define SIZE_SMALL 1024#define SIZE_LARGE (16*1024)static struct pci_pool *small_buffer_pool, *large_buffer_pool;static intinit_safe_buffers(){ small_buffer_pool = pci_pool_create("pci_small_buffer", S3C2410_FAKE_PCIDEV, SIZE_SMALL, 0 /* byte alignment */, 0 /* no page-crossing issues */, GFP_KERNEL | GFP_DMA); if (small_buffer_pool == 0){ printk(KERN_ERR"%s: could not allocate small pci pool\n", __func__); return -1; } large_buffer_pool = pci_pool_create("pci_large_buffer", S3C2410_FAKE_PCIDEV, SIZE_LARGE, 0 /* byte alignment */, 0 /* no page-crossing issues */, GFP_KERNEL | GFP_DMA); if (large_buffer_pool == 0){ printk(KERN_ERR"%s: could not allocate large pci pool\n", __func__); pci_pool_destroy(small_buffer_pool); small_buffer_pool = 0; return -1; } return 0;}static void __exitdestroy_safe_buffer_pools(void){ if (small_buffer_pool) pci_pool_destroy(small_buffer_pool); if (large_buffer_pool) pci_pool_destroy(large_buffer_pool); small_buffer_pool = large_buffer_pool = 0;}/* allocate a 'safe' buffer and keep track of it */static struct safe_buffer *alloc_safe_buffer(void *ptr, size_t size, int direction){ struct safe_buffer *buf; struct pci_pool *pool; void *safe; dma_addr_t safe_dma_addr;#ifdef PCI_BUF_DEBUG printk("%s: alloc_safe_buffer(size=%d)\n", __func__, size);#endif buf = kmalloc(sizeof(struct safe_buffer), GFP_ATOMIC); if (buf == 0) { printk(KERN_WARNING "%s: kmalloc failed\n", __func__); return 0; } if (size <= SIZE_SMALL) { pool = small_buffer_pool; safe = pci_pool_alloc(pool, GFP_ATOMIC, &safe_dma_addr); } else if (size <= SIZE_LARGE) { pool = large_buffer_pool; safe = pci_pool_alloc(pool, GFP_ATOMIC, &safe_dma_addr); } else {#ifdef PCI_BUF_DEBUG printk(KERN_DEBUG "%s: resorting to pci_alloc_consistent\n"__func__);#endif pool = 0; safe = pci_alloc_consistent(S3C2410_FAKE_PCIDEV, size,&safe_dma_addr); } if (safe == 0) { printk(KERN_WARNING"%s: could not alloc dma memory (size=%d)\n",__func__, size); kfree(buf); return 0; } buf->ptr = ptr; buf->size = size; buf->direction = direction; buf->pool = pool; buf->safe = safe; buf->safe_dma_addr = safe_dma_addr; MOD_INC_USE_COUNT; list_add(&buf->node, &safe_buffers); return buf;}/* determine if a buffer is from our "safe" pool */static struct safe_buffer *find_safe_buffer(dma_addr_t safe_dma_addr){ struct list_head *entry; list_for_each(entry, &safe_buffers) { struct safe_buffer *b = list_entry(entry, struct safe_buffer, node); if (b->safe_dma_addr == safe_dma_addr) { return b; } } return 0;}static voidfree_safe_buffer(struct safe_buffer *buf){#ifdef PCI_BUF_DEBUG printk("%s: free_safe_buffer(buf=%p)\n", __func__,buf);#endif list_del(&buf->node); if (buf->pool) pci_pool_free(buf->pool, buf->safe, buf->safe_dma_addr); else pci_free_consistent(S3C2410_FAKE_PCIDEV, buf->size, buf->safe, buf->safe_dma_addr); kfree(buf); MOD_DEC_USE_COUNT;}static inline intdma_range_is_safe(dma_addr_t addr, size_t size){ unsigned int physaddr = S3C2410_DMA_ADDR((unsigned int) addr ); /* Any address within one megabyte of the start of the target * bank will be OK. This is an overly conservative test: * other addresses can be OK depending on the dram * configuration. * * We take care to ensure the entire dma region is within * the safe range. */ return ((physaddr + size - 1) < (1<<20));}/* * see if a buffer address is in an 'unsafe' range. if it is * allocate a 'safe' buffer and copy the unsafe buffer into it. * substitute the safe buffer for the unsafe one. * (basically move the buffer from an unsafe area to a safe one) * * we assume calls to map_single are symmetric with calls to unmap_single... */dma_addr_ts3c2410_map_single(struct pci_dev *hwdev, void *virtptr, size_t size, int direction){ unsigned long flags; dma_addr_t dma_addr;#ifdef PCI_BUF_DEBUG printk("%s: pci_map_single(hwdev=%p,ptr=%p,size=%d,dir=%x) \n", __func__,hwdev, virtptr, size, direction);#endif local_irq_save(flags); dma_addr = virt_to_bus(virtptr); /* we assume here that a buffer will never be >=64k */// if ( (((unsigned long)dma_addr) & 0x100000) ||// ((((unsigned long)dma_addr)+size) & 0x100000) ) if(!dma_range_is_safe(dma_addr, size)) { struct safe_buffer *buf; buf = alloc_safe_buffer(virtptr, size, direction); if (buf == 0) { printk(KERN_ERR "%s: unable to map unsafe buffer %p!\n", __func__, virtptr); local_irq_restore(flags); return 0; }#ifdef PCI_BUF_DEBUG printk("%s: unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n", __func__, buf->ptr, (void *) virt_to_bus(buf->ptr), buf->safe, (void *) buf->safe_dma_addr);#endif if ((direction == PCI_DMA_TODEVICE) ||(direction == PCI_DMA_BIDIRECTIONAL)) {#ifdef PCI_BUF_DEBUG printk("%s: copy out from unsafe %p, to safe %p, size %d\n",__func__, virtptr, buf->safe, size);#endif memcpy(buf->safe, virtptr, size); } consistent_sync(buf->safe, size, direction); } else { consistent_sync(virtptr, size, direction); } local_irq_restore(flags); return dma_addr;}/* * see if a mapped address was really a "safe" buffer and if so, * copy the data from the safe buffer back to the unsafe buffer * and free up the safe buffer. * (basically return things back to the way they should be) */voids3c2410_unmap_single(struct pci_dev *hwdev, dma_addr_t dma_addr, size_t size, int direction){ unsigned long flags; struct safe_buffer *buf; void *tmp;#ifdef PCI_BUF_DEBUG printk("%s(ptr=%p,size=%d,dir=%x)\n", __func__, (void *) dma_addr, size, direction);#endif if(direction == PCI_DMA_NONE) BUG(); local_irq_save(flags); buf = find_safe_buffer(dma_addr); if (buf) { if(buf->size != size) BUG(); if(buf->direction != direction) BUG();#ifdef PCI_BUF_DEBUG printk("%s: unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n", __func__, buf->ptr, (void *) virt_to_bus(buf->ptr), buf->safe, (void *) buf->safe_dma_addr);#endif if ((direction == PCI_DMA_FROMDEVICE) || (direction == PCI_DMA_BIDIRECTIONAL)) {#ifdef PCI_BUF_DEBUG printk("%s: copy back from safe %p, to unsafe %p size %d\n", __func__, buf->safe, buf->ptr, size);#endif memcpy(buf->ptr, buf->safe, size); free_safe_buffer(buf); } }else { /* assume this is normal memory */ tmp = bus_to_virt(dma_addr); consistent_sync(tmp, size, PCI_DMA_FROMDEVICE); } local_irq_restore(flags);}EXPORT_SYMBOL(s3c2410_map_single);EXPORT_SYMBOL(s3c2410_unmap_single);static int __init s3c2410_init_safe_buffers(void){ int ret; printk("Initializing S3C2410 buffer pool for DMA workaround\n"); ret=init_safe_buffers(); return ret;}static void __exit s3c2410_free_safe_buffers(void){ if(!list_empty(&safe_buffers)) BUG(); destroy_safe_buffer_pools();}module_init(s3c2410_init_safe_buffers);module_exit(s3c2410_free_safe_buffers);MODULE_AUTHOR("kingmonkey <kangdazhi@163.com>");MODULE_DESCRIPTION("Special pci_{map/unmap/dma_sync}_* routines for S3C2410 .");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -