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;
}