PCI mapper
From: Thiago Robert dos Santos
Date: Tue Nov 16 2004 - 10:00:03 EST
All,
I have developed a module that maps a given device's memory into user
space.
The module is very simple (see the source code attached). It just
defines the following file operations: open, release, ioctl and mmap.
I'm having a problem in porting this module to the 2.6 series.
Apparently, everything is working fine but I just can't access the
device's memory (even tough I get a valid point from the mmap system
call).
Can anyone help me?
Thanks in advance.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/fs.h> /* for file_operations */
#include <linux/pci.h> /* for pci_find_device */
#include <linux/slab.h> /* for kmalloc */
#include <asm/segment.h> /* for memcpy */
#include <asm/io.h> /* for virt_to_phys */
#include <linux/byteorder/little_endian.h>
// TODO: Deveria ser definido no Makefile
#define DBLEV 31
#include "debug.h"
#include "pcimap.h"
MODULE_AUTHOR ("Thiago Robert");
MODULE_DESCRIPTION ("PCI Device Mapper");
MODULE_LICENSE ("GPL");
/* MODULE PARAMETERS */
int dev = PCIMAP_MAJOR;
int n_devs = PCIMAP_N_DEVS;
int vendor_id = VENDOR_ID;
int device_id = DEVICE_ID;
module_param (dev, int, 0); // PCI Map major device number
module_param (n_devs, int, 0); // Number of PCI Map devices
module_param (vendor_id, int, 0); // PCI vendor id
module_param (device_id, int, 0); // PCI device id
/* Offset for PCI Regions */
static const int pciregs[PCIMAP_N_REGS] = {
PCI_BASE_ADDRESS_0,
PCI_BASE_ADDRESS_1,
PCI_BASE_ADDRESS_2,
PCI_BASE_ADDRESS_3,
PCI_BASE_ADDRESS_4,
PCI_BASE_ADDRESS_5
};
/* Mapped PCI devices */
static struct PCI_Device *pcidevs;
static int
pcimap_open (struct inode *inode, struct file *filp)
{
int unit = 0, reg = 0;
unit = iminor (inode) >> 4;
reg = iminor (inode) & 0x0f;
debug (DB_TRACE, "PCIMAP: pcimap_open(unit=%d,reg=%d)\n", unit, reg);
if ((unit >= n_devs) || (!pcidevs[unit].found)
|| reg >= PCIMAP_N_REGS)
return -ENODEV;
if (!pcidevs[unit].reg[reg].size)
return -ENODEV;
/* PCI Map devices are not sharable */
if (pcidevs[unit].reg[reg].busy)
return -EBUSY;
pcidevs[unit].reg[reg].busy = 1;
return 0;
}
static int
pcimap_close (struct inode *inode, struct file *filp)
{
int unit = 0, reg = 0;
unit = iminor (inode) >> 4;
reg = iminor (inode) & 0x0f;
debug (DB_TRACE, "PCIMAP: pcimap_close(unit=%d,reg=%d)\n", unit, reg);
pcidevs[unit].reg[reg].busy = 0;
return 0;
}
static int
pcimap_mmap2 (struct file *filp, struct vm_area_struct *vma)
{
int unit, reg;
unsigned long size, phy_addr;
unit = MINOR(filp->f_dentry->d_inode->i_rdev) >> 4;
reg = MINOR(filp->f_dentry->d_inode->i_rdev) & 0x0f;
size = vma->vm_end - vma->vm_start;
debug (DB_TRACE,
"DEBUG: pcimap_mmap(unit=%d,reg=%d,start=%lx,size=%lx,off=%lx)\n",
unit, reg, vma->vm_start, size, vma->vm_pgoff * PAGE_SIZE);
/* Mapping must be page aligned and not larger than the region size */
if ((size + vma->vm_pgoff * PAGE_SIZE) > pcidevs[unit].reg[reg].size)
{
return -EINVAL;
}
/* Get device's memory physical address for mapping */
phy_addr =
pcidevs[unit].reg[reg].phy_addr + vma->vm_pgoff * PAGE_SIZE;
/* Isn't it necessary to call ioremap_nocache? how? */
// vma->vm_start = (unsigned int) ioremap_nocache(phy_addr, size);
/* Map device's memory in the requested address space range */
if (remap_page_range (vma, phy_addr, 0, size, vma->vm_page_prot))
{
return -EAGAIN;
}
filp->f_dentry->d_inode->i_count.counter++;
return 0;
}
static int
pcimap_mmap (struct file *filp, struct vm_area_struct *vma)
{
// /lib/modules/2.6.5-1.358/build/include/linux/mm.h --> definicao vm_area_stuct
int unit, reg;
unsigned long size, phy_addr;
unit = MINOR(filp->f_dentry->d_inode->i_rdev) >> 4;
reg = MINOR(filp->f_dentry->d_inode->i_rdev) & 0x0f;
size = vma->vm_end - vma->vm_start;
debug (DB_TRACE,
"PCIMAP: pcimap_mmap(unit=%d,reg=%d,start=%lx,size=%lx,off=%lx)\n",
unit, reg, vma->vm_start, size, vma->vm_pgoff * PAGE_SIZE);
debug (DB_TRACE,
"DEBUG: pcimap_mmap(vm_end=%lx, vm_start=%lx, vm_pgoff=%lx, PAGE_SIZE=%lx)\n",
vma->vm_end, vma->vm_start, vma->vm_pgoff, PAGE_SIZE);
/* Mapping must be page aligned and not larger than the region size */
if ((size + vma->vm_pgoff * PAGE_SIZE) > pcidevs[unit].reg[reg].size)
{
return -EINVAL;
}
/* Get device's memory physical address for mapping */
phy_addr =
pcidevs[unit].reg[reg].phy_addr + vma->vm_pgoff * PAGE_SIZE;
debug (DB_TRACE,
"DEBUG: pcimap_mmap(phy=%lx, phy2=%x)\n", phy_addr, pcidevs[unit].reg[reg].phy_addr);
/* Isn't it necessary to call ioremap_nocache? how? */
// vma->vm_start = (unsigned int) ioremap_nocache(phy_addr, size);
/* Map device's memory in the requested address space range */
if (remap_page_range (vma, phy_addr, 0, size, vma->vm_page_prot))
{
return -EAGAIN;
}
filp->f_dentry->d_inode->i_count.counter++;
debug (DB_TRACE,
"DEBUG: pcimap_mmap(counter=%d)\n", filp->f_dentry->d_inode->i_count.counter);
return 0;
}
static int
pcimap_ioctl (struct inode *inode, struct file *filp, unsigned int cmd,
unsigned long arg)
{
int unit = 0, reg = 0;
unit = iminor (inode) >> 4;
reg = iminor (inode) & 0x0f;
debug (DB_TRACE,
"PCIMAP: pcimap_ioctl(unit=%d,reg=%d,cmd=%x,arg=%lx)\n", unit,
reg, cmd, arg);
switch (cmd)
{
case PCIMAP_GET_SIZE: /* Return DMA buffer's size */
return pcidevs[unit].reg[reg].size;
break;
case PCIMAP_GET_PHY_ADDR: /* Return DMA buffer's physical address */
return pcidevs[unit].reg[reg].phy_addr;
break;
default:
return -EINVAL;
}
return 0;
}
/* PCIMAP FILE OPERATIONS */
struct file_operations pcimap_fops = {
.mmap = pcimap_mmap, /* mmap */
.ioctl = pcimap_ioctl, /* ioctl */
.open = pcimap_open, /* open */
.release = pcimap_close, /* close */
};
int
pcimap_init (void)
{
int i, j, err;
struct pci_dev *pci_dev;
debug (DB_TRACE,
"PCIMAP: Loading PCI Map(dev=%d,n_devs=%d,vendor_id=%x,device_id=%x)\n",
dev, n_devs, vendor_id, device_id);
/* Check for mandatory module parameters */
if ((vendor_id == -1) || (device_id == -1))
{
printk (KERN_WARNING
"PCI vendor and device id must be specified!\n");
return -EINVAL;
}
/* Allocate the control structure for PCI Maps */
pcidevs = kmalloc (n_devs * sizeof (struct PCI_Device), GFP_KERNEL);
if (!pcidevs)
{
return -ENOMEM;
}
memset (pcidevs, 0, n_devs * sizeof (struct PCI_Device));
i = 0;
pci_dev = NULL;
do
{
/* Look for our PCI device */
pci_dev = pci_find_device (vendor_id, device_id, pci_dev);
if (!pci_dev)
{
break;
}
/* (Re)configure device for bus mastering */
pci_read_config_word (pci_dev, PCI_COMMAND, &pcidevs[i].cmd);
if (!(pcidevs[i].cmd & PCI_COMMAND_MEMORY))
{
pcidevs[i].cmd |= PCI_COMMAND_MEMORY;
debug (DB_WARNING,
"PCIMAP: PCI_COMMAND_MEMORY not set for device!\n");
}
if (!(pcidevs[i].cmd & PCI_COMMAND_MASTER))
{
pcidevs[i].cmd |= PCI_COMMAND_MASTER;
debug (DB_WARNING,
"PCIMAP: PCI_COMMAND_MASTER not set for device!\n");
}
if (!(pcidevs[i].cmd & PCI_COMMAND_INVALIDATE))
{
pcidevs[i].cmd |= PCI_COMMAND_INVALIDATE;
debug (DB_WARNING,
"PCIMAP: PCI_COMMAND_INVALIDATE not set for device!\n");
}
pci_write_config_word (pci_dev, PCI_COMMAND, pcidevs[i].cmd);
/* Get device's configuration */
pci_read_config_word (pci_dev, PCI_COMMAND, &pcidevs[i].cmd);
pci_read_config_word (pci_dev, PCI_STATUS, &pcidevs[i].stat);
pci_read_config_byte (pci_dev, PCI_REVISION_ID,
&pcidevs[i].rev);
/* Get regions */
for (j = 0; j < PCIMAP_N_REGS; j++)
{
/* Get reagion info */
pci_read_config_dword (pci_dev, pciregs[j],
&pcidevs[i].reg[j].phy_addr);
pci_write_config_dword (pci_dev, pciregs[j], ~0);
pci_read_config_dword (pci_dev, pciregs[j],
&pcidevs[i].reg[j].size);
pci_write_config_dword (pci_dev, pciregs[j],
pcidevs[i].reg[j].phy_addr);
/* Check for memory region */
if (!pcidevs[i].reg[j].size
|| (pcidevs[i].reg[j].
phy_addr & PCI_BASE_ADDRESS_SPACE_IO))
{
pcidevs[i].reg[j].phy_addr = 0;
pcidevs[i].reg[j].size = 0;
continue;
}
/* We only handle 32 bit addressed memory above 1 Mb */
if ((pcidevs[i].reg[j].
phy_addr & PCI_BASE_ADDRESS_MEM_TYPE_MASK) &
(PCI_BASE_ADDRESS_MEM_TYPE_1M |
PCI_BASE_ADDRESS_MEM_TYPE_64))
{
pcidevs[i].reg[j].phy_addr = 0;
pcidevs[i].reg[j].size = 0;
continue;
}
/* Adjust address and size */
pcidevs[i].reg[j].phy_addr &=
PCI_BASE_ADDRESS_MEM_MASK;
pcidevs[i].reg[j].size =
~(pcidevs[i].reg[j].
size & PCI_BASE_ADDRESS_MEM_MASK) + 1;
pcidevs[i].regs |= 1 << j;
}
if (pcidevs[i].regs)
{
pcidevs[i].found = 1;
}
/* debug (DB_INFO, "PCIMAP: debug\n");
debug (DB_INFO, "pcidev[%d]->ctrl=%x\n", i, pcidevs[i].cmd);
debug (DB_INFO, "pcidev[%d]->stat=%x\n", i, pcidevs[i].stat);
debug (DB_INFO, "pcidev[%d]->rev=%x\n", i, pcidevs[i].rev);
debug (DB_INFO, "pcidev[%d]->regs=%x\n", i, pcidevs[i].regs);
for (j = 0; j < PCIMAP_N_REGS; j++)
{
if (pcidevs[i].reg[j].size)
{
debug (DB_INFO,
"pcidev[%d]->reg[%d]->phy_addr=%x\n",
i, j, pcidevs[i].reg[j].phy_addr);
debug (DB_INFO,
"pcidev[%d]->reg[%d]->size=%d\n", i, j,
pcidevs[i].reg[j].size);
}
}
*/
}
while ((++i < n_devs) && pci_dev);
if (i == 0)
{
kfree (pcidevs);
return -ENODEV;
}
/* Register the device driver */
err = register_chrdev (dev, "pcimap", &pcimap_fops);
if (err < 0)
{
return err;
}
return 0;
}
void
pcimap_exit (void)
{
debug (DB_TRACE, "PCIMAP: Unloading PCI Map\n");
/* Unregister the device driver */
unregister_chrdev (dev, "pcimap");
/* Release PCI Maps control structure */
kfree (pcidevs);
}
module_init (pcimap_init);
module_exit (pcimap_exit);#ifndef PCIMAP_H
#define PCIMAP_H
#include <linux/ioctl.h>
#define PCIMAP_MAJOR 125
#define PCIMAP_N_DEVS 2
#define PCIMAP_N_REGS 6
#define VENDOR_ID 0x14C1
#define DEVICE_ID 0x8043
#define PCIMAP_GET_SIZE _IOR(PCIMAP_MAJOR, 0, unsigned long)
#define PCIMAP_GET_PHY_ADDR _IOR(PCIMAP_MAJOR, 1, unsigned long)
struct PCI_Region
{
int busy;
unsigned int phy_addr;
unsigned int size;
};
struct PCI_Device
{
int found;
unsigned short cmd;
unsigned short stat;
unsigned char rev;
int regs;
struct PCI_Region reg[PCIMAP_N_REGS];
};
#endif /* PCIMAP_H */typedef enum
{
DB_ABORT,
DB_ERROR,
DB_WARNING,
DB_INFO,
DB_TRACE
} Debug_Level;
void debug (Debug_Level level, const char *format, ...);
#ifdef DBLEV
extern char *debug_levels[];
#define debug(level, format, args...) \
if((1 << level) & (DBLEV))\
printk(KERN_INFO format, ## args);
#else /* DBLEV */
#define debug(level, format, args...)
#endif /* DBLEV */Attachment:
Makefile
Description: Binary data
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "pcimap.h"
int
main (int argc, char *argv[])
{
int i, fd0;
char * ptr = NULL;
unsigned long phy_addr, size;
if (argc != 4)
{
fprintf (stderr, "Usage: %s device offset size!\n", argv[0]);
return -1;
}
fd0 = open (argv[1], O_RDWR);
if (fd0 < 0)
{
perror ("open");
return -1;
}
size = ioctl (fd0, PCIMAP_GET_SIZE, NULL);
if (size < 0)
{
perror ("ioctl");
return -1;
}
printf ("PCI device memory size: %ld\n", size);
phy_addr = ioctl (fd0, PCIMAP_GET_PHY_ADDR, NULL);
if (phy_addr < 0)
{
perror ("ioctl");
return -1;
}
printf ("PCI device memory physical address: %lx\n", phy_addr);
ptr = (char *) mmap (0, atoi (argv[3]), PROT_READ | PROT_WRITE,
MAP_SHARED, fd0, atoi (argv[2]));
if ((int) ptr == -1)
{
printf ("Error code: %d\n", (int) ptr);
perror ("mmap");
return -1;
}
else
{
printf ("PCI device memory logical address: %p\n", ptr);
// strcpy (ptr, "PCIMAP test OK!");
printf ("PCI device memory contents: ");
for (i = atoi (argv[2]); i < atoi (argv[3]); i++)
{
// printf("%c", ptr[i]);
putchar (ptr[i]);
}
putchar ('\n');
}
close (fd0);
return 0;
}