Help with dma on PCI driver
From: Luis Filipe Rossi
Date: Wed Mar 30 2011 - 06:07:37 EST
I tryied to post this on using google groups, but as i had some
filtering problem on other lists, i am sending using the direct
e-mail. Please answer direct to me as i am not subscribed (i am trying
to use a newsreader). Sorry if i am missing any information. If so,
please just tell me what so i can provide it.
I am developing a driver for a custom PCI board. I am following the
example on Essential Linux Device Drivers, but when i am calling the
pci_alloc_consitent i get the following error:
Mar 29 15:18:42 luis-desktop kernel: [ 4520.075823] BUG: unable to
handle kernel NULL pointer dereference at 00000004
Mar 29 15:18:42 luis-desktop kernel: [ 4520.078277] IP: [<c010797f>]
dma_generic_alloc_coherent+0xaf/0xc0
Mar 29 15:18:42 luis-desktop kernel: [ 4520.078277] *pde = 0a417067
*pte = 00000000
Mar 29 15:18:42 luis-desktop kernel: [ 4520.078277] Oops: 0002 [#1] SMP
I am running on a x86 (Pentium III) with Ubuntu Kernel 2.6.32-24
I tryed to take a look at ldd3 but no answer so far...
The idea is to alloc a buffer so my PCI card can master the bus and
place the data on that buffer....
The driver code is:
//*
* pci_bridge-driver.c - template Linux driver for the opencores' pci
bridge . Works on * kernel 2.6.x
*
* tested on Xubuntu, kernel 2.6.20-15-generic
*
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY
* RIGHTS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Build/use notes (you'll probably need to be superuser to run most of
* the steps below):
*
* 1) How to build the driver:
*
* $ make
*
* 2) How to install the driver:
*
* $ insmod pci_bridge-driver.ko
*
* TODO: change the code and use misc device and udev, so to avoid the
stuff below (3-4) !!
* 3) If pci_bridge_init_major (below) is 0, then obtain the major number:
*
* $ cat /proc/devices
*
* Look for the line that contains the string "pci_bridge". The major
* number is to the left.
*
* 4) Make the pci_bridge device special file. Substitute the major
* number obtained above for <major_number>.
*
* $ mknod /dev/pci_bridge c <major_number> 0
*
* 5) How to remove the driver:
*
* $ rmmod pci_bridge-driver
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
//#include <asm/byteorder.h> /* PCI is little endian */
#include <asm/uaccess.h> /* copy to/from user */
#include "kint.h"
#define PCI_BRIDGE_DEVICE_ID 0x0001
#define PCI_BRIDGE_VENDOR_ID 0x1895
#define PCI_DRIVER_NAME "pci_bridge" /* driver name */
#define BRIDGE_MEM_MAPPED 0
#define BRIDGE_IO_MAPPED 1
/*
* PCI device IDs supported by this driver. The PCI_DEVICE macro sets
* the vendor and device fields to its input arguments, sets subvendor
* and subdevice to PCI_ANY_ID. It does not set the class fields.
*/
static struct pci_device_id pci_bridge_ids[] =
{
{ PCI_DEVICE(PCI_BRIDGE_VENDOR_ID, PCI_BRIDGE_DEVICE_ID), },
{ 0, }
};
/*
* For completeness, tell the module loading and hotplug systems
* what PCI devices this module supports.
*/
MODULE_DEVICE_TABLE(pci, pci_bridge_ids);
/*
* pci_register_driver parameter.
*/
static int pci_bridge_probe(struct pci_dev *, const struct pci_device_id *);
static void pci_bridge_remove(struct pci_dev *);
static struct pci_driver pci_bridge_driver =
{
.name = PCI_DRIVER_NAME,
.id_table = pci_bridge_ids,
.probe = pci_bridge_probe,
.remove = pci_bridge_remove,
};
/*
* File operations i/f.
*/
int pci_bridge_open(struct inode *, struct file *);
int pci_bridge_release(struct inode *, struct file *);
ssize_t pci_bridge_read(struct file *, char __user *, size_t, loff_t *);
ssize_t pci_bridge_write(struct file *, const char __user *, size_t, loff_t *);
int pci_bridge_ioctl(struct inode *pnode, struct file *filp, unsigned
int cmd, unsigned long arg);
// seek file operation function
loff_t bridge_lseek(struct file *filp, loff_t offset, int origin);
static void dma_descriptor_release(struct pci_dev *pdev);
static void dma_descriptor_setup(struct pci_dev *pdev);
static struct file_operations pci_bridge_fops =
{
read: pci_bridge_read,
write: pci_bridge_write,
open: pci_bridge_open,
release: pci_bridge_release,
ioctl: pci_bridge_ioctl,
llseek: bridge_lseek
};
static int __init pci_bridge_init(void);
static void __exit pci_bridge_exit(void);
/*
* Driver major number. 0 = allocate dynamically.
*/
static int pci_bridge_init_major = 0;
static int pci_bridge_major;
/*
* Per-device structure.
*/
static struct pci_bridge_dev
{
struct cdev cdev; /* Char device structure */
struct pci_dev *pcidev; /* PCI device pointer */
int current_resource;
u32 page_addr;
u8 num_of_bases;
int base_map[6];
u32 bases[6];
u32 base_size[6];
u32 base_page_offset;
u32 offset;
u8 interrupt_line;
} *pci_bridge_devices;
static struct device_data
{
void *dma_buffer_rx;
dma_addr_t dma_bus_rx;
void *dma_buffer_tx;
dma_addr_t dma_bus_tx;
}*c_memory_map;
/*
* pci_bridge_probe - pci_driver probe function. Just enable the PCI device.
* Could also check various configuration registers, find a specific PCI
* device, request a specific region, etc.
*/
static int pci_bridge_probe(struct pci_dev *pcidev, const struct
pci_device_id *id)
{
struct pci_bridge_dev *dev;
printk("pci_bridge_probe called ...\n");
if(pcidev == NULL)
{
printk(KERN_NOTICE "pci_bridge_probe: PCI DEV is NULL\n");
return -EINVAL;
}
dev = pci_bridge_devices; // only one device for now
if(dev == NULL)
printk("pci_bridge_probe: device structure not allocated\n");
else
{
pci_enable_device(pcidev);
pci_set_master(pcidev);
dev->pcidev = pcidev;
}
return 0;
}
/*
* pci_bridge_remove - pci_driver remove function. Release allocated resources,
* etc.
*/
static void __devexit pci_bridge_remove(struct pci_dev *dev)
{
printk("pci_bridge_remove called ...\n");
}
/*
* pci_bridge_init - module init function. By convention, the function is
* declared static, even though it is not exported to the rest of the
* kernel unless explicitly requested via the EXPORT_SYMBOL macro. The
* __init qualifier tells the loader that the function is only used at
* module initialization time.
*/
static int __init pci_bridge_init(void)
{
struct pci_bridge_dev *dev;
dev_t devno;
int result;
unsigned short num_of_bases;
u32 base_address;
printk("pci_bridge_init called ...\n");
/*
* Allocate the per-device structure(s).
*/
pci_bridge_devices = kmalloc(sizeof(struct pci_bridge_dev), GFP_KERNEL);
if(pci_bridge_devices == NULL)
{
result = -ENOMEM;
goto fail;
}
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if(pci_bridge_init_major)
{
pci_bridge_major = pci_bridge_init_major;
devno = MKDEV(pci_bridge_major, 0);
result = register_chrdev_region(devno, 1, PCI_DRIVER_NAME);
}
else
{
result = alloc_chrdev_region(&devno, 0, 1, PCI_DRIVER_NAME);
pci_bridge_major = MAJOR(devno);
}
if(result < 0)
{
printk(KERN_NOTICE "pci_bridge: can't get major %d\n", pci_bridge_major);
goto fail;
}
dev = pci_bridge_devices;/* only one device for now */
/*
* Initialize and add this device's character device table entry.
*/
dev->pcidev = NULL;
cdev_init(&dev->cdev, &pci_bridge_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &pci_bridge_fops;
dev->offset = 0;
result = cdev_add(&dev->cdev, devno, 1);
if(result)
{
printk(KERN_NOTICE "Error %d adding %s device", result, PCI_DRIVER_NAME);
goto fail;
}
if((result = pci_register_driver(&pci_bridge_driver)) != 0)
{
printk(KERN_NOTICE "Error %d registering %s PCI device",result,
PCI_DRIVER_NAME);
goto fail;
}
if(dev->pcidev == NULL)
{
printk(KERN_NOTICE "PCI DEV is NULL, probe failed?\n");
goto fail;
}
base_address = pci_resource_start(dev->pcidev, 0);
printk("<1> First base address register found at %08X \n ",
pci_resource_start(dev->pcidev, 0));
num_of_bases = 0;
while
((base_address = pci_resource_start(dev->pcidev, num_of_bases))!=
0x00000000 && (num_of_bases < 6))
{
unsigned long flags;
flags = pci_resource_flags(dev->pcidev, num_of_bases);
dev->bases[num_of_bases] = base_address;
dev->base_size[num_of_bases] = pci_resource_end(dev->pcidev,
num_of_bases) - base_address + 1;
// check if resource is IO mapped
if (flags & IORESOURCE_IO)
dev->base_map[num_of_bases] = BRIDGE_IO_MAPPED;
else
dev->base_map[num_of_bases] = BRIDGE_MEM_MAPPED;
num_of_bases++;
}
if (num_of_bases < 1)
printk("<1>No implemented base address registers found! \n ");
dev->current_resource = - 1;
// store number of bases in structure
dev->num_of_bases = num_of_bases;
printk("num_of_bases found %d \n", num_of_bases);
// display information about all base addresses found in this procedure
for (num_of_bases = 0; num_of_bases < dev->num_of_bases; num_of_bases++)
{
printk("<1>BAR%d range from %08X to %08X \n ", num_of_bases,
dev->bases[num_of_bases], dev->bases[num_of_bases] +
dev->base_size[num_of_bases]);
}
if(pci_read_config_byte(dev->pcidev,PCI_INTERRUPT_LINE, &dev->interrupt_line))
{
printk("Could not get interrupt line");
}
else
{
printk("Interrupt Line is %d \n", dev->interrupt_line);
}
dma_descriptor_setup(dev->pcidev);
return 0;
fail:
pci_bridge_exit();
return result;
}
/*
* pci_bridge_exit - module exit function. Release resources allocated
* by pci_bridge_init.
*/
static void __exit pci_bridge_exit(void)
{
printk("pci_bridge_exit called ...\n");
if(pci_bridge_devices)
{
struct pci_bridge_dev *dev;
dev = &pci_bridge_devices[0];
dma_descriptor_release(dev->pcidev);
cdev_del(&dev->cdev);
kfree(pci_bridge_devices);
pci_bridge_devices = NULL;
}
unregister_chrdev_region(MKDEV(pci_bridge_major, 0), 1);
pci_bridge_major = 0;
pci_unregister_driver(&pci_bridge_driver);
}
/*
* pci_bridge_open - open file processing.
*/
int pci_bridge_open(struct inode *inode, struct file *filep)
{
struct pci_bridge_dev *dev;
dev = container_of(inode->i_cdev, struct pci_bridge_dev, cdev);
filep->private_data = dev; // used by read, write, etc
dev->current_resource = -1;
/* Success */
return 0;
}
/*
* pci_bridge_release - close processing.
*/
int pci_bridge_release(struct inode *inode, struct file *filep)
{
/* Success */
return 0;
}
/*
* seek file operation function
*/
loff_t bridge_lseek(struct file *filp, loff_t offset, int origin)
{
struct pci_bridge_dev *dev;
loff_t requested_offset;
int resource_num;
dev = filp->private_data;
resource_num = dev->current_resource;
switch (origin)
{
case SEEK_CUR:requested_offset = dev->offset + offset; break;
case SEEK_END:requested_offset = dev->base_size[resource_num] + offset; break;
default:requested_offset = offset; break;
}
if ((requested_offset < 0) || (requested_offset >
dev->base_size[resource_num]))
return -EFAULT;
dev->offset = requested_offset;
return requested_offset;
}
/*
* pci_bridge_read - read processing.
*/
ssize_t pci_bridge_read (struct file *filp, char *buf, size_t count,
loff_t *offset_out )
{
struct pci_bridge_dev *dev;
unsigned long current_address;
unsigned long actual_count;
unsigned long offset;
int resource_num;
int i;
unsigned int value;
unsigned int *kern_buf;
unsigned int *kern_buf_tmp;
unsigned long size;
int result;
dev = filp->private_data;
offset = dev->offset;
resource_num = dev->current_resource;
size = dev->base_size[resource_num];
current_address = dev->page_addr + dev->base_page_offset + dev->offset;
if (dev->current_resource < 0)
return -ENODEV;
if (offset == size)
return 0;
if ( (offset + count) > size )
actual_count = size - offset;
else
actual_count = count;
// verify range if it is OK to copy from
if ((result = access_ok(VERIFY_WRITE, buf, actual_count)) ==0)
return result;
kern_buf = kmalloc(actual_count, GFP_KERNEL | GFP_DMA);
kern_buf_tmp = kern_buf;
if (kern_buf <= 0)
return 0;
memcpy_fromio(kern_buf, current_address, actual_count);
i = actual_count/4;
while(i--)
{
// value = readl(current_address);
value = *(kern_buf);
put_user(value, ((unsigned int *)buf));
buf += 4;
++kern_buf;
// current_address += 4;
}
kfree(kern_buf_tmp);
dev->offset = dev->offset + actual_count;
*(offset_out) = dev->offset;
return actual_count;
}
/*
* pci_bridge_write - write processing.
*/
ssize_t pci_bridge_write (struct file *filp, const char *buf, size_t
count, loff_t *offset_out)
{
struct pci_bridge_dev *dev;
unsigned long current_address;
unsigned long actual_count;
unsigned long offset;
int resource_num;
int i;
int value;
unsigned long size;
int result;
int *kern_buf;
int *kern_buf_tmp;
dev = filp->private_data;
current_address = dev->page_addr + dev->base_page_offset + dev->offset;
resource_num = dev->current_resource;
size = dev->base_size[resource_num];
offset = dev->offset;
if (dev->current_resource < 0)
return -ENODEV;
if (offset == size)
return 0;
if ( (offset + count) > size )
actual_count = size - offset;
else
actual_count = count;
// verify range if it is OK to copy from
if ((result = access_ok(VERIFY_READ, buf, actual_count)) == 0)
return result;
kern_buf = kmalloc(actual_count, GFP_KERNEL | GFP_DMA);
kern_buf_tmp = kern_buf;
if (kern_buf <= 0)
return 0;
i = actual_count/4;
while(i--)
{
get_user(value, ((int *)buf));
// writel(value, current_address);
*kern_buf = value;
buf += 4;
//current_address += 4;
++kern_buf;
}
memcpy_toio(current_address, kern_buf_tmp, actual_count);
kfree(kern_buf_tmp);
dev->offset = dev->offset + actual_count;
*(offset_out) = dev->offset;
return actual_count;
}
/*
* helper function for memory remaping
*/
int open_mem_mapped(struct pci_bridge_dev *dev)
{
int resource_num = dev->current_resource;
unsigned long num_of_pages = 0;
unsigned long base = dev->bases[resource_num];
unsigned long size = dev->base_size[resource_num];
//printk("\n current resource=%d , size = %d, base=%08X",
dev->current_resource , size, dev->bases[resource_num]);
if (!(num_of_pages = (unsigned long)(size/PAGE_SIZE)));
num_of_pages++;
dev->base_page_offset = base & ~PAGE_MASK;
if ((dev->base_page_offset + size) < (num_of_pages*PAGE_SIZE))
num_of_pages++;
// remap memory mapped space
dev->page_addr = (unsigned long)ioremap(base & PAGE_MASK,
num_of_pages * PAGE_SIZE);
if (dev->page_addr == 0x00000000)
return -ENOMEM;
return 0;
}
/*
* ioctl: see kint.h for the meaning of args
*/
int pci_bridge_ioctl(struct inode *pnode, struct file *filp, unsigned
int cmd, unsigned long arg)
{
int error = 0;
unsigned long base;
unsigned long base_size;
struct pci_bridge_dev *dev;
dev = filp->private_data;
if (_IOC_TYPE(cmd) != BRIDGE_IOC_NUM) return -EINVAL;
if (_IOC_NR(cmd) > BRIDGE_IOC_MAX_NUM) return -EINVAL;
switch (cmd)
{
case BRIDGE_IOC_CURRESGET:
// current resource - they start at 1
return (dev->current_resource + 1);
case BRIDGE_IOC_CURRESSET:
// check if resource is in a range of implemented resources
if (arg < 0 )
return -EINVAL;
// unmap previous resource if it was mapped
if (dev->current_resource >= 0)
{
iounmap((void *)dev->page_addr);
}
if (arg == 0)
{
// previous resource unmaped - that's all
dev->current_resource = -1;
return 0;
}
if (dev->num_of_bases < arg)
return -ENODEV;
// IO mapped not supported yet
if (dev->base_map[arg-1] == BRIDGE_IO_MAPPED)
{
// set current resource to none, since it was unmapped
dev->current_resource = -1;
return -ENODEV;
}
dev->current_resource= (int)(arg-1);
// remap new resource
if ( (error = open_mem_mapped(dev)) )
{
dev->current_resource = -1;
return error;
}
return 0;
case BRIDGE_IOC_CURBASE:
// check if any resource is currently activated
if (dev->current_resource>=0)
{
base = dev->bases[dev->current_resource];
printk("\n CURR_RES = %d",dev->current_resource );
}
else
base = 0x00000000;
*(unsigned long *)arg = base;
return 0;
case BRIDGE_IOC_CURBASEMAP:
// check if any resource is currently activated
if (dev->current_resource>=0)
base = dev->page_addr;
else
base = 0x00000000;
*(unsigned long *)arg = base;
return 0;
case BRIDGE_IOC_CURBASESIZE:
// check if any resource is currently activated
if (dev->current_resource>=0)
base_size = dev->base_size[dev->current_resource];
else
base_size = 0x00000000;
*(unsigned long *)arg = base_size;
return 0;
case BRIDGE_IOC_NUMOFRES:
return (dev->num_of_bases);
default:
return -EINVAL;
}
}
#define TX_BUFFER_FLAGS 0x00
#define RX_BUFFER_FLAGS 0x00
#define TX_DATA_LEN 0x04
#define RX_DATA_LEN 0x04
#define TX_BUFFER_OFFSET 0x0c
#define RX_BUFFER_OFFSET 0x0c
#define TX_BUFFER_SIZE 4096
#define RX_BUFFER_SIZE 4096
#define WB_RX_BUFFER_AM 0xFF000000
#define WB_TX_BUFFER_AM 0xFF000000
static void dma_descriptor_setup(struct pci_dev *pdev)
{
unsigned long current_address;
unsigned int register_value;
int error = 0;
unsigned int c_tx_data;
printk("DMA ALLOC ROUTINE STARTED\n");
c_memory_map->dma_buffer_rx = pci_alloc_consistent(pdev,
RX_BUFFER_SIZE+RX_BUFFER_OFFSET, &c_memory_map->dma_bus_rx);
printk("RX DMA BUFFER READY\n");
c_memory_map->dma_buffer_tx = pci_alloc_consistent(pdev,
TX_BUFFER_SIZE+TX_BUFFER_OFFSET, &c_memory_map->dma_bus_tx);
printk("TX DMA BUFFER READY\n");
pci_bridge_devices->current_resource = 0;
if ( (error = open_mem_mapped(pci_bridge_devices)) )
{
pci_bridge_devices->current_resource = -1;
printk("Could not set BAR0 on DMA alloc\n");
return;
}
printk("PCI ADDRESS FOR TX IS %d AND PCI ADDRESS FOR RX IS %d\n",
c_memory_map->dma_bus_rx, c_memory_map->dma_bus_tx);
current_address = pci_bridge_devices->page_addr +
pci_bridge_devices->base_page_offset + BRIDGE_W_AM1_ADDR;
register_value = WB_RX_BUFFER_AM;
memcpy_toio(current_address, ®ister_value, 4);
current_address = pci_bridge_devices->page_addr +
pci_bridge_devices->base_page_offset + BRIDGE_W_AM2_ADDR;
register_value = WB_TX_BUFFER_AM;
memcpy_toio(current_address, ®ister_value, 4);
}
static void dma_descriptor_release(struct pci_dev *pdev)
{
pci_free_consistent(pdev, RX_BUFFER_SIZE+RX_BUFFER_OFFSET,
c_memory_map->dma_buffer_rx, c_memory_map->dma_bus_rx);
pci_free_consistent(pdev, TX_BUFFER_SIZE+TX_BUFFER_OFFSET,
c_memory_map->dma_buffer_tx, c_memory_map->dma_bus_tx);
}
MODULE_LICENSE("GPL");
module_init(pci_bridge_init);
module_exit(pci_bridge_exit);
Any help would be apreciated..
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/